summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2020-06-30 13:54:38 +0200
committerJules Laplace <julescarbon@gmail.com>2020-06-30 13:54:52 +0200
commit7d166ddbbbb8a7db6da3052ab01bd9e44c6f94e5 (patch)
treef1099035fa23a8c359e996cab6b11f6bf1e22fab
parent3132458de93217dbd2ebaee3faae046f30f818e1 (diff)
zoom and scroll the waveform
-rw-r--r--animism-align/frontend/actions.js2
-rw-r--r--animism-align/frontend/types.js3
-rw-r--r--animism-align/frontend/views/align/align.actions.js12
-rw-r--r--animism-align/frontend/views/align/align.reducer.js10
-rw-r--r--animism-align/frontend/views/align/components/ticks.component.js14
-rw-r--r--animism-align/frontend/views/align/components/timeline.component.js87
-rw-r--r--animism-align/frontend/views/align/components/waveform.component.js108
7 files changed, 163 insertions, 73 deletions
diff --git a/animism-align/frontend/actions.js b/animism-align/frontend/actions.js
index 69b06f0..6ce2d02 100644
--- a/animism-align/frontend/actions.js
+++ b/animism-align/frontend/actions.js
@@ -1,6 +1,7 @@
import { bindActionCreators } from 'redux'
import { actions as crudActions } from './api'
+import * as alignActions from './views/align/align.actions'
import * as siteActions from './views/site/site.actions'
import { store } from './store'
@@ -10,6 +11,7 @@ export default
.map(a => [a, crudActions[a]])
.concat([
['site', siteActions],
+ ['align', alignActions],
])
.map(p => [p[0], bindActionCreators(p[1], store.dispatch)])
.concat([
diff --git a/animism-align/frontend/types.js b/animism-align/frontend/types.js
index 337f711..d38cd68 100644
--- a/animism-align/frontend/types.js
+++ b/animism-align/frontend/types.js
@@ -7,6 +7,9 @@ export const peaks = crud_type('peaks', [])
export const text = crud_type('text', [])
export const timestamp = crud_type('timestamp', [])
export const paragraph = crud_type('paragraph', [])
+export const align = crud_type('align', [
+ 'set_display_setting',
+])
export const site = with_type('site', [
])
diff --git a/animism-align/frontend/views/align/align.actions.js b/animism-align/frontend/views/align/align.actions.js
index 10dec44..36e17b0 100644
--- a/animism-align/frontend/views/align/align.actions.js
+++ b/animism-align/frontend/views/align/align.actions.js
@@ -3,3 +3,15 @@ import { store, history } from '../../store'
import { api, post, pad, preloadImage } from '../../util'
import actions from '../../actions'
import { session } from '../../session'
+
+import { ZOOM_STEPS } from './constants'
+
+export const setScrollPosition = start_ts => dispatch => (
+ dispatch({ type: types.align.set_display_setting, key: 'start_ts', value: start_ts })
+)
+
+export const setZoom = zoom => dispatch => {
+ if (0 <= zoom && zoom < ZOOM_STEPS.length) {
+ dispatch({ type: types.align.set_display_setting, key: 'zoom', value: zoom })
+ }
+}
diff --git a/animism-align/frontend/views/align/align.reducer.js b/animism-align/frontend/views/align/align.reducer.js
index e9e98f3..c0dcfac 100644
--- a/animism-align/frontend/views/align/align.reducer.js
+++ b/animism-align/frontend/views/align/align.reducer.js
@@ -24,6 +24,16 @@ export default function alignReducer(state = initialState, action) {
duration: action.data.length / 2,
}
}
+
+ case types.align.set_display_setting:
+ return {
+ ...state,
+ timeline: {
+ ...state.timeline,
+ [action.key]: action.value,
+ }
+ }
+
default:
return state
}
diff --git a/animism-align/frontend/views/align/components/ticks.component.js b/animism-align/frontend/views/align/components/ticks.component.js
index 832144b..69866f0 100644
--- a/animism-align/frontend/views/align/components/ticks.component.js
+++ b/animism-align/frontend/views/align/components/ticks.component.js
@@ -12,13 +12,13 @@ export default class Ticks extends Component {
let secondsPerPixel = ZOOM_STEPS[zoom] / 10 // 0.1 sec / step
let pixelTimeDuration = 1 / secondsPerPixel // secs per pixel
let widthTimeDuration = width / pixelTimeDuration // secs per pixel
- console.log(secondsPerPixel, pixelTimeDuration)
- console.log('width in seconds', widthTimeDuration)
+ // console.log(secondsPerPixel, pixelTimeDuration)
+ // console.log('width in seconds', widthTimeDuration)
let secondsPerTick = ZOOM_LABEL_STEPS[zoom] // secs
let pixelsPerLabel = secondsPerTick * pixelTimeDuration
let pixelsPerTick = ZOOM_TICK_STEPS[zoom]
- console.log('pixels per label', pixelsPerLabel)
+ // console.log('pixels per label', pixelsPerLabel)
let subdivision = secondsPerTick
while (pixelsPerLabel < 200) {
@@ -27,13 +27,13 @@ export default class Ticks extends Component {
subdivision *= 2
}
- console.log('start ts', start_ts)
+ // console.log('start ts', start_ts)
let pixelOffset = (start_ts / secondsPerPixel)
let pixelRemainder = pixelOffset % pixelsPerLabel
let startOffset = pixelsPerLabel - pixelRemainder
let startTiming = (pixelOffset + startOffset) * secondsPerPixel
- let labelCount = Math.ceil(width / pixelsPerLabel)
+ let labelCount = Math.ceil(width / pixelsPerLabel) + 2
let offset, timing, tickLabels = [], ticks = []
for (var i = -1; i < labelCount; i++) {
offset = i * pixelsPerLabel + startOffset - 20
@@ -70,7 +70,7 @@ export default class Ticks extends Component {
/>
)
}
- let tickCount = Math.ceil(width / pixelsPerTick) + 1
+ let tickCount = Math.ceil(width / pixelsPerTick) + 6
for (var i = 0; i < tickCount; i += 1) {
offset = i * pixelsPerTick + startOffset - pixelsPerLabel
if (offset > durationOffset) {
@@ -84,7 +84,7 @@ export default class Ticks extends Component {
/>
)
}
- console.log(ticks.length)
+ // console.log(ticks.length)
return (
<div className='ticks'>
diff --git a/animism-align/frontend/views/align/components/timeline.component.js b/animism-align/frontend/views/align/components/timeline.component.js
index 0a97c3a..5e07c0b 100644
--- a/animism-align/frontend/views/align/components/timeline.component.js
+++ b/animism-align/frontend/views/align/components/timeline.component.js
@@ -1,28 +1,25 @@
import React, { Component } from 'react'
// import { Link } from 'react-router-dom'
-// import { bindActionCreators } from 'redux'
+import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import actions from '../../../actions'
-// import * as uploadActions from './upload.actions'
+// import * as alignActions from '../align.actions'
+import Waveform from './waveform.component'
import Ticks from './ticks.component'
-import { DEFAULT_HEIGHT, WAVEFORM_HEIGHT, ZOOM_STEPS, ZOOM_LABEL_STEPS, ZOOM_TICK_STEPS } from '../constants'
+import { ZOOM_STEPS } from '../constants'
+import { clamp } from '../../../util'
class Timeline extends Component {
constructor(props){
super(props)
- this.canvasRef = React.createRef()
this.handleKeydown = this.handleKeydown.bind(this)
+ this.handleWheel = this.handleWheel.bind(this)
}
componentDidMount() {
this.bind()
- this.resize()
- this.draw()
- }
- componentDidUpdate() {
- this.draw()
}
componentWillUnmount() {
this.unbind()
@@ -34,65 +31,24 @@ class Timeline extends Component {
document.removeEventListener('keydown', this.handleKeydown)
}
handleKeydown(e) {
- // console.log(e.shiftKey, e.keyCode)
- }
- resize() {
- const canvas = this.canvasRef.current
- canvas.width = window.innerWidth
- canvas.height = DEFAULT_HEIGHT
- }
- draw() {
- const canvas = this.canvasRef.current
- const ctx = canvas.getContext('2d')
- const w = window.innerWidth
- this.clearCanvas(ctx, w)
- this.drawCurve(ctx, w)
- }
- clearCanvas(ctx, w) {
- const h = WAVEFORM_HEIGHT
- ctx.clearRect(0, 0, w, h)
- ctx.fillStyle = 'rgba(0,0,0,0.5)'
- ctx.fillRect(0, 0, w, h)
- ctx.fillStyle = 'rgba(64,128,192,0.5)'
- }
- drawCurve(ctx, w) {
- let { peaks, timeline } = this.props
- let { start_ts, zoom, duration } = timeline
- let stepsPerPixel = ZOOM_STEPS[zoom] // 0.1 sec / step
- let indexesPerPixel = stepsPerPixel * 2
- let stepMin = start_ts * 10
- let stepDuration = duration * 10 / stepsPerPixel
- let stepMax = stepDuration - stepMin
- let stepWidth = Math.min(stepMax, w)
- stepWidth += 2 + (stepWidth % 2)
- let i = 0
- let step = stepMin
- let waveformPeak = WAVEFORM_HEIGHT / 2
- let origin = (1 - peaks[step]) * waveformPeak
- let y = origin
- let peak
- ctx.beginPath()
- ctx.moveTo(0, y)
- for (i = 0; i < stepWidth; i++) {
- step = i * indexesPerPixel + stepMin
- peak = peaks[step]
- y = (1 - peak) * waveformPeak
- ctx.lineTo(i, y)
- }
- for (i = stepWidth - 1; i > 0; i--) {
- step = i * indexesPerPixel + stepMin
- peak = peaks[step]
- y = (1 + peaks[step]) * waveformPeak
- ctx.lineTo(i, y)
+ if (e.shiftKey && e.keyCode === 189) {
+ actions.align.setZoom(this.props.timeline.zoom - 1)
+ } else if (e.shiftKey && e.keyCode === 187) {
+ actions.align.setZoom(this.props.timeline.zoom + 1)
}
- ctx.lineTo(0, origin)
- ctx.fillStyle = 'rgba(255,255,255,0.8)'
- ctx.fill()
+ }
+ handleWheel(e) {
+ let { start_ts, zoom, duration } = this.props.timeline
+ let secondsPerPixel = ZOOM_STEPS[zoom] / 10 // 0.1 sec / step
+ let widthTimeDuration = window.innerWidth * secondsPerPixel // secs per pixel
+
+ start_ts = clamp(start_ts + e.deltaY * ZOOM_STEPS[zoom], 0, duration - widthTimeDuration / 2)
+ actions.align.setScrollPosition(start_ts)
}
render() {
return (
- <div className='timeline'>
- <canvas ref={this.canvasRef} />
+ <div className='timeline' onWheel={this.handleWheel}>
+ <Waveform />
<Ticks timeline={this.props.timeline} />
</div>
)
@@ -101,11 +57,10 @@ class Timeline extends Component {
const mapStateToProps = state => ({
timeline: state.align.timeline,
- peaks: state.site.peaks,
})
const mapDispatchToProps = dispatch => ({
- // uploadActions: bindActionCreators({ ...uploadActions }, dispatch),
+ // alignActions: bindActionCreators({ ...alignActions }, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(Timeline)
diff --git a/animism-align/frontend/views/align/components/waveform.component.js b/animism-align/frontend/views/align/components/waveform.component.js
new file mode 100644
index 0000000..d4ff913
--- /dev/null
+++ b/animism-align/frontend/views/align/components/waveform.component.js
@@ -0,0 +1,108 @@
+import React, { Component } from 'react'
+// import { Link } from 'react-router-dom'
+// import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+
+import actions from '../../../actions'
+// import * as uploadActions from './upload.actions'
+
+import { DEFAULT_HEIGHT, WAVEFORM_HEIGHT, ZOOM_STEPS, ZOOM_LABEL_STEPS, ZOOM_TICK_STEPS } from '../constants'
+
+class Waveform extends Component {
+ constructor(props){
+ super(props)
+ this.canvasRef = React.createRef()
+ }
+ componentDidMount() {
+ this.resize()
+ this.draw()
+ }
+ componentDidUpdate() {
+ this.draw()
+ }
+ resize() {
+ const canvas = this.canvasRef.current
+ canvas.width = window.innerWidth
+ canvas.height = DEFAULT_HEIGHT
+ }
+ draw() {
+ const canvas = this.canvasRef.current
+ const ctx = canvas.getContext('2d')
+ const w = window.innerWidth
+ this.clearCanvas(ctx, w)
+ this.drawCurve(ctx, w)
+ }
+ clearCanvas(ctx, w) {
+ const h = WAVEFORM_HEIGHT
+ ctx.clearRect(0, 0, w, h)
+ ctx.fillStyle = 'rgba(0,0,0,0.5)'
+ ctx.fillRect(0, 0, w, h)
+ ctx.fillStyle = 'rgba(64,128,192,0.5)'
+ }
+ drawCurve(ctx, w) {
+ let { peaks, timeline } = this.props
+ let { start_ts, zoom, duration } = timeline
+
+ // console.log(start_ts)
+ // start_ts *= 10
+
+ let secondsPerPixel = ZOOM_STEPS[zoom] / 10 // 0.1 sec / step
+ let stepsPerPixel = ZOOM_STEPS[zoom] // 0.1 sec / step
+ let indexesPerPixel = stepsPerPixel * 2
+
+ let widthTimeDuration = window.innerWidth * secondsPerPixel // secs per pixel
+
+ let timeMin = start_ts
+ let timeMax = Math.min(start_ts + widthTimeDuration, duration)
+
+ let pixelMin = timeMin / secondsPerPixel
+ let pixelMax = timeMax / secondsPerPixel
+
+ // console.log('wf start_ts', pixelMin)
+
+ let stepMin = Math.floor(pixelMin * 2)
+ let stepMax = Math.floor(pixelMax * 2)
+ let stepWidth = stepMax - stepMin
+
+ let i = 0
+ let step = stepMin
+ let waveformPeak = WAVEFORM_HEIGHT / 2
+ let origin = (1 - peaks[step]) * waveformPeak
+ let y = origin
+ let peak
+
+ ctx.beginPath()
+ ctx.moveTo(0, y)
+ for (i = 0; i < stepWidth; i++) {
+ step = i * indexesPerPixel + stepMin
+ peak = peaks[step]
+ y = (1 - peak) * waveformPeak
+ ctx.lineTo(i, y)
+ }
+ for (i = stepWidth - 1; i > 0; i--) {
+ step = i * indexesPerPixel + stepMin
+ peak = peaks[step]
+ y = (1 + peak) * waveformPeak
+ ctx.lineTo(i, y)
+ }
+ ctx.lineTo(0, origin)
+ ctx.fillStyle = 'rgba(255,255,255,0.8)'
+ ctx.fill()
+ }
+ render() {
+ return (
+ <canvas ref={this.canvasRef} />
+ )
+ }
+}
+
+const mapStateToProps = state => ({
+ timeline: state.align.timeline,
+ peaks: state.site.peaks,
+})
+
+const mapDispatchToProps = dispatch => ({
+ // uploadActions: bindActionCreators({ ...uploadActions }, dispatch),
+})
+
+export default connect(mapStateToProps, mapDispatchToProps)(Waveform)