summaryrefslogtreecommitdiff
path: root/animism-align/frontend/views
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2020-07-02 00:35:06 +0200
committerJules Laplace <julescarbon@gmail.com>2020-07-02 00:35:06 +0200
commit3e2c1d432d73823e66e19d0081b498ace467b231 (patch)
tree67a8b66eb8334b97e031f2c91da668591132ede3 /animism-align/frontend/views
parent250527589e003420a84f36c4191d2e574f1ad28c (diff)
display the form
Diffstat (limited to 'animism-align/frontend/views')
-rw-r--r--animism-align/frontend/views/align/align.actions.js36
-rw-r--r--animism-align/frontend/views/align/align.container.js8
-rw-r--r--animism-align/frontend/views/align/align.css32
-rw-r--r--animism-align/frontend/views/align/align.reducer.js7
-rw-r--r--animism-align/frontend/views/align/align.util.js28
-rw-r--r--animism-align/frontend/views/align/components/annotations/annotation.form.js95
-rw-r--r--animism-align/frontend/views/align/components/timeline/cursor.component.js (renamed from animism-align/frontend/views/align/components/cursor.component.js)11
-rw-r--r--animism-align/frontend/views/align/components/timeline/playButton.component.js (renamed from animism-align/frontend/views/align/components/playButton.component.js)6
-rw-r--r--animism-align/frontend/views/align/components/timeline/playCursor.component.js (renamed from animism-align/frontend/views/align/components/playCursor.component.js)4
-rw-r--r--animism-align/frontend/views/align/components/timeline/ticks.component.js (renamed from animism-align/frontend/views/align/components/ticks.component.js)4
-rw-r--r--animism-align/frontend/views/align/components/timeline/waveform.component.js (renamed from animism-align/frontend/views/align/components/waveform.component.js)4
-rw-r--r--animism-align/frontend/views/align/containers/annotations.container.js44
-rw-r--r--animism-align/frontend/views/align/containers/script.container.js34
-rw-r--r--animism-align/frontend/views/align/containers/timeline.container.js (renamed from animism-align/frontend/views/align/components/timeline.component.js)46
-rw-r--r--animism-align/frontend/views/audio/audio.actions.js2
-rw-r--r--animism-align/frontend/views/site/site.actions.js4
-rw-r--r--animism-align/frontend/views/timestamp/timestamp.reducer.js1
17 files changed, 325 insertions, 41 deletions
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',
}
})