diff options
Diffstat (limited to 'animism-align/frontend/app/views/editor/paragraph/components')
6 files changed, 324 insertions, 0 deletions
diff --git a/animism-align/frontend/app/views/editor/paragraph/components/paragraph.form.js b/animism-align/frontend/app/views/editor/paragraph/components/paragraph.form.js new file mode 100644 index 0000000..55cb74e --- /dev/null +++ b/animism-align/frontend/app/views/editor/paragraph/components/paragraph.form.js @@ -0,0 +1,107 @@ +import React, { Component } from 'react' +// import { Link } from 'react-router-dom' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import actions from 'app/actions' + +import { timestamp, capitalize } from 'app/utils' +import { Select, Checkbox } from 'app/common' + +const PARAGRAPH_TYPES = [ + 'paragraph', 'intro_paragraph', 'blockquote', 'pullquote', 'big_text', 'hidden', +].map(name => ({ name, label: capitalize(name.replace('_', ' ')) })) + +class ParagraphForm extends Component { + constructor(props){ + super(props) + this.handleChange = this.handleChange.bind(this) + this.handleSelect = this.handleSelect.bind(this) + this.handleSettingsSelect = this.handleSettingsSelect.bind(this) + this.handleSubmit = this.handleSubmit.bind(this) + } + componentDidMount() { + if (this.textareaRef && this.textareaRef.current) { + this.textareaRef.current.focus() + } + } + handleChange(e) { + const { name, value } = e.target + this.handleSelect(name, value) + } + handleSelect(name, value) { + const { onUpdate, paragraph } = this.props + onUpdate({ + ...paragraph, + [name]: value, + }) + } + handleSettingsSelect(name, value) { + const { onUpdate, paragraph } = this.props + onUpdate({ + ...paragraph, + settings: { + ...paragraph.settings, + [name]: value, + } + }) + } + handleSubmit() { + const { paragraph, onClose } = this.props + actions.paragraph.update(paragraph) + .then(response => { + console.log(response) + onClose() + }) + } + render() { + const { paragraph, y } = this.props + console.log(paragraph) + return ( + <div + className='paragraphForm' + style={{ + top: y, + }} + > + {this.renderButtons()} + <div> + <Checkbox + label="Hide in transcript" + name="hide_in_transcript" + checked={paragraph.settings ? paragraph.settings.hide_in_transcript : false} + onChange={this.handleSettingsSelect} + /> + </div> + </div> + ) + } + renderButtons() { + const { paragraph } = this.props + return ( + <div className='row buttons'> + <div className='row'> + <Select + name='type' + selected={paragraph.type} + options={PARAGRAPH_TYPES} + defaultOption='text' + onChange={this.handleSelect} + /> + <div className='ts'>{timestamp(paragraph.start_ts, 1, true)}</div> + </div> + <div> + <button onClick={this.handleSubmit}>Save</button> + </div> + </div> + ) + } +} + +const mapStateToProps = state => ({ +}) + +const mapDispatchToProps = dispatch => ({ +}) + +export default connect(mapStateToProps, mapDispatchToProps)(ParagraphForm) diff --git a/animism-align/frontend/app/views/editor/paragraph/components/paragraph.list.js b/animism-align/frontend/app/views/editor/paragraph/components/paragraph.list.js new file mode 100644 index 0000000..f7c9c58 --- /dev/null +++ b/animism-align/frontend/app/views/editor/paragraph/components/paragraph.list.js @@ -0,0 +1,103 @@ +import React, { Component } from 'react' +import { Route } from 'react-router-dom' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import { floatLT, floatInRange, capitalize } from 'app/utils' +import ParagraphForm from '../components/paragraph.form' + +class ParagraphList extends Component { + state = { + currentParagraph: -1, + currentAnnotation: -1, + } + + componentDidUpdate(prevProps) { + if (this.props.audio.play_ts === prevProps.audio.play_ts) return + if (!this.props.paragraphs) return + this.setCurrentParagraph() + } + + setCurrentParagraph() { + const { play_ts } = this.props.audio + const insideParagraph = this.props.paragraphs.some(paragraph => { + if (floatInRange(paragraph.start_ts, play_ts, paragraph.end_ts)) { + this.setCurrentAnnotation(paragraph, play_ts) + return true + } + return false + }) + if (!insideParagraph) { + this.setState({ + currentParagraph: -1, + currentAnnotation: -1, + }) + } + } + + setCurrentAnnotation(paragraph, play_ts) { + const { id: currentParagraph, annotations } = paragraph + const possibleAnnotations = annotations.filter(a => a.type === 'sentence') + // console.log(possibleAnnotations) + if (!possibleAnnotations.length) return + let currentAnnotation + let annotation + let i = 0, next_i + let len = possibleAnnotations.length + for (let i = 0; i < len - 1; i++) { + next_i = i + 1 + if (next_i < len && floatLT(play_ts, possibleAnnotations[next_i].start_ts)) { + currentAnnotation = possibleAnnotations[i].id + break + } + } + if (!currentAnnotation) { + currentAnnotation = possibleAnnotations[len-1].id + } + this.setState({ currentParagraph, currentAnnotation }) + } + + render() { + const { + paragraphs, media, + paragraphElementLookup, selectedParagraph, + onAnnotationClick, onParagraphDoubleClick, + currentSection, + } = this.props + const { currentParagraph, currentAnnotation } = this.state + if (!paragraphs) return null + return paragraphs.map((paragraph, i) => { + if (selectedParagraph && selectedParagraph.id === paragraph.id) { + paragraph = selectedParagraph + } + if (paragraph.type in paragraphElementLookup) { + const ParagraphElement = paragraphElementLookup[paragraph.type] + return ( + <ParagraphElement + key={paragraph.id + "_" + i} + paragraph={paragraph} + media={media} + currentSection={currentSection} + currentParagraph={paragraph.id === currentParagraph} + currentAnnotation={paragraph.id === currentParagraph && currentAnnotation} + onAnnotationClick={onAnnotationClick} + onDoubleClick={onParagraphDoubleClick} + /> + ) + // } else { + // return <div key={paragraph.id}>{'(' + capitalize(paragraph.type) + ')'}</div> + } + }) + } +} + +const mapStateToProps = state => ({ + audio: state.audio, + media: state.media.index, + currentSection: state.viewer.currentSection, +}) + +const mapDispatchToProps = dispatch => ({ +}) + +export default connect(mapStateToProps, mapDispatchToProps)(ParagraphList) diff --git a/animism-align/frontend/app/views/editor/paragraph/components/paragraphTypes/index.js b/animism-align/frontend/app/views/editor/paragraph/components/paragraphTypes/index.js new file mode 100644 index 0000000..5180d5e --- /dev/null +++ b/animism-align/frontend/app/views/editor/paragraph/components/paragraphTypes/index.js @@ -0,0 +1,26 @@ +import React from 'react' + +import { + Paragraph, ParagraphHeading +} from './paragraphTypes.text' + +import { + MediaVideo +} from './paragraphTypes.video' + +import { + MediaImage +} from './paragraphTypes.image' + +export const paragraphElementLookup = { + paragraph: React.memo(Paragraph), + intro_paragraph: React.memo(Paragraph), + hidden: React.memo(Paragraph), + blockquote: React.memo(Paragraph), + pullquote: React.memo(Paragraph), + big_text: React.memo(Paragraph), + section_heading: React.memo(ParagraphHeading), + // heading_text: React.memo(ParagraphHeading), + video: React.memo(MediaVideo), + image: React.memo(MediaImage), +} diff --git a/animism-align/frontend/app/views/editor/paragraph/components/paragraphTypes/paragraphTypes.image.js b/animism-align/frontend/app/views/editor/paragraph/components/paragraphTypes/paragraphTypes.image.js new file mode 100644 index 0000000..4eab2cc --- /dev/null +++ b/animism-align/frontend/app/views/editor/paragraph/components/paragraphTypes/paragraphTypes.image.js @@ -0,0 +1,17 @@ +import React, { Component } from 'react' + +export const MediaImage = ({ paragraph, media, currentParagraph, currentAnnotation, onAnnotationClick, onDoubleClick }) => { + if (!media.lookup) return <div /> + const className = currentParagraph ? 'media image current' : 'media image' + const annotation = paragraph.annotations[0] + const item = media.lookup[annotation.settings.media_id] + if (!item) return <div>Media not found: {annotation.settings.media_id}</div> + + return ( + <div + className={className} + onDoubleClick={e => onDoubleClick(e, paragraph)} + > + </div> + ) +} diff --git a/animism-align/frontend/app/views/editor/paragraph/components/paragraphTypes/paragraphTypes.text.js b/animism-align/frontend/app/views/editor/paragraph/components/paragraphTypes/paragraphTypes.text.js new file mode 100644 index 0000000..f0529c8 --- /dev/null +++ b/animism-align/frontend/app/views/editor/paragraph/components/paragraphTypes/paragraphTypes.text.js @@ -0,0 +1,55 @@ +import React, { Component } from 'react' + +import { ROMAN_NUMERALS } from 'app/constants' + +export const Paragraph = ({ paragraph, currentParagraph, currentAnnotation, onAnnotationClick, onDoubleClick }) => { + let className = paragraph.type + if (className !== 'paragraph') className += ' paragraph' + if (currentParagraph) className += ' current' + return ( + <div + className={className} + onDoubleClick={e => onDoubleClick(e, paragraph)} + > + {paragraph.annotations.map(annotation => { + if (annotation.type === 'footnote') { + return ( + <span + key={annotation.id} + className='footnote' + onClick={e => onAnnotationClick(e, paragraph, annotation)} + dangerouslySetInnerHTML={{ __html: annotation.footnote_id }} + /> + ) + } + return ( + <span + key={annotation.id} + className={ + annotation.type === 'pullquote_credit' + ? 'pullquote_credit' + : annotation.id === currentAnnotation + ? 'current' + : '' + } + onClick={e => onAnnotationClick(e, paragraph, annotation)} + dangerouslySetInnerHTML={{ __html: annotation.text }} + /> + ) + })} + </div> + ) +} + +export const ParagraphHeading = ({ paragraph, currentParagraph, currentAnnotation, onAnnotationClick, onDoubleClick }) => { + let className = currentParagraph ? 'section_heading current' : 'section_heading' + const text = paragraph.annotations.map(annotation => annotation.text).join(' ') + return ( + <div + className={className} + onDoubleClick={e => onDoubleClick(e, paragraph)} + > + <span>{ROMAN_NUMERALS[paragraph.sectionIndex]}{'. '}{text}</span> + </div> + ) +} diff --git a/animism-align/frontend/app/views/editor/paragraph/components/paragraphTypes/paragraphTypes.video.js b/animism-align/frontend/app/views/editor/paragraph/components/paragraphTypes/paragraphTypes.video.js new file mode 100644 index 0000000..ba7a996 --- /dev/null +++ b/animism-align/frontend/app/views/editor/paragraph/components/paragraphTypes/paragraphTypes.video.js @@ -0,0 +1,16 @@ +import React, { Component } from 'react' + +export const MediaVideo = ({ paragraph, media, currentParagraph, currentAnnotation, onAnnotationClick, onDoubleClick }) => { + if (!media.lookup) return <div /> + const className = currentParagraph ? 'media current' : 'media' + const annotation = paragraph.annotations[0] + const item = media.lookup[annotation.settings.media_id] + if (!item) return <div>Media not found: {annotation.settings.media_id}</div> + return ( + <div + className={className} + onDoubleClick={e => onDoubleClick(e, paragraph)} + > + </div> + ) +} |
