summaryrefslogtreecommitdiff
path: root/animism-align/frontend/app/views/editor/annotation
diff options
context:
space:
mode:
Diffstat (limited to 'animism-align/frontend/app/views/editor/annotation')
-rw-r--r--animism-align/frontend/app/views/editor/annotation/annotation.form.css67
-rw-r--r--animism-align/frontend/app/views/editor/annotation/annotation.index.css125
-rw-r--r--animism-align/frontend/app/views/editor/annotation/annotations.container.js31
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotation.form.js186
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotation.index.js126
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.gallery.js132
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.image.js120
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.text.js153
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.utility.js172
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.video.js160
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotationForms/index.js46
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.gallery.js75
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.image.js54
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.text.js121
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.utility.js107
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.video.js51
-rw-r--r--animism-align/frontend/app/views/editor/annotation/components/annotationTypes/index.js57
17 files changed, 1783 insertions, 0 deletions
diff --git a/animism-align/frontend/app/views/editor/annotation/annotation.form.css b/animism-align/frontend/app/views/editor/annotation/annotation.form.css
new file mode 100644
index 0000000..fa663c7
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/annotation.form.css
@@ -0,0 +1,67 @@
+/* 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 div.textarea {
+ margin-bottom: 0.5rem;
+}
+.annotationForm img {
+ max-width: 100%;
+ max-height: 6rem;
+}
+
+.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;
+}
+.annotationForm .checkbox {
+ margin-top: 0.5rem;
+ margin-bottom: 0.5rem;
+} \ No newline at end of file
diff --git a/animism-align/frontend/app/views/editor/annotation/annotation.index.css b/animism-align/frontend/app/views/editor/annotation/annotation.index.css
new file mode 100644
index 0000000..afedbde
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/annotation.index.css
@@ -0,0 +1,125 @@
+/* Annotation index */
+
+.annotationIndex {
+ width: 800px;
+}
+.annotationIndex .annotation {
+ position: absolute;
+ left: 5px;
+ max-width: 300px;
+ 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: 2;
+ background-image: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.4));
+}
+.annotationIndex .annotation.media {
+ width: 300px;
+ left: calc(405px + 0.5rem);
+}
+.annotationIndex .annotation.sentence.even {
+ background-color: #83b;
+}
+.annotationIndex .annotation.sentence.odd {
+ background-color: #537;
+}
+.annotationIndex .annotation.section_heading {
+ background-color: #983;
+ z-index: 1;
+}
+.annotationIndex .annotation.heading_text {
+ background-color: #838;
+}
+.annotationIndex .annotation.paragraph_end {
+ background-color: #003;
+ border-top: 1px solid #888;
+ width: 300px;
+ padding: 1px;
+}
+.annotationIndex .annotation.utility {
+ left: calc(505px + 0.5rem);
+ width: 200px;
+}
+.annotationIndex .annotation.footnote {
+ left: calc(605px + 0.5rem);
+ width: 200px;
+}
+.annotationIndex .annotation.text_plate {
+ left: calc(605px + 0.5rem);
+ width: 250px;
+}
+.annotationIndex .annotation.utility.curtain {
+ background-image: linear-gradient(rgba(255,255,255,1.0), rgba(255,255,255,1.0));
+ width: 15rem;
+ padding: 0;
+ overflow: hidden;
+}
+.annotationIndex .annotation.utility.curtain span {
+ position: absolute;
+ top: 0; left: 0;
+ z-index: 1;
+ color: black;
+ text-shadow: 0 0 3px #fff, 0 0 2px #fff, 0 0 1px #fff;
+ padding: 0.25rem;
+}
+.annotationIndex .annotation.utility.curtain .fadeIn {
+ z-index: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ background-image: linear-gradient(rgba(0,0,0,1.0), rgba(255,255,255,1.0));
+ background-size: cover;
+}
+.annotationIndex .annotation.utility.curtain .fadeOut {
+ z-index: 0;
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ background-image: linear-gradient(rgba(255,255,255,1.0), rgba(0,0,0,1.0));
+ background-size: cover;
+}
+
+/* Condensed layout (first lines) */
+
+.annotationIndex.condensed .annotation.sentence {
+ z-index: 0;
+ white-space: pre;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.annotationIndex.condensed .annotation.section_heading {
+ z-index: 2;
+}
+.annotationIndex.condensed .annotation.heading_text {
+ 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: 4;
+}
+.annotationIndex.collapsed .annotation.section_heading {
+ z-index: 3;
+}
+.annotationIndex.collapsed .annotation.heading_text {
+ z-index: 2;
+}
+.annotationIndex.collapsed .annotation.paragraph_end {
+ border-top-color: #333;
+}
diff --git a/animism-align/frontend/app/views/editor/annotation/annotations.container.js b/animism-align/frontend/app/views/editor/annotation/annotations.container.js
new file mode 100644
index 0000000..3a247d0
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/annotations.container.js
@@ -0,0 +1,31 @@
+import React, { Component } from 'react'
+import { connect } from 'react-redux'
+
+import AnnotationForm from './components/annotation.form'
+import AnnotationIndex from './components/annotation.index'
+
+import './annotation.form.css'
+import './annotation.index.css'
+
+class Annotations extends Component {
+ constructor(props){
+ super(props)
+ }
+ render() {
+ return (
+ <div className='annotations'>
+ <AnnotationIndex />
+ {this.props.annotation.id &&
+ <AnnotationForm />
+ }
+ </div>
+ )
+ }
+}
+
+const mapStateToProps = state => ({
+ timeline: state.align.timeline,
+ annotation: state.align.annotation,
+})
+
+export default connect(mapStateToProps)(Annotations)
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotation.form.js b/animism-align/frontend/app/views/editor/annotation/components/annotation.form.js
new file mode 100644
index 0000000..6804cdd
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotation.form.js
@@ -0,0 +1,186 @@
+import React, { Component } from 'react'
+// import { Link } from 'react-router-dom'
+import { connect } from 'react-redux'
+
+import actions from 'app/actions'
+
+import { ANNOTATION_SELECT_OPTIONS, MEDIA_ANNOTATION_TYPES } from 'app/constants'
+import { timestamp } from 'app/utils'
+import { timeToPosition } from 'app/utils/align.utils'
+import { Select } from 'app/common'
+
+import { annotationFormLookup } from './annotationForms'
+
+const annotationTextTypes = new Set([
+ 'sentence', 'section_heading', 'heading_text', 'pullquote_credit', 'footnote', 'text_plate',
+])
+
+class AnnotationForm extends Component {
+ constructor(props){
+ 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)
+ this.handleDestroy = this.handleDestroy.bind(this)
+ this.textareaRef = React.createRef()
+ }
+ componentDidMount() {
+ if (this.textareaRef && this.textareaRef.current) {
+ this.textareaRef.current.focus()
+ }
+ }
+ handleKeyDown(e) {
+ if (e.keyCode === 27) { // escape
+ actions.align.hideAnnotationForm()
+ return
+ }
+ // console.log(e.keyCode)
+ if (!e.metaKey && !e.ctrlKey) return
+ let { start_ts } = this.props.annotation
+ switch (e.keyCode) {
+ case 38: // up
+ e.preventDefault()
+ start_ts -= 0.1
+ actions.align.updateAnnotationForm('start_ts', Math.max(0, start_ts))
+ actions.audio.seek(start_ts)
+ actions.align.setCursor(start_ts)
+ break
+ case 40: // down
+ e.preventDefault()
+ start_ts += 0.1
+ actions.align.updateAnnotationForm('start_ts', Math.max(0, start_ts))
+ actions.audio.seek(start_ts)
+ actions.align.setCursor(start_ts)
+ break
+ case 83: // ctrl-S
+ e.preventDefault()
+ this.handleSubmit()
+ default:
+ break
+ }
+ }
+ handleChange(e) {
+ const { name, value } = e.target
+ this.handleSelect(name, value)
+ }
+ 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)
+ }
+ handleSubmit() {
+ const { annotation } = this.props
+ if (annotation.type === 'paragraph_end') {
+ annotation.text = ''
+ }
+ if (annotation.type in MEDIA_ANNOTATION_TYPES) {
+ if (!annotation.settings.media_id) return
+ annotation.text = ''
+ }
+ if (annotation.id === 'new') {
+ delete annotation.id
+ actions.annotation.create(annotation)
+ .then(response => {
+ console.log(response)
+ actions.align.hideAnnotationForm()
+ })
+ } else {
+ actions.annotation.update(annotation)
+ .then(response => {
+ console.log(response)
+ actions.align.hideAnnotationForm()
+ })
+ }
+ }
+ handleDestroy() {
+ const { annotation } = this.props
+ if (annotation.id === 'new') {
+ actions.align.hideAnnotationForm()
+ } else {
+ actions.annotation.destroy(annotation)
+ .then(response => {
+ console.log(response)
+ actions.align.hideAnnotationForm()
+ })
+ }
+ }
+ render() {
+ const { timeline, annotation } = this.props
+ return (
+ <div
+ className='annotationForm'
+ style={{
+ top: timeToPosition(annotation.start_ts, timeline),
+ }}
+ >
+ {this.renderButtons()}
+ {annotationTextTypes.has(annotation.type) && this.renderTextarea()}
+ {(annotation.type in annotationFormLookup) && this.renderElementForm()}
+ </div>
+ )
+ }
+ renderButtons() {
+ const { annotation } = this.props
+ return (
+ <div className='row buttons'>
+ <div>
+ <Select
+ name='type'
+ selected={annotation.type}
+ options={ANNOTATION_SELECT_OPTIONS}
+ defaultOption='text'
+ onChange={this.handleSelect}
+ />
+ <div className='ts'>{timestamp(annotation.start_ts, 1, true)}</div>
+ </div>
+ <div>
+ {annotation.id !== 'new' && <button onClick={this.handleDestroy}>Delete</button>}
+ <button onClick={this.handleSubmit}>Save</button>
+ </div>
+ </div>
+ )
+ }
+ renderTextarea() {
+ const { annotation } = this.props
+ return (
+ <div className='textarea'>
+ <textarea
+ name='text'
+ value={annotation.text}
+ onKeyDown={this.handleKeyDown}
+ onChange={this.handleChange}
+ ref={this.textareaRef}
+ />
+ </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 => ({
+ annotation: state.align.annotation,
+ timeline: state.align.timeline,
+ media: state.media.index,
+})
+
+export default connect(mapStateToProps)(AnnotationForm)
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotation.index.js b/animism-align/frontend/app/views/editor/annotation/components/annotation.index.js
new file mode 100644
index 0000000..e2c7a51
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotation.index.js
@@ -0,0 +1,126 @@
+import React, { PureComponent } from 'react'
+import { connect } from 'react-redux'
+
+import actions from 'app/actions'
+
+import { ZOOM_STEPS, INNER_HEIGHT } from 'app/constants'
+import { clamp } from 'app/utils'
+import { positionToTime, timeToPosition } from 'app/utils/align.utils'
+
+import { AnnotationElementLookup } from './annotationTypes'
+
+class AnnotationIndex extends PureComponent {
+ state = {
+ items: [],
+ }
+ constructor(props){
+ super(props)
+ this.handleClick = this.handleClick.bind(this)
+ }
+ componentDidUpdate(prevProps) {
+ if (this.props.index.loading) return
+ if (prevProps.timeline !== this.props.timeline || prevProps.index !== this.props.index) {
+ this.update()
+ }
+ }
+ update() {
+ let { timeline, index } = this.props
+ let { start_ts, zoom, duration } = this.props.timeline
+ const { order, lookup } = index
+
+ let secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 // 0.1 sec / step
+ let widthTimeDuration = INNER_HEIGHT * secondsPerPixel // secs per pixel
+
+ let timeMin = start_ts - 60.0
+ let timeMax = Math.min(start_ts + widthTimeDuration, duration)
+
+ const items = order.filter(id => {
+ const { start_ts: ts } = lookup[id]
+ return (timeMin < ts && ts < timeMax)
+ }).map(id => lookup[id])
+ this.setState({ items })
+ }
+ handleClick(e, annotation) {
+ e.stopPropagation()
+ if (!annotation) return
+ if (e.shiftKey) {
+ e.preventDefault()
+ this.handleParagraphSelection(annotation, e.metaKey)
+ }
+ actions.audio.seek(annotation.start_ts)
+ actions.align.setSelectedAnnotation(annotation)
+ }
+ handleParagraphSelection(annotation, shouldClear) {
+ const { selected_paragraph_id } = this.props.timeline
+ if (!selected_paragraph_id || selected_paragraph_id === -1 || shouldClear) {
+ if (annotation.paragraph_id && !shouldClear) {
+ actions.align.setSelectedParagraph(annotation.paragraph_id)
+ } else {
+ actions.paragraph.create({
+ type: 'paragraph',
+ episode_id: this.props.episode_id,
+ start_ts: annotation.start_ts,
+ }).then(data => {
+ actions.align.setSelectedParagraph(data.res.id)
+ annotation.paragraph_id = data.res.id
+ actions.annotation.update(annotation)
+ })
+ }
+ } else if (selected_paragraph_id !== annotation.paragraph_id) {
+ annotation.paragraph_id = selected_paragraph_id
+ actions.annotation.update(annotation)
+ }
+ }
+ handleDoubleClick(e, annotation) {
+ e.stopPropagation()
+ actions.align.showEditAnnotationForm(annotation)
+ }
+ render() {
+ const { timeline, media, annotationInForm, selectedAnnotation } = this.props
+ const { start_ts, zoom, selected_annotation_id } = timeline
+ const { items } = this.state
+ const className = (zoom < 2)
+ ? 'annotationIndex'
+ : (zoom < 3)
+ ? 'annotationIndex condensed'
+ : 'annotationIndex collapsed'
+ return (
+ <div className={className}>
+ {items.map(annotation => {
+ if (annotationInForm && annotation.id === annotationInForm.id) {
+ return null
+ }
+ if (annotation.id === selected_annotation_id) {
+ annotation = selectedAnnotation
+ }
+ const { id, type, start_ts } = annotation
+ const AnnotationElement = AnnotationElementLookup[type]
+ const y = timeToPosition(start_ts, timeline)
+ return (
+ <AnnotationElement
+ key={id}
+ y={y}
+ selected={annotation.id === selected_annotation_id}
+ annotation={annotation}
+ media={media}
+ timeline={timeline}
+ onClick={this.handleClick}
+ onDoubleClick={this.handleDoubleClick}
+ />
+ )
+ })}
+ </div>
+ )
+ }
+}
+
+const mapStateToProps = state => ({
+ timeline: state.align.timeline,
+ annotationInForm: state.align.annotation,
+ selectedAnnotation: state.align.selectedAnnotation,
+ index: state.annotation.index,
+ media: state.media.index.lookup,
+ episode_id: state.site.episode.id,
+})
+
+export default connect(mapStateToProps)(AnnotationIndex)
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.gallery.js b/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.gallery.js
new file mode 100644
index 0000000..0a8b3fb
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.gallery.js
@@ -0,0 +1,132 @@
+import React, { Component } from 'react'
+
+import { CURTAIN_COLOR_SELECT_OPTIONS } from 'app/constants'
+import { TextInput, Select, Checkbox, LabelDescription } from 'app/common'
+import { AnnotationFormFullscreen } from './annotationForm.utility'
+import { makeMediaItems, makeGalleryItems } from 'app/utils/annotation.utils'
+
+export const AnnotationFormGallery = ({ annotation, media, handleSettingsSelect, handleSettingsChange }) => {
+ if (!media.lookup) return <div />
+ const image_list_items = makeMediaItems(media, ['gallery'])
+ return (
+ <div className='options'>
+ <Select
+ name='media_id'
+ className="media_id"
+ selected={annotation.settings.media_id}
+ options={image_list_items}
+ defaultOption='Choose a gallery'
+ onChange={handleSettingsSelect}
+ />
+
+ <TextInput
+ title="Title"
+ name="title"
+ placeholder="Enter title or leave blank"
+ data={annotation.settings}
+ onChange={handleSettingsChange}
+ autoComplete="off"
+ />
+
+ <Checkbox
+ label="Fullscreen"
+ name="fullscreen"
+ checked={annotation.settings.fullscreen}
+ onChange={handleSettingsSelect}
+ />
+
+ <Checkbox
+ label="Inline"
+ name="inline"
+ checked={annotation.settings.inline}
+ onChange={handleSettingsSelect}
+ />
+
+ <Checkbox
+ label="Hide in transcript"
+ name="hide_in_transcript"
+ checked={annotation.settings.hide_in_transcript}
+ onChange={handleSettingsSelect}
+ />
+
+ <Select
+ title='Color'
+ name='color'
+ selected={annotation.settings.color}
+ options={CURTAIN_COLOR_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+
+ <Select
+ title='Arrow Color'
+ name='arrow_color'
+ selected={annotation.settings.arrow_color}
+ options={CURTAIN_COLOR_SELECT_OPTIONS}
+ defaultOption='Color of UI arrows'
+ onChange={handleSettingsSelect}
+ />
+
+ {(annotation.settings.fullscreen && !annotation.settings.inline) && (
+ <AnnotationFormFullscreen
+ annotation={annotation}
+ handleSettingsChange={handleSettingsChange}
+ handleSettingsSelect={handleSettingsSelect}
+ />
+ )}
+ </div>
+ )
+}
+
+export const AnnotationFormGalleryAdvance = ({ annotation, media, handleSettingsSelect, handleSettingsChange }) => {
+ if (!media.lookup) return <div />
+ const image_list_items = makeMediaItems(media, ['gallery'])
+ const { gallery_items, thumbnail } = makeGalleryItems(annotation, media)
+ return (
+ <div className='options'>
+ <Select
+ name='media_id'
+ className="media_id"
+ selected={annotation.settings.media_id}
+ options={image_list_items}
+ defaultOption='Choose a gallery'
+ onChange={handleSettingsSelect}
+ />
+
+ {gallery_items && (
+ <Select
+ name='frame_index'
+ selected={annotation.settings.frame_index}
+ options={gallery_items}
+ defaultOption='Choose an image'
+ onChange={handleSettingsSelect}
+ />
+ )}
+
+ {thumbnail && (
+ <img src={thumbnail.url} />
+ )}
+
+ <Checkbox
+ label="Advance half a frame forward to fit two images in view"
+ name="half_frame"
+ checked={annotation.settings.half_frame}
+ onChange={handleSettingsSelect}
+ />
+
+ <Checkbox
+ label="Hide in transcript"
+ name="hide_in_transcript"
+ checked={annotation.settings.hide_in_transcript}
+ onChange={handleSettingsSelect}
+ />
+
+ <Checkbox
+ label="Show in checklist"
+ name="show_in_checklist"
+ checked={annotation.settings.show_in_checklist}
+ onChange={handleSettingsSelect}
+ />
+ </div>
+ )
+}
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.image.js b/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.image.js
new file mode 100644
index 0000000..3de2ac5
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.image.js
@@ -0,0 +1,120 @@
+import React, { Component } from 'react'
+
+import { CURTAIN_COLOR_SELECT_OPTIONS, IMAGE_INLINE_SIZE_OPTIONS } from 'app/constants'
+import { Select, Checkbox, TextInput } from 'app/common'
+import { AnnotationFormFullscreen } from './annotationForm.utility'
+import { makeMediaItems, makeGalleryItems } from 'app/utils/annotation.utils'
+
+export const AnnotationFormImage = ({ annotation, media, handleSettingsSelect, handleSettingsChange }) => {
+ if (!media.lookup) return <div />
+ const image_list_items = makeMediaItems(media, ['image', 'gallery'])
+ const { gallery_items, thumbnail } = makeGalleryItems(annotation, media)
+ console.log(annotation)
+ return (
+ <div className='options'>
+ <Select
+ name='media_id'
+ className="media_id"
+ selected={annotation.settings.media_id}
+ options={image_list_items}
+ defaultOption='Choose an image'
+ onChange={handleSettingsSelect}
+ />
+
+ {gallery_items && (
+ <Select
+ name='frame_index'
+ selected={annotation.settings.frame_index}
+ options={gallery_items}
+ defaultOption='Choose an image'
+ onChange={handleSettingsSelect}
+ />
+ )}
+
+ {thumbnail && (
+ <img src={thumbnail.url} />
+ )}
+
+ <Checkbox
+ label="Fullscreen"
+ name="fullscreen"
+ checked={annotation.settings.fullscreen}
+ onChange={handleSettingsSelect}
+ />
+
+ <Select
+ title='Color'
+ name='color'
+ selected={annotation.settings.color}
+ options={CURTAIN_COLOR_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+
+ {(annotation.settings.fullscreen) && (
+ <div>
+ <Checkbox
+ label="Hide inline image"
+ name="hide_poster_inline"
+ checked={annotation.settings.hide_poster_inline}
+ onChange={handleSettingsSelect}
+ />
+ <Select
+ title='Inline size'
+ name='inline_size'
+ selected={annotation.settings.inline_size}
+ options={IMAGE_INLINE_SIZE_OPTIONS}
+ defaultOption='Pick a size'
+ onChange={handleSettingsSelect}
+ />
+ <Checkbox
+ label="Hide speaker icon"
+ name="hide_speaker_icon"
+ checked={annotation.settings.hide_speaker_icon}
+ onChange={handleSettingsSelect}
+ />
+ <Select
+ title='Inline background color'
+ name='inline_color'
+ selected={annotation.settings.inline_color}
+ options={CURTAIN_COLOR_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+ </div>
+ )}
+
+ <Checkbox
+ label="Hide caption"
+ name="hide_caption"
+ checked={annotation.settings.hide_caption}
+ onChange={handleSettingsSelect}
+ />
+
+ <Checkbox
+ label="Hide in transcript"
+ name="hide_in_transcript"
+ checked={annotation.settings.hide_in_transcript}
+ onChange={handleSettingsSelect}
+ />
+
+ <TextInput
+ title="Override start time"
+ name="override_start_ts"
+ className="number"
+ placeholder="0:00"
+ data={annotation.settings}
+ onChange={handleSettingsChange}
+ autoComplete="off"
+ />
+
+ {(annotation.settings.fullscreen && !annotation.settings.inline) && (
+ <AnnotationFormFullscreen
+ annotation={annotation}
+ handleSettingsChange={handleSettingsChange}
+ handleSettingsSelect={handleSettingsSelect}
+ />
+ )}
+ </div>
+ )
+}
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.text.js b/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.text.js
new file mode 100644
index 0000000..8bc7675
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.text.js
@@ -0,0 +1,153 @@
+import React, { Component } from 'react'
+
+import {
+ CURTAIN_COLOR_SELECT_OPTIONS,
+ CURTAIN_STYLE_SELECT_OPTIONS,
+ BLACK_WHITE_SELECT_OPTIONS,
+ IMAGE_BACKGROUND_SIZE_OPTIONS,
+} from 'app/constants'
+import { Select, Checkbox, TextInput, LabelDescription } from 'app/common'
+import { AnnotationFormFullscreen } from './annotationForm.utility'
+import { makeMediaItems } from 'app/utils/annotation.utils'
+
+export const AnnotationFormSectionHeading = ({ annotation, media, handleSettingsSelect, handleSettingsChange }) => {
+ const image_list_items = makeMediaItems(media, ['image', 'video', 'gallery'])
+ return (
+ <div className='options'>
+ <Checkbox
+ label="Hidden"
+ name="hidden"
+ checked={annotation.settings.hidden}
+ onChange={handleSettingsSelect}
+ />
+ <Select
+ title='Background Color'
+ name='color'
+ selected={annotation.settings.color}
+ options={BLACK_WHITE_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+ <Select
+ title='Cover image'
+ name='media_id'
+ className="media_id"
+ selected={annotation.settings.media_id}
+ options={image_list_items}
+ defaultOption='Choose an image'
+ onChange={handleSettingsSelect}
+ />
+ <Select
+ title='Cover style'
+ name='cover_style'
+ className="cover_style"
+ selected={annotation.settings.cover_style}
+ options={IMAGE_BACKGROUND_SIZE_OPTIONS}
+ defaultOption='Cover image caption style'
+ onChange={handleSettingsSelect}
+ />
+ <LabelDescription>
+ {'Background color for section'}
+ </LabelDescription>
+ <Select
+ title='Transition Color'
+ name='transition_color'
+ selected={annotation.settings.transition_color}
+ options={CURTAIN_COLOR_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+ <LabelDescription>
+ {'Player will fade from this color when section begins'}
+ </LabelDescription>
+ {!annotation.settings.no_audio &&
+ <div>
+ <Select
+ title='Section Nav Color'
+ name='section_nav_color'
+ selected={annotation.settings.section_nav_color}
+ options={BLACK_WHITE_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+ <LabelDescription>
+ {'Section nav thumbnail icon color'}
+ </LabelDescription>
+ </div>
+ }
+ <Checkbox
+ label="Section does not have audio"
+ name="no_audio"
+ checked={annotation.settings.no_audio}
+ onChange={handleSettingsSelect}
+ />
+ <LabelDescription>
+ {'Check if this is a text-only section'}
+ </LabelDescription>
+ </div>
+ )
+}
+
+export const AnnotationFormTextPlate = ({ annotation, handleSettingsSelect, handleSettingsChange }) => {
+ return (
+ <div className='options'>
+ <Select
+ title='Color'
+ name='color'
+ selected={annotation.settings.color}
+ options={CURTAIN_COLOR_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+ <Select
+ title='Curtain style'
+ name='transition_color'
+ selected={annotation.settings.transition_color}
+ options={CURTAIN_STYLE_SELECT_OPTIONS}
+ defaultOption='Pick a style'
+ onChange={handleSettingsSelect}
+ />
+ <AnnotationFormFullscreen
+ annotation={annotation}
+ handleSettingsChange={handleSettingsChange}
+ handleSettingsSelect={handleSettingsSelect}
+ />
+ </div>
+ )
+}
+
+export const AnnotationFormSubtitle = ({ annotation, handleSettingsSelect, handleSettingsChange }) => {
+ return (
+ <div className='options'>
+ <Select
+ title='Color'
+ name='color'
+ selected={annotation.settings.color}
+ options={CURTAIN_COLOR_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+
+ <AnnotationFormFullscreen
+ annotation={annotation}
+ handleSettingsChange={handleSettingsChange}
+ handleSettingsSelect={handleSettingsSelect}
+ />
+ </div>
+ )
+}
+
+export const AnnotationFormFootnote = ({ annotation, handleSettingsSelect, handleSettingsChange }) => {
+ return (
+ <div className='options'>
+ <TextInput
+ title="Actual Timestamp"
+ name="actual_ts"
+ placeholder="0:00"
+ data={annotation.settings}
+ onChange={handleSettingsChange}
+ autoComplete="off"
+ />
+ </div>
+ )
+}
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.utility.js b/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.utility.js
new file mode 100644
index 0000000..7e823cc
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.utility.js
@@ -0,0 +1,172 @@
+import React, { Component } from 'react'
+
+import { timestamp } from 'app/utils'
+import { TextInput, LabelDescription, Select, Checkbox } from 'app/common'
+import { CURTAIN_COLOR_SELECT_OPTIONS, CURTAIN_STYLE_SELECT_OPTIONS } from 'app/constants'
+import { annotationFadeTimings } from 'app/utils/annotation.utils'
+import { makeMediaItems } from 'app/utils/annotation.utils'
+
+export const AnnotationFormIntro = ({ annotation, media, handleSettingsChange, handleSettingsSelect }) => {
+ if (!media.lookup) return <div />
+ const image_list_items = makeMediaItems(media, 'file')
+ return (
+ <div className='options'>
+ <Select
+ name='media_id'
+ className="media_id"
+ selected={annotation.settings.media_id}
+ options={image_list_items}
+ defaultOption='Choose a file'
+ onChange={handleSettingsSelect}
+ />
+
+ <TextInput
+ title="Title"
+ name="title"
+ placeholder="Enter title or leave blank"
+ data={annotation.settings}
+ onChange={handleSettingsChange}
+ autoComplete="off"
+ />
+
+ <TextInput
+ title="Subtitle"
+ name="subtitle"
+ placeholder="Enter subtitle or leave blank"
+ data={annotation.settings}
+ onChange={handleSettingsChange}
+ autoComplete="off"
+ />
+
+ <Select
+ title='Color'
+ name='color'
+ selected={annotation.settings.color}
+ options={CURTAIN_COLOR_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+
+ <TextInput
+ title="Actual start time"
+ name="intro_start_ts"
+ placeholder="0:00"
+ data={annotation.settings}
+ onChange={handleSettingsChange}
+ autoComplete="off"
+ />
+
+ <LabelDescription>
+ {'Timestamp where voiceover starts, after any intro sound effect.'}
+ </LabelDescription>
+
+ <AnnotationFormFullscreen
+ annotation={annotation}
+ handleSettingsChange={handleSettingsChange}
+ handleSettingsSelect={handleSettingsSelect}
+ />
+ </div>
+ )
+}
+
+export const AnnotationFormCurtain = ({ annotation, handleSettingsChange, handleSettingsSelect }) => {
+ return (
+ <div className='options'>
+ <TextInput
+ title="Curtain text"
+ name="curtain_text"
+ placeholder="Enter text or leave blank"
+ data={annotation.settings}
+ onChange={handleSettingsChange}
+ autoComplete="off"
+ />
+
+ <Select
+ title='Color'
+ name='color'
+ selected={annotation.settings.color}
+ options={CURTAIN_COLOR_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+
+ <Select
+ title='Style'
+ name='curtain_style'
+ selected={annotation.settings.curtain_style}
+ options={CURTAIN_STYLE_SELECT_OPTIONS}
+ defaultOption='Pick a style'
+ onChange={handleSettingsSelect}
+ />
+
+ <Checkbox
+ label="Contains flashing light"
+ name="flashing_light_warning"
+ checked={annotation.settings.flashing_light_warning}
+ onChange={handleSettingsSelect}
+ />
+
+ <AnnotationFormFullscreen
+ alwaysAccessible
+ annotation={annotation}
+ handleSettingsChange={handleSettingsChange}
+ handleSettingsSelect={handleSettingsSelect}
+ />
+ </div>
+ )
+}
+
+export const AnnotationFormFullscreen = ({ annotation, handleSettingsChange, handleSettingsSelect }) => {
+ const {
+ fadeInDuration, fadeOutDuration, duration,
+ start_ts, end_ts, fade_in_end_ts, fade_out_start_ts,
+ } = annotationFadeTimings(annotation)
+ return (
+ <div>
+ <TextInput
+ title="Total duration"
+ name="duration"
+ className="number"
+ placeholder="0:00"
+ data={annotation.settings}
+ onChange={handleSettingsChange}
+ autoComplete="off"
+ />
+ <LabelDescription>
+ {duration}
+ {' 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>
+ {fadeInDuration}
+ {' 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>
+ {fadeOutDuration}
+ {' seconds, starts at '}
+ {timestamp(fade_out_start_ts)}
+ </LabelDescription>
+ </div>
+ )
+}
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.video.js b/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.video.js
new file mode 100644
index 0000000..dd5f640
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotationForms/annotationForm.video.js
@@ -0,0 +1,160 @@
+import React, { Component } from 'react'
+
+import { CURTAIN_COLOR_SELECT_OPTIONS, IMAGE_BACKGROUND_SIZE_OPTIONS } from 'app/constants'
+import { Select, Checkbox, TextInput, LabelDescription } from 'app/common'
+import { AnnotationFormFullscreen } from './annotationForm.utility'
+import { makeMediaItems } from 'app/utils/annotation.utils'
+
+export const AnnotationFormVideo = ({ annotation, media, handleSettingsSelect, handleSettingsChange }) => {
+ if (!media.lookup) return <div />
+ const video_list_items = makeMediaItems(media, ['video'])
+ return (
+ <div className='options'>
+ <Select
+ name='media_id'
+ className="media_id"
+ selected={annotation.settings.media_id}
+ options={video_list_items}
+ defaultOption='Choose a video'
+ onChange={handleSettingsSelect}
+ />
+ <Checkbox
+ label="Autoplay"
+ name="autoplay"
+ checked={annotation.settings.autoplay}
+ onChange={handleSettingsSelect}
+ />
+ <Checkbox
+ label="Fullscreen"
+ name="fullscreen"
+ checked={annotation.settings.fullscreen}
+ onChange={handleSettingsSelect}
+ />
+ <Checkbox
+ label="Inline"
+ name="inline"
+ checked={annotation.settings.inline}
+ onChange={handleSettingsSelect}
+ />
+ <Checkbox
+ label="Loop"
+ name="loop"
+ checked={annotation.settings.loop}
+ onChange={handleSettingsSelect}
+ />
+ <Checkbox
+ label="Unmute"
+ name="unmuted"
+ checked={annotation.settings.unmuted}
+ onChange={handleSettingsSelect}
+ />
+ {annotation.settings.inline && (
+ <Checkbox
+ label="Hide Controls"
+ name="hide_controls"
+ checked={annotation.settings.hide_controls}
+ onChange={handleSettingsSelect}
+ />
+ )}
+ {annotation.settings.inline && (
+ <Checkbox
+ label="Show poster image"
+ name="poster"
+ checked={annotation.settings.poster}
+ onChange={handleSettingsSelect}
+ />
+ )}
+ {(annotation.settings.fullscreen && !annotation.settings.inline) && (
+ <Checkbox
+ label="Hide inline video poster"
+ name="hide_poster_inline"
+ checked={annotation.settings.hide_poster_inline}
+ onChange={handleSettingsSelect}
+ />
+ )}
+ {(annotation.settings.fullscreen && !annotation.settings.inline) && (
+ <Checkbox
+ label="Can play full video at end of section"
+ name="can_play_full_video"
+ checked={annotation.settings.can_play_full_video}
+ onChange={handleSettingsSelect}
+ />
+ )}
+ <Checkbox
+ label="Hide in transcript"
+ name="hide_in_transcript"
+ checked={annotation.settings.hide_in_transcript}
+ onChange={handleSettingsSelect}
+ />
+ <Select
+ title='Color'
+ name='color'
+ selected={annotation.settings.color}
+ options={CURTAIN_COLOR_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+ <Select
+ title='Poster Size'
+ name='poster_size'
+ selected={annotation.settings.poster_size}
+ options={IMAGE_BACKGROUND_SIZE_OPTIONS}
+ defaultOption='Select size'
+ onChange={handleSettingsSelect}
+ />
+ <Select
+ title='Poster background color'
+ name='poster_background_color'
+ selected={annotation.settings.poster_background_color}
+ options={CURTAIN_COLOR_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+ <TextInput
+ title="Video start time"
+ name="video_start_ts"
+ className="number"
+ placeholder="0:00"
+ data={annotation.settings}
+ onChange={handleSettingsChange}
+ autoComplete="off"
+ />
+ <LabelDescription>
+ {'Auto-advances the video to this point when starting'}
+ </LabelDescription>
+
+ {(annotation.settings.fullscreen && !annotation.settings.inline) && (
+ <AnnotationFormFullscreen
+ annotation={annotation}
+ handleSettingsChange={handleSettingsChange}
+ handleSettingsSelect={handleSettingsSelect}
+ />
+ )}
+ </div>
+ )
+}
+
+export const AnnotationFormVideoSetVolume = ({ annotation, handleSettingsChange }) => {
+ return (
+ <div className='options'>
+ <TextInput
+ title="Volume"
+ name="volume"
+ className="number"
+ placeholder="1.0"
+ data={annotation.settings}
+ onChange={handleSettingsChange}
+ autoComplete="off"
+ />
+ <TextInput
+ title="Fade Time"
+ name="duration"
+ className="number"
+ placeholder="0:01"
+ data={annotation.settings}
+ onChange={handleSettingsChange}
+ autoComplete="off"
+ />
+ </div>
+ )
+} \ No newline at end of file
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotationForms/index.js b/animism-align/frontend/app/views/editor/annotation/components/annotationForms/index.js
new file mode 100644
index 0000000..8a1be48
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotationForms/index.js
@@ -0,0 +1,46 @@
+import {
+ AnnotationFormSectionHeading,
+ AnnotationFormTextPlate,
+ AnnotationFormFootnote,
+ AnnotationFormSubtitle,
+} from './annotationForm.text'
+
+import {
+ AnnotationFormVideo,
+ AnnotationFormVideoSetVolume,
+} from './annotationForm.video'
+
+import {
+ AnnotationFormImage,
+} from './annotationForm.image'
+
+import {
+ AnnotationFormGallery,
+ AnnotationFormGalleryAdvance,
+} from './annotationForm.gallery'
+
+import {
+ AnnotationFormCurtain,
+ AnnotationFormIntro,
+} from './annotationForm.utility'
+
+export const annotationFormLookup = {
+ section_heading: AnnotationFormSectionHeading,
+ text_plate: AnnotationFormTextPlate,
+ footnote: AnnotationFormFootnote,
+ subtitle: AnnotationFormSubtitle,
+
+ image: AnnotationFormImage,
+ video: AnnotationFormVideo,
+ video_set_volume: AnnotationFormVideoSetVolume,
+
+ gallery: AnnotationFormGallery,
+ carousel: AnnotationFormGallery,
+ grid: AnnotationFormGallery,
+ vitrine: AnnotationFormGallery,
+
+ gallery_advance: AnnotationFormGalleryAdvance,
+
+ intro: AnnotationFormIntro,
+ curtain: AnnotationFormCurtain,
+}
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.gallery.js b/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.gallery.js
new file mode 100644
index 0000000..c7df7f8
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.gallery.js
@@ -0,0 +1,75 @@
+import React, { Component } from 'react'
+
+import { durationToHeight } from 'app/utils/align.utils'
+import { capitalize } from 'app/utils'
+import { annotationFadeTimings } from 'app/utils/annotation.utils'
+
+import { checkAnnotationMediaNotReady, AnnotationMediaLoading } from './annotationTypes.utility'
+
+export const AnnotationGallery = ({ y, annotation, media, timeline, selected, onClick, onDoubleClick }) => {
+ const { text } = annotation
+ const className = selected ? 'annotation media gallery selected' : 'annotation media gallery'
+ if (checkAnnotationMediaNotReady(annotation, media)) {
+ return <AnnotationMediaLoading y={y} annotation={annotation} className={className} onClick={onClick} onDoubleClick={onDoubleClick} />
+ }
+ const mediaItem = media[annotation.settings.media_id]
+
+ const {
+ fadeInDuration, fadeOutDuration, duration,
+ start_ts, end_ts, fade_in_end_ts, fade_out_start_ts,
+ } = annotationFadeTimings(annotation)
+ const durationHeight = annotation.settings.fullscreen ? durationToHeight(duration, timeline) : 'auto'
+ const fadeInHeight = durationToHeight(fadeInDuration, timeline)
+ const fadeOutHeight = durationToHeight(fadeOutDuration, timeline)
+
+ const style = {
+ top: y,
+ }
+ if (annotation.settings.fullscreen && !annotation.settings.inline) {
+ style.height = durationHeight
+ }
+
+ return (
+ <div
+ className={className}
+ style={style}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ >
+ <div className='meta center'>
+ <div>
+ {capitalize(annotation.type)}<br/>
+ <i>{mediaItem.title}</i><br />
+ {mediaItem.author}<br />
+ {mediaItem.date}
+ </div>
+ </div>
+ </div>
+ )
+}
+
+
+export const AnnotationGalleryAdvance = ({ y, annotation, media, timeline, selected, onClick, onDoubleClick }) => {
+ const className = selected ? 'annotation media gallery_advance selected' : 'annotation media gallery_advance'
+ const style = {
+ top: y,
+ }
+ let index = parseInt(annotation.settings.frame_index) + 1
+ if (annotation.settings.half_frame) {
+ index += 0.5
+ }
+ return (
+ <div
+ className={className}
+ style={style}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ >
+ <div className='meta center'>
+ <div>
+ Advance gallery to frame {index}
+ </div>
+ </div>
+ </div>
+ )
+}
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.image.js b/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.image.js
new file mode 100644
index 0000000..d0a573e
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.image.js
@@ -0,0 +1,54 @@
+import React, { Component } from 'react'
+
+import { durationToHeight } from 'app/utils/align.utils'
+import { annotationFadeTimings, thumbnailURL } from 'app/utils/annotation.utils'
+
+import { checkAnnotationMediaNotReady, AnnotationMediaLoading } from './annotationTypes.utility'
+
+export const AnnotationImage = ({ y, annotation, media, timeline, selected, onClick, onDoubleClick }) => {
+ const { text } = annotation
+ const className = selected ? 'annotation media image selected' : 'annotation media image'
+ if (checkAnnotationMediaNotReady(annotation, media)) {
+ return <AnnotationMediaLoading y={y} annotation={annotation} className={className} onClick={onClick} onDoubleClick={onDoubleClick} />
+ }
+ const mediaItem = media[annotation.settings.media_id]
+
+ const {
+ fadeInDuration, fadeOutDuration, duration,
+ start_ts, end_ts, fade_in_end_ts, fade_out_start_ts,
+ } = annotationFadeTimings(annotation)
+ const durationHeight = annotation.settings.fullscreen ? durationToHeight(duration, timeline) : 'auto'
+ const fadeInHeight = durationToHeight(fadeInDuration, timeline)
+ const fadeOutHeight = durationToHeight(fadeOutDuration, timeline)
+
+ const style = {
+ top: y,
+ }
+ if (annotation.settings.fullscreen && !annotation.settings.inline) {
+ style.height = durationHeight
+ }
+
+ return (
+ <div
+ className={className}
+ style={style}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ >
+ <div className='meta center'>
+ <div>
+ <i>{mediaItem.title}</i>{' - '}{mediaItem.author}
+ {mediaItem.type === 'gallery' && (
+ ' [Frame ' + (1 + parseInt(annotation.settings.frame_index)) + ']'
+ )}
+ </div>
+ </div>
+ </div>
+ )
+}
+
+/*
+ <div className='img'>
+ <img src={thumbnailURL(mediaItem)} alt={mediaItem.title} />
+ </div>
+*/ \ No newline at end of file
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.text.js b/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.text.js
new file mode 100644
index 0000000..ea65610
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.text.js
@@ -0,0 +1,121 @@
+import React, { Component } from 'react'
+
+export const AnnotationSentence = ({ y, annotation, selected, onClick, onDoubleClick }) => {
+ const { start_ts, text, paragraph_id } = annotation
+ let className = !paragraph_id
+ ? 'annotation sentence'
+ : (paragraph_id % 2)
+ ? 'annotation sentence odd'
+ : 'annotation sentence even'
+ if (selected) className += ' selected'
+ return (
+ <div
+ className={className}
+ style={{ top: y }}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ dangerouslySetInnerHTML={{ __html: text }}
+ />
+ )
+}
+
+export const AnnotationHeadingText = ({ y, annotation, selected, onClick, onDoubleClick }) => {
+ const { start_ts, text } = annotation
+ const className = selected ? 'annotation heading_text selected' : 'annotation heading_text'
+ return (
+ <div
+ className={className}
+ style={{ top: y }}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ >
+ {text}
+ </div>
+ )
+}
+
+export const AnnotationSectionHeading = ({ y, annotation, selected, onClick, onDoubleClick }) => {
+ const { start_ts, text } = annotation
+ const className = selected ? 'annotation section_heading selected' : 'annotation section_heading'
+ return (
+ <div
+ className={className}
+ style={{ top: y }}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ >
+ {text}
+ </div>
+ )
+}
+
+export const AnnotationParagraphEnd = ({ y, annotation, selected, onClick, onDoubleClick }) => {
+ const { start_ts, text } = annotation
+ const className = selected ? 'annotation paragraph_end selected' : 'annotation paragraph_end'
+ return (
+ <div
+ className={className}
+ style={{ top: y }}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ >
+ </div>
+ )
+}
+
+export const AnnotationFootnote = ({ y, annotation, selected, onClick, onDoubleClick }) => {
+ const { start_ts, text, paragraph_id } = annotation
+ let className = !paragraph_id
+ ? 'annotation sentence footnote'
+ : (paragraph_id % 2)
+ ? 'annotation sentence footnote odd'
+ : 'annotation sentence footnote even'
+ if (selected) className += ' selected'
+ return (
+ <div
+ className={className}
+ style={{ top: y }}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ dangerouslySetInnerHTML={{ __html: text }}
+ />
+ )
+}
+
+export const AnnotationTextPlate = ({ y, annotation, selected, onClick, onDoubleClick }) => {
+ const { start_ts, text, paragraph_id } = annotation
+ let className = !paragraph_id
+ ? 'annotation text_plate'
+ : (paragraph_id % 2)
+ ? 'annotation text_plate odd'
+ : 'annotation text_plate even'
+ if (selected) className += ' selected'
+ return (
+ <div
+ className={className}
+ style={{ top: y }}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ dangerouslySetInnerHTML={{ __html: text }}
+ />
+ )
+}
+
+export const AnnotationSubtitle = ({ y, annotation, selected, onClick, onDoubleClick }) => {
+ const { start_ts, text, paragraph_id } = annotation
+ let className = !paragraph_id
+ ? 'annotation subtitle'
+ : (paragraph_id % 2)
+ ? 'annotation subtitle odd'
+ : 'annotation subtitle even'
+ if (selected) className += ' selected'
+ return (
+ <div
+ className={className}
+ style={{ top: y }}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ dangerouslySetInnerHTML={{ __html: text }}
+ />
+ )
+}
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.utility.js b/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.utility.js
new file mode 100644
index 0000000..1238163
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.utility.js
@@ -0,0 +1,107 @@
+import React, { Component } from 'react'
+
+import { annotationFadeTimings } from 'app/utils/annotation.utils'
+import { durationToHeight } from 'app/utils/align.utils'
+
+export const AnnotationIntro = ({ y, annotation, timeline, selected, onClick, onDoubleClick }) => {
+ const className = selected ? 'annotation utility intro selected' : 'annotation utility intro'
+ const {
+ fadeInDuration, fadeOutDuration, duration,
+ start_ts, end_ts, fade_in_end_ts, fade_out_start_ts,
+ } = annotationFadeTimings(annotation)
+ const durationHeight = durationToHeight(duration, timeline)
+ const fadeInHeight = durationToHeight(fadeInDuration, timeline)
+ const fadeOutHeight = durationToHeight(fadeOutDuration, timeline)
+ let style = {
+ top: y,
+ }
+ if (annotation.settings.fullscreen) {
+ style.height = durationHeight
+ }
+ return (
+ <div
+ className={className}
+ style={style}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ >
+ <div style={{ height: fadeInHeight }} className='fadeIn' />
+ <div style={{ height: fadeOutHeight }} className='fadeOut' />
+ <span style={{ top: fadeInHeight }}>
+ Intro:<br/>
+ {annotation.settings.title}<br />
+ {annotation.settings.subtitle}<br />
+ </span>
+ </div>
+ )
+}
+
+export const AnnotationSchedule = ({ y, annotation, timeline, selected, onClick, onDoubleClick }) => {
+ const className = selected ? 'annotation utility schedule selected' : 'annotation utility schedule'
+ let style = {
+ top: y,
+ }
+ return (
+ <div
+ className={className}
+ style={style}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ >
+ Schedule
+ </div>
+ )
+}
+
+export const AnnotationCurtain = ({ y, annotation, timeline, selected, onClick, onDoubleClick }) => {
+ const className = selected ? 'annotation utility curtain selected' : 'annotation utility curtain'
+ const {
+ fadeInDuration, fadeOutDuration, duration,
+ start_ts, end_ts, fade_in_end_ts, fade_out_start_ts,
+ } = annotationFadeTimings(annotation)
+ const durationHeight = durationToHeight(duration, timeline)
+ const fadeInHeight = durationToHeight(fadeInDuration, timeline)
+ const fadeOutHeight = durationToHeight(fadeOutDuration, timeline)
+ return (
+ <div
+ className={className}
+ style={{ top: y, height: durationHeight }}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ >
+ <div style={{ height: fadeInHeight }} className='fadeIn' />
+ <div style={{ height: fadeOutHeight }} className='fadeOut' />
+ <span style={{ top: fadeInHeight }}>
+ Curtain: {annotation.settings.color}.
+ {annotation.settings.curtain_text}
+ </span>
+ </div>
+ )
+}
+
+export const checkAnnotationMediaNotReady = (annotation, media) => {
+ return (!media) || (!(annotation.settings.media_id in media))
+}
+
+export const AnnotationMediaLoading = ({ y, annotation, media, className, onClick, onDoubleClick }) => {
+ if (!media) {
+ return (
+ <div
+ className={className}
+ style={{ top: y }}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ >MEDIA NOT SET</div>
+ )
+ }
+ if (!(annotation.settings.media_id in media)) {
+ return (
+ <div
+ className={className}
+ style={{ top: y }}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ >MEDIA NOT FOUND</div>
+ )
+ }
+}
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.video.js b/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.video.js
new file mode 100644
index 0000000..4ea9595
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/annotationTypes.video.js
@@ -0,0 +1,51 @@
+import React, { Component } from 'react'
+
+import { annotationFadeTimings } from 'app/utils/annotation.utils'
+import { durationToHeight } from 'app/utils/align.utils'
+import { checkAnnotationMediaNotReady, AnnotationMediaLoading } from './annotationTypes.utility'
+
+export const AnnotationVideo = ({ y, annotation, media, timeline, selected, onClick, onDoubleClick }) => {
+ const { start_ts, text } = annotation
+ const className = selected ? 'annotation media video selected' : 'annotation media video'
+ if (checkAnnotationMediaNotReady(annotation, media)) {
+ return <AnnotationMediaLoading y={y} annotation={annotation} className={className} onClick={onClick} onDoubleClick={onDoubleClick} />
+ }
+ const data = media[annotation.settings.media_id]
+ const {
+ fadeInDuration, fadeOutDuration, duration,
+ } = annotationFadeTimings(annotation)
+ const durationHeight = annotation.settings.fullscreen ? durationToHeight(duration, timeline) : 'auto'
+ return (
+ <div
+ className={className}
+ style={{ top: y, height: durationHeight }}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ >
+ <div className='meta center'>
+ <div>
+ <i>{data.title}</i><br />
+ {data.author}<br />
+ {data.date}
+ </div>
+ </div>
+ </div>
+ )
+}
+
+export const AnnotationVideoSetVolume = ({ y, annotation, timeline, selected, onClick, onDoubleClick }) => {
+ const className = selected ? 'annotation utility video_set_volume selected' : 'annotation utility video_set_volume'
+ let style = {
+ top: y,
+ }
+ return (
+ <div
+ className={className}
+ style={style}
+ onClick={e => onClick(e, annotation)}
+ onDoubleClick={e => onDoubleClick(e, annotation)}
+ >
+ Set volume to {annotation.settings.volume}
+ </div>
+ )
+}
diff --git a/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/index.js b/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/index.js
new file mode 100644
index 0000000..1a89dbd
--- /dev/null
+++ b/animism-align/frontend/app/views/editor/annotation/components/annotationTypes/index.js
@@ -0,0 +1,57 @@
+import React from 'react'
+
+import {
+ AnnotationSentence,
+ AnnotationHeadingText,
+ AnnotationSectionHeading,
+ AnnotationParagraphEnd,
+ AnnotationFootnote,
+ AnnotationTextPlate,
+ AnnotationSubtitle,
+} from './annotationTypes.text'
+
+import {
+ AnnotationVideo,
+ AnnotationVideoSetVolume,
+} from './annotationTypes.video'
+
+import {
+ AnnotationImage,
+} from './annotationTypes.image'
+
+import {
+ AnnotationGallery,
+ AnnotationGalleryAdvance,
+} from './annotationTypes.gallery'
+
+import {
+ AnnotationCurtain,
+ AnnotationIntro,
+ AnnotationSchedule,
+} from './annotationTypes.utility'
+
+export const AnnotationElementLookup = {
+ sentence: React.memo(AnnotationSentence),
+ pullquote_credit: React.memo(AnnotationSentence),
+ heading_text: React.memo(AnnotationHeadingText),
+ section_heading: React.memo(AnnotationSectionHeading),
+ paragraph_end: React.memo(AnnotationParagraphEnd),
+ footnote: React.memo(AnnotationFootnote),
+ text_plate: React.memo(AnnotationTextPlate),
+ subtitle: React.memo(AnnotationSubtitle),
+
+ video: React.memo(AnnotationVideo),
+ video_set_volume: React.memo(AnnotationVideoSetVolume),
+
+ image: React.memo(AnnotationImage),
+ gallery: React.memo(AnnotationGallery),
+ carousel: React.memo(AnnotationGallery),
+ grid: React.memo(AnnotationGallery),
+ vitrine: React.memo(AnnotationGallery),
+
+ gallery_advance: React.memo(AnnotationGalleryAdvance),
+
+ intro: React.memo(AnnotationIntro),
+ schedule: React.memo(AnnotationSchedule),
+ curtain: React.memo(AnnotationCurtain),
+}