diff options
| -rw-r--r-- | animism-align/cli/app/sql/models/annotation.py | 2 | ||||
| -rw-r--r-- | animism-align/cli/app/sql/models/paragraph.py | 4 | ||||
| -rw-r--r-- | animism-align/cli/app/sql/versions/202007041642_create_database.py (renamed from animism-align/cli/app/sql/versions/202007041633_create_database.py) | 8 | ||||
| -rw-r--r-- | animism-align/frontend/app.js | 1 | ||||
| -rw-r--r-- | animism-align/frontend/views/align/align.actions.js | 8 | ||||
| -rw-r--r-- | animism-align/frontend/views/align/align.css | 20 | ||||
| -rw-r--r-- | animism-align/frontend/views/align/components/annotations/annotation.form.js | 6 | ||||
| -rw-r--r-- | animism-align/frontend/views/align/components/annotations/annotation.index.js | 83 | ||||
| -rw-r--r-- | animism-align/frontend/views/align/components/annotations/annotation.types.js | 40 | ||||
| -rw-r--r-- | animism-align/frontend/views/align/containers/annotations.container.js | 8 | ||||
| -rw-r--r-- | animism-align/frontend/views/align/containers/timeline.container.js | 3 |
11 files changed, 171 insertions, 12 deletions
diff --git a/animism-align/cli/app/sql/models/annotation.py b/animism-align/cli/app/sql/models/annotation.py index 6cc476c..cc53bd6 100644 --- a/animism-align/cli/app/sql/models/annotation.py +++ b/animism-align/cli/app/sql/models/annotation.py @@ -27,7 +27,7 @@ class Annotation(Base): 'paragraph_id': self.paragraph_id, 'start_ts': self.start_ts, 'end_ts': self.end_ts, - 'sentence': self.description, + 'text': self.text, 'settings': self.settings, } diff --git a/animism-align/cli/app/sql/models/paragraph.py b/animism-align/cli/app/sql/models/paragraph.py index 7c7bcd7..790c9f0 100644 --- a/animism-align/cli/app/sql/models/paragraph.py +++ b/animism-align/cli/app/sql/models/paragraph.py @@ -14,12 +14,16 @@ class Paragraph(Base): __tablename__ = 'paragraph' id = Column(Integer, primary_key=True) type = Column(String(16, convert_unicode=True), nullable=False) + start_ts = Column(Float, nullable=False) + end_ts = Column(Float, nullable=True) settings = Column(JSON, default={}, nullable=True) def toJSON(self): return { 'id': self.id, 'type': self.type, + 'start_ts': self.start_ts, + 'end_ts': self.end_ts, 'settings': self.settings, } diff --git a/animism-align/cli/app/sql/versions/202007041633_create_database.py b/animism-align/cli/app/sql/versions/202007041642_create_database.py index f8336e5..bec0cc3 100644 --- a/animism-align/cli/app/sql/versions/202007041633_create_database.py +++ b/animism-align/cli/app/sql/versions/202007041642_create_database.py @@ -1,8 +1,8 @@ """create database -Revision ID: f8936a84e584 +Revision ID: 4ede1524f909 Revises: -Create Date: 2020-07-04 16:33:01.643193 +Create Date: 2020-07-04 16:42:00.443043 """ from alembic import op @@ -11,7 +11,7 @@ import sqlalchemy_utc # revision identifiers, used by Alembic. -revision = 'f8936a84e584' +revision = '4ede1524f909' down_revision = None branch_labels = None depends_on = None @@ -22,6 +22,8 @@ def upgrade(): op.create_table('paragraph', sa.Column('id', sa.Integer(), nullable=False), sa.Column('type', sa.String(length=16, _expect_unicode=True), nullable=False), + sa.Column('start_ts', sa.Float(), nullable=False), + sa.Column('end_ts', sa.Float(), nullable=True), sa.Column('settings', sa.JSON(), nullable=True), sa.PrimaryKeyConstraint('id') ) diff --git a/animism-align/frontend/app.js b/animism-align/frontend/app.js index 975b5b2..1c40c09 100644 --- a/animism-align/frontend/app.js +++ b/animism-align/frontend/app.js @@ -19,6 +19,7 @@ export default class App extends Component { componentDidMount() { actions.site.loadText() actions.site.loadPeaks() + actions.annotation.index() } render() { return ( diff --git a/animism-align/frontend/views/align/align.actions.js b/animism-align/frontend/views/align/align.actions.js index b3883ae..b10f257 100644 --- a/animism-align/frontend/views/align/align.actions.js +++ b/animism-align/frontend/views/align/align.actions.js @@ -38,11 +38,19 @@ export const showNewAnnotationForm = (start_ts, text) => dispatch => { data: { id: 'new', start_ts, + end_ts: 0.0, text: croppedText, type: 'sentence', + settings: {}, } }) } +export const showEditAnnotationForm = (annotation) => dispatch => { + dispatch({ + type: types.align.set_temporary_annotation, + data: annotation, + }) +} export const updateAnnotationForm = (key, value) => dispatch => { dispatch({ type: types.align.update_temporary_annotation, key, value }) diff --git a/animism-align/frontend/views/align/align.css b/animism-align/frontend/views/align/align.css index 323dc9a..e4629b7 100644 --- a/animism-align/frontend/views/align/align.css +++ b/animism-align/frontend/views/align/align.css @@ -112,4 +112,22 @@ canvas { } .annotationForm .row { justify-content: space-between; -}
\ No newline at end of file +} +.annotationIndex { + width: 305px; +} +.annotation { + position: absolute; + left: 5px; + max-width: 300px; + padding: 0.25rem 0.375rem; + box-shadow: 2px 2px 4px rgba(0,0,0,0.5); + border-radius: 2px; + cursor: pointer; +} +.annotation.sentence { + background-color: #83b; +} +.annotation.header { + background-color: #838; +} 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 9432948..b62c36e 100644 --- a/animism-align/frontend/views/align/components/annotations/annotation.form.js +++ b/animism-align/frontend/views/align/components/annotations/annotation.form.js @@ -24,9 +24,13 @@ class AnnotationForm extends Component { this.handleSubmit = this.handleSubmit.bind(this) } handleKeyDown(e) { + if (e.keyCode === 27) { // escape + actions.align.hideAnnotationForm() + return + } + // console.log(e.keyCode) if (!e.metaKey && !e.ctrlKey) return let { start_ts } = this.props.annotation - console.log(e.keyCode) switch (e.keyCode) { case 38: // up e.preventDefault() diff --git a/animism-align/frontend/views/align/components/annotations/annotation.index.js b/animism-align/frontend/views/align/components/annotations/annotation.index.js new file mode 100644 index 0000000..7b562c2 --- /dev/null +++ b/animism-align/frontend/views/align/components/annotations/annotation.index.js @@ -0,0 +1,83 @@ +import React, { Component } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import actions from '../../../../actions' + +import { ZOOM_STEPS } from '../../constants' +import { clamp } from '../../../../util' +import { positionToTime, timeToPosition } from '../../align.util' + +import { AnnotationElementLookup } from './annotation.types' + +class AnnotationIndex extends Component { + state = { + items: [], + } + constructor(props){ + super(props) + this.handleClick = this.handleClick.bind(this) + } + componentDidUpdate(prevProps) { + if (this.props.index.loading) return + if (prevProps.timeline !== this.props.timeline || prevProps.index !== this.props.index) { + this.update() + } + } + update() { + let { timeline, index } = this.props + let { start_ts, zoom, duration } = this.props.timeline + const { order, lookup } = index + + let secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 // 0.1 sec / step + let widthTimeDuration = window.innerHeight * secondsPerPixel // secs per pixel + + let timeMin = start_ts - 30.0 + let timeMax = Math.min(start_ts + widthTimeDuration, duration) + + const items = order.filter(id => { + const { start_ts: ts } = lookup[id] + return (timeMin < ts && ts < timeMax) + }).map(id => lookup[id]) + this.setState({ items }) + } + handleClick(annotation) { + actions.audio.seek(annotation.start_ts) + } + handleDoubleClick(annotation) { + actions.align.showEditAnnotationForm(annotation) + } + render() { + const { timeline } = this.props + const { start_ts } = timeline + const { items } = this.state + return ( + <div className='annotationIndex'> + {items.map(annotation => { + const { id, type, start_ts } = annotation + const AnnotationElement = AnnotationElementLookup[type] + const y = timeToPosition(start_ts, timeline) + return ( + <AnnotationElement + key={id} + y={y} + annotation={annotation} + onClick={this.handleClick} + onDoubleClick={this.handleDoubleClick} + /> + ) + })} + </div> + ) + } +} + +const mapStateToProps = state => ({ + timeline: state.align.timeline, + index: state.annotation.index, +}) + +const mapDispatchToProps = dispatch => ({ +}) + +export default connect(mapStateToProps, mapDispatchToProps)(AnnotationIndex) diff --git a/animism-align/frontend/views/align/components/annotations/annotation.types.js b/animism-align/frontend/views/align/components/annotations/annotation.types.js new file mode 100644 index 0000000..465844f --- /dev/null +++ b/animism-align/frontend/views/align/components/annotations/annotation.types.js @@ -0,0 +1,40 @@ +import React, { Component } from 'react' + +import actions from '../../../../actions' + +import { ZOOM_STEPS } from '../../constants' +import { clamp } from '../../../../util' +import { positionToTime, timeToPosition } from '../../align.util' + +export const AnnotationSentence = ({ y, annotation, onClick, onDoubleClick }) => { + const { start_ts, text } = annotation + return ( + <div + className='annotation sentence' + style={{ top: y }} + onClick={() => onClick(annotation)} + onDoubleClick={() => onDoubleClick(annotation)} + > + {text} + </div> + ) +} + +export const AnnotationHeader = ({ y, annotation, onClick, onDoubleClick }) => { + const { start_ts, text } = annotation + return ( + <div + className='annotation header' + style={{ top: y }} + onClick={() => onClick(annotation)} + onDoubleClick={() => onDoubleClick(annotation)} + > + {text} + </div> + ) +} + +export const AnnotationElementLookup = { + sentence: AnnotationSentence, + header: AnnotationHeader, +}
\ No newline at end of file diff --git a/animism-align/frontend/views/align/containers/annotations.container.js b/animism-align/frontend/views/align/containers/annotations.container.js index e32757b..b6cdace 100644 --- a/animism-align/frontend/views/align/containers/annotations.container.js +++ b/animism-align/frontend/views/align/containers/annotations.container.js @@ -11,15 +11,16 @@ import { clamp } from '../../../util' import { positionToTime } from '../align.util' import AnnotationForm from '../components/annotations/annotation.form' +import AnnotationIndex from '../components/annotations/annotation.index' class Annotations extends Component { constructor(props){ super(props) - // this.handleKeydown = this.handleKeydown.bind(this) } render() { return ( <div className='annotations'> + <AnnotationIndex /> {this.props.annotation.start_ts && <AnnotationForm /> } @@ -28,11 +29,6 @@ class Annotations extends Component { } } -/* -- get the first sentence from the text -- display the form at that point -*/ - const mapStateToProps = state => ({ timeline: state.align.timeline, annotation: state.align.annotation, diff --git a/animism-align/frontend/views/align/containers/timeline.container.js b/animism-align/frontend/views/align/containers/timeline.container.js index 4167d2d..23b9435 100644 --- a/animism-align/frontend/views/align/containers/timeline.container.js +++ b/animism-align/frontend/views/align/containers/timeline.container.js @@ -53,6 +53,9 @@ class Timeline extends Component { } else { // console.log(e.keyCode) switch (e.keyCode) { + case 27: // escape + actions.align.hideAnnotationForm() + break case 32: // spacebar actions.audio.toggle() break |
