diff --git a/.travis.yml b/.travis.yml index a4451d2f48..40eea21bf9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,4 +48,3 @@ cache: - clients/crunchbase/ui/node_modules - clients/pitchmap/ui/node_modules - clients/splore/node_modules - - clients/tagshow/node_modules diff --git a/README.md b/README.md index f4dbf19601..840216282a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ go build # What next? * Learn the core tools: [`server`](clients/server), [`splore`](clients/splore), [`shove`](clients/shove), [`csv_importer`](clients/csv_importer), [`json_importer`](clients/json_importer), [`xml_importer`](clients/xml_importer) -* Run sample apps: [`sfcrime`](clients/sfcrime), [`tagshow`](clients/tagshow) (photo viewer) +* Run sample apps: [`sfcrime`](clients/sfcrime) * NomDL reference (TODO) * Go SDK reference (TODO) * JavaScript SDK reference (TODO) diff --git a/clients/tagshow/.gitignore b/clients/tagshow/.gitignore deleted file mode 100644 index 0e8fd9e30a..0000000000 --- a/clients/tagshow/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.babelrc -.eslintrc -.flowconfig -node_modules -tagshow.js diff --git a/clients/tagshow/.npm_build_helper.py b/clients/tagshow/.npm_build_helper.py deleted file mode 100644 index 419a389681..0000000000 --- a/clients/tagshow/.npm_build_helper.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/python - -import os, subprocess, sys - -SRC = ['babel-regenerator-runtime', 'src/main.js'] -OUT = 'tagshow.js' - -def main(): - env = os.environ - env['NODE_ENV'] = sys.argv[1] - env['BABEL_ENV'] = sys.argv[1] - if 'NOMS_SERVER' not in env: - env['NOMS_SERVER'] = 'http://localhost:8000' - subprocess.check_call(['node_modules/.bin/webpack'] + SRC + [OUT], env=env, shell=False) - - -if __name__ == "__main__": - main() diff --git a/clients/tagshow/README.md b/clients/tagshow/README.md deleted file mode 100644 index 547eeefdac..0000000000 --- a/clients/tagshow/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Tagshow - -This is a slideshow that displays photos that have a particular tag. - -## Setup - -* Tag some of your photos at either Flickr and/or Picasa (aka Google Photos) -* Import photos from Flickr and/or Picasa - * To import photos from Flickr: - * Apply for a [Flickr API key](https://www.flickr.com/services/apps/create/apply) - * `cd $GOPATH/src/github.com/attic-labs/noms/clients/flickr` - * `go build` - * `./flickr -api-key="" -api-key-secret="" -ldb="/tmp/tagshowdemo" -ds="flickr"` - * To import photos from Picasa: - * `cd $GOPATH/src/github.com/attic-labs/noms/clients/picasa` - * `go build` - * `./picasa` - * Follow the instructions printed out to create Google API credentials - * `./picasa -api-key="" -api-key-secret="" -ldb="/tmp/tagshowdemo" -ds="picasa"` -* Index photos by tag - * `cd $GOPATH/src/github.com/attic-labs/noms/clients/tagdex` - * `go build` - * `./tagdex -ldb="/tmp/tagshowdemo" -in="" -out="tagdex"` - -## Run - -``` -cd $GOPATH/src/github.com/attic-labs/noms/clients/server -go build -./server -ldb="/tmp/tagshowdemo" - -cd ../tagshow -./build.py -./node_modules/.bin/http-server -``` - -Then, navigate to [http://localhost:8080](http://localhost:8080). - -## Develop - -``` -./build.py # only necessary first time -NOMS_SERVER="http://localhost:8000" npm run start -``` - -This will start watchify which is continually building a shippable (but non minified) build diff --git a/clients/tagshow/build.py b/clients/tagshow/build.py deleted file mode 100755 index 78d5632132..0000000000 --- a/clients/tagshow/build.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/python - -import os, os.path, subprocess, sys - -sys.path.append(os.path.abspath('../../tools')) - -import noms.symlink as symlink - -def main(): - symlink.Force('../../js/.babelrc', os.path.abspath('.babelrc')) - symlink.Force('../../js/.eslintrc', os.path.abspath('.eslintrc')) - symlink.Force('../../js/.flowconfig', os.path.abspath('.flowconfig')) - - subprocess.check_call(['npm', 'install'], shell=False) - subprocess.check_call(['npm', 'run', 'build'], env=os.environ, shell=False) - - -if __name__ == "__main__": - main() diff --git a/clients/tagshow/index.html b/clients/tagshow/index.html deleted file mode 100644 index a87d713ac0..0000000000 --- a/clients/tagshow/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - -tagshow - - - - -
- diff --git a/clients/tagshow/package.json b/clients/tagshow/package.json deleted file mode 100644 index 24993f0b15..0000000000 --- a/clients/tagshow/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "noms-tagshow", - "dependencies": { - "noms": "file:../../js", - "noms-webpack-config": "file:../../js/webpack-config", - "query-string": "^2.4.0", - "react": "^0.14.1", - "react-dom": "^0.14.1" - }, - "devDependencies": { - "babel-cli": "^6.3.15", - "babel-core": "^6.3.15", - "babel-eslint": "^4.1.5", - "babel-plugin-syntax-async-functions": "^6.3.13", - "babel-plugin-syntax-flow": "^6.3.13", - "babel-plugin-transform-async-to-generator": "^6.3.13", - "babel-plugin-transform-class-properties": "^6.3.13", - "babel-plugin-transform-es2015-destructuring": "^6.3.15", - "babel-plugin-transform-es2015-modules-commonjs": "^6.3.16", - "babel-plugin-transform-es2015-parameters": "^6.3.13", - "babel-polyfill": "^6.3.14", - "babel-preset-es2015": "^6.3.13", - "babel-preset-react": "^6.3.13", - "chai": "^3.2.0", - "eslint": "^1.9.0", - "eslint-plugin-react": "^3.8.0", - "flow-bin": "^0.21.0", - "http-server": "^0.8.5", - "mocha": "^2.3.0" - }, - "scripts": { - "start": "python .npm_build_helper.py development", - "build": "python .npm_build_helper.py production", - "TODO:": "re-enable tests (e.g. eq_test.js)", - "test": "eslint src/ && flow src/" - } -} diff --git a/clients/tagshow/src/datasetpicker.js b/clients/tagshow/src/datasetpicker.js deleted file mode 100644 index aef7f02cea..0000000000 --- a/clients/tagshow/src/datasetpicker.js +++ /dev/null @@ -1,75 +0,0 @@ -// @flow - -import eq from './eq.js'; -import React from 'react'; -import type {ChunkStore} from 'noms'; -import {invariant, NomsMap, readValue, Ref} from 'noms'; - -type DefaultProps = { - selected: string -}; - -type Props = { - store: ChunkStore, - onChange: (value: string) => void, - selected: string -}; - -type State = { - datasets: Set -}; - -export default class DatasetPicker extends React.Component { - state: State; - static defaultProps: DefaultProps; - - constructor(props: Props) { - super(props); - this.state = { - datasets: new Set(), - }; - } - - handleSelectChange(e: Event) { - invariant(e.target instanceof HTMLSelectElement); - this.props.onChange(e.target.value); - } - - async _updateDatasets(props: Props) : Promise { - const store = props.store; - const rootRef = await store.getRoot(); - const datasets: NomsMap = await readValue(rootRef, store); - invariant(datasets); - const s = new Set(); - await datasets.forEach((v, k) => { - s.add(k); - }); - this.setState({ - datasets: s, - }); - } - - shouldComponentUpdate(nextProps: Props, nextState: State) : boolean { - return !eq(nextProps, this.props) || !eq(nextState, this.state); - } - - render() : React.Element { - this._updateDatasets(this.props); - - const options = []; - for (const v of this.state.datasets) { - options.push(); - } - return
- Choose dataset: -
- -
; - } -} - -DatasetPicker.defaultProps = {selected: ''}; diff --git a/clients/tagshow/src/eq.js b/clients/tagshow/src/eq.js deleted file mode 100644 index 9c758013b3..0000000000 --- a/clients/tagshow/src/eq.js +++ /dev/null @@ -1,54 +0,0 @@ -// @flow - -import {Ref} from 'noms'; - -export default function eq(a: any, b: any) : boolean { - // Babel bug: https://github.com/babel/babel/issues/3046 - let i; - if (a === b) return true; - const ta = typeof a; - const tb = typeof b; - if (ta !== tb) return false; - if (a === null || b === null) return false; - if (ta !== 'object') return false; - if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) return false; - if (a instanceof Ref) { - return a.equals(b); - } - // https://github.com/attic-labs/noms/issues/615 - // const ar = a.ref; - // const br = b.ref; - // if (ar && br) { - // return eq(ar, br); - // } - if (a instanceof Array) { - if (a.length !== b.length) return false; - for (i = 0; i < a.length; i++) { - if (!eq(a[i], b[i])) return false; - } - return true; - } - if (a instanceof Set) { - if (a.size !== b.size) return false; - // has uses object identity - return eq([...a].sort(), [...b].sort()); - } - if (a instanceof Map) { - if (a.size !== b.size) return false; - // get uses object identity - const compare = (a, b) => { - if (eq(a[0], b[0])) return 0; - if (a[0] < b[0]) return -1; - return 1; - }; - return eq([...a].sort(compare), [...b].sort(compare)); - } - - const ka = Object.keys(a); - const kb = Object.keys(b); - if (ka.length !== kb.length) return false; - for (i = 0; i < ka.length; i++) { - if (!eq(a[ka[i]], b[ka[i]])) return false; - } - return true; -} diff --git a/clients/tagshow/src/eq_test.js b/clients/tagshow/src/eq_test.js deleted file mode 100644 index fde114c0b8..0000000000 --- a/clients/tagshow/src/eq_test.js +++ /dev/null @@ -1,67 +0,0 @@ -// @flow - -import {assert} from 'chai'; -import {suite, test} from 'mocha'; -import eq from './eq.js'; -import {Ref} from 'noms'; - -suite('eq', () => { - test('different', () => { - const r0 = Ref.parse('sha1-0000000000000000000000000000000000000000'); - const r1 = Ref.parse('sha1-0000000000000000000000000000000000000001'); - - const different = [ - null, - undefined, - true, - false, - 1, - 2, - '1', - {}, - {a: 3}, - {a: 4}, - {a: 3, b: 5}, - [], - [6], - [7], - [6, 7], - new Set(), - new Set([8]), - new Set([9]), - new Set([8, 9]), - new Map(), - new Map([[10, 11]]), - new Map([[10, 12]]), - new Map([[10, 11], [13, 14]]), - r0, - r1, - ]; - for (let i = 0; i < different.length; i++) { - for (let j = 0; j < different.length; j++) { - assert.equal(i === j, eq(different[i], different[j]), `${different[i]} == ${different[j]}`); - } - } - }); - - test('same', () => { - const r1 = Ref.parse('sha1-0000000000000000000000000000000000000000'); - const r2 = Ref.parse('sha1-0000000000000000000000000000000000000000'); - const same = [ - [new Set([1, 2]), new Set([2, 1])], - [new Map([[1, 2], [3, 4]]), new Map([[3, 4], [1, 2]])], - [{a: 1, b: 2}, {b: 2, a: 1}], - [new Set([{a: 1}]), new Set([{a: 1}])], - [new Map([[{a: 1}, {b: 2}]]), new Map([[{a: 1}, {b: 2}]])], - [new Set([r1]), new Set([r2])], - [new Map([[r1, 42]]), new Map([[r2, 42]])], - ]; - for (const vs of same) { - for (let i = 0; i < vs.length; i++) { - for (let j = 0; j < vs.length; j++) { - assert.equal(true, eq(vs[i], vs[i]), `${vs[i]} == ${vs[j]}`); - } - } - } - }); -}); diff --git a/clients/tagshow/src/main.js b/clients/tagshow/src/main.js deleted file mode 100644 index 6135040e13..0000000000 --- a/clients/tagshow/src/main.js +++ /dev/null @@ -1,28 +0,0 @@ -// @flow - -import {DataStore, HttpStore} from 'noms'; -import queryString from 'query-string'; -import React from 'react'; // eslint-disable-line no-unused-vars -import ReactDOM from 'react-dom'; -import Root from './root.js'; - -window.onload = window.onhashchange = render; - -const nomsServer: ?string = process.env.NOMS_SERVER; -if (!nomsServer) { - throw new Error('NOMS_SERVER not set'); -} - -function updateQuery(qs: {[key: string]: string}) { - location.hash = queryString.stringify(qs); -} - -function render() { - const qs = Object.freeze(queryString.parse(location.hash)); - const target = document.getElementById('root'); - const store = new DataStore(new HttpStore(nomsServer)); - - ReactDOM.render( - , - target); -} diff --git a/clients/tagshow/src/photo.js b/clients/tagshow/src/photo.js deleted file mode 100644 index 02ababbcf7..0000000000 --- a/clients/tagshow/src/photo.js +++ /dev/null @@ -1,85 +0,0 @@ -// @flow - -import eq from './eq.js'; -import React from 'react'; -import type {ChunkStore, Ref, NomsMap, Struct} from 'noms'; -import {readValue} from 'noms'; - -type DefaultProps = { - onLoad: () => void, -}; - -type Props = { - onLoad: () => void, - photoRef: Ref, - style: Object, - store: ChunkStore -}; - -type State = { - photo: ?Struct, - sizes: Array<{size: Struct, url: string}> -}; - -export default class Photo extends React.Component { - state: State; - static defaultProps: DefaultProps; - - constructor(props: Props) { - super(props); - this.state = { - photo: null, - sizes: [], - }; - } - - shouldComponentUpdate(nextProps: Props, nextState: State) : boolean { - return !eq(nextProps, this.props) || !eq(nextState, this.state); - } - - async _updatePhoto(props: Props) : Promise { - function area(size: Struct) : number { - return size.get('Width') * size.get('Height'); - } - - const photo: Struct = await readValue(props.photoRef, props.store); - - // Sizes is a Map(Size, String) where the string is a URL. - const sizes = []; - const s: NomsMap = photo.get('Sizes'); - await s.forEach((url, size) => { - sizes.push({size, url}); - }); - sizes.sort((a, b) => area(a.size) - area(b.size)); - this.setState({photo, sizes}); - } - - render() : ?React.Element { - this._updatePhoto(this.props); - - if (!this.state.photo || this.state.sizes.length === 0) { - return null; - } - - return ( - - ); - } - - getURL() : string { - // If there are some remote URLs we can use, just pick the most appropriate size. We need the - // smallest one that is bigger than our current dimensions. - const sizes = this.state.sizes; - const w = this.props.style.width || 0; - const h = this.props.style.height || 0; - const size = sizes.find(({size}) => size.get('Width') >= w && size.get('Height') >= h); - return size ? size.url : sizes[sizes.length - 1].url; - } -} - -Photo.defaultProps = { - onLoad() {}, -}; diff --git a/clients/tagshow/src/preview.js b/clients/tagshow/src/preview.js deleted file mode 100644 index a50ea43739..0000000000 --- a/clients/tagshow/src/preview.js +++ /dev/null @@ -1,23 +0,0 @@ -// @flow - -import Photo from './photo.js'; -import React from 'react'; -import type {ChunkStore, Ref} from 'noms'; - -const photoStyle = { - display: 'inline-block', - marginRight: 5, - height: 300, -}; - -type Props = { - photos: Array, - store: ChunkStore, -}; - -export default function Preview(props: Props) : React.Element { - return
{ - props.photos.map(r => ) - }
; -} diff --git a/clients/tagshow/src/root.js b/clients/tagshow/src/root.js deleted file mode 100644 index 48e2a6503f..0000000000 --- a/clients/tagshow/src/root.js +++ /dev/null @@ -1,130 +0,0 @@ -// @flow - -import DatasetPicker from './datasetpicker.js'; -import eq from './eq.js'; -import React from 'react'; -import SlideShow from './slideshow.js'; -import TagChooser from './tagchooser.js'; -import type {DataStore} from 'noms'; -import {invariant, NomsMap, NomsSet, Ref} from 'noms'; - -type QueryStringObject = {[key: string]: string}; - -type Props = { - store: DataStore, - qs: QueryStringObject, - updateQuery: (qs: QueryStringObject) => void, -}; - -type State = { - selectedTags: Set, - selectedPhotos: Array, - tags: Array, -}; - -export default class Root extends React.Component { - state: State; - - constructor(props: Props) { - super(props); - this.state = { - selectedTags: new Set(), - selectedPhotos: [], - tags: [], - }; - } - - async _updateState(props: Props) : Promise { - const selectedTags = this.getSelectedTags(props); - const tags = []; - const selectedPhotos: Array = []; - - if (props.qs.ds) { - const {store} = props; - const commit = await store.head(props.qs.ds); - invariant(commit); - const v = commit.get('value'); - if (v instanceof NomsMap) { - const seenRefs: Set = new Set(); - - const sets = []; - - await v.forEach((value, tag) => { - tags.push(tag); - if (selectedTags.has(tag) && value instanceof NomsSet) { - sets.push(value); - } - }); - - for (const s of sets) { - await s.forEach(r => { - const rs = r.toString(); - if (!seenRefs.has(rs)) { - seenRefs.add(rs); - selectedPhotos.push(r); - } - }); - } - - // This sorts the photos deterministically, by the ref - // TODO: Sort by create date if it ends up that the common image type - // has a create date. - selectedPhotos.sort((a, b) => a.equals(b) ? 0 : a.less(b) ? -1 : 1); - tags.sort(); - } - - this.setState({selectedTags, tags, selectedPhotos}); - } - } - - shouldComponentUpdate(nextProps: Props, nextState: State) : boolean { - return !eq(nextProps, this.props) || !eq(nextState, this.state); - } - - handleDataSetPicked(ds: string) { - const qs = Object.assign({}, this.props.qs, {ds}); - this.props.updateQuery(qs); - } - - getSelectedTags(props: Props) : Set { - const tags = props.qs.tags; - if (!tags) { - return new Set(); - } - return new Set(tags.split(',')); - } - - handleTagsChange(selectedTags: Set) { - // FIXME: https://github.com/facebook/flow/issues/1059 - const workaround: any = selectedTags; - const tags = [...workaround].join(','); - const qs = Object.assign({}, this.props.qs, {tags}); - this.props.updateQuery(qs); - } - - handleTagsConfirm() { - const qs = Object.assign({}, this.props.qs, {show: '1'}); - this.props.updateQuery(qs); - } - - render() : React.Element { - this._updateState(this.props); - - if (!this.props.qs.ds) { - return this.handleDataSetPicked(ds)}/>; - } - - if (!this.props.qs.show || this.state.selectedTags.size === 0) { - return this.handleTagsChange(selectedTags)} - onConfirm={() => this.handleTagsConfirm()}/>; - } - - return ; - } -} diff --git a/clients/tagshow/src/slideshow.js b/clients/tagshow/src/slideshow.js deleted file mode 100644 index 9038954f3b..0000000000 --- a/clients/tagshow/src/slideshow.js +++ /dev/null @@ -1,109 +0,0 @@ -// @flow - -import Photo from './photo.js'; -import React from 'react'; -import type {ChunkStore, Ref} from 'noms'; - -const containerStyle = { - position: 'absolute', - left: 0, - top: 0, - width: '100%', - height: '100%', - overflow: 'hidden', - - display: 'flex', - alignItems: 'center', - justifyContent: 'center', -}; - -type Props = { - store: ChunkStore, - photos: Array, -}; - -type State = { - index: number, -}; - -export default class SlideShow extends React.Component { - state: State; - - constructor(props: Props) { - super(props); - this.state = { - index: 0, - }; - } - - handleTimeout() { - let index = this.state.index + 1; - if (index >= this.props.photos.length) { - index = 0; - } - this.setState({index}); - } - - render() : ?React.Element { - const photoRef = this.props.photos[this.state.index]; - if (!photoRef) { - return null; - } - - return ( -
- this.handleTimeout()} /> -
- ); - } -} - -type ItemProps = { - onTimeout: () => void, - photoRef: Ref, - store: ChunkStore -}; - -type ItemState = { - timerId: number -}; - -class Item extends React.Component { - state: ItemState; - - constructor(props: ItemProps) { - super(props); - this.state = { - timerId: 0, - }; - } - - setTimeout() { - this.setState({ - timerId: window.setTimeout(this.props.onTimeout, 3000), - }); - } - - componentWillUnmount() { - window.clearTimeout(this.state.timerId); - } - - render() : React.Element { - const style = { - objectFit: 'contain', - width: window.innerWidth, - height: window.innerHeight, - }; - - return ( - this.setTimeout()} - photoRef={this.props.photoRef} - style={style}/> - ); - } -} diff --git a/clients/tagshow/src/tagchooser.js b/clients/tagshow/src/tagchooser.js deleted file mode 100644 index cb6c7341bc..0000000000 --- a/clients/tagshow/src/tagchooser.js +++ /dev/null @@ -1,74 +0,0 @@ -// @flow - -import Preview from './preview.js'; -import React from 'react'; -import TagList from './taglist.js'; -import type {ChunkStore, Ref} from 'noms'; - -const styles = { - root: { - display: 'flex', - flexDirection: 'column', - height: '100%', - }, - - panes: { - display: 'flex', - flex: 1, - }, - - left: { - overflowX: 'hidden', - overflowY: 'auto', - marginRight: '1em', - }, - - right: { - flex: 1, - overflowX: 'hidden', - overflowY: 'auto', - padding: '1em', - }, - - bottom: { - textAlign: 'center', - }, - - button: { - fontSize: '1.5em', - margin: '1em', - width: '50%', - }, -}; - -type Props = { - store: ChunkStore, - tags: Array, - selectedPhotos: Array, - selectedTags: Set, - onChange: (selected: Set) => void, - onConfirm: () => void, -}; - -export default function TagChooser(props: Props) : React.Element { - return ( -
-
-
- -
-
- -
-
-
- -
-
- ); -} diff --git a/clients/tagshow/src/taglist.js b/clients/tagshow/src/taglist.js deleted file mode 100644 index 235864906c..0000000000 --- a/clients/tagshow/src/taglist.js +++ /dev/null @@ -1,34 +0,0 @@ -// @flow - -import React from 'react'; - -const tagStyle = { - display: 'block', - margin: '3px', - marginRight: '25px', - whiteSpace: 'nowrap', -}; - -type Props = { - selected: Set, - tags: Array, - onChange: (selected: Set) => void, -}; - -function handleChange(props: Props, tag: string) { - const selected = new Set(props.selected); - selected.has(tag) ? selected.delete(tag) : selected.add(tag); - props.onChange(selected); -} - -export default function TagList(props: Props) : React.Element { - const tags = [...props.tags].sort(); - const labels = tags.map(tag => ); - - return
{labels}
; -} diff --git a/clients/tagshow/webpack.config.js b/clients/tagshow/webpack.config.js deleted file mode 100644 index 4f2dd3789a..0000000000 --- a/clients/tagshow/webpack.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = require('noms-webpack-config')({ - requiredEnvVars: ['NOMS_SERVER'], -});