tagshow: factor out a preview view

This commit is contained in:
Aaron Boodman
2015-09-03 14:36:25 -07:00
parent 53e9d156de
commit 718d222c9b
6 changed files with 150 additions and 79 deletions

45
clients/tagshow/photo.js Normal file
View File

@@ -0,0 +1,45 @@
'use strict';
var Immutable = require('immutable');
var ImmutableRenderMixin = require('react-immutable-render-mixin');
var noms = require('noms')
var React = require('react');
var Photo = React.createClass({
mixins: [ImmutableRenderMixin],
propTypes: {
onTimeout: React.PropTypes.func.isRequired,
photoRef: React.PropTypes.instanceOf(noms.Ref),
style: React.PropTypes.object,
},
getInitialState: function() {
return {
blob: null,
};
},
handleLoad: function(e) {
URL.revokeObjectURL(e.target.src);
},
render: function() {
this.props.photoRef.deref().then(
p => p.get('image').deref()).then(
b => this.setState({blob: b}));
if (this.state.blob == null) {
return <span style={this.props.style}>loading...</span>;
}
return (
<img
style={this.props.style}
src={URL.createObjectURL(this.state.blob)}
onLoad={this.handleLoad}/>
);
},
});
module.exports = React.createFactory(Photo);

View File

@@ -0,0 +1,33 @@
'use strict';
var Immutable = require('immutable');
var ImmutableRenderMixin = require('react-immutable-render-mixin');
var noms = require('noms')
var Photo = require('./photo.js');
var React = require('react');
var photoStyle = {
display: 'inline-block',
marginRight: '1em',
maxHeight: 300,
};
var Preview = React.createClass({
mixins: [ImmutableRenderMixin],
propTypes: {
photos: React.PropTypes.instanceOf(Immutable.List),
},
render: function() {
return (
<div>
{
this.props.photos.map(p => <Photo photoRef={p} style={photoStyle}/>).toArray()
}
</div>
);
},
});
module.exports = React.createFactory(Preview);

View File

