summaryrefslogtreecommitdiff
path: root/animism-align/frontend/app
diff options
context:
space:
mode:
Diffstat (limited to 'animism-align/frontend/app')
-rw-r--r--animism-align/frontend/app/actions.js2
-rw-r--r--animism-align/frontend/app/common/form.component.js2
-rw-r--r--animism-align/frontend/app/constants.js13
-rw-r--r--animism-align/frontend/app/types.js4
-rw-r--r--animism-align/frontend/app/utils/align.utils.js (renamed from animism-align/frontend/app/views/align/align.util.js)5
-rw-r--r--animism-align/frontend/app/utils/annotation.utils.js0
-rw-r--r--animism-align/frontend/app/utils/index.js11
-rw-r--r--animism-align/frontend/app/views/align/align.actions.js2
-rw-r--r--animism-align/frontend/app/views/align/align.container.js2
-rw-r--r--animism-align/frontend/app/views/align/align.css108
-rw-r--r--animism-align/frontend/app/views/align/components/annotations/annotation.form.css55
-rw-r--r--animism-align/frontend/app/views/align/components/annotations/annotation.form.js37
-rw-r--r--animism-align/frontend/app/views/align/components/annotations/annotation.index.css76
-rw-r--r--animism-align/frontend/app/views/align/components/annotations/annotation.index.js6
-rw-r--r--animism-align/frontend/app/views/align/components/annotations/annotationForms/annotationForm.utility.js80
-rw-r--r--animism-align/frontend/app/views/align/components/annotations/annotationForms/index.js11
-rw-r--r--animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.image.js4
-rw-r--r--animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.utility.js (renamed from animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.util.js)14
-rw-r--r--animism-align/frontend/app/views/align/components/annotations/annotationTypes/annotationTypes.video.js4
-rw-r--r--animism-align/frontend/app/views/align/components/annotations/annotationTypes/index.js5
-rw-r--r--animism-align/frontend/app/views/align/containers/annotations.container.js3
-rw-r--r--animism-align/frontend/app/views/align/containers/timeline.container.js3
-rw-r--r--animism-align/frontend/app/views/annotation/annotation.util.js26
-rw-r--r--animism-align/frontend/app/views/media/containers/media.index.js2
-rw-r--r--animism-align/frontend/app/views/paragraph/components/paragraph.list.js84
-rw-r--r--animism-align/frontend/app/views/paragraph/containers/paragraphEditor.container.js21
-rw-r--r--animism-align/frontend/app/views/paragraph/paragraph.reducer.js6
-rw-r--r--animism-align/frontend/app/views/paragraph/transcript.actions.js69
-rw-r--r--animism-align/frontend/app/views/viewer/checklist/checklist.content.js2
-rw-r--r--animism-align/frontend/app/views/viewer/sections/viewer.sections.js2
-rw-r--r--animism-align/frontend/app/views/viewer/transcript/transcript.container.js12
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 => ({