diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-07-02 00:35:06 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-07-02 00:35:06 +0200 |
| commit | 3e2c1d432d73823e66e19d0081b498ace467b231 (patch) | |
| tree | 67a8b66eb8334b97e031f2c91da668591132ede3 /animism-align/frontend | |
| parent | 250527589e003420a84f36c4191d2e574f1ad28c (diff) | |
display the form
Diffstat (limited to 'animism-align/frontend')
20 files changed, 331 insertions, 42 deletions
diff --git a/animism-align/frontend/common/app.css b/animism-align/frontend/common/app.css index d9f9946..699505f 100644 --- a/animism-align/frontend/common/app.css +++ b/animism-align/frontend/common/app.css @@ -37,7 +37,7 @@ body { } .app .body { display: flex; - flex-direction: column; + flex-direction: row; flex-grow: 1; position: relative; height: 100%; diff --git a/animism-align/frontend/types.js b/animism-align/frontend/types.js index 9d16a4b..d888a0a 100644 --- a/animism-align/frontend/types.js +++ b/animism-align/frontend/types.js @@ -9,6 +9,7 @@ export const timestamp = crud_type('timestamp', []) export const paragraph = crud_type('paragraph', []) export const align = crud_type('align', [ 'set_display_setting', + 'set_temporary_annotation', ]) export const audio = with_type('audio', [ diff --git a/animism-align/frontend/util/index.js b/animism-align/frontend/util/index.js index 0615f93..b105f55 100644 --- a/animism-align/frontend/util/index.js +++ b/animism-align/frontend/util/index.js @@ -275,6 +275,10 @@ export const orderByFn = (s='name asc') => { mapFn = a => [parseInt(a.size) || 0, a] sortFn = numericSort[direction] break + case 'start_ts': + mapFn = a => [parseFloat(a.start_ts) || 0, a] + sortFn = numericSort[direction] + break case 'date': mapFn = a => [+new Date(a.date || a.created_at), a] sortFn = numericSort[direction] diff --git a/animism-align/frontend/views/align/align.actions.js b/animism-align/frontend/views/align/align.actions.js index 2e1c795..82e4799 100644 --- a/animism-align/frontend/views/align/align.actions.js +++ b/animism-align/frontend/views/align/align.actions.js @@ -24,3 +24,39 @@ export const throttledSetZoom = throttle(zoom => dispatch => { export const setCursor = cursor_ts => dispatch => ( dispatch({ type: types.align.set_display_setting, key: 'cursor_ts', value: cursor_ts }) ) + +export const showNewTimestampForm = (start_ts, text) => dispatch => { + let croppedText; + if (store.getState().align.annotation.start_ts) { + croppedText = store.getState().align.annotation.text + } else { + croppedText = cutFirstSentence(text) + } + console.log(croppedText) + dispatch({ + type: types.align.set_temporary_annotation, + data: { + id: 'new', + start_ts, + text: croppedText, + type: 'sentence', + } + }) +} + +const cutFirstSentence = text => { + const textToCrop = text.trim().replace("\n", " ").split("\n")[0] + let cropIndex = textToCrop.indexOf('. ') + 1 + if (!cropIndex) cropIndex = textToCrop.length + const croppedText = textToCrop.substr(0, cropIndex).trim() + const updatedText = text.trim().replace(croppedText, '').trim() + actions.site.updateText(updatedText) + return croppedText +} + +export const hideTimestampForm = () => dispatch => { + dispatch({ + type: types.align.set_temporary_annotation, + data: {} + }) +} diff --git a/animism-align/frontend/views/align/align.container.js b/animism-align/frontend/views/align/align.container.js index 387bd86..80d8a57 100644 --- a/animism-align/frontend/views/align/align.container.js +++ b/animism-align/frontend/views/align/align.container.js @@ -5,7 +5,8 @@ import { connect } from 'react-redux' import './align.css' -import Timeline from './components/timeline.component.js' +import Timeline from './containers/timeline.container.js' +import Script from './containers/script.container.js' import actions from '../../actions' import { Header } from '../../common' // import * as uploadActions from './upload.actions' @@ -20,7 +21,10 @@ class Container extends Component { render() { return ( <div className='body'> - <Timeline /> + <div className='row'> + <Timeline /> + </div> + <Script /> </div> ) } diff --git a/animism-align/frontend/views/align/align.css b/animism-align/frontend/views/align/align.css index 38a1c8b..a366d40 100644 --- a/animism-align/frontend/views/align/align.css +++ b/animism-align/frontend/views/align/align.css @@ -6,6 +6,7 @@ height: 100%; display: flex; flex-direction: row; + justify-content: space-between; background: linear-gradient( 0deg, rgba(0, 0, 64, 0.5), @@ -13,6 +14,9 @@ ); padding: 0; } + +/* Timeline */ + canvas { display: block; } @@ -23,7 +27,9 @@ canvas { width: 300px; cursor: crosshair; } - +.timelineColumn { + position: relative; +} .ticks .tick { position: absolute; right: 0; @@ -64,6 +70,9 @@ canvas { text-align: right; text-shadow: 0 0 2px #000, 0 0 2px #000, 0 0 2px #000; } + +/* Audio player */ + .playButton { position: absolute; top: 0; left: 0; @@ -81,3 +90,24 @@ canvas { .playButton.paused { background-image: url('/static/img/icons_play_white.svg'); } + +/* Script */ + +.script { + align-self: flex-end; + height: 100%; +} + +/* Annotations */ + +.annotations { + position: relative; + width: 300px; +} +.annotationForm { + position: absolute; + left: 0; +} +.annotationForm .row { + justify-content: space-between; +}
\ No newline at end of file diff --git a/animism-align/frontend/views/align/align.reducer.js b/animism-align/frontend/views/align/align.reducer.js index 5640cde..9064b56 100644 --- a/animism-align/frontend/views/align/align.reducer.js +++ b/animism-align/frontend/views/align/align.reducer.js @@ -8,6 +8,7 @@ const initialState = { zoom: 1, duration: 0, }, + annotation: {}, options: { } } @@ -34,6 +35,12 @@ export default function alignReducer(state = initialState, action) { } } + case types.align.set_temporary_annotation: + return { + ...state, + annotation: action.data, + } + default: return state } diff --git a/animism-align/frontend/views/align/align.util.js b/animism-align/frontend/views/align/align.util.js new file mode 100644 index 0000000..32cbc35 --- /dev/null +++ b/animism-align/frontend/views/align/align.util.js @@ -0,0 +1,28 @@ +import { ZOOM_STEPS } from './constants' +import { clamp } from '../../util' + +export const positionToTime = (y, { start_ts, zoom, duration }) => { + const secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 + const widthTimeDuration = window.innerHeight * secondsPerPixel + const timeMin = start_ts + const timeMax = Math.min(start_ts + widthTimeDuration, duration) + const timeWidth = timeMax - timeMin + return clamp(y * secondsPerPixel + start_ts, 0, timeMax) +} + +export const timeToPosition = (ts, { start_ts, zoom, duration }) => { + const height = window.innerHeight + const secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 + const widthTimeDuration = height * secondsPerPixel + const timeMin = start_ts + const timeMax = Math.min(start_ts + widthTimeDuration, duration) + const timeWidth = timeMax - timeMin + const timeHalfHeight = height * secondsPerPixel / 2 + if (ts < timeMin - timeHalfHeight) { + return -9999 + } + if (ts > timeMax) { + return -9999 + } + return (ts - timeMin) / timeWidth * height +} diff --git a/animism-align/frontend/views/align/components/annotations/annotation.form.js b/animism-align/frontend/views/align/components/annotations/annotation.form.js new file mode 100644 index 0000000..9b02478 --- /dev/null +++ b/animism-align/frontend/views/align/components/annotations/annotation.form.js @@ -0,0 +1,95 @@ +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 alignActions from '../align.actions' + +import { ZOOM_STEPS } from '../../constants' +import { clamp } from '../../../../util' +import { timeToPosition } from '../../align.util' +import { Select } from '../../../../common' + +const TIMESTAMP_TYPES = ['sentence', 'header'].map(name => ({ name, label: name })) + +class AnnotationForm extends Component { + state = { + data: {}, + } + constructor(props){ + super(props) + this.handleChange = this.handleChange.bind(this) + this.handleSelect = this.handleSelect.bind(this) + } + componentDidMount(){ + this.setState({ + data: { ...this.props.annotation }, + }) + } + componentDidUpdate(prevProps){ + if (this.props.annotation !== prevProps.annotation) { + this.setState({ + data: { ...this.props.annotation }, + }) + } + } + handleChange(e) { + const { name, value } = e.target + this.handleSelect(name, value) + } + handleSelect(name, value) { + this.setState({ + data: { + ...this.state.data, + [name]: value, + } + }) + } + render() { + const { timeline } = this.props + const { data } = this.state + if (!data.start_ts) return <div></div> + return ( + <div + className='annotationForm' + style={{ + top: timeToPosition(data.start_ts, timeline), + }} + > + <div> + <textarea + value={data.text} + onChange={this.handleChange} + /> + </div> + <div className='row'> + <Select + name='type' + selected={data.type} + options={TIMESTAMP_TYPES} + defaultOption='text' + onChange={this.handleSelect} + /> + <button>Save</button> + </div> + </div> + ) + } +} + + +/* +- get the first sentence from the text +- display the form at that point +*/ + +const mapStateToProps = state => ({ + annotation: state.align.annotation, + timeline: state.align.timeline, +}) + +const mapDispatchToProps = dispatch => ({ +}) + +export default connect(mapStateToProps, mapDispatchToProps)(AnnotationForm) diff --git a/animism-align/frontend/views/align/components/cursor.component.js b/animism-align/frontend/views/align/components/timeline/cursor.component.js index a5ad438..f621b37 100644 --- a/animism-align/frontend/views/align/components/cursor.component.js +++ b/animism-align/frontend/views/align/components/timeline/cursor.component.js @@ -1,12 +1,13 @@ import React, { Component } from 'react' -import { ZOOM_STEPS } from '../constants' -import { timestamp } from '../../../util' +import { ZOOM_STEPS } from '../../constants' +import { timestamp } from '../../../../util' -const Cursor = ({ timeline }) => { +const Cursor = ({ timeline, annotation }) => { const { start_ts, zoom, cursor_ts, duration } = timeline + const ts = annotation.start_ts || cursor_ts const secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 - const y = (cursor_ts - start_ts) / secondsPerPixel + const y = (ts - start_ts) / secondsPerPixel return ( <div className='cursor' @@ -16,7 +17,7 @@ const Cursor = ({ timeline }) => { > <div className='line' /> <div className='tickLabel'> - {timestamp(cursor_ts, 1)} + {timestamp(ts, 1)} </div> </div> ) diff --git a/animism-align/frontend/views/align/components/playButton.component.js b/animism-align/frontend/views/align/components/timeline/playButton.component.js index fcb1422..486eaee 100644 --- a/animism-align/frontend/views/align/components/playButton.component.js +++ b/animism-align/frontend/views/align/components/timeline/playButton.component.js @@ -3,11 +3,11 @@ import React, { Component } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' -import actions from '../../../actions' +import actions from '../../../../actions' // import * as alignActions from '../align.actions' -import { ZOOM_STEPS } from '../constants' -import { clamp } from '../../../util' +import { ZOOM_STEPS } from '../../constants' +import { clamp } from '../../../../util' const PlayButton = ({ audio }) => { return ( diff --git a/animism-align/frontend/views/align/components/playCursor.component.js b/animism-align/frontend/views/align/components/timeline/playCursor.component.js index f191bb1..71e6a3a 100644 --- a/animism-align/frontend/views/align/components/playCursor.component.js +++ b/animism-align/frontend/views/align/components/timeline/playCursor.component.js @@ -3,8 +3,8 @@ import React, { Component } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' -import { ZOOM_STEPS } from '../constants' -import { timestamp } from '../../../util' +import { ZOOM_STEPS } from '../../constants' +import { timestamp } from '../../../../util' const PlayCursor = ({ timeline, audio }) => { const { start_ts, zoom, duration } = timeline diff --git a/animism-align/frontend/views/align/components/ticks.component.js b/animism-align/frontend/views/align/components/timeline/ticks.component.js index 55821ae..72f9bd0 100644 --- a/animism-align/frontend/views/align/components/ticks.component.js +++ b/animism-align/frontend/views/align/components/timeline/ticks.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' -import { ZOOM_STEPS, ZOOM_LABEL_STEPS, ZOOM_TICK_STEPS } from '../constants' -import { timestamp } from '../../../util' +import { ZOOM_STEPS, ZOOM_LABEL_STEPS, ZOOM_TICK_STEPS } from '../../constants' +import { timestamp } from '../../../../util' export default class Ticks extends Component { render() { diff --git a/animism-align/frontend/views/align/components/waveform.component.js b/animism-align/frontend/views/align/components/timeline/waveform.component.js index e454623..128204a 100644 --- a/animism-align/frontend/views/align/components/waveform.component.js +++ b/animism-align/frontend/views/align/components/timeline/waveform.component.js @@ -3,10 +3,10 @@ import React, { Component } from 'react' // import { bindActionCreators } from 'redux' import { connect } from 'react-redux' -import actions from '../../../actions' +import actions from '../../../../actions' // import * as uploadActions from './upload.actions' -import { WAVEFORM_SIZE, ZOOM_STEPS, ZOOM_LABEL_STEPS, ZOOM_TICK_STEPS } from '../constants' +import { WAVEFORM_SIZE, ZOOM_STEPS, ZOOM_LABEL_STEPS, ZOOM_TICK_STEPS } from '../../constants' class Waveform extends Component { constructor(props){ diff --git a/animism-align/frontend/views/align/containers/annotations.container.js b/animism-align/frontend/views/align/containers/annotations.container.js new file mode 100644 index 0000000..e32757b --- /dev/null +++ b/animism-align/frontend/views/align/containers/annotations.container.js @@ -0,0 +1,44 @@ +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 alignActions from '../align.actions' + +import { ZOOM_STEPS } from '../constants' +import { clamp } from '../../../util' +import { positionToTime } from '../align.util' + +import AnnotationForm from '../components/annotations/annotation.form' + +class Annotations extends Component { + constructor(props){ + super(props) + // this.handleKeydown = this.handleKeydown.bind(this) + } + render() { + return ( + <div className='annotations'> + {this.props.annotation.start_ts && + <AnnotationForm /> + } + </div> + ) + } +} + +/* +- get the first sentence from the text +- display the form at that point +*/ + +const mapStateToProps = state => ({ + timeline: state.align.timeline, + annotation: state.align.annotation, +}) + +const mapDispatchToProps = dispatch => ({ +}) + +export default connect(mapStateToProps, mapDispatchToProps)(Annotations) diff --git a/animism-align/frontend/views/align/containers/script.container.js b/animism-align/frontend/views/align/containers/script.container.js new file mode 100644 index 0000000..ce3dee8 --- /dev/null +++ b/animism-align/frontend/views/align/containers/script.container.js @@ -0,0 +1,34 @@ +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 alignActions from '../align.actions' + +class Timeline extends Component { + constructor(props){ + super(props) + } + render() { + if (this.props.text.loading) return <div /> + return ( + <textarea + className='script' + onChange={e => actions.site.updateText(e.target.value)} + value={this.props.text} + /> + ) + } +} + + +const mapStateToProps = state => ({ + text: state.site.text, +}) + +const mapDispatchToProps = dispatch => ({ + // alignActions: bindActionCreators({ ...alignActions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(Timeline) diff --git a/animism-align/frontend/views/align/components/timeline.component.js b/animism-align/frontend/views/align/containers/timeline.container.js index 3b12cb5..3795751 100644 --- a/animism-align/frontend/views/align/components/timeline.component.js +++ b/animism-align/frontend/views/align/containers/timeline.container.js @@ -6,14 +6,16 @@ import { connect } from 'react-redux' import actions from '../../../actions' // import * as alignActions from '../align.actions' -import Waveform from './waveform.component' -import Ticks from './ticks.component' -import Cursor from './cursor.component' -import PlayButton from './playButton.component' -import PlayCursor from './playCursor.component' +import Annotations from '../containers/annotations.container' +import Waveform from '../components/timeline/waveform.component' +import Ticks from '../components/timeline/ticks.component' +import Cursor from '../components/timeline/cursor.component' +import PlayButton from '../components/timeline/playButton.component' +import PlayCursor from '../components/timeline/playCursor.component' -import { ZOOM_STEPS } from '../constants' +import { WAVEFORM_SIZE, ZOOM_STEPS } from '../constants' import { clamp } from '../../../util' +import { positionToTime } from '../align.util' class Timeline extends Component { constructor(props){ @@ -49,7 +51,7 @@ class Timeline extends Component { break } } else { - console.log(e.keyCode) + // console.log(e.keyCode) switch (e.keyCode) { case 32: // spacebar actions.audio.toggle() @@ -77,7 +79,7 @@ class Timeline extends Component { actions.align.throttledSetZoom(this.props.timeline.zoom - 1) } } else if (e.altKey) { - actions.audio.seek(start_ts) + actions.audio.jump(e.deltaY * ZOOM_STEPS[zoom]) } else { actions.align.setScrollPosition(start_ts) } @@ -88,7 +90,11 @@ class Timeline extends Component { } handleClick(e) { const play_ts = positionToTime(e.pageY, this.props.timeline) - actions.audio.seek(play_ts) + if (e.pageX > WAVEFORM_SIZE * 0.67) { + actions.align.showNewTimestampForm(play_ts, this.props.text) + } else { + actions.audio.seek(play_ts) + } } render() { return ( @@ -97,9 +103,12 @@ class Timeline extends Component { onWheel={this.handleWheel} onMouseMove={this.handleMouseMove} > - <Waveform onClick={this.handleClick} /> - <Ticks timeline={this.props.timeline} /> - <Cursor timeline={this.props.timeline} /> + <div className='timelineColumn'> + <Waveform onClick={this.handleClick} /> + <Ticks timeline={this.props.timeline} /> + <Cursor timeline={this.props.timeline} annotation={this.props.annotation} /> + </div> + <Annotations timeline={this.props.timeline} /> <PlayCursor /> <PlayButton /> </div> @@ -107,19 +116,10 @@ class Timeline extends Component { } } -const positionToTime = (y, { start_ts, zoom, duration }) => { - const secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 - const widthTimeDuration = window.innerHeight * secondsPerPixel - - const timeMin = start_ts - const timeMax = Math.min(start_ts + widthTimeDuration, duration) - const timeWidth = timeMax - timeMin - - return clamp(y * secondsPerPixel + start_ts, 0, timeMax) -} - const mapStateToProps = state => ({ timeline: state.align.timeline, + annotation: state.align.annotation, + text: state.site.text, }) const mapDispatchToProps = dispatch => ({ diff --git a/animism-align/frontend/views/audio/audio.actions.js b/animism-align/frontend/views/audio/audio.actions.js index 963d8b0..4b1bcdc 100644 --- a/animism-align/frontend/views/audio/audio.actions.js +++ b/animism-align/frontend/views/audio/audio.actions.js @@ -37,4 +37,4 @@ export const toggle = () => dispatch => { } else { play()(dispatch) } -}
\ No newline at end of file +} diff --git a/animism-align/frontend/views/site/site.actions.js b/animism-align/frontend/views/site/site.actions.js index 8157175..c7c228e 100644 --- a/animism-align/frontend/views/site/site.actions.js +++ b/animism-align/frontend/views/site/site.actions.js @@ -10,3 +10,7 @@ export const loadPeaks = (asdf) => dispatch => { export const loadText = (asdf) => dispatch => { api(dispatch, types.text, 'text', '/static/data_store/peaks/text.txt') } + +export const updateText = text => dispatch => { + dispatch({ type: types.text.loaded, data: text }) +}
\ No newline at end of file diff --git a/animism-align/frontend/views/timestamp/timestamp.reducer.js b/animism-align/frontend/views/timestamp/timestamp.reducer.js index cf18966..e57110e 100644 --- a/animism-align/frontend/views/timestamp/timestamp.reducer.js +++ b/animism-align/frontend/views/timestamp/timestamp.reducer.js @@ -5,6 +5,7 @@ import { crudState, crudReducer } from '../../api/crud.reducer' const initialState = crudState('timestamp', { options: { + sort: 'start_ts desc', } }) |
