diff options
Diffstat (limited to 'app/client/common')
| -rw-r--r-- | app/client/common/currentTask.component.js | 2 | ||||
| -rw-r--r-- | app/client/common/fileList.component.js | 2 | ||||
| -rw-r--r-- | app/client/common/fileViewer.component.js | 87 | ||||
| -rw-r--r-- | app/client/common/index.js | 5 | ||||
| -rw-r--r-- | app/client/common/taskList.component.js | 2 | ||||
| -rw-r--r-- | app/client/common/timeline.component.js | 166 |
6 files changed, 232 insertions, 32 deletions
diff --git a/app/client/common/currentTask.component.js b/app/client/common/currentTask.component.js index ef976bc..c053a13 100644 --- a/app/client/common/currentTask.component.js +++ b/app/client/common/currentTask.component.js @@ -31,7 +31,7 @@ function CurrentTask ({ cpu, gpu, processor }) { ? <span>(currently #{epoch})</span> : ""} <br/><br/> - <div className='quiet'>{last_message}</div> + <div className='quiet lastMessage'>{last_message}</div> </div> ) } diff --git a/app/client/common/fileList.component.js b/app/client/common/fileList.component.js index 8f79148..12f6865 100644 --- a/app/client/common/fileList.component.js +++ b/app/client/common/fileList.component.js @@ -119,7 +119,7 @@ export const FileRow = props => { <div className={"epoch " + util.hush_null(epoch)[0]}>{epoch > 0 ? 'ep. ' + epoch : ''}</div> } {fields.has('date') && - <div className={"date " + util.carbon_date(date)}>{moment(date).format("YYYY-MM-DD")}</div> + <div className={"date " + util.carbon_date(date)} title={moment(date).format("YYYY-MM-DD HH:mm")}>{moment(date).format("YYYY-MM-DD")}</div> } {fields.has('datetime') && <div className={"datetime"}> diff --git a/app/client/common/fileViewer.component.js b/app/client/common/fileViewer.component.js index d98073b..4938650 100644 --- a/app/client/common/fileViewer.component.js +++ b/app/client/common/fileViewer.component.js @@ -21,11 +21,37 @@ const video_types = { 'mp4': 'video/mp4', } +const THROTTLE_FETCH_TIME = 200 + class FileViewer extends Component { state = { loading: false, stale: false, - buffer: {} + buffer: {}, + url: null, + } + + componentDidMount(){ + this.fetch() + } + + componentDidUpdate(prevProps){ + if (this.props.file !== prevProps.file) { + this.deferFetch() + } + } + + componentWillUnmount(){ + if (this.state.url) { + window.URL.revokeObjectURL(this.state.url) + } + } + + deferFetch(){ + clearTimeout(this.timeout) + this.timeout = setTimeout(() => { + this.fetch() + }, THROTTLE_FETCH_TIME) } fetch() { @@ -40,48 +66,56 @@ class FileViewer extends Component { this.setState({ buffer: null, loading: true }) if (thumbnail) { - console.log('fetch thumbnail', fn) + // console.log('fetch thumbnail', fn) const size = parseInt(thumbnail) || 200 actions.socket .thumbnail({ module, fn, size }) .then(this.loadBuffer.bind(this)) } else { - console.log('fetch file', fn) + // console.log('fetch file', fn) actions.socket .read_file({ module, fn }) .then(this.loadBuffer.bind(this)) } } + loadBuffer(buffer) { - console.log('fetched buffer', buffer) + // console.log('fetched buffer', buffer) + const { name, buf } = buffer + const ext = extension(name) + if (this.state.url) { + window.URL.revokeObjectURL(this.state.url) + } + let url + if (buf) { + if (ext in image_types) { + url = getURLFor(buf, image_types[ext]) + } else if (ext in audio_types) { + url = getURLFor(buf, audio_types[ext]) + } else if (ext in video_types) { + url = getURLFor(buf, video_types[ext]) + } else { + url = ab2str(buf) + } + } const { stale } = this.state - this.setState({ buffer, loading: false, stale: false, }, () => { - console.log('loaded') + this.setState({ ext, url, buffer, loading: false, stale: false, }, () => { + // console.log('loaded') if (stale) { - console.log('stale, fetching...') + // console.log('stale, fetching...') this.fetch() } }) } - componentDidMount(){ - this.fetch() - } - - componentDidUpdate(nextProps){ - if (this.props.file !== nextProps.file) { - this.fetch() - } - } - render() { const { file } = this.props if (!file) { return <div className='fileViewer'></div> } - const { loading, buffer } = this.state + const { loading, buffer, url, ext } = this.state if (loading) { - return <div className='fileViewer'>Loading...</div> + return <div className='fileViewer'><span>Loading...</span></div> } const { error, @@ -90,24 +124,23 @@ class FileViewer extends Component { buf, } = buffer if (error) { - return <div className='fileViewer'>{error}</div> + return <div className='fileViewer'><span>{error}</span></div> } if (!name) { return <div className='fileViewer'></div> } - if (!buf) { - return <div className='fileViewer'>File empty</div> + if (!buf || !url) { + return <div className='fileViewer'><span>File empty</span></div> } - const ext = extension(name) let tag; if (ext in image_types) { - tag = <img src={getURLFor(buf, image_types[ext])} /> + tag = <img src={url} /> } else if (ext in audio_types) { - tag = <audio src={getURLFor(buf, audio_types[ext])} controls autoplay /> + tag = <audio src={url} controls autoplay /> } else if (ext in video_types) { - tag = <video src={getURLFor(buf, video_types[ext])} controls autoplay /> + tag = <video src={url} controls autoplay /> } else { - tag = <div className='text'>{ab2str(buf)}</div> + tag = <div className='text'>{url}</div> } return ( <div className='fileViewer'>{tag}</div> diff --git a/app/client/common/index.js b/app/client/common/index.js index e120597..15511fd 100644 --- a/app/client/common/index.js +++ b/app/client/common/index.js @@ -21,8 +21,9 @@ import Progress from './progress.component' import Select from './select.component' import SelectGroup from './selectGroup.component' import Slider from './slider.component' -import TextInput from './textInput.component' import TaskList from './taskList.component' +import TextInput from './textInput.component' +import Timeline from './timeline.component' import * as Views from './views' export { @@ -34,5 +35,5 @@ export { TextInput, NumberInput, Slider, Select, SelectGroup, Button, Checkbox, CurrentTask, TaskList, - ButtonGrid, AugmentationGrid, + ButtonGrid, AugmentationGrid, Timeline, }
\ No newline at end of file diff --git a/app/client/common/taskList.component.js b/app/client/common/taskList.component.js index 272ff80..a2f1df0 100644 --- a/app/client/common/taskList.component.js +++ b/app/client/common/taskList.component.js @@ -59,7 +59,7 @@ class TaskList extends Component { <div className='row'> <div className='activity'>{task.activity} {task.module}</div> <div className='dataset'>{dataset_link}</div> - <div className={"age " + util.carbon_date(task.updated_at)}>{util.get_age(task.updated_at)}</div> + <div className={"age " + util.carbon_date(task.updated_at)} title={task.updated_at}>{util.get_age(task.updated_at)}</div> <div className='options'> <span className='destroy' onClick={() => this.handleDestroy(task)}>x</span> </div> diff --git a/app/client/common/timeline.component.js b/app/client/common/timeline.component.js new file mode 100644 index 0000000..d3739b1 --- /dev/null +++ b/app/client/common/timeline.component.js @@ -0,0 +1,166 @@ +import { h, Component } from 'preact' +import { clamp, norm } from '../util/math' + +const initialState = { + dir: '/', + start: null, + end: null, + cursor: null, + selection: null, + width: 0, + ratio: 0, + loading: true +} + +export default class Timeline extends Component { + state = { ...initialState } + + constructor() { + super() + this.handleMouseDown = this.handleMouseDown.bind(this) + this.handleMouseMove = this.handleMouseMove.bind(this) + this.handleMouseUp = this.handleMouseUp.bind(this) + this.computeOffset = this.computeOffset.bind(this) + } + + componentDidMount() { + const { sequence } = this.props + window.addEventListener('resize', this.computeOffset) + window.addEventListener('mousemove', this.handleMouseMove) + window.addEventListener('mouseup', this.handleMouseUp) + this.computeOffset() + if (sequence) { + this.reset() + } + } + + componentDidUpdate(prevProps) { + const { sequence } = this.props + if (sequence !== prevProps.sequence) { + this.reset() + } + } + + componentWillUnmount(){ + window.removeEventListener('resize', this.computeOffset) + window.removeEventListener('mousemove', this.handleMouseMove) + window.removeEventListener('mouseup', this.handleMouseUp) + } + + reset(){ + const { sequence } = this.props + if (!sequence || !sequence.length) return + const len = sequence.length - 1 + const start = { frame: sequence[0], i: 0 } + const end = { frame: sequence[len], i: len } + const width = Math.sqrt(clamp(sequence.length / 15000, 0.3, 1.0)) * Math.min(window.innerWidth - 40, 1000) + const ratio = width / sequence.length + setTimeout(() => this.computeOffset()) + this.setState({ + ...initialState, + width, + ratio, + start, + end, + }) + } + + computeOffset(){ + if (this.ref) { + this.offset = this.ref.getBoundingClientRect() + } + } + + computeFrame(e){ + const { sequence } = this.props + if (!sequence || !sequence.length) return null + let x = (e.pageX - this.offset.left) / this.offset.width + let y = (e.pageY - this.offset.top) / this.offset.height + if (this.state.dragging) { + x = clamp(x, 0, 1) + y = clamp(y, 0, 1) + } + if (0 <= x && x <= 1 && 0 <= y && y <= 1) { + const index = Math.floor(x * (sequence.length-1)) + return { frame: sequence[index], i: index } + } + return null + } + + handleMouseDown(e) { + this.setState({ dragging: true }) + const frame = this.computeFrame(e) + if (frame) { + this.props.onPick && this.props.onPick(frame) + this.props.onSelect && this.props.onSelect({ start: frame, end: frame }) + this.setState({ + start: frame, + end: frame, + }) + } + } + + handleMouseMove(e) { + const frame = this.computeFrame(e) + if (frame) { + this.props.onCursor && this.props.onCursor(frame) + if (this.state.dragging) { + let start = this.state.start + let end = frame + this.setState({ + cursor: frame, + end: frame, + }) + if (this.props.onSelect) { + if (end.i < start.i) { + [start, end] = [end, start] + } + this.props.onSelect({ start, end }) + } + } else { + this.setState({ + cursor: frame + }) + } + } + } + + handleMouseUp(e) { + if (!this.state.dragging) return + let { start, end } = this.state + if (end.i < start.i) { + [start, end] = [end, start] + } + this.props.onSelect && this.props.onSelect({ start, end }) + this.setState({ dragging: false }) + } + + render() { + const { sequence } = this.props + const { loading, start, end, cursor, width, ratio } = this.state + return ( + <div + className='timeline' + style={{ width }} + ref={ref => this.ref = ref} + onMouseDown={this.handleMouseDown} + > + {ratio && start && end && this.renderSelection(start, end, ratio)} + {ratio && cursor && this.renderCursor(cursor, ratio)} + </div> + ) + } + renderCursor(cursor, ratio){ + const left = cursor.i * ratio + return ( + <div key='cursor' className='cursor' style={{ left }} /> + ) + } + renderSelection(start, end, ratio){ + const left = Math.min(start.i, end.i) * ratio + const width = Math.max(Math.abs(start.i - end.i) * ratio, 1) + return ( + <div key='selection' className='selection' style={{ left, width }} /> + ) + } +} |
