summaryrefslogtreecommitdiff
path: root/app/client/common/timeline.component.js
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2018-09-23 18:34:30 +0200
committerJules Laplace <julescarbon@gmail.com>2018-09-23 18:34:30 +0200
commite2c13867baf66a6ae22c975563f8755e782d07a9 (patch)
treefa7c51a6a6628a5b1e1173c6cd1a381ff15cc500 /app/client/common/timeline.component.js
parentf3885f7d43dffe4e0fcf49e3e8f9f9e248bc6f76 (diff)
adding timeline component
Diffstat (limited to 'app/client/common/timeline.component.js')
-rw-r--r--app/client/common/timeline.component.js166
1 files changed, 166 insertions, 0 deletions
diff --git a/app/client/common/timeline.component.js b/app/client/common/timeline.component.js
new file mode 100644
index 0000000..3008c37
--- /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.2, 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 }} />
+ )
+ }
+}