diff options
Diffstat (limited to 'animism-align')
29 files changed, 442 insertions, 170 deletions
diff --git a/animism-align/cli/app/controllers/__pycache__/crud_controller.cpython-37.pyc b/animism-align/cli/app/controllers/__pycache__/crud_controller.cpython-37.pyc Binary files differdeleted file mode 100644 index e28baa6..0000000 --- a/animism-align/cli/app/controllers/__pycache__/crud_controller.cpython-37.pyc +++ /dev/null diff --git a/animism-align/cli/app/controllers/annotation_controller.py b/animism-align/cli/app/controllers/annotation_controller.py index 8d91d1c..962d8f5 100644 --- a/animism-align/cli/app/controllers/annotation_controller.py +++ b/animism-align/cli/app/controllers/annotation_controller.py @@ -12,7 +12,13 @@ class AnnotationView(CrudView): default_sort = "start_ts" def on_create(self, session, form, item): - item.settings = form['settings'] + if 'paragraph_id' in form: + item.paragraph_id = form['paragraph_id'] + if 'settings' in form: + item.settings = form['settings'] def on_update(self, session, form, item): - item.settings = form['settings'] + if 'paragraph_id' in form: + item.paragraph_id = form['paragraph_id'] + if 'settings' in form: + item.settings = form['settings'] diff --git a/animism-align/cli/app/controllers/paragraph_controller.py b/animism-align/cli/app/controllers/paragraph_controller.py index 8056f51..807135d 100644 --- a/animism-align/cli/app/controllers/paragraph_controller.py +++ b/animism-align/cli/app/controllers/paragraph_controller.py @@ -12,7 +12,9 @@ class ParagraphView(CrudView): default_sort = "start_ts" def on_create(self, session, form, item): - item.settings = form['settings'] + if 'settings' in form: + item.settings = form['settings'] def on_update(self, session, form, item): - item.settings = form['settings'] + if 'settings' in form: + item.settings = form['settings'] diff --git a/animism-align/frontend/app.js b/animism-align/frontend/app.js index 9270cc8..137d933 100644 --- a/animism-align/frontend/app.js +++ b/animism-align/frontend/app.js @@ -4,6 +4,7 @@ import { Route } from 'react-router' import actions from './actions' +import Header from './views/nav/header.component' import * as views from './views' const viewList = Object.keys(views).map(name => { @@ -26,6 +27,7 @@ export default class App extends Component { return ( <ConnectedRouter history={this.props.history}> <div className='app'> + <Header /> {viewList} <Route exact key='root' path='/' render={() => { // redirect to index!! diff --git a/animism-align/frontend/common/app.css b/animism-align/frontend/common/app.css index 699505f..3482820 100644 --- a/animism-align/frontend/common/app.css +++ b/animism-align/frontend/common/app.css @@ -74,84 +74,6 @@ li { line-height: 1.5; } -/* header */ - -header { - height: 3.125rem; - font-size: 0.875rem; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - background: rgba(16,32,64,0.5); - color: white; - z-index: 50; -} -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 .vcat-btn { - font-size: 0.875rem; - padding-left: 0.5rem; - letter-spacing: 0.0625rem; -} -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; -} - /* headings */ h1 { diff --git a/animism-align/frontend/common/form.css b/animism-align/frontend/common/form.css index b3b022e..ca7c27b 100644 --- a/animism-align/frontend/common/form.css +++ b/animism-align/frontend/common/form.css @@ -265,6 +265,9 @@ button:disabled:after { .buttons button { margin-right: 0.75rem; } +.buttons button:last-child { + margin-right: 0; +} button.submit { border-color: #d8f; color: #fff; diff --git a/animism-align/frontend/common/header.component.js b/animism-align/frontend/common/header.component.js deleted file mode 100644 index 9e96e80..0000000 --- a/animism-align/frontend/common/header.component.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react' -// import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import { Link } from 'react-router-dom' -import { session } from '../session' - -function Header(props) { - return ( - <header> - <div> - <Link to="/" className="logo"><b>{props.site.siteTitle}</b></Link> - </div> - <div> - <span className='username' onClick={() => changeUsername()}> - {' → '}{props.username} - </span> - </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/common/index.js b/animism-align/frontend/common/index.js index 3647203..0678a93 100644 --- a/animism-align/frontend/common/index.js +++ b/animism-align/frontend/common/index.js @@ -1,4 +1,4 @@ -export { default as Header } from './header.component' +// export { default as Header } from './header.component' export { MenuButton, SmallMenuButton, MenuRoute, } from './menubutton.component' diff --git a/animism-align/frontend/util/index.js b/animism-align/frontend/util/index.js index b105f55..a4a8537 100644 --- a/animism-align/frontend/util/index.js +++ b/animism-align/frontend/util/index.js @@ -52,11 +52,16 @@ export const courtesyS = (n, s) => n + ' ' + (n === 1 ? s : s + 's') export const padSeconds = n => n < 10 ? '0' + n : n -export const timestamp = (n = 0, fps = 1) => { +export const timestamp = (n = 0, fps = 1, ms = false) => { if (n < 0) return '' + let s = '' n /= fps + if (ms) { + const mantissa = Math.round((n % 1) * 10) + s = '.' + mantissa + } n = Math.round(n) - let s = padSeconds(Math.round(n) % 60) + s = padSeconds(n % 60) + s n = Math.floor(n / 60) if (n > 60) { return Math.floor(n / 60) + ':' + padSeconds(n % 60) + ':' + s 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 |