@@ -20,7 +20,7 @@ var Root = React.createClass({
getInitialState: function() {
return {
selected: Immutable.Set(),
selectedPhotos: Immutable.Set(),
selectedPhotos: Immutable.List(),
};
},
@@ -38,8 +38,36 @@ var Root = React.createClass({
return Immutable.Set(tags);
},
handleTagChoose: function(tags) {
this.props.updateQuery(this.props.qs.set('tags', tags.toArray().join(',')).set('show', 1));
setSelectedPhotos: function(ds, selectedTags) {
ds
.then(head => head.get('value').deref())
.then(tags => {
return Promise.all(
tags.filter((v, t) => selectedTags.has(t))
.valueSeq()
.map(ref => ref.deref()))
}).then(sets => {
this.setState({
selectedPhotos:
Immutable.List(
Immutable.Set(sets[0])
.intersect(...sets)
// This sorts the photos deterministically, by the ref of their image
// blob.
// TODO: Sort by create date if it ends up that the common image type
// has a create date.
.sort((a, b) => a.ref < b.ref)
)
});
});
},
handleTagsChange: function(tags) {
this.props.updateQuery(this.props.qs.set('tags', tags.toArray().join(',')));
},
handleTagsChoose: function() {
this.props.updateQuery(this.props.qs.set('show', 1));
},
render: function() {
@@ -49,19 +77,23 @@ var Root = React.createClass({
var dataset = noms.getDataset(this.props.pRoot, this.props.qs.get('ds'))
.then(ref => ref.deref());
var selectedTags = this.getSelectedTags();
if (!this.props.qs.get('show')) {
this.setSelectedPhotos(dataset, selectedTags);
if (!this.props.qs.get('show') || selectedTags.isEmpty()) {
return (
<TagChooser
ds={dataset}
selected={this.getSelectedTags()}
onChoose={this.handleTagChoose}/>
selectedPhotos={this.state.selectedPhotos}
selectedTags={this.getSelectedTags()}
onChange={this.handleTagsChange}
onChoose={this.handleTagsChoose}/>
);
}
return (
<SlideShow ds={dataset} tags={selectedTags}/>
<SlideShow ds={dataset} photos={this.state.selectedPhotos}/>
);
},
});

View File

@@ -2,7 +2,8 @@
var Immutable = require('immutable');
var ImmutableRenderMixin = require('react-immutable-render-mixin');
var noms = require('noms')
var noms = require('noms');
var Photo = require('./photo.js');
var React = require('react');
var containerStyle = {
@@ -29,49 +30,25 @@ var SlideShow = React.createClass({
propTypes: {
ds: React.PropTypes.instanceOf(Immutable.Map),
tags: React.PropTypes.instanceOf(Immutable.Set),
photos: React.PropTypes.instanceOf(Immutable.Set),
},
getInitialState: function() {
return {
photos: Immutable.Set(),
index: 0,
nextSlideTime: 0,
}
},
handleTimeout: function() {
var newIndex = this.state.index + 1;
if (newIndex >= this.state.photos.size) {
if (newIndex >= this.props.photos.size) {
newIndex = 0;
}
this.setState({index: newIndex});
},
render: function() {
this.props.ds
.then(head => head.get('value').deref())
.then(tags => {
return Promise.all(
tags.filter((v, t) => this.props.tags.has(t))
.valueSeq()
.map(ref => ref.deref()))
}).then(sets => {
this.setState({
photos:
Immutable.List(
Immutable.Set(sets[0])
.intersect(...sets)
// This sorts the photos deterministically, by the ref of their image
// blob.
// TODO: Sort by create date if it ends up that the common image type
// has a create date.
.sort((a, b) => a.ref < b.ref)
)
});
});
var photoRef = this.state.photos.get(this.state.index);
var photoRef = this.props.photos.get(this.state.index);
if (!photoRef) {
return null;
}
@@ -97,8 +74,7 @@ var Item = React.createClass({
getInitialState: function() {
return {
blob: null,
tags: Immutable.Set(),
timerId: 0,
};
},
@@ -112,28 +88,11 @@ var Item = React.createClass({
window.clearTimeout(this.state.timerId);
},
handleLoad: function(e) {
URL.revokeObjectURL(e.target.src);
},
render: function() {
this.props.photoRef.deref().then(
p => p.get('image').deref()).then(
b => this.setState({blob: b}));
this.props.photoRef.deref().then(
p => p.get('tags').deref()).then(
tags => this.setState({tags: tags}));
if (this.state.blob == null) {
return <span>loading...</span>;
}
return (
<img
style={imageStyle}
src={URL.createObjectURL(this.state.blob)}
onLoad={this.handleLoad}/>
<Photo
photoRef={this.props.photoRef}
style={imageStyle}/>
);
},
});

View File

@@ -2,6 +2,7 @@
var Immutable = require('immutable');
var ImmutableRenderMixin = require('react-immutable-render-mixin');
var Preview = require('./preview.js');
var React = require('react');
var TagList = require('./taglist.js');
@@ -10,36 +11,36 @@ var TagChooser = React.createClass({
propTypes: {
ds: React.PropTypes.instanceOf(Immutable.Map),
selected: React.PropTypes.instanceOf(Immutable.Set),
selectedPhotos: React.PropTypes.instanceOf(Immutable.Set),
selectedTags: React.PropTypes.instanceOf(Immutable.Set),
onChange: React.PropTypes.func.isRequired,
onChoose: React.PropTypes.func.isRequired,
},
getInitialState: function() {
return {
selected: this.props.selected,
tags: Immutable.Map(),
};
},
handleOnChange: function(selected) {
this.setState({selected: selected});
},
handleSubmit: function(e) {
this.props.onChoose(this.state.selected);
this.props.onChoose();
e.preventDefault();
},
render: function() {
return (
<form onSubmit={this.handleSubmit}>
<input type="submit" value="OK!"/>
<br/>
<TagList
ds={this.props.ds}
selected={this.state.selected}
onChange={this.handleOnChange}/>
</form>
<table width="100%">
<tr>
<td style={{verticalAlign: 'top'}}>
<form onSubmit={this.handleSubmit}>
<input type="submit" value="OK!"/>
<br/>
<TagList
ds={this.props.ds}
selected={this.props.selectedTags}
onChange={this.props.onChange}/>
</form>
</td>
<td style={{verticalAlign: 'top'}} width="100%">
<Preview photos={this.props.selectedPhotos}/>
</td>
</tr>
</table>
);
},
});

View File

@@ -6,6 +6,7 @@ var React = require('react');
var tagStyle = {
display: 'block',
whiteSpace: 'nowrap',
};
var TagCloud = React.createClass({