summaryrefslogtreecommitdiff
path: root/animism-align/frontend/views
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2020-07-06 17:57:25 +0200
committerJules Laplace <julescarbon@gmail.com>2020-07-06 17:57:25 +0200
commit6f5ff3cdfac3fc154281fdda7c1ec9ff7ebbd1fa (patch)
tree9a51dd4b32edf3b1bc8c24d95273575dab774d34 /animism-align/frontend/views
parent349ee65db67aa0d28d3861530e8e7e1b5cc27c48 (diff)
paragraph list view
Diffstat (limited to 'animism-align/frontend/views')
-rw-r--r--animism-align/frontend/views/align/align.css25
-rw-r--r--animism-align/frontend/views/align/align.reducer.js1
-rw-r--r--animism-align/frontend/views/align/align.util.js13
-rw-r--r--animism-align/frontend/views/align/components/annotations/annotation.form.js21
-rw-r--r--animism-align/frontend/views/align/components/annotations/annotation.index.js32
-rw-r--r--animism-align/frontend/views/align/components/annotations/annotation.types.js8
-rw-r--r--animism-align/frontend/views/align/components/player/playButton.component.js (renamed from animism-align/frontend/views/align/components/timeline/playButton.component.js)0
-rw-r--r--animism-align/frontend/views/align/components/timeline/playCursor.component.js7
-rw-r--r--animism-align/frontend/views/align/components/timeline/ticks.component.js11
-rw-r--r--animism-align/frontend/views/align/components/timeline/waveform.component.js6
-rw-r--r--animism-align/frontend/views/align/constants.js3
-rw-r--r--animism-align/frontend/views/align/containers/timeline.container.js9
-rw-r--r--animism-align/frontend/views/annotation/annotation.reducer.js2
-rw-r--r--animism-align/frontend/views/index.js1
-rw-r--r--animism-align/frontend/views/nav/header.component.js42
-rw-r--r--animism-align/frontend/views/nav/nav.css73
-rw-r--r--animism-align/frontend/views/paragraph/components/paragraph.types.js44
-rw-r--r--animism-align/frontend/views/paragraph/containers/paragraphList.container.js92
-rw-r--r--animism-align/frontend/views/paragraph/paragraph.container.js35
-rw-r--r--animism-align/frontend/views/paragraph/paragraph.css36
20 files changed, 417 insertions, 44 deletions
diff --git a/animism-align/frontend/views/align/align.css b/animism-align/frontend/views/align/align.css
index d430e1f..12d5b1d 100644
--- a/animism-align/frontend/views/align/align.css
+++ b/animism-align/frontend/views/align/align.css
@@ -1,6 +1,9 @@
* {
}
+.body.loading > div {
+ padding: 1rem;
+}
.body {
width: 100%;
height: 100%;
@@ -74,8 +77,8 @@ canvas {
/* Audio player */
.playButton {
- position: absolute;
- top: 0; left: 0;
+ /*position: absolute;*/
+ /*top: 0; left: 0;*/
width: 3rem; height: 3rem;
padding: 1rem;
background: #000;
@@ -104,6 +107,9 @@ canvas {
position: relative;
width: 450px;
}
+
+/* Annotation form */
+
.annotationForm {
width: 401px;
padding: 0.5rem;
@@ -118,7 +124,18 @@ canvas {
}
.annotationForm .row {
justify-content: space-between;
+ align-items: center;
}
+.annotationForm .row > div {
+ display: flex;
+ align-items: center;
+}
+.annotationForm .ts {
+ color: #fff;
+}
+
+/* Annotation index */
+
.annotationIndex {
width: 405px;
}
@@ -132,6 +149,8 @@ canvas {
border-radius: 2px;
font-size: 12px;
cursor: pointer;
+ user-select: none;
+ background-color: #768;
}
.annotation.selected {
border-color: #bbf;
@@ -143,7 +162,7 @@ canvas {
background-color: #83b;
}
.annotation.sentence.odd {
- background-color: #638;
+ background-color: #537;
}
.annotation.header {
background-color: #838;
diff --git a/animism-align/frontend/views/align/align.reducer.js b/animism-align/frontend/views/align/align.reducer.js
index 679f63b..dc471a6 100644
--- a/animism-align/frontend/views/align/align.reducer.js
+++ b/animism-align/frontend/views/align/align.reducer.js
@@ -21,6 +21,7 @@ export default function alignReducer(state = initialState, action) {
case types.peaks.loaded:
console.log('peaks duration:', action.data.length / 10)
return state
+
case types.align.set_display_setting:
return {
...state,
diff --git a/animism-align/frontend/views/align/align.util.js b/animism-align/frontend/views/align/align.util.js
index ce72aef..d7b4c39 100644
--- a/animism-align/frontend/views/align/align.util.js
+++ b/animism-align/frontend/views/align/align.util.js
@@ -1,9 +1,13 @@
import { ZOOM_STEPS } from './constants'
import { clamp } from '../../util'
+import actions from '../../actions'
+
+import { HEADER_MARGIN, INNER_HEIGHT } from './constants'
export const positionToTime = (y, { start_ts, zoom, duration }) => {
+ y -= HEADER_MARGIN
const secondsPerPixel = ZOOM_STEPS[zoom] * 0.1
- const widthTimeDuration = window.innerHeight * secondsPerPixel
+ const widthTimeDuration = INNER_HEIGHT * secondsPerPixel
const timeMin = start_ts
const timeMax = Math.min(start_ts + widthTimeDuration, duration)
const timeWidth = timeMax - timeMin
@@ -11,20 +15,19 @@ export const positionToTime = (y, { start_ts, zoom, duration }) => {
}
export const timeToPosition = (ts, { start_ts, zoom, duration }) => {
- const height = window.innerHeight
const secondsPerPixel = ZOOM_STEPS[zoom] * 0.1
- const widthTimeDuration = height * secondsPerPixel
+ const widthTimeDuration = INNER_HEIGHT * secondsPerPixel
const timeMin = start_ts
const timeMax = Math.min(start_ts + widthTimeDuration, duration)
const timeWidth = timeMax - timeMin
- const timeHalfHeight = height * secondsPerPixel / 2
+ const timeHalfHeight = INNER_HEIGHT * secondsPerPixel / 2
if (ts < timeMin - timeHalfHeight) {
return -9999
}
if (ts > timeMax) {
return -9999
}
- return (ts - timeMin) / timeWidth * height
+ return (ts - timeMin) / timeWidth * INNER_HEIGHT
}
export const getFirstPunctuationMarkIndex = text => {
diff --git a/animism-align/frontend/views/align/components/annotations/annotation.form.js b/animism-align/frontend/views/align/components/annotations/annotation.form.js
index 6972f93..e18d6df 100644
--- a/animism-align/frontend/views/align/components/annotations/annotation.form.js
+++ b/animism-align/frontend/views/align/components/annotations/annotation.form.js
@@ -7,7 +7,7 @@ import actions from '../../../../actions'
// import * as alignActions from '../align.actions'
import { ZOOM_STEPS } from '../../constants'
-import { clamp } from '../../../../util'
+import { clamp, timestamp } from '../../../../util'
import { timeToPosition } from '../../align.util'
import { Select } from '../../../../common'
@@ -95,14 +95,17 @@ class AnnotationForm extends Component {
>
{annotation.type === 'sentence' && this.renderTextarea()}
{annotation.type === 'header' && this.renderTextarea()}
- <div className='row'>
- <Select
- name='type'
- selected={annotation.type}
- options={ANNOTATION_TYPES}
- defaultOption='text'
- onChange={this.handleSelect}
- />
+ <div className='row buttons'>
+ <div>
+ <Select
+ name='type'
+ selected={annotation.type}
+ options={ANNOTATION_TYPES}
+ defaultOption='text'
+ onChange={this.handleSelect}
+ />
+ <div className='ts'>{timestamp(annotation.start_ts, 1, true)}</div>
+ </div>
<button onClick={this.handleSubmit}>Save</button>
</div>
</div>
diff --git a/animism-align/frontend/views/align/components/annotations/annotation.index.js b/animism-align/frontend/views/align/components/annotations/annotation.index.js
index 8121d1d..d5c1eed 100644
--- a/animism-align/frontend/views/align/components/annotations/annotation.index.js
+++ b/animism-align/frontend/views/align/components/annotations/annotation.index.js
@@ -4,7 +4,7 @@ import { connect } from 'react-redux'
import actions from '../../../../actions'
-import { ZOOM_STEPS } from '../../constants'
+import { ZOOM_STEPS, INNER_HEIGHT } from '../../constants'
import { clamp } from '../../../../util'
import { positionToTime, timeToPosition } from '../../align.util'
@@ -30,7 +30,7 @@ class AnnotationIndex extends Component {
const { order, lookup } = index
let secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 // 0.1 sec / step
- let widthTimeDuration = window.innerHeight * secondsPerPixel // secs per pixel
+ let widthTimeDuration = INNER_HEIGHT * secondsPerPixel // secs per pixel
let timeMin = start_ts - 30.0
let timeMax = Math.min(start_ts + widthTimeDuration, duration)
@@ -43,9 +43,37 @@ class AnnotationIndex extends Component {
}
handleClick(e, annotation) {
e.stopPropagation()
+ if (e.shiftKey) {
+ e.preventDefault()
+ this.handleParagraphSelection(annotation)
+ }
actions.audio.seek(annotation.start_ts)
actions.align.setSelectedAnnotation(annotation.id)
}
+ handleParagraphSelection(annotation) {
+ const { selected_paragraph_id } = this.props.timeline
+ console.log("___________________")
+ console.log(annotation)
+ console.log(selected_paragraph_id)
+ if (!selected_paragraph_id || selected_paragraph_id === -1) {
+ if (annotation.paragraph_id) {
+ actions.align.setSelectedParagraph(annotation.paragraph_id)
+ } else {
+ actions.paragraph.create({
+ type: 'paragraph',
+ start_ts: annotation.start_ts,
+ }) .then(data => {
+ console.log(data.res)
+ 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)
diff --git a/animism-align/frontend/views/align/components/annotations/annotation.types.js b/animism-align/frontend/views/align/components/annotations/annotation.types.js
index 7639821..f95589d 100644
--- a/animism-align/frontend/views/align/components/annotations/annotation.types.js
+++ b/animism-align/frontend/views/align/components/annotations/annotation.types.js
@@ -8,9 +8,11 @@ import { positionToTime, timeToPosition } from '../../align.util'
export const AnnotationSentence = ({ y, annotation, selected, onClick, onDoubleClick }) => {
const { start_ts, text, paragraph_id } = annotation
- let className = (paragraph_id % 2)
- ? 'annotation sentence odd'
- : 'annotation sentence even'
+ let className = !paragraph_id
+ ? 'annotation sentence'
+ : (paragraph_id % 2)
+ ? 'annotation sentence odd'
+ : 'annotation sentence even'
if (selected) className += ' selected'
return (
<div
diff --git a/animism-align/frontend/views/align/components/timeline/playButton.component.js b/animism-align/frontend/views/align/components/player/playButton.component.js
index 486eaee..486eaee 100644
--- a/animism-align/frontend/views/align/components/timeline/playButton.component.js
+++ b/animism-align/frontend/views/align/components/player/playButton.component.js
diff --git a/animism-align/frontend/views/align/components/timeline/playCursor.component.js b/animism-align/frontend/views/align/components/timeline/playCursor.component.js
index 71e6a3a..e03d212 100644
--- a/animism-align/frontend/views/align/components/timeline/playCursor.component.js
+++ b/animism-align/frontend/views/align/components/timeline/playCursor.component.js
@@ -24,13 +24,6 @@ const PlayCursor = ({ timeline, audio }) => {
)
}
-/*
- <div className='tickLabel'>
- {timestamp(cursor_ts, 1)}
- </div>
-
-*/
-
const mapStateToProps = state => ({
timeline: state.align.timeline,
audio: state.audio,
diff --git a/animism-align/frontend/views/align/components/timeline/ticks.component.js b/animism-align/frontend/views/align/components/timeline/ticks.component.js
index 72f9bd0..747fb7a 100644
--- a/animism-align/frontend/views/align/components/timeline/ticks.component.js
+++ b/animism-align/frontend/views/align/components/timeline/ticks.component.js
@@ -1,16 +1,15 @@
import React, { Component } from 'react'
-import { ZOOM_STEPS, ZOOM_LABEL_STEPS, ZOOM_TICK_STEPS } from '../../constants'
+import { ZOOM_STEPS, ZOOM_LABEL_STEPS, ZOOM_TICK_STEPS, INNER_HEIGHT } from '../../constants'
import { timestamp } from '../../../../util'
export default class Ticks extends Component {
render() {
let { start_ts, zoom, duration } = this.props.timeline
- const width = window.innerHeight
let secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 // 0.1 sec / step
- let widthTimeDuration = width * secondsPerPixel // secs per pixel
+ let widthTimeDuration = INNER_HEIGHT * secondsPerPixel // secs per pixel
let timeMin = start_ts
let timeMax = Math.min(start_ts + widthTimeDuration, duration)
@@ -26,11 +25,11 @@ export default class Ticks extends Component {
let startOffset = pixelsPerLabel - (pixelMin % pixelsPerLabel)
let startTiming = (pixelMin + startOffset) * secondsPerPixel
- let labelCount = Math.ceil(width / pixelsPerLabel) + 1
+ let labelCount = Math.ceil(INNER_HEIGHT / pixelsPerLabel) + 1
let offset, timing, tickLabels = [], ticks = []
for (var i = -1; i < labelCount; i++) {
offset = i * pixelsPerLabel + startOffset
- if (offset > width) continue
+ if (offset > INNER_HEIGHT) continue
timing = i * secondsPerLabel + startTiming
if (timing > duration) {
break
@@ -63,7 +62,7 @@ export default class Ticks extends Component {
/>
)
}
- let tickCount = Math.ceil(width / pixelsPerTick) + 6
+ let tickCount = Math.ceil(INNER_HEIGHT / pixelsPerTick) + 6
for (var i = 0; i < tickCount; i += 1) {
offset = i * pixelsPerTick + startOffset - pixelsPerLabel
if (offset > durationOffset) {
diff --git a/animism-align/frontend/views/align/components/timeline/waveform.component.js b/animism-align/frontend/views/align/components/timeline/waveform.component.js
index 785b020..16ceaf6 100644
--- a/animism-align/frontend/views/align/components/timeline/waveform.component.js
+++ b/animism-align/frontend/views/align/components/timeline/waveform.component.js
@@ -6,7 +6,7 @@ import { connect } from 'react-redux'
import actions from '../../../../actions'
// import * as uploadActions from './upload.actions'
-import { WAVEFORM_SIZE, ZOOM_STEPS, ZOOM_LABEL_STEPS, ZOOM_TICK_STEPS } from '../../constants'
+import { WAVEFORM_SIZE, ZOOM_STEPS, ZOOM_LABEL_STEPS, ZOOM_TICK_STEPS, INNER_HEIGHT } from '../../constants'
class Waveform extends Component {
constructor(props){
@@ -23,12 +23,12 @@ class Waveform extends Component {
resize() {
const canvas = this.canvasRef.current
canvas.width = WAVEFORM_SIZE
- canvas.height = window.innerHeight
+ canvas.height = INNER_HEIGHT
}
draw() {
const canvas = this.canvasRef.current
const ctx = canvas.getContext('2d')
- const h = window.innerHeight
+ const h = INNER_HEIGHT
this.clearCanvas(ctx, h)
this.drawCurve(ctx, h)
}
diff --git a/animism-align/frontend/views/align/constants.js b/animism-align/frontend/views/align/constants.js
index c797784..cf504d3 100644
--- a/animism-align/frontend/views/align/constants.js
+++ b/animism-align/frontend/views/align/constants.js
@@ -29,3 +29,6 @@ export const ZOOM_TICK_STEPS = [
60,
600,
]
+
+export const HEADER_MARGIN = 50
+export const INNER_HEIGHT = window.innerHeight - HEADER_MARGIN
diff --git a/animism-align/frontend/views/align/containers/timeline.container.js b/animism-align/frontend/views/align/containers/timeline.container.js
index 760da82..ba6b7e0 100644
--- a/animism-align/frontend/views/align/containers/timeline.container.js
+++ b/animism-align/frontend/views/align/containers/timeline.container.js
@@ -10,10 +10,10 @@ import Annotations from '../containers/annotations.container'
import Waveform from '../components/timeline/waveform.component'
import Ticks from '../components/timeline/ticks.component'
import Cursor from '../components/timeline/cursor.component'
-import PlayButton from '../components/timeline/playButton.component'
+import PlayButton from '../components/player/playButton.component'
import PlayCursor from '../components/timeline/playCursor.component'
-import { WAVEFORM_SIZE, ZOOM_STEPS } from '../constants'
+import { WAVEFORM_SIZE, ZOOM_STEPS, INNER_HEIGHT } from '../constants'
import { clamp } from '../../../util'
import { positionToTime } from '../align.util'
@@ -84,7 +84,7 @@ class Timeline extends Component {
let { start_ts, zoom, duration } = this.props.timeline
let { deltaY } = e
let secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 // 0.1 sec / step
- let widthTimeDuration = window.innerHeight * secondsPerPixel // secs per pixel
+ let widthTimeDuration = INNER_HEIGHT * secondsPerPixel // secs per pixel
start_ts += Math.round((deltaY / 8) * ZOOM_STEPS[zoom])
start_ts = clamp(start_ts, 0, Math.max(0, duration - widthTimeDuration / 2))
if (e.shiftKey) {
@@ -105,8 +105,8 @@ class Timeline extends Component {
actions.align.setCursor(cursor_ts)
}
handleContainerClick(e) {
- console.log('container click')
actions.align.clearSelectedAnnotation()
+ actions.align.clearSelectedParagraph()
}
handleTimelineClick(e) {
const play_ts = positionToTime(e.pageY, this.props.timeline)
@@ -131,7 +131,6 @@ class Timeline extends Component {
</div>
<Annotations timeline={this.props.timeline} />
<PlayCursor />
- <PlayButton />
</div>
)
}
diff --git a/animism-align/frontend/views/annotation/annotation.reducer.js b/animism-align/frontend/views/annotation/annotation.reducer.js
index 80be5b2..98b785e 100644
--- a/animism-align/frontend/views/annotation/annotation.reducer.js
+++ b/animism-align/frontend/views/annotation/annotation.reducer.js
@@ -5,7 +5,7 @@ import { crudState, crudReducer } from '../../api/crud.reducer'
const initialState = crudState('annotation', {
options: {
- sort: 'start_ts desc',
+ sort: 'start_ts asc',
}
})
diff --git a/animism-align/frontend/views/index.js b/animism-align/frontend/views/index.js
index 9af42ce..b7b633e 100644
--- a/animism-align/frontend/views/index.js
+++ b/animism-align/frontend/views/index.js
@@ -1,2 +1,3 @@
export { default as align } from './align/align.container'
+export { default as paragraph } from './paragraph/paragraph.container'
export { default as upload } from './upload/upload.container'
diff --git a/animism-align/frontend/views/nav/header.component.js b/animism-align/frontend/views/nav/header.component.js
new file mode 100644
index 0000000..be9a6dc
--- /dev/null
+++ b/animism-align/frontend/views/nav/header.component.js
@@ -0,0 +1,42 @@
+import React from 'react'
+// import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+import { Link } from 'react-router-dom'
+
+import PlayButton from '../align/components/player/playButton.component'
+
+import './nav.css'
+
+function Header(props) {
+ return (
+ <header>
+ <PlayButton />
+ <div>
+ <Link to="/align">Align</Link>
+ <Link to="/paragraph">Paragraphs</Link>
+ <Link to="/media">Media</Link>
+ </div>
+ </header>
+ )
+}
+
+// const changeUsername = () => {
+// const username = prompt("Please enter your username:", session('username'))
+// if (username && username.length) {
+// session.set('username', username)
+// document.querySelector('Header div span').innerText = ' → ' + username // very naughty
+// }
+// }
+
+
+const mapStateToProps = (state) => ({
+ // auth: state.auth,
+ site: state.site,
+ // username: session.get('username'),
+ // isAuthenticated: state.auth.isAuthenticated,
+})
+
+const mapDispatchToProps = (dispatch) => ({
+})
+
+export default connect(mapStateToProps, mapDispatchToProps)(Header)
diff --git a/animism-align/frontend/views/nav/nav.css b/animism-align/frontend/views/nav/nav.css
new file mode 100644
index 0000000..485ace2
--- /dev/null
+++ b/animism-align/frontend/views/nav/nav.css
@@ -0,0 +1,73 @@
+/* header */
+
+header {
+ height: 3.125rem;
+ font-size: 0.875rem;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ background: rgba(32,16,64,0.8);
+ color: white;
+ z-index: 50;
+ position: relative;
+}
+header b {
+ font-weight: 900;
+}
+header a {
+ color: rgba(255,255,255,0.95);
+ text-decoration: none;
+ font-size: 0.875rem;
+ font-weight: 500;
+}
+header > div:first-child {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ padding-left: 1.5rem;
+}
+header > div:last-child {
+ padding-right: 1.5rem;
+}
+header > div > button {
+ padding: 0.25rem;
+ margin: 0 0 0 0.5rem;
+ background: #000;
+ border-color: #888;
+ color: #888;
+}
+header > div > button:hover {
+ border-color: #fff;
+ color: #fff;
+}
+header > div:last-child a {
+ padding: 0.5rem;
+}
+header .btn-link:focus,
+header .btn-link:hover,
+header .btn-link:active,
+header a:focus,
+header a:hover,
+header a:active {
+ text-decoration: none;
+ color: white;
+}
+header a:focus,
+header a:hover,
+header a:active {
+ color: white;
+}
+.menuToggle {
+ width: 1.625rem;
+ height: 1.625rem;
+ cursor: pointer;
+ line-height: 1;
+}
+header a.navbar-brand {
+ font-size: .8rem;
+}
+
+header .username {
+ cursor: pointer;
+} \ No newline at end of file
diff --git a/animism-align/frontend/views/paragraph/components/paragraph.types.js b/animism-align/frontend/views/paragraph/components/paragraph.types.js
new file mode 100644
index 0000000..c200a19
--- /dev/null
+++ b/animism-align/frontend/views/paragraph/components/paragraph.types.js
@@ -0,0 +1,44 @@
+import React, { Component } from 'react'
+
+import actions from '../../../actions'
+
+export const Paragraph = ({ paragraph, selectedParagraph, selectedAnnotation, onAnnotationClick, onDoubleClick }) => {
+ const className = paragraph.type
+ if (selectedParagraph) className += ' selected'
+ return (
+ <div
+ className={className}
+ onDoubleClick={e => onDoubleClick(e, paragraph)}
+ >
+ {paragraph.annotations.map(annotation => (
+ <span
+ key={annotation.id}
+ className={annotation.id === selectedAnnotation ? 'selected' : ''}
+ onClick={e => onAnnotationClick(e, paragraph, annotation)}
+ >
+ {annotation.text}
+ </span>
+ ))}
+ </div>
+ )
+}
+
+export const ParagraphHeader = ({ paragraph, selectedParagraph, selectedAnnotation, onAnnotationClick, onDoubleClick }) => {
+ const className = selectedParagraph ? 'header selected' : 'header'
+ const text = paragraph.annotations.map(annotation => annotation.text).join(' ')
+ console.log(text)
+ return (
+ <div
+ className={className}
+ onDoubleClick={e => onDoubleClick(e, paragraph)}
+ >
+ {text}
+ </div>
+ )
+}
+
+export const ParagraphElementLookup = {
+ paragraph: Paragraph,
+ blockquote: Paragraph,
+ header: ParagraphHeader,
+}
diff --git a/animism-align/frontend/views/paragraph/containers/paragraphList.container.js b/animism-align/frontend/views/paragraph/containers/paragraphList.container.js
new file mode 100644
index 0000000..059baff
--- /dev/null
+++ b/animism-align/frontend/views/paragraph/containers/paragraphList.container.js
@@ -0,0 +1,92 @@
+import React, { Component } from 'react'
+import { Route } from 'react-router-dom'
+import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+
+import actions from '../../../actions'
+import { ParagraphElementLookup } from '../components/paragraph.types'
+
+class ParagraphList extends Component {
+ state = {
+ paragraphs: [],
+ }
+ constructor(props) {
+ super(props)
+ this.onAnnotationClick = this.onAnnotationClick.bind(this)
+ this.onParagraphDoubleClick = this.onParagraphDoubleClick.bind(this)
+ }
+ componentDidMount() {
+ this.build()
+ }
+ build() {
+ const { order: annotationOrder, lookup: annotationLookup } = this.props.annotation
+ const { lookup: paragraphLookup } = this.props.paragraph
+ let currentParagraph = {}
+ const paragraphs = []
+ annotationOrder.forEach((annotation_id, i) => {
+ const annotation = annotationLookup[annotation_id]
+ const paragraph = paragraphLookup[annotation.paragraph_id]
+ if (annotation.paragraph_id !== currentParagraph.id) {
+ const paragraph_type = getParagraphType(annotation, paragraph)
+ currentParagraph = {
+ id: annotation.paragraph_id || ('index_' + i),
+ type: paragraph_type,
+ annotations: [],
+ }
+ paragraphs.push(currentParagraph)
+ }
+ currentParagraph.annotations.push(annotation)
+ })
+ this.setState({ paragraphs })
+ }
+ onAnnotationClick(e, paragraph, annotation){
+ //
+ }
+ onParagraphDoubleClick(e, paragraph) {
+ //
+ }
+ render() {
+ const { paragraphs } = this.state
+ return (
+ <div className='paragraphs'>
+ <div className='content'>
+ {paragraphs.map(paragraph => {
+ if (paragraph.type in ParagraphElementLookup) {
+ const ParagraphElement = ParagraphElementLookup[paragraph.type]
+ return (
+ <ParagraphElement
+ key={paragraph.id}
+ paragraph={paragraph}
+ selectedParagraph={false}
+ selectedAnnotation={-1}
+ onAnnotationClick={this.onAnnotationClick}
+ onDoubleClick={this.onParagraphDoubleClick}
+ />
+ )
+ } else {
+ return <div key={paragraph.id}>{'(empty)'}</div>
+ }
+ })}
+ </div>
+ </div>
+ )
+ }
+}
+
+const getParagraphType = (annotation, paragraph) => {
+ if (!paragraph) {
+ return annotation.type
+ }
+ return paragraph.type
+}
+
+const mapStateToProps = state => ({
+ paragraph: state.paragraph.index,
+ annotation: state.annotation.index,
+ audio: state.audio,
+})
+
+const mapDispatchToProps = dispatch => ({
+})
+
+export default connect(mapStateToProps, mapDispatchToProps)(ParagraphList)
diff --git a/animism-align/frontend/views/paragraph/paragraph.container.js b/animism-align/frontend/views/paragraph/paragraph.container.js
new file mode 100644
index 0000000..ecd5417
--- /dev/null
+++ b/animism-align/frontend/views/paragraph/paragraph.container.js
@@ -0,0 +1,35 @@
+import React, { Component } from 'react'
+import { Route } from 'react-router-dom'
+import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+
+import './paragraph.css'
+
+import actions from '../../actions'
+import { Loader } from '../../common'
+
+import ParagraphList from './containers/paragraphList.container'
+
+class ParagraphContainer extends Component {
+ render() {
+ if (!this.props.annotation.lookup || !this.props.paragraph.lookup) {
+ return <div className='body loading'><Loader /></div>
+ }
+ return (
+ <div className='body'>
+ <ParagraphList />
+ </div>
+ )
+ }
+}
+
+const mapStateToProps = state => ({
+ paragraph: state.paragraph.index,
+ annotation: state.annotation.index,
+})
+
+const mapDispatchToProps = dispatch => ({
+ // alignActions: bindActionCreators({ ...alignActions }, dispatch),
+})
+
+export default connect(mapStateToProps, mapDispatchToProps)(ParagraphContainer)
diff --git a/animism-align/frontend/views/paragraph/paragraph.css b/animism-align/frontend/views/paragraph/paragraph.css
new file mode 100644
index 0000000..e96fd4f
--- /dev/null
+++ b/animism-align/frontend/views/paragraph/paragraph.css
@@ -0,0 +1,36 @@
+.paragraphs {
+ width: 100%;
+ height: calc(100% - 3.125rem);
+ overflow: scroll;
+ padding-top: 1rem;
+ padding-left: 1rem;
+}
+
+.paragraphs .content {
+ font-family: 'Georgia', serif;
+ width: 650px;
+ padding-bottom: 6rem;
+}
+.paragraphs .content > div {
+ margin-bottom: 16px;
+}
+
+.paragraphs .header {
+ font-size: 32px;
+}
+
+.paragraphs .paragraph {
+ font-size: 16px;
+ line-height: 1.5;
+}
+.paragraphs .paragraph span:after {
+ content: ' ';
+}
+
+.paragraphs .blockquote {
+ line-height: 1.5;
+ padding-left: 3rem;
+}
+.paragraphs .blockquote span:after {
+ content: ' ';
+} \ No newline at end of file