diff options
Diffstat (limited to 'animism-align/frontend/app')
31 files changed, 439 insertions, 232 deletions
diff --git a/animism-align/frontend/app/actions.js b/animism-align/frontend/app/actions.js index 54c5dc2..270c9a7 100644 --- a/animism-align/frontend/app/actions.js +++ b/animism-align/frontend/app/actions.js @@ -2,6 +2,7 @@ import { bindActionCreators } from 'redux' import { crud_actions } from 'app/api/crud.actions' import * as viewerActions from 'app/views/viewer/viewer.actions' +import * as transcriptActions from 'app/views/paragraph/transcript.actions' import * as audioActions from 'app/views/audio/audio.actions' import * as alignActions from 'app/views/align/align.actions' import * as siteActions from 'app/views/site/site.actions' @@ -23,6 +24,7 @@ export default ['align', alignActions], ['audio', audioActions], ['viewer', viewerActions], + ['transcript', transcriptActions], ]) .map(p => [p[0], bindActionCreators(p[1], store.dispatch)]) .concat([ diff --git a/animism-align/frontend/app/common/form.component.js b/animism-align/frontend/app/common/form.component.js index c727544..3b55162 100644 --- a/animism-align/frontend/app/common/form.component.js +++ b/animism-align/frontend/app/common/form.component.js @@ -2,7 +2,7 @@ import React, { Component } from 'react' import { courtesyS } from 'app/utils' export const TextInput = props => ( - <label className={props.error ? 'error' : 'text'}> + <label className={(props.error ? 'error' : 'text') + (props.className ? ' ' + props.className : '')}> {props.title && <span>{props.title}</span>} <input type="text" diff --git a/animism-align/frontend/app/constants.js b/animism-align/frontend/app/constants.js index fc7e591..b7757d0 100644 --- a/animism-align/frontend/app/constants.js +++ b/animism-align/frontend/app/constants.js @@ -42,3 +42,16 @@ export const MEDIA_TYPES = new Set([ 'image', 'gallery', 'vitrine', 'video', ]) + +export const CURTAIN_COLORS = [ + { label: 'white', backgroundColor: '#ffffff', textColor: '#000000' }, + { label: 'dark blue', backgroundColor: '#1a1f33', textColor: '#ffffff' }, + { label: 'dark gray', backgroundColor: '#222222', textColor: '#ffffff' }, + { label: 'black', backgroundColor: '#000000', textColor: '#ffffff' }, +] + +export const CURTAIN_COLOR_SELECT_OPTIONS = CURTAIN_COLORS.map(color => ({ + label: color.label, + name: color.label, +})) + diff --git a/animism-align/frontend/app/types.js b/animism-align/frontend/app/types.js index 80db842..4ccf483 100644 --- a/animism-align/frontend/app/types.js +++ b/animism-align/frontend/app/types.js @@ -7,7 +7,9 @@ export const media = crud_type('media', []) export const peaks = crud_type('peaks', []) export const text = crud_type('text', []) export const annotation = crud_type('annotation', []) -export const paragraph = crud_type('paragraph', []) +export const paragraph = crud_type('paragraph', [ + 'update_transcript', +]) export const vimeo = crud_type('vimeo', []) export const align = crud_type('align', [ 'set_display_setting', diff --git a/animism-align/frontend/app/views/align/align.util.js b/animism-align/frontend/app/utils/align.utils.js index e873bbf..f64c5c8 100644 --- a/animism-align/frontend/app/views/align/align.util.js +++ b/animism-align/frontend/app/utils/align.utils.js @@ -58,8 +58,3 @@ export const cutFirstSentence = text => { actions.site.updateText(updatedText) return croppedText } - -export const thumbnailURL = data => { - if (data.type === 'video') return data.settings.video.thumbnail_url - if (data.type === 'image') return data.settings.thumbnail.url -} diff --git a/animism-align/frontend/app/utils/annotation.utils.js b/animism-align/frontend/app/utils/annotation.utils.js new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/animism-align/frontend/app/utils/annotation.utils.js diff --git a/animism-align/frontend/app/utils/index.js b/animism-align/frontend/app/utils/index.js index c2dd464..273c0ef 100644 --- a/animism-align/frontend/app/utils/index.js +++ b/animism-align/frontend/app/utils/index.js @@ -70,6 +70,17 @@ export const timestamp = (n = 0, fps = 1, ms = false) => { return (n % 60) + ':' + s } +export const timestampToSeconds = time_str => { + const time_str_parts = time_str.trim().split(":").map(s => parseFloat(s)) + if (time_str_parts.length === 3) { + return (time_str_parts[0] * 60 + time_str_parts[1]) * 60 + time_str_parts[2] + } + if (time_str_parts.length === 2) { + return time_str_parts[0] * 60 + time_str_parts[1] + } + return time_str_parts[0] +} + export const percent = n => (n * 100).toFixed(1) + '%' export const px = (n, w) => Math.round(n * w) + 'px' diff --git a/animism-align/frontend/app/views/align/align.actions.js b/animism-align/frontend/app/views/align/align.actions.js index 90c9593..6074b34 100644 --- a/animism-align/frontend/app/views/align/align.actions.js +++ b/animism-align/frontend/app/views/align/align.actions.js @@ -7,7 +7,7 @@ import throttle from 'lodash.throttle' import debounce from 'lodash.debounce' import { ZOOM_STEPS } from 'app/constants' -import { getFirstPunctuationMarkIndex, cutFirstSentence } from 'app/views/align/align.util' +import { getFirstPunctuationMarkIndex, cutFirstSentence } from 'app/utils/align.utils' export const setScrollPosition = start_ts => dispatch => ( dispatch({ type: types.align.set_display_setting, key: 'start_ts', value: start_ts }) diff --git a/animism-align/frontend/app/views/align/align.container.js b/animism-align/frontend/app/views/align/align.container.js index a659fdd..7b19536 100644 --- a/animism-align/frontend/app/views/align/align.container.js +++ b/animism-align/frontend/app/views/align/align.container.js @@ -4,6 +4,8 @@ import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import './align.css' +import './components/annotations/annotation.form.css' +import './components/annotations/annotation.index.css' import Timeline from 'app/views/align/containers/timeline.container.js' import Script from 'app/views/align/containers/script.container.js' diff --git a/animism-align/frontend/app/views/align/align.css b/animism-align/frontend/app/views/align/align.css index bbf3bc2..ff07162 100644 --- a/animism-align/frontend/app/views/align/align.css +++ b/animism-align/frontend/app/views/align/align.css @@ -39,8 +39,10 @@ canvas { width: 4px; height: 1px; background: #ddd; + pointer-events: none; } .ticks .tickLabel { + pointer-events: none; position: absolute; right: 6px; font-size: 12px; @@ -107,109 +109,3 @@ canvas { position: relative; width: 450px; } - -/* Annotation form */ - -.annotationForm { - width: 401px; - padding: 0.5rem; - position: absolute; - left: 0.25rem; - background: #448; - box-shadow: 0 0 2px #000, 0 0 4px #000; - z-index: 10; -} -.annotationForm textarea { - width: 100%; -} -.annotationForm .row { - justify-content: space-between; - align-items: center; -} -.annotationForm .row > div { - display: flex; - align-items: center; -} -.annotationForm .buttons { - margin-bottom: 0.5rem; -} -.annotationForm .ts { - color: #fff; -} -.annotationForm .select.media_id { - width: 100%; - margin-right: 0; -} - -/* Annotation index */ - -.annotationIndex { - width: 800px; -} -.annotationIndex .annotation { - position: absolute; - left: 5px; - max-width: 400px; - padding: 0.25rem 0.375rem; - box-shadow: 0px 0px 3px rgba(0,0,0,1.0); - border: 1px solid transparent; - border-radius: 2px; - font-size: 12px; - cursor: pointer; - user-select: none; - background-color: #768; -} -.annotation.selected { - border-color: #bbf; - box-shadow: 0px 0px 4px rgba(0,0,0,1.0), 0px 0px 2px rgba(0,0,0,1.0); - z-index: 1; - background-image: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.4)); -} -.annotationIndex .annotation.media { - left: calc(405px + 0.5rem); -} -.annotation.sentence.even { - background-color: #83b; -} -.annotation.sentence.odd { - background-color: #537; -} -.annotation.header { - background-color: #838; -} -.annotation.paragraph_end { - background-color: #003; - border-top: 1px solid #888; - width: 100%; - padding: 1px; -} - -/* Condensed layout (first lines) */ - -.annotationIndex.condensed .annotation.sentence { - z-index: 0; - white-space: pre; - overflow: hidden; - text-overflow: ellipsis; -} -.annotationIndex.condensed .annotation.header { - z-index: 1; -} -.annotationIndex.condensed .annotation.paragraph_end { - border-top-color: #888; -} - -/* Collapsed layout (borders) */ - -.annotationIndex.collapsed .annotation.sentence { - height: 2px; overflow: hidden; padding: 0; z-index: 0; -} -.annotationIndex.collapsed .annotation.sentence.selected { - z-index: 1; -} -.annotationIndex.collapsed .annotation.header { - z-index: 2; -} -.annotationIndex.collapsed .annotation.paragraph_end { - border-top-color: #333; -} diff --git a/animism-align/frontend/app/views/align/components/annotations/annotation.form.css b/animism-align/frontend/app/views/align/components/annotations/annotation.form.css new file mode 100644 index 0000000..fdb4abe --- /dev/null +++ b/animism-align/frontend/app/views/align/components/annotations/annotation.form.css @@ -0,0 +1,55 @@ +/* Annotation form */ + +.annotationForm { + width: 401px; + padding: 0.5rem; + position: absolute; + left: 0.25rem; + background: #448; + box-shadow: 0 0 2px #000, 0 0 4px #000; + z-index: 10; +} +.annotationForm textarea { + width: 100%; +} +.annotationForm .row { + justify-content: space-between; + align-items: center; +} +.annotationForm .row > div { + display: flex; + align-items: center; +} +.annotationForm .buttons { + margin-bottom: 0.5rem; +} +.annotationForm .ts { + color: #fff; +} +.annotationForm .select.media_id { + width: 100%; + margin-right: 0; +} +.annotationForm .options label span:first-child { + display: inline-block; + width: 6rem; +} +.annotationForm .options .description { + font-size: 0.75rem; + margin-top: 0.25rem; + margin-bottom: 0.5rem; +} +.annotationForm .color input[type="text"].number { + width: 8rem; +} +.annotationForm .color input[type="text"] { + width: 8rem; +} +.annotationForm .color input[type="color"] { + background: transparent; + border: 0; + height: 1.7rem; + padding: 0; + margin: 0 0.5rem 0 0; + width: 1.4rem; +} diff --git a/animism-align/frontend/app/views/align/components/annotations/annotation.form.js b/animism-align/frontend/app/views/align/components/annotations/annotation.form.js index f4620bc..7882884 100644 --- a/animism-align/frontend/app/views/align/components/annotations/annotation.form.js +++ b/animism-align/frontend/app/views/align/components/annotations/annotation.form.js @@ -7,18 +7,16 @@ import actions from 'app/actions' import { ZOOM_STEPS } from 'app/constants' import { clamp, timestamp, capitalize } from 'app/utils' -import { timeToPosition } from 'app/views/align/align.util' +import { timeToPosition } from 'app/utils/align.utils' import { Select } from 'app/common' -import { - AnnotationFormVideo, - AnnotationFormImage, -} from './annotationForms' +import { annotationFormLookup } from './annotationForms' const ANNOTATION_TYPES = [ 'sentence', 'header', 'paragraph_end', 'video', 'image', 'image_carousel', + 'curtain', ].map(name => ({ name, label: capitalize(name.replace('_', ' ')) })) class AnnotationForm extends Component { @@ -26,6 +24,7 @@ class AnnotationForm extends Component { super(props) this.handleChange = this.handleChange.bind(this) this.handleSelect = this.handleSelect.bind(this) + this.handleSettingsChange = this.handleSettingsChange.bind(this) this.handleSettingsSelect = this.handleSettingsSelect.bind(this) this.handleKeyDown = this.handleKeyDown.bind(this) this.handleSubmit = this.handleSubmit.bind(this) @@ -74,6 +73,10 @@ class AnnotationForm extends Component { handleSelect(name, value) { actions.align.updateAnnotationForm(name, value) } + handleSettingsChange(e) { + const { name, value } = e.target + this.handleSettingsSelect(name, value) + } handleSettingsSelect(name, value) { if (name.indexOf('_id') !== -1) value = parseInt(value) || 0 actions.align.updateAnnotationSettings(name, value) @@ -111,7 +114,7 @@ class AnnotationForm extends Component { } } render() { - const { timeline, annotation, media } = this.props + const { timeline, annotation } = this.props return ( <div className='annotationForm' @@ -122,15 +125,7 @@ class AnnotationForm extends Component { {this.renderButtons()} {annotation.type === 'sentence' && this.renderTextarea()} {annotation.type === 'header' && this.renderTextarea()} - {annotation.type === 'video' && - <AnnotationFormVideo annotation={annotation} media={media} handleSettingsSelect={this.handleSettingsSelect} /> - } - {annotation.type === 'image' && - <AnnotationFormImage annotation={annotation} media={media} handleSettingsSelect={this.handleSettingsSelect} /> - } - {annotation.type === 'image_carousel' && - <AnnotationFormImageCarousel annotation={annotation} media={media} handleSettingsSelect={this.handleSettingsSelect} /> - } + {(annotation.type in annotationFormLookup) && this.renderElementForm()} </div> ) } @@ -169,6 +164,18 @@ class AnnotationForm extends Component { </div> ) } + renderElementForm() { + const { annotation, media } = this.props + const AnnotationFormElement = annotationFormLookup[annotation.type] + return ( + <AnnotationFormElement + annotation={annotation} + media={media} + handleSettingsChange={this.handleSettingsChange} + handleSettingsSelect={this.handleSettingsSelect} + /> + ) + } } const mapStateToProps = state => ({ diff --git a/animism-align/frontend/app/views/align/components/annotations/annotation.index.css b/animism-align/frontend/app/views/align/components/annotations/annotation.index.css new file mode 100644 index 0000000..d39e5de --- /dev/null +++ b/animism-align/frontend/app/views/align/components/annotations/annotation.index.css @@ -0,0 +1,76 @@ +/* Annotation index */ + +.annotationIndex { + width: 800px; +} +.annotationIndex .annotation { + position: absolute; + left: 5px; + max-width: 400px; + padding: 0.25rem 0.375rem; + box-shadow: 0px 0px 3px rgba(0,0,0,1.0); + border: 1px solid transparent; + border-radius: 2px; + font-size: 12px; + cursor: pointer; + user-select: none; + background-color: #768; +} +.annotation.selected { + border-color: #bbf; + box-shadow: 0px 0px 4px rgba(0,0,0,1.0), 0px 0px 2px rgba(0,0,0,1.0); + z-index: 1; + background-image: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.4)); +} +.annotationIndex .annotation.media { + left: calc(405px + 0.5rem); +} +.annotation.sentence.even { + background-color: #83b; +} +.annotation.sentence.odd { + background-color: #537; +} +.annotation.header { + background-color: #838; +} +.annotation.paragraph_end { + background-color: #003; + border-top: 1px solid #888; + width: 100%; + padding: 1px; +} +.annotationIndex .annotation.utility { + left: calc(405px + 0.5rem); +} + + +/* Condensed layout (first lines) */ + +.annotationIndex.condensed .annotation.sentence { + z-index: 0; + white-space: pre; + overflow: hidden; + text-overflow: ellipsis; +} +.annotationIndex.condensed .annotation.header { + z-index: 1; +} +.annotationIndex.condensed .annotation.paragraph_end { + border-top-color: #888; +} + +/* Collapsed layout (borders) */ + +.annotationIndex.collapsed .annotation.sentence { + height: 2px; overflow: hidden; padding: 0; z-index: 0; +} +.annotationIndex.collapsed .annotation.sentence.selected { + z-index: 1; +} +.annotationIndex.collapsed .annotation.header { + z-index: 2; +} +.annotationIndex.collapsed .annotation.paragraph_end { + border-top-color: #333; +} diff --git a/animism-align/frontend/app/views/align/components/annotations/annotation.index.js b/animism-align/frontend/app/views/align/components/annotations/annotation.index.js index aa31268..da1038f 100644 --- a/animism-align/frontend/app/views/align/components/annotations/annotation.index.js +++ b/animism-align/frontend/app/views/align/components/annotations/annotation.index.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react' +import React, { PureComponent } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' @@ -6,11 +6,11 @@ import actions from 'app/actions' import { ZOOM_STEPS, INNER_HEIGHT } from 'app/constants' import { clamp } from 'app/utils' -import { positionToTime, timeToPosition } from 'app/views/align/align.util' +import { positionToTime, timeToPosition } from 'app/utils/align.utils' import { AnnotationElementLookup } from './annotationTypes' -class AnnotationIndex extends Component { +class AnnotationIndex extends PureComponent { state = { items: [], } diff --git a/animism-align/frontend/app/views/align/components/annotations/annotationForms/annotationForm.utility.js b/animism-align/frontend/app/views/align/components/annotations/annotationForms/annotationForm.utility.js new file mode 100644 index 0000000..2b6f868 --- /dev/null +++ b/animism-align/frontend/app/views/align/components/annotations/annotationForms/annotationForm.utility.js @@ -0,0 +1,80 @@ +import React, { Component } from 'react' + +import { timestamp } from 'app/utils' +import { TextInput, LabelDescription, Select } from 'app/common' +import { CURTAIN_COLOR_SELECT_OPTIONS } from 'app/constants' +import { curtainTimings } from 'app/utils/annotation.utils' + +export const AnnotationFormCurtain = ({ annotation, handleSettingsChange, handleSettingsSelect }) => { + const { + fadeInDurationInSeconds, fadeOutDurationInSeconds, durationInSeconds, + start_ts, end_ts, fade_in_end_ts, fade_out_start_ts, + } = curtainTimings(annotations) + + return ( + <div className='options'> + <TextInput + title="Total duration" + name="duration" + className="number" + placeholder="0:00" + data={annotation.settings} + onChange={handleSettingsChange} + autoComplete="off" + /> + <LabelDescription> + {durationInSeconds} + {' seconds, ends at '} + {timestamp(end_ts)} + </LabelDescription> + + <TextInput + title="Fade in duration" + name="fade_in_duration" + className="number" + placeholder="0:00" + data={annotation.settings} + onChange={handleSettingsChange} + autoComplete="off" + /> + <LabelDescription> + {fadeInDurationInSeconds} + {' seconds, ends at '} + {timestamp(fade_in_end_ts)} + </LabelDescription> + + <TextInput + title="Fade out duration" + name="fade_out_duration" + className="number" + placeholder="0:00" + data={annotation.settings} + onChange={handleSettingsChange} + autoComplete="off" + /> + <LabelDescription> + {fadeOutDurationInSeconds} + {' seconds, starts at '} + {timestamp(fade_out_start_ts)} + </LabelDescription> + + <Select + title='Color' + name='color' + selected={annotation.settings.color} + options={CURTAIN_COLOR_SELECT_OPTIONS} + defaultOption='Pick a color' + onChange={handleSettingsSelect} + /> + + <TextInput + title="Curtain text" + name="curtain_text" + placeholder="Enter text or leave blank" + data={annotation.settings} + onChange={handleSettingsChange} + autoComplete="off" + /> + </div> + ) +} diff --git a/animism-align/frontend/app/views/align/components/annotations/annotationForms/index.js b/animism-align/frontend/app/views/align/components/annotations/annotationForms/index.js index 1411efc..29f9def 100644 --- a/animism-align/frontend/app/views/align/components/annotations/annotationForms/index.js +++ b/animism-align/frontend/app/views/align/components/annotations/annotationForms/index.js @@ -6,7 +6,12 @@ import { AnnotationFormImage, } from './annotationForm.image' -export { - AnnotationFormImage, - AnnotationFormVideo, +import { + AnnotationFormCurtain, +} from './annotationForm.utility' + +export const annotationFormLookup = { + image: AnnotationFormImage, + video: AnnotationFormVideo, + curtain: AnnotationFormCurtain, } diff --git a/animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.image.js b/animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.image.js index ec4d25e..00c653a 100644 --- a/animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.image.js +++ b/animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.image.js @@ -1,8 +1,8 @@ import React, { Component } from 'react' -import { thumbnailURL } from 'app/views/align/align.util' +import { thumbnailURL } from 'app/utils/annotation.utils' -import { checkAnnotationMediaNotReady, AnnotationMediaLoading } from './annotationTypes.util' +import { checkAnnotationMediaNotReady, AnnotationMediaLoading } from './annotationTypes.utility' export const AnnotationImage = ({ y, annotation, media, selected, onClick, onDoubleClick }) => { const { start_ts, text } = annotation diff --git a/animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.util.js b/animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.utility.js index 17abebd..8bd0b9d 100644 --- a/animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.util.js +++ b/animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.utility.js @@ -1,5 +1,19 @@ import React, { Component } from 'react' +export const AnnotationCurtain = ({ y, annotation, selected, onClick, onDoubleClick }) => { + const className = selected ? 'annotation utility curtain selected' : 'annotation utility curtain' + return ( + <div + className={className} + style={{ top: y }} + onClick={e => onClick(e, annotation)} + onDoubleClick={e => onDoubleClick(e, annotation)} + > + CURTAIN + </div> + ) +} + export const checkAnnotationMediaNotReady = (annotation, media) => { return (!media) || (!(annotation.settings.media_id in media)) } diff --git a/animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.video.js b/animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.video.js index f51ac71..d2fc4e5 100644 --- a/animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.video.js +++ b/animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.video.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' -import { thumbnailURL } from 'app/views/align/align.util' -import { checkAnnotationMediaNotReady, AnnotationMediaLoading } from './annotationTypes.util' +import { thumbnailURL } from 'app/utils/annotation.utils' +import { checkAnnotationMediaNotReady, AnnotationMediaLoading } from './annotationTypes.utility' export const AnnotationVideo = ({ y, annotation, media, selected, onClick, onDoubleClick }) => { const { start_ts, text } = annotation diff --git a/animism-align/frontend/app/views/align/components/annotations/annotationTypes/index.js b/animism-align/frontend/app/views/align/components/annotations/annotationTypes/index.js index 560063b..ee30167 100644 --- a/animism-align/frontend/app/views/align/components/annotations/annotationTypes/index.js +++ b/animism-align/frontend/app/views/align/components/annotations/annotationTypes/index.js @@ -13,10 +13,15 @@ import { AnnotationImage, } from './annotationTypes.image' +import { + AnnotationCurtain, +} from './annotationTypes.utility' + export const AnnotationElementLookup = { sentence: React.memo(AnnotationSentence), header: React.memo(AnnotationHeader), paragraph_end: React.memo(AnnotationParagraphEnd), video: React.memo(AnnotationVideo), image: React.memo(AnnotationImage), + curtain: React.memo(AnnotationCurtain), } diff --git a/animism-align/frontend/app/views/align/containers/annotations.container.js b/animism-align/frontend/app/views/align/containers/annotations.container.js index 9c12f9a..7a2cf79 100644 --- a/animism-align/frontend/app/views/align/containers/annotations.container.js +++ b/animism-align/frontend/app/views/align/containers/annotations.container.js @@ -8,7 +8,7 @@ import actions from 'app/actions' import { ZOOM_STEPS } from 'app/constants' import { clamp } from 'app/utils' -import { positionToTime } from 'app/views/align/align.util' +import { positionToTime } from 'app/utils/align.utils' import AnnotationForm from 'app/views/align/components/annotations/annotation.form' import AnnotationIndex from 'app/views/align/components/annotations/annotation.index' @@ -18,7 +18,6 @@ class Annotations extends Component { super(props) } render() { - console.log(this.props.annotation) return ( <div className='annotations'> <AnnotationIndex /> diff --git a/animism-align/frontend/app/views/align/containers/timeline.container.js b/animism-align/frontend/app/views/align/containers/timeline.container.js index c208e08..3d3cc33 100644 --- a/animism-align/frontend/app/views/align/containers/timeline.container.js +++ b/animism-align/frontend/app/views/align/containers/timeline.container.js @@ -4,7 +4,6 @@ import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import actions from 'app/actions' -// import * as alignActions from '../align.actions' import Annotations from 'app/views/align/containers/annotations.container' import Waveform from 'app/views/align/components/timeline/waveform.component' @@ -15,7 +14,7 @@ import PlayCursor from 'app/views/align/components/timeline/playCursor.component import { WAVEFORM_SIZE, ZOOM_STEPS, INNER_HEIGHT } from 'app/constants' import { clamp } from 'app/utils' -import { positionToTime } from 'app/views/align/align.util' +import { positionToTime } from 'app/utils/align.utils' class Timeline extends Component { constructor(props){ diff --git a/animism-align/frontend/app/views/annotation/annotation.util.js b/animism-align/frontend/app/views/annotation/annotation.util.js new file mode 100644 index 0000000..82edd9a --- /dev/null +++ b/animism-align/frontend/app/views/annotation/annotation.util.js @@ -0,0 +1,26 @@ +import { timestampToSeconds } from 'app/utils' + +export const thumbnailURL = data => { + switch (data.type) { + case 'video': return data.settings.video.thumbnail_url + case 'image': return data.settings.thumbnail.url + default: return null + } +} + +export const curtainTimings = annotation => { + const fadeInDurationInSeconds = timestampToSeconds(annotation.settings.fade_in_duration || '0') + const fadeOutDurationInSeconds = timestampToSeconds(annotation.settings.fade_out_duration || '0') + const durationInSeconds = timestampToSeconds(annotation.settings.duration || '0') + + const start_ts = annotation.start_ts + const end_ts = start_ts + durationInSeconds + const fade_in_end_ts = start_ts + fadeInDurationInSeconds + const fade_out_start_ts = end_ts - fadeOutDurationInSeconds + + return { + fadeInDurationInSeconds, fadeOutDurationInSeconds, durationInSeconds, + start_ts, end_ts, fade_in_end_ts, fade_out_start_ts, + } +} + diff --git a/animism-align/frontend/app/views/media/containers/media.index.js b/animism-align/frontend/app/views/media/containers/media.index.js index eaf9db2..ef6e3be 100644 --- a/animism-align/frontend/app/views/media/containers/media.index.js +++ b/animism-align/frontend/app/views/media/containers/media.index.js @@ -7,7 +7,7 @@ import { formatDateTime } from 'app/utils' import { MenuButton, SmallMenuButton, Loader } from 'app/common' import actions from 'app/actions' -import { thumbnailURL } from 'app/views/align/align.util' +import { thumbnailURL } from 'app/utils/annotation.utils' import MediaIndexOptions from '../components/media.indexOptions' import MediaMenu from '../components/media.menu' diff --git a/animism-align/frontend/app/views/paragraph/components/paragraph.list.js b/animism-align/frontend/app/views/paragraph/components/paragraph.list.js index e1e7970..3492d7f 100644 --- a/animism-align/frontend/app/views/paragraph/components/paragraph.list.js +++ b/animism-align/frontend/app/views/paragraph/components/paragraph.list.js @@ -4,32 +4,23 @@ import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import { floatLT, floatLTE } from 'app/utils' -import actions from 'app/actions' import ParagraphForm from '../components/paragraph.form' import { MEDIA_TYPES } from 'app/constants' class ParagraphList extends Component { state = { - paragraphs: [], currentParagraph: -1, currentAnnotation: -1, } - componentDidMount() { - this.build() - } - componentDidUpdate(prevProps) { - if (this.props.paragraph !== prevProps.paragraph) { - this.build() - } if (this.props.audio.play_ts === prevProps.audio.play_ts) return this.setCurrentParagraph() } setCurrentParagraph() { const { play_ts } = this.props.audio - const insideParagraph = this.state.paragraphs.some(paragraph => { + const insideParagraph = this.props.paragraphs.some(paragraph => { if (floatLTE(paragraph.start_ts, play_ts) && floatLT(play_ts, paragraph.end_ts)) { this.setCurrentAnnotation(paragraph, play_ts) return true @@ -62,66 +53,13 @@ class ParagraphList extends Component { this.setState({ currentParagraph, currentAnnotation }) } - build() { - const { order: annotationOrder, lookup: annotationLookup } = this.props.annotation - const { lookup: paragraphLookup } = this.props.paragraph - let currentParagraph = {} - let sectionCount = 0 - const paragraphs = [] - // loop over the annotations in time order - annotationOrder.forEach((annotation_id, i) => { - const annotation = annotationLookup[annotation_id] - const paragraph = paragraphLookup[annotation.paragraph_id] - // if this annotation is media, insert it after the current paragraph - if (MEDIA_TYPES.has(annotation.type)) { - paragraphs.push({ - id: ('index_' + i), - type: annotation.type, - start_ts: annotation.start_ts, - end_ts: 0, - annotations: [annotation], - }) - return - } - // if this annotation is from a different paragraph, make a new paragraph - if (annotation.type === 'header' || annotation.paragraph_id !== currentParagraph.id) { - const paragraph_type = getParagraphType(annotation, paragraph) - currentParagraph = { - id: annotation.paragraph_id || ('index_' + i), - type: paragraph_type, - start_ts: annotation.start_ts, - end_ts: 0, - annotations: [], - } - if (annotation.type === 'header') { - currentParagraph.sectionIndex = sectionCount++ - currentParagraph.id = 'section_' + currentParagraph.sectionIndex - } - paragraphs.push(currentParagraph) - } - // if this annotation is a paragraph_end, set the end timestamp - if (annotation.type === 'paragraph_end') { - currentParagraph.end_ts = annotation.start_ts - } - // otherwise, just append this annotation to the paragraph - else { - currentParagraph.annotations.push(annotation) - } - }) - for (let i = 0; i < (paragraphs.length - 1); i++) { - if (!paragraphs[i].end_ts) { - paragraphs[i].end_ts = paragraphs[i+1].start_ts - 0.1 - } - } - this.setState({ paragraphs }) - } - render() { const { - media, paragraphElementLookup, selectedParagraph, - onAnnotationClick, onParagraphDoubleClick + paragraphs, media, + paragraphElementLookup, selectedParagraph, + onAnnotationClick, onParagraphDoubleClick, } = this.props - const { paragraphs, currentParagraph, currentAnnotation } = this.state + const { currentParagraph, currentAnnotation } = this.state return paragraphs.map(paragraph => { if (selectedParagraph && selectedParagraph.id === paragraph.id) { paragraph = selectedParagraph @@ -146,19 +84,7 @@ class ParagraphList extends Component { } } -const getParagraphType = (annotation, paragraph) => { - if (annotation.type === 'header') { - return annotation.type - } - if (!paragraph) { - return annotation.type - } - return paragraph.type -} - const mapStateToProps = state => ({ - paragraph: state.paragraph.index, - annotation: state.annotation.index, audio: state.audio, media: state.media.index, }) diff --git a/animism-align/frontend/app/views/paragraph/containers/paragraphEditor.container.js b/animism-align/frontend/app/views/paragraph/containers/paragraphEditor.container.js index c031d8a..aba9cca 100644 --- a/animism-align/frontend/app/views/paragraph/containers/paragraphEditor.container.js +++ b/animism-align/frontend/app/views/paragraph/containers/paragraphEditor.container.js @@ -21,6 +21,16 @@ class ParagraphEditor extends Component { this.handleCloseParagraphForm = this.handleCloseParagraphForm.bind(this) this.updateSelectedParagraph = this.updateSelectedParagraph.bind(this) } + + componentDidMount() { + actions.transcript.buildParagraphs() + } + + componentDidUpdate(prevProps) { + if (this.props.paragraph !== prevProps.paragraph) { + actions.transcript.buildParagraphs() + } + } handleAnnotationClick(e, paragraph, annotation){ actions.audio.seek(annotation.start_ts) @@ -46,12 +56,13 @@ class ParagraphEditor extends Component { } render() { - // const { media } = this.props - const { paragraphs, selectedParagraph, selectedParagraphOffset } = this.state + const { paragraphs } = this.props + const { selectedParagraph, selectedParagraphOffset } = this.state return ( <div className='paragraphs'> <div className='content'> <ParagraphList + paragraphs={paragraphs} paragraphElementLookup={paragraphElementLookup} selectedParagraph={selectedParagraph} onAnnotationClick={this.handleAnnotationClick} @@ -72,10 +83,8 @@ class ParagraphEditor extends Component { } const mapStateToProps = state => ({ - // paragraph: state.paragraph.index, - // annotation: state.annotation.index, - // audio: state.audio, - // media: state.media.index, + paragraph: state.paragraph.index, + paragraphs: state.paragraph.paragraphs, }) const mapDispatchToProps = dispatch => ({ diff --git a/animism-align/frontend/app/views/paragraph/paragraph.reducer.js b/animism-align/frontend/app/views/paragraph/paragraph.reducer.js index c3babc8..5695ab3 100644 --- a/animism-align/frontend/app/views/paragraph/paragraph.reducer.js +++ b/animism-align/frontend/app/views/paragraph/paragraph.reducer.js @@ -4,6 +4,7 @@ import { session, getDefault, getDefaultInt } from 'app/session' import { crudState, crudReducer } from 'app/api/crud.reducer' const initialState = crudState('paragraph', { + paragraphs: [], options: { } }) @@ -14,6 +15,11 @@ export default function paragraphReducer(state = initialState, action) { // console.log(action.type, action) state = reducer(state, action) switch (action.type) { + case types.paragraph.update_transcript: + return { + ...state, + paragraphs: action.paragraphs, + } default: return state } diff --git a/animism-align/frontend/app/views/paragraph/transcript.actions.js b/animism-align/frontend/app/views/paragraph/transcript.actions.js new file mode 100644 index 0000000..3d2b045 --- /dev/null +++ b/animism-align/frontend/app/views/paragraph/transcript.actions.js @@ -0,0 +1,69 @@ +import * as types from 'app/types' +import { store, history, dispatch } from 'app/store' +import { MEDIA_TYPES } from 'app/constants' + +export const buildParagraphs = () => dispatch => { + const state = store.getState() + const { order: annotationOrder, lookup: annotationLookup } = state.annotation.index + const { lookup: paragraphLookup } = state.paragraph.index + let currentParagraph = {} + let sectionCount = 0 + const paragraphs = [] + // loop over the annotations in time order + annotationOrder.forEach((annotation_id, i) => { + const annotation = annotationLookup[annotation_id] + const paragraph = paragraphLookup[annotation.paragraph_id] + // if this annotation is media, insert it after the current paragraph + if (MEDIA_TYPES.has(annotation.type)) { + paragraphs.push({ + id: ('index_' + i), + type: annotation.type, + start_ts: annotation.start_ts, + end_ts: 0, + annotations: [annotation], + }) + return + } + // if this annotation is from a different paragraph, make a new paragraph + if (annotation.type === 'header' || annotation.paragraph_id !== currentParagraph.id) { + const paragraph_type = getParagraphType(annotation, paragraph) + currentParagraph = { + id: annotation.paragraph_id || ('index_' + i), + type: paragraph_type, + start_ts: annotation.start_ts, + end_ts: 0, + annotations: [], + } + if (annotation.type === 'header') { + currentParagraph.sectionIndex = sectionCount++ + currentParagraph.id = 'section_' + currentParagraph.sectionIndex + } + paragraphs.push(currentParagraph) + } + // if this annotation is a paragraph_end, set the end timestamp + if (annotation.type === 'paragraph_end') { + currentParagraph.end_ts = annotation.start_ts + } + // otherwise, just append this annotation to the paragraph + else { + currentParagraph.annotations.push(annotation) + } + }) + for (let i = 0; i < (paragraphs.length - 1); i++) { + if (!paragraphs[i].end_ts) { + paragraphs[i].end_ts = paragraphs[i+1].start_ts - 0.1 + } + } + dispatch({ type: types.paragraph.update_transcript, paragraphs }) +} + +const getParagraphType = (annotation, paragraph) => { + if (annotation.type === 'header') { + return annotation.type + } + if (!paragraph) { + return annotation.type + } + return paragraph.type +} + diff --git a/animism-align/frontend/app/views/viewer/checklist/checklist.content.js b/animism-align/frontend/app/views/viewer/checklist/checklist.content.js index 842947f..e4e47ec 100644 --- a/animism-align/frontend/app/views/viewer/checklist/checklist.content.js +++ b/animism-align/frontend/app/views/viewer/checklist/checklist.content.js @@ -4,7 +4,7 @@ import { connect } from 'react-redux' import actions from 'app/actions' import { ROMAN_NUMERALS } from 'app/constants' import { pad } from 'app/utils' -import { thumbnailURL } from 'app/views/align/align.util' +import { thumbnailURL } from 'app/utils/annotation.utils' import { PlayIcon } from '../nav/viewer.icons' class ChecklistContent extends Component { diff --git a/animism-align/frontend/app/views/viewer/sections/viewer.sections.js b/animism-align/frontend/app/views/viewer/sections/viewer.sections.js index 5cf40eb..cd5b4f0 100644 --- a/animism-align/frontend/app/views/viewer/sections/viewer.sections.js +++ b/animism-align/frontend/app/views/viewer/sections/viewer.sections.js @@ -5,7 +5,7 @@ import { connect } from 'react-redux' import actions from 'app/actions' import ViewerSectionsNav from './viewer.sections.nav' import { ROMAN_NUMERALS } from 'app/constants' -import { thumbnailURL } from 'app/views/align/align.util' +import { thumbnailURL } from 'app/utils/annotation.utils' class ViewerSections extends Component { componentDidMount() { diff --git a/animism-align/frontend/app/views/viewer/transcript/transcript.container.js b/animism-align/frontend/app/views/viewer/transcript/transcript.container.js index d3d3e9f..c48af47 100644 --- a/animism-align/frontend/app/views/viewer/transcript/transcript.container.js +++ b/animism-align/frontend/app/views/viewer/transcript/transcript.container.js @@ -15,21 +15,30 @@ class Transcript extends Component { this.handleAnnotationClick = this.handleAnnotationClick.bind(this) this.handleParagraphDoubleClick = this.handleParagraphDoubleClick.bind(this) } + + componentDidMount() { + actions.transcript.buildParagraphs() + } + handleAnnotationClick(e, annotation) { console.log(annotation) } + handleParagraphDoubleClick(e, paragraph) { return } + handleClose() { actions.viewer.hideSection('transcript') } + render() { - const { viewer } = this.props + const { viewer, paragraphs } = this.props return ( <div className="transcript"> <div className='content'> <ParagraphList + paragraphs={paragraphs} paragraphElementLookup={transcriptElementLookup} onAnnotationClick={this.handleAnnotationClick} onParagraphDoubleClick={this.handleParagraphDoubleClick} @@ -49,6 +58,7 @@ class Transcript extends Component { const mapStateToProps = state => ({ viewer: state.viewer, + paragraphs: state.paragraph.paragraphs, }) const mapDispatchToProps = dispatch => ({ |
