diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-07-17 19:42:40 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-07-17 19:42:40 +0200 |
| commit | 649fec4f153ea1c72d2fa3ea89d7d3998237de3a (patch) | |
| tree | 6b50fae411aed507f109885c58f47607b413ce6f | |
| parent | 4bb72b9f6d2a56fc6bd67f4248fcabfcc8166493 (diff) | |
paragraph to make paragraphs blockquote, hidden, etc
9 files changed, 155 insertions, 303 deletions
diff --git a/animism-align/frontend/views/paragraph/components/paragraph.form.js b/animism-align/frontend/views/paragraph/components/paragraph.form.js index d90b663..de3114c 100644 --- a/animism-align/frontend/views/paragraph/components/paragraph.form.js +++ b/animism-align/frontend/views/paragraph/components/paragraph.form.js @@ -1,153 +1,87 @@ import React, { Component } from 'react' -import { Link } from 'react-router-dom' +// import { Link } from 'react-router-dom' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' -import { session } from '../../../session' +import actions from '../../../actions' -import { TextInput, LabelDescription, TextArea, Checkbox, SubmitButton, Loader } from '../../../common' +import { clamp, timestamp, capitalize } from '../../../util' +import { Select } from '../../../common' -const newGraph = () => ({ - path: '', - title: '', - username: session('username'), - description: '', -}) +const PARAGRAPH_TYPES = [ + 'paragraph', 'blockquote', 'hidden', +].map(name => ({ name, label: capitalize(name.replace('_', ' ')) })) -export default class GraphForm extends Component { - state = { - title: "", - submitTitle: "", - data: { ...newGraph() }, - errorFields: new Set([]), +class ParagraphForm extends Component { + constructor(props){ + super(props) + this.handleChange = this.handleChange.bind(this) + this.handleSelect = this.handleSelect.bind(this) + this.handleSubmit = this.handleSubmit.bind(this) } - componentDidMount() { - const { data, isNew } = this.props - const title = isNew ? 'new project' : 'editing ' + data.title - const submitTitle = isNew ? "Create Graph" : "Save Changes" - this.setState({ - title, - submitTitle, - errorFields: new Set([]), - data: { - ...newGraph(), - ...data - }, - }) + if (this.textareaRef && this.textareaRef.current) { + this.textareaRef.current.focus() + } } - handleChange(e) { - const { errorFields } = this.state const { name, value } = e.target - if (errorFields.has(name)) { - errorFields.delete(name) - } - let sanitizedValue = value - if (name === 'path') { - sanitizedValue = sanitizedValue.toLowerCase().replace(/ /, '-').replace(/[!@#$%^&*()[\]{}]/, '-').replace(/-+/, '-') - } - this.setState({ - errorFields, - data: { - ...this.state.data, - [name]: sanitizedValue, - } - }) + this.handleSelect(name, value) } - handleSelect(name, value) { - const { errorFields } = this.state - if (errorFields.has(name)) { - errorFields.delete(name) - } - this.setState({ - errorFields, - data: { - ...this.state.data, - [name]: value, - } + const { onUpdate, paragraph } = this.props + onUpdate({ + ...paragraph, + [name]: value, }) } - - handleSubmit(e) { - e.preventDefault() - const { isNew, onSubmit } = this.props - const { data } = this.state - const requiredKeys = "title username path description".split(" ") - const validKeys = "title username path description".split(" ") - const validData = validKeys.reduce((a,b) => { a[b] = data[b]; return a }, {}) - const errorFields = requiredKeys.filter(key => !validData[key]) - if (errorFields.length) { - console.log('error', errorFields, validData) - this.setState({ errorFields: new Set(errorFields) }) - } else { - if (isNew) { - // side effect: set username if we're creating a new graph - session.set('username', data.username) - } else { - validData.id = data.id - } - console.log('submit', validData) - onSubmit(validData) - } + handleSubmit() { + const { paragraph, onClose } = this.props + actions.paragraph.update(paragraph) + .then(response => { + console.log(response) + onClose() + }) } - render() { - const { isNew } = this.props - const { title, submitTitle, errorFields, data } = this.state + const { paragraph, y } = this.props return ( - <div className='form'> - <h1>{title}</h1> - <form onSubmit={this.handleSubmit.bind(this)}> - <TextInput - title="Path" - name="path" - required - data={data} - error={errorFields.has('path')} - onChange={this.handleChange.bind(this)} - autoComplete="off" - /> - <LabelDescription> - {data.path - ? 'Project URLs will be: /' + data.path + '/example' - : 'Enter the base path for this project.'} - </LabelDescription> - <TextInput - title="Title" - name="title" - required - data={data} - error={errorFields.has('title')} - onChange={this.handleChange.bind(this)} - autoComplete="off" - /> - <TextInput - title="Author" - name="username" - required - data={data} - error={errorFields.has('username')} - onChange={this.handleChange.bind(this)} - autoComplete="off" - /> - <TextArea - title="Description" - name="description" - data={data} - onChange={this.handleChange.bind(this)} - /> - <SubmitButton - title={submitTitle} - onClick={this.handleSubmit.bind(this)} + <div + className='paragraphForm' + style={{ + top: y, + }} + > + {this.renderButtons()} + </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} /> - {!!errorFields.size && - <label> - <span></span> - <span>Please complete the required fields =)</span> - </label> - } - </form> + <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/views/paragraph/components/paragraphTypes/index.js b/animism-align/frontend/views/paragraph/components/paragraphTypes/index.js index 990c911..04546f6 100644 --- a/animism-align/frontend/views/paragraph/components/paragraphTypes/index.js +++ b/animism-align/frontend/views/paragraph/components/paragraphTypes/index.js @@ -10,6 +10,7 @@ import { export const ParagraphElementLookup = { paragraph: React.memo(Paragraph), + hidden: React.memo(Paragraph), blockquote: React.memo(Paragraph), header: React.memo(ParagraphHeader), video: React.memo(MediaVideo), diff --git a/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.text.js b/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.text.js index 5f8dbc3..39ad661 100644 --- a/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.text.js +++ b/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.text.js @@ -1,9 +1,9 @@ import React, { Component } from 'react' -export const Paragraph = ({ paragraph, selectedParagraph, selectedAnnotation, onAnnotationClick, onDoubleClick }) => { +export const Paragraph = ({ paragraph, currentParagraph, currentAnnotation, onAnnotationClick, onDoubleClick }) => { let className = paragraph.type if (className !== 'paragraph') className += ' paragraph' - if (selectedParagraph) className += ' selected' + if (currentParagraph) className += ' current' return ( <div className={className} @@ -12,7 +12,7 @@ export const Paragraph = ({ paragraph, selectedParagraph, selectedAnnotation, on {paragraph.annotations.map(annotation => ( <span key={annotation.id} - className={annotation.id === selectedAnnotation ? 'selected' : ''} + className={annotation.id === currentAnnotation ? 'current' : ''} onClick={e => onAnnotationClick(e, paragraph, annotation)} > {' '}{annotation.text}{' '} @@ -22,8 +22,8 @@ export const Paragraph = ({ paragraph, selectedParagraph, selectedAnnotation, on ) } -export const ParagraphHeader = ({ paragraph, selectedParagraph, selectedAnnotation, onAnnotationClick, onDoubleClick }) => { - let className = selectedParagraph ? 'header selected' : 'header' +export const ParagraphHeader = ({ paragraph, currentParagraph, currentAnnotation, onAnnotationClick, onDoubleClick }) => { + let className = currentParagraph ? 'header current' : 'header' const text = paragraph.annotations.map(annotation => annotation.text).join(' ') return ( <div diff --git a/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.video.js b/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.video.js index 8e224f3..f5f874f 100644 --- a/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.video.js +++ b/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.video.js @@ -2,9 +2,9 @@ import React, { Component } from 'react' import VimeoPlayer from '@u-wave/react-vimeo' -export const MediaVideo = ({ paragraph, media, selectedParagraph, selectedAnnotation, onAnnotationClick, onDoubleClick }) => { +export const MediaVideo = ({ paragraph, media, currentParagraph, currentAnnotation, onAnnotationClick, onDoubleClick }) => { if (!media.lookup) return <div /> - const className = selectedParagraph ? 'media selected' : 'media' + 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> diff --git a/animism-align/frontend/views/paragraph/containers/paragraph.edit.js b/animism-align/frontend/views/paragraph/containers/paragraph.edit.js deleted file mode 100644 index ce1b404..0000000 --- a/animism-align/frontend/views/paragraph/containers/paragraph.edit.js +++ /dev/null @@ -1,53 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { connect } from 'react-redux' - -import { history } from '../../../store' -import actions from '../../../actions' - -import { Loader } from '../../../common' - -import GraphForm from '../components/graph.form' - -class GraphEdit extends Component { - componentDidMount() { - console.log(this.props.match.params.id) - actions.graph.show(this.props.match.params.id) - } - - handleSubmit(data) { - actions.graph.update(data) - .then(response => { - // response - console.log(response) - history.push('/' + data.path) - }) - } - - render() { - const { show } = this.props.graph - if (show.loading || !show.res) { - return ( - <div className='form'> - <Loader /> - </div> - ) - } - return ( - <GraphForm - data={show.res} - onSubmit={this.handleSubmit.bind(this)} - /> - ) - } -} - -const mapStateToProps = state => ({ - graph: state.graph, -}) - -const mapDispatchToProps = dispatch => ({ - // searchActions: bindActionCreators({ ...searchActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(GraphEdit) diff --git a/animism-align/frontend/views/paragraph/containers/paragraph.index.js b/animism-align/frontend/views/paragraph/containers/paragraph.index.js deleted file mode 100644 index 35c2d82..0000000 --- a/animism-align/frontend/views/paragraph/containers/paragraph.index.js +++ /dev/null @@ -1,53 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import { Loader } from '../../../common' -import actions from '../../../actions' -// import * as uploadActions from './upload.actions' - -class GraphIndex extends Component { - componentDidMount() { - actions.graph.index() - } - render() { - const { index } = this.props - // console.log(this.props) - if (!index.order) { - return ( - <div className='graphIndex'> - <Loader /> - </div> - ) - } - // console.log(state) - return ( - <div className='graphIndex'> - <div> - <b>welcome, swimmer</b> - <Link to='/index/new'>+ new project</Link> - </div> - {index.order.map(id => { - const graph = index.lookup[id] - return ( - <div key={id}> - <Link to={'/' + graph.path}>{graph.title}</Link> - <Link to={'/index/' + id + '/edit'}>{'edit project'}</Link> - </div> - ) - })} - </div> - ) - } -} - -const mapStateToProps = state => ({ - index: state.graph.index, -}) - -const mapDispatchToProps = dispatch => ({ - // uploadActions: bindActionCreators({ ...uploadActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(GraphIndex) diff --git a/animism-align/frontend/views/paragraph/containers/paragraph.new.js b/animism-align/frontend/views/paragraph/containers/paragraph.new.js deleted file mode 100644 index be96bf5..0000000 --- a/animism-align/frontend/views/paragraph/containers/paragraph.new.js +++ /dev/null @@ -1,44 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { connect } from 'react-redux' - -import { history } from '../../../store' -import actions from '../../../actions' - -import GraphForm from '../components/graph.form' - -class GraphNew extends Component { - handleSubmit(data) { - console.log(data) - actions.graph.create(data) - .then(res => { - console.log(res) - if (res.res && res.res.id) { - history.push('/' + res.res.path) - } - }) - .catch(err => { - console.error('error') - }) - } - - render() { - return ( - <GraphForm - isNew - data={{}} - onSubmit={this.handleSubmit.bind(this)} - /> - ) - } -} - -const mapStateToProps = state => ({ - graph: state.graph, -}) - -const mapDispatchToProps = dispatch => ({ - // searchActions: bindActionCreators({ ...searchActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(GraphNew) diff --git a/animism-align/frontend/views/paragraph/containers/paragraphList.container.js b/animism-align/frontend/views/paragraph/containers/paragraphList.container.js index deeb347..1361205 100644 --- a/animism-align/frontend/views/paragraph/containers/paragraphList.container.js +++ b/animism-align/frontend/views/paragraph/containers/paragraphList.container.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux' import actions from '../../../actions' import { ParagraphElementLookup } from '../components/paragraphTypes' +import ParagraphForm from '../components/paragraph.form' const floatLT = (a,b) => ((a*10|0) < (b*10|0)) const floatLTE = (a,b) => ((a*10|0) === (b*10|0) || floatLT(a,b)) @@ -16,19 +17,30 @@ class ParagraphList extends Component { paragraphs: [], currentParagraph: -1, currentAnnotation: -1, + selectedParagraph: null, + selectedParagraphOffset: 0, } + constructor(props) { super(props) - this.onAnnotationClick = this.onAnnotationClick.bind(this) - this.onParagraphDoubleClick = this.onParagraphDoubleClick.bind(this) + this.handleAnnotationClick = this.handleAnnotationClick.bind(this) + this.handleParagraphDoubleClick = this.handleParagraphDoubleClick.bind(this) + this.handleCloseParagraphForm = this.handleCloseParagraphForm.bind(this) + this.updateSelectedParagraph = this.updateSelectedParagraph.bind(this) } + 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 => { @@ -45,6 +57,7 @@ class ParagraphList extends Component { }) } } + setCurrentAnnotation(paragraph, play_ts) { const { id: currentParagraph, annotations } = paragraph let currentAnnotation @@ -62,6 +75,7 @@ class ParagraphList extends Component { } this.setState({ currentParagraph, currentAnnotation }) } + build() { const { order: annotationOrder, lookup: annotationLookup } = this.props.annotation const { lookup: paragraphLookup } = this.props.paragraph @@ -104,27 +118,44 @@ class ParagraphList extends Component { } }) for (let i = 0; i < (paragraphs.length - 1); i++) { - // console.log(paragraphs[i].end_ts) if (!paragraphs[i].end_ts) { paragraphs[i].end_ts = paragraphs[i+1].start_ts - 0.1 } } - // console.log(paragraphs) this.setState({ paragraphs }) } - onAnnotationClick(e, paragraph, annotation){ + + handleAnnotationClick(e, paragraph, annotation){ actions.audio.seek(annotation.start_ts) } - onParagraphDoubleClick(e, paragraph) { - // + handleParagraphDoubleClick(e, paragraph) { + console.log(e.target.parentNode) + let paragraphNode = e.target + if (!paragraphNode.classList.contains('paragraph')) { + paragraphNode = paragraphNode.parentNode + } + this.setState({ + selectedParagraph: { ...paragraph }, + selectedParagraphOffset: paragraphNode.offsetTop + }) + } + updateSelectedParagraph(selectedParagraph) { + this.setState({ selectedParagraph }) } + handleCloseParagraphForm() { + this.setState({ selectedParagraph: null }) + } + render() { const { media } = this.props - const { paragraphs, currentParagraph, currentAnnotation } = this.state + const { paragraphs, selectedParagraph, selectedParagraphOffset, currentParagraph, currentAnnotation } = this.state return ( <div className='paragraphs'> <div className='content'> {paragraphs.map(paragraph => { + if (selectedParagraph && selectedParagraph.id === paragraph.id) { + paragraph = selectedParagraph + } if (paragraph.type in ParagraphElementLookup) { const ParagraphElement = ParagraphElementLookup[paragraph.type] return ( @@ -132,16 +163,24 @@ class ParagraphList extends Component { key={paragraph.id} paragraph={paragraph} media={media} - selectedParagraph={paragraph.id === currentParagraph} - selectedAnnotation={paragraph.id === currentParagraph && currentAnnotation} - onAnnotationClick={this.onAnnotationClick} - onDoubleClick={this.onParagraphDoubleClick} + currentParagraph={paragraph.id === currentParagraph} + currentAnnotation={paragraph.id === currentParagraph && currentAnnotation} + onAnnotationClick={this.handleAnnotationClick} + onDoubleClick={this.handleParagraphDoubleClick} /> ) } else { return <div key={paragraph.id}>{'(waiting to implement' + paragraph.type + ')'}</div> } })} + {selectedParagraph && + <ParagraphForm + paragraph={selectedParagraph} + onUpdate={this.updateSelectedParagraph} + onClose={this.handleCloseParagraphForm} + y={selectedParagraphOffset} + /> + } </div> </div> ) diff --git a/animism-align/frontend/views/paragraph/paragraph.css b/animism-align/frontend/views/paragraph/paragraph.css index 07399d6..13438e0 100644 --- a/animism-align/frontend/views/paragraph/paragraph.css +++ b/animism-align/frontend/views/paragraph/paragraph.css @@ -14,6 +14,7 @@ width: 650px; margin: 0 auto; padding-bottom: 6rem; + position: relative; } .paragraphs .content > div { @@ -35,9 +36,13 @@ padding-left: 3rem; } +.paragraphs .hidden { + opacity: 0.5; +} + /* current paragraph */ -.paragraphs .paragraph.selected { +.paragraphs .paragraph.current { background: rgba(0,0,0,0.0); } @@ -48,7 +53,7 @@ cursor: pointer; } -.paragraphs .paragraph .selected { +.paragraphs .paragraph .current { box-shadow: -2px -3px 0 #fff, 2px -3px 0 #fff, -2px 3px 0 #fff, @@ -56,4 +61,27 @@ box-decoration-break: clone; background: black; color: white; -}
\ No newline at end of file +} + +/* paragraph form */ + +.paragraphForm { + position: absolute; + right: -305px; + width: 300px; + padding: 0.5rem; + background: #ddd; + box-shadow: 2px 2px 4px rgba(0,0,0,0.2); +} +.paragraphForm .select div { + color: #ddd; + font-family: 'Roboto', sans-serif; +} +.paragraphForm .row { + justify-content: space-between; + align-items: center; +} +.paragraphForm .row > div { + display: flex; + align-items: center; +} |
