Delete tagshow

This commit is contained in:
Benjamin Kalman
2016-02-10 17:00:14 -08:00
parent 3331fe4006
commit 472833e90e
19 changed files with 1 additions and 829 deletions
-1
View File
@@ -48,4 +48,3 @@ cache:
- clients/crunchbase/ui/node_modules
- clients/pitchmap/ui/node_modules
- clients/splore/node_modules
- clients/tagshow/node_modules
+1 -1
View File
@@ -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)
-5
View File
@@ -1,5 +0,0 @@
.babelrc
.eslintrc
.flowconfig
node_modules
tagshow.js
-18
View File
@@ -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()
-46
View File
@@ -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="<apikey>" -api-key-secret="<apikeysecret>" -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="<apikey>" -api-key-secret="<apikeysecret>" -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="<input dataset to read (flickr or picasa)>" -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
-19
View File
@@ -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()
-20
View File
@@ -1,20 +0,0 @@
<!doctype html>
<head>
<meta charset="UTF-8">
<title>tagshow</title>
<script src="tagshow.js"></script>
<style>
html, body, #root {
background: black;
color: white;
margin: 0;
overflow: hidden;
padding: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
-37
View File
@@ -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/"
}
}
-75
View File
@@ -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<string>
};
export default class DatasetPicker extends React.Component<DefaultProps, Props, State> {
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<void> {
const store = props.store;
const rootRef = await store.getRoot();
const datasets: NomsMap<string, Ref> = 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(<option value={v} key={v}>{v}</option>);
}
return <form>
Choose dataset:
<br/>
<select value={this.props.selected}
onChange={e => this.handleSelectChange(e)}>
<option/>
{options}
</select>
</form>;
}
}
DatasetPicker.defaultProps = {selected: ''};
-54
View File
@@ -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;
}
-67
View File
@@ -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]}`);
}
}
}
});
});
-28
View File
@@ -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(
<Root qs={qs} store={store} updateQuery={updateQuery}/>,
target);
}
-85
View File
@@ -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<DefaultProps, Props, State> {
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<void> {
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<Struct, string> = 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 (
<img
style={this.props.style}
src={this.getURL()}
onLoad={this.props.onLoad}/>
);
}
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() {},
};
-23
View File
@@ -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<Ref>,
store: ChunkStore,
};
export default function Preview(props: Props) : React.Element {
return <div>{
props.photos.map(r => <Photo key={r.toString()} photoRef={r} store={props.store}
style={photoStyle}/>)
}</div>;
}
-130
View File
@@ -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<string>,
selectedPhotos: Array<Ref>,
tags: Array<string>,
};
export default class Root extends React.Component<void, Props, State> {
state: State;
constructor(props: Props) {
super(props);
this.state = {
selectedTags: new Set(),
selectedPhotos: [],
tags: [],
};
}
async _updateState(props: Props) : Promise<void> {
const selectedTags = this.getSelectedTags(props);
const tags = [];
const selectedPhotos: Array<Ref> = [];
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<string> = 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<string> {
const tags = props.qs.tags;
if (!tags) {
return new Set();
}
return new Set(tags.split(','));
}
handleTagsChange(selectedTags: Set<string>) {
// 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 <DatasetPicker store={this.props.store}
onChange={ds => this.handleDataSetPicked(ds)}/>;
}
if (!this.props.qs.show || this.state.selectedTags.size === 0) {
return <TagChooser
store={this.props.store}
tags={this.state.tags}
selectedPhotos={this.state.selectedPhotos}
selectedTags={this.state.selectedTags}
onChange={selectedTags => this.handleTagsChange(selectedTags)}
onConfirm={() => this.handleTagsConfirm()}/>;
}
return <SlideShow store={this.props.store} photos={this.state.selectedPhotos}/>;
}
}
-109
View File
@@ -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<Ref>,
};
type State = {
index: number,
};
export default class SlideShow extends React.Component<void, Props, State> {
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 (
<div style={containerStyle}>
<Item
photoRef={photoRef}
store={this.props.store}
onTimeout={() => this.handleTimeout()} />
</div>
);
}
}
type ItemProps = {
onTimeout: () => void,
photoRef: Ref,
store: ChunkStore
};
type ItemState = {
timerId: number
};
class Item extends React.Component<void, ItemProps, ItemState> {
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 (
<Photo
store={this.props.store}
onLoad={() => this.setTimeout()}
photoRef={this.props.photoRef}
style={style}/>
);
}
}
-74
View File
@@ -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<string>,
selectedPhotos: Array<Ref>,
selectedTags: Set<string>,
onChange: (selected: Set<string>) => void,
onConfirm: () => void,
};
export default function TagChooser(props: Props) : React.Element {
return (
<div style={styles.root}>
<div style={styles.panes}>
<div style={styles.left}>
<TagList
tags={props.tags}
selected={props.selectedTags}
onChange={props.onChange}/>
</div>
<div style={styles.right}>
<Preview photos={props.selectedPhotos} store={props.store}/>
</div>
</div>
<div style={styles.bottom}>
<button style={styles.button} onClick={props.onConfirm}>
PUSH THIS BUTTON
</button>
</div>
</div>
);
}
-34
View File
@@ -1,34 +0,0 @@
// @flow
import React from 'react';
const tagStyle = {
display: 'block',
margin: '3px',
marginRight: '25px',
whiteSpace: 'nowrap',
};
type Props = {
selected: Set<string>,
tags: Array<string>,
onChange: (selected: Set<string>) => 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 => <label style={tagStyle} key={tag}>
<input type="checkbox" name="tc"
checked={props.selected.has(tag)}
onChange={() => handleChange(props, tag) }/>
{tag}
</label>);
return <div>{labels}</div>;
}
-3
View File
@@ -1,3 +0,0 @@
module.exports = require('noms-webpack-config')({
requiredEnvVars: ['NOMS_SERVER'],
});