summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2020-07-17 19:42:40 +0200
committerJules Laplace <julescarbon@gmail.com>2020-07-17 19:42:40 +0200
commit649fec4f153ea1c72d2fa3ea89d7d3998237de3a (patch)
tree6b50fae411aed507f109885c58f47607b413ce6f
parent4bb72b9f6d2a56fc6bd67f4248fcabfcc8166493 (diff)
paragraph to make paragraphs blockquote, hidden, etc
-rw-r--r--animism-align/frontend/views/paragraph/components/paragraph.form.js196
-rw-r--r--animism-align/frontend/views/paragraph/components/paragraphTypes/index.js1
-rw-r--r--animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.text.js10
-rw-r--r--animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.video.js4
-rw-r--r--animism-align/frontend/views/paragraph/containers/paragraph.edit.js53
-rw-r--r--animism-align/frontend/views/paragraph/containers/paragraph.index.js53
-rw-r--r--animism-align/frontend/views/paragraph/containers/paragraph.new.js44
-rw-r--r--animism-align/frontend/views/paragraph/containers/paragraphList.container.js63
-rw-r--r--animism-align/frontend/views/paragraph/paragraph.css34
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;
+}