diff options
Diffstat (limited to 'animism-align/frontend/views')
64 files changed, 0 insertions, 4240 deletions
diff --git a/animism-align/frontend/views/align/align.actions.js b/animism-align/frontend/views/align/align.actions.js deleted file mode 100644 index 2ace824..0000000 --- a/animism-align/frontend/views/align/align.actions.js +++ /dev/null @@ -1,93 +0,0 @@ -import * as types from '../../types' -import { store, history, dispatch } from '../../store' -import { api, post, pad, preloadImage } from '../../util' -import actions from '../../actions' -import { session } from '../../session' -import throttle from 'lodash.throttle' -import debounce from 'lodash.debounce' - -import { ZOOM_STEPS } from './constants' -import { getFirstPunctuationMarkIndex, cutFirstSentence } from './align.util' - -export const setScrollPosition = start_ts => dispatch => ( - dispatch({ type: types.align.set_display_setting, key: 'start_ts', value: start_ts }) -) - -export const setZoom = zoom => dispatch => { - if (0 <= zoom && zoom < ZOOM_STEPS.length) { - dispatch({ type: types.align.set_display_setting, key: 'zoom', value: zoom }) - } -} -export const throttledSetZoom = throttle(zoom => dispatch => { - setZoom(zoom)(dispatch) -}, 250, { leading: true }) - -export const setCursor = cursor_ts => dispatch => ( - dispatch({ type: types.align.set_display_setting, key: 'cursor_ts', value: cursor_ts }) -) - -export const setSelectedAnnotation = annotation => dispatch => { - dispatch({ type: types.align.set_selected_annotation, data: annotation }) - debouncedUpdateAnnotation.flush() -} -export const clearSelectedAnnotation = () => dispatch => { - dispatch({ type: types.align.clear_selected_annotation }) - debouncedUpdateAnnotation.flush() -} -export const updateSelectedAnnotation = annotation => dispatch => { - dispatch({ type: types.align.set_selected_annotation, data: { ...annotation } }) - debouncedUpdateAnnotation(annotation) -} -export const debouncedUpdateAnnotation = debounce(annotation => { - console.log('updating annotation', annotation) - actions.annotation.update(annotation) -}, 2000, { leading: false, trailing: true }) - - -export const setSelectedParagraph = paragraph_id => dispatch => { - dispatch({ type: types.align.set_display_setting, key: 'selected_paragraph_id', value: paragraph_id }) -} -export const clearSelectedParagraph = paragraph_id => dispatch => { - dispatch({ type: types.align.set_display_setting, key: 'selected_paragraph_id', value: -1 }) -} - -export const showNewAnnotationForm = (start_ts, text) => dispatch => { - let croppedText; - if (store.getState().align.annotation.start_ts) { - croppedText = store.getState().align.annotation.text - } else { - croppedText = cutFirstSentence(text) - } - // console.log(croppedText) - dispatch({ - type: types.align.set_temporary_annotation, - 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 }) -} -export const updateAnnotationSettings = (key, value) => dispatch => { - dispatch({ type: types.align.update_temporary_annotation_settings, key, value }) -} - -export const hideAnnotationForm = () => dispatch => { - dispatch({ - type: types.align.set_temporary_annotation, - data: {} - }) -} diff --git a/animism-align/frontend/views/align/align.container.js b/animism-align/frontend/views/align/align.container.js deleted file mode 100644 index 94036e1..0000000 --- a/animism-align/frontend/views/align/align.container.js +++ /dev/null @@ -1,30 +0,0 @@ -import React, { Component } from 'react' -import { Route } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import './align.css' - -import Timeline from './containers/timeline.container.js' -import Script from './containers/script.container.js' -import actions from '../../actions' -import { Header } from '../../common' - -class Container extends Component { - componentDidMount() { - document.body.scrollTo(0, 0) - document.body.parentNode.scrollTo(0, 0) - } - render() { - return ( - <div className='body'> - <div className='row'> - <Timeline /> - </div> - <Script /> - </div> - ) - } -} - -export default Container diff --git a/animism-align/frontend/views/align/align.css b/animism-align/frontend/views/align/align.css deleted file mode 100644 index bbf3bc2..0000000 --- a/animism-align/frontend/views/align/align.css +++ /dev/null @@ -1,215 +0,0 @@ -* { - -} -.body.loading > div { - padding: 1rem; -} -.body { - width: 100%; - height: 100%; - display: flex; - flex-direction: row; - justify-content: space-between; - background: linear-gradient( - 0deg, - rgba(0, 0, 64, 0.5), - rgba(64, 64, 128, 0.5) - ); - padding: 0; -} - -/* Timeline */ - -canvas { - display: block; -} -.timeline { - display: flex; - flex-direction: row; - position: relative; - width: 300px; - cursor: crosshair; -} -.timelineColumn { - position: relative; -} -.ticks .tick { - position: absolute; - right: 0; - width: 4px; - height: 1px; - background: #ddd; -} -.ticks .tickLabel { - position: absolute; - right: 6px; - font-size: 12px; - width: 40px; - margin-top: -7px; - text-align: right; - text-shadow: 0 0 2px #00f; -} -.timeline .cursor { - width: 100%; - position: absolute; - left: 0; - pointer-events: none; -} -.timeline .cursor .line { - width: 100%; - height: 1px; - background: #00f; -} -.timeline .cursor.playCursor .line { - background: #ddd; -} -.timeline .cursor .tickLabel { - position: absolute; - pointer-events: none; - right: 6px; - font-size: 12px; - width: 40px; - margin-top: -7px; - text-align: right; - text-shadow: 0 0 2px #000, 0 0 2px #000, 0 0 2px #000; -} - -/* Audio player */ - -.playButton { - /*position: absolute;*/ - /*top: 0; left: 0;*/ - width: 3rem; height: 3rem; - padding: 1rem; - background: transparent; - cursor: pointer; - background-size: contain; - background-repeat: no-repeat; - background-position: center; -} -.playButton.playing { - background-image: url('/static/img/icons_pause_white.svg'); -} -.playButton.paused { - background-image: url('/static/img/icons_play_white.svg'); -} - -/* Script */ - -.script { - height: calc(100vh - 3.15rem); - z-index: 1; -} - -/* Annotations */ - -.annotations { - position: relative; - width: 450px; -} - -/* Annotation form */ - -.annotationForm { - width: 401px; - padding: 0.5rem; - position: absolute; - left: 0.25rem; - background: #448; - box-shadow: 0 0 2px #000, 0 0 4px #000; - z-index: 10; -} -.annotationForm textarea { - width: 100%; -} -.annotationForm .row { - justify-content: space-between; - align-items: center; -} -.annotationForm .row > div { - display: flex; - align-items: center; -} -.annotationForm .buttons { - margin-bottom: 0.5rem; -} -.annotationForm .ts { - color: #fff; -} -.annotationForm .select.media_id { - width: 100%; - margin-right: 0; -} - -/* Annotation index */ - -.annotationIndex { - width: 800px; -} -.annotationIndex .annotation { - position: absolute; - left: 5px; - max-width: 400px; - padding: 0.25rem 0.375rem; - box-shadow: 0px 0px 3px rgba(0,0,0,1.0); - border: 1px solid transparent; - border-radius: 2px; - font-size: 12px; - cursor: pointer; - user-select: none; - background-color: #768; -} -.annotation.selected { - border-color: #bbf; - box-shadow: 0px 0px 4px rgba(0,0,0,1.0), 0px 0px 2px rgba(0,0,0,1.0); - z-index: 1; - background-image: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.4)); -} -.annotationIndex .annotation.media { - left: calc(405px + 0.5rem); -} -.annotation.sentence.even { - background-color: #83b; -} -.annotation.sentence.odd { - background-color: #537; -} -.annotation.header { - background-color: #838; -} -.annotation.paragraph_end { - background-color: #003; - border-top: 1px solid #888; - width: 100%; - padding: 1px; -} - -/* Condensed layout (first lines) */ - -.annotationIndex.condensed .annotation.sentence { - z-index: 0; - white-space: pre; - overflow: hidden; - text-overflow: ellipsis; -} -.annotationIndex.condensed .annotation.header { - z-index: 1; -} -.annotationIndex.condensed .annotation.paragraph_end { - border-top-color: #888; -} - -/* Collapsed layout (borders) */ - -.annotationIndex.collapsed .annotation.sentence { - height: 2px; overflow: hidden; padding: 0; z-index: 0; -} -.annotationIndex.collapsed .annotation.sentence.selected { - z-index: 1; -} -.annotationIndex.collapsed .annotation.header { - z-index: 2; -} -.annotationIndex.collapsed .annotation.paragraph_end { - border-top-color: #333; -} diff --git a/animism-align/frontend/views/align/align.reducer.js b/animism-align/frontend/views/align/align.reducer.js deleted file mode 100644 index fc948e8..0000000 --- a/animism-align/frontend/views/align/align.reducer.js +++ /dev/null @@ -1,85 +0,0 @@ -import * as types from '../../types' -import { session, getDefault, getDefaultInt } from '../../session' - -const initialState = { - timeline: { - cursor_ts: -1, - start_ts: 0, - zoom: 1, - duration: 0, - selected_annotation_id: -1, - selected_paragraph_id: -1, - }, - annotation: {}, - selectedAnnotation: {}, - options: { - } -} - -export default function alignReducer(state = initialState, action) { - // console.log(action.type, action) - switch (action.type) { - case types.peaks.loaded: - console.log('peaks duration:', action.data.length / 10) - return state - - case types.align.set_display_setting: - return { - ...state, - timeline: { - ...state.timeline, - [action.key]: action.value, - } - } - - case types.align.set_selected_annotation: - return { - ...state, - timeline: { - ...state.timeline, - selected_annotation_id: action.data.id, - }, - selectedAnnotation: action.data, - } - - case types.align.clear_selected_annotation: - return { - ...state, - timeline: { - ...state.timeline, - selected_annotation_id: -1, - }, - selectedAnnotation: {}, - } - - case types.align.set_temporary_annotation: - return { - ...state, - annotation: action.data, - } - - case types.align.update_temporary_annotation: - return { - ...state, - annotation: { - ...state.annotation, - [action.key]: action.value, - } - } - - case types.align.update_temporary_annotation_settings: - return { - ...state, - annotation: { - ...state.annotation, - settings: { - ...state.annotation.settings, - [action.key]: action.value, - } - } - } - - default: - return state - } -} diff --git a/animism-align/frontend/views/align/align.util.js b/animism-align/frontend/views/align/align.util.js deleted file mode 100644 index 37d4181..0000000 --- a/animism-align/frontend/views/align/align.util.js +++ /dev/null @@ -1,65 +0,0 @@ -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 = INNER_HEIGHT * secondsPerPixel - const timeMin = start_ts - const timeMax = Math.min(start_ts + widthTimeDuration, duration) - const timeWidth = timeMax - timeMin - return clamp(y * secondsPerPixel + start_ts, 0, timeMax) -} - -export const timeToPosition = (ts, { start_ts, zoom, duration }) => { - const secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 - const widthTimeDuration = INNER_HEIGHT * secondsPerPixel - const timeMin = start_ts - const timeMax = Math.min(start_ts + widthTimeDuration, duration) - const timeWidth = timeMax - timeMin - const timeHalfHeight = INNER_HEIGHT * secondsPerPixel / 2 - if (ts < timeMin - timeHalfHeight) { - return -9999 - } - if (ts > timeMax) { - return -9999 - } - return (ts - timeMin) / timeWidth * INNER_HEIGHT -} - -export const getFirstPunctuationMarkIndex = text => { - const indexes = [ - text.indexOf('. '), - text.indexOf('? '), - text.indexOf('! '), - text.indexOf('." '), - text.indexOf('?" '), - text.indexOf('!" '), - text.indexOf('.” '), - text.indexOf('?” '), - text.indexOf('!” '), - ] - - return indexes.reduce((a, b) => { - if (b < 0) return a - return Math.min(a, b) - }, Infinity) + 1 -} - -export const cutFirstSentence = text => { - const textToCrop = text.trim().replace("\n", " ").split("\n")[0] - let cropIndex = getFirstPunctuationMarkIndex(textToCrop) - if (!cropIndex) cropIndex = textToCrop.length - const croppedText = textToCrop.substr(0, cropIndex).trim() - const updatedText = text.trim().replace(croppedText, '').trim() - actions.site.updateText(updatedText) - return croppedText -} - -export const thumbnailURL = data => { - if (data.type === 'video') return data.settings.video.thumbnail_url - if (data.type === 'image') return data.settings.thumbnail.url -} diff --git a/animism-align/frontend/views/align/components/annotations/annotation.form.js b/animism-align/frontend/views/align/components/annotations/annotation.form.js deleted file mode 100644 index 01b1663..0000000 --- a/animism-align/frontend/views/align/components/annotations/annotation.form.js +++ /dev/null @@ -1,183 +0,0 @@ -import React, { Component } from 'react' -// import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import actions from '../../../../actions' -// import * as alignActions from '../align.actions' - -import { ZOOM_STEPS } from '../../constants' -import { clamp, timestamp, capitalize } from '../../../../util' -import { timeToPosition } from '../../align.util' -import { Select } from '../../../../common' - -import { - AnnotationFormVideo, - AnnotationFormImage, -} from './annotationForms' - -const ANNOTATION_TYPES = [ - 'sentence', 'header', 'paragraph_end', 'video', 'image', 'image_carousel', -].map(name => ({ name, label: capitalize(name.replace('_', ' ')) })) - -class AnnotationForm extends Component { - constructor(props){ - super(props) - this.handleChange = this.handleChange.bind(this) - this.handleSelect = this.handleSelect.bind(this) - this.handleSettingsSelect = this.handleSettingsSelect.bind(this) - this.handleKeyDown = this.handleKeyDown.bind(this) - this.handleSubmit = this.handleSubmit.bind(this) - this.handleDestroy = this.handleDestroy.bind(this) - this.textareaRef = React.createRef() - } - componentDidMount() { - if (this.textareaRef && this.textareaRef.current) { - this.textareaRef.current.focus() - } - } - 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 - switch (e.keyCode) { - case 38: // up - e.preventDefault() - start_ts -= 0.1 - actions.align.updateAnnotationForm('start_ts', Math.max(0, start_ts)) - actions.audio.seek(start_ts) - actions.align.setCursor(start_ts) - break - case 40: // down - e.preventDefault() - start_ts += 0.1 - actions.align.updateAnnotationForm('start_ts', Math.max(0, start_ts)) - actions.audio.seek(start_ts) - actions.align.setCursor(start_ts) - break - case 83: // ctrl-S - e.preventDefault() - this.handleSubmit() - default: - break - } - } - handleChange(e) { - const { name, value } = e.target - this.handleSelect(name, value) - } - handleSelect(name, value) { - actions.align.updateAnnotationForm(name, value) - } - handleSettingsSelect(name, value) { - if (name.indexOf('_id') !== -1) value = parseInt(value) || 0 - actions.align.updateAnnotationSettings(name, value) - } - handleSubmit() { - const { annotation } = this.props - if (annotation.type === 'paragraph_end') { - annotation.text = '' - } - if (annotation.id === 'new') { - delete annotation.id - actions.annotation.create(annotation) - .then(response => { - console.log(response) - actions.align.hideAnnotationForm() - }) - } else { - actions.annotation.update(annotation) - .then(response => { - console.log(response) - actions.align.hideAnnotationForm() - }) - } - } - handleDestroy() { - const { annotation } = this.props - if (annotation.id === 'new') { - actions.align.hideAnnotationForm() - } else { - actions.annotation.destroy(annotation) - .then(response => { - console.log(response) - actions.align.hideAnnotationForm() - }) - } - } - render() { - const { timeline, annotation, media } = this.props - if (!annotation.start_ts) return <div></div> - return ( - <div - className='annotationForm' - style={{ - top: timeToPosition(annotation.start_ts, timeline), - }} - > - {this.renderButtons()} - {annotation.type === 'sentence' && this.renderTextarea()} - {annotation.type === 'header' && this.renderTextarea()} - {annotation.type === 'video' && - <AnnotationFormVideo annotation={annotation} media={media} handleSettingsSelect={this.handleSettingsSelect} /> - } - {annotation.type === 'image' && - <AnnotationFormImage annotation={annotation} media={media} handleSettingsSelect={this.handleSettingsSelect} /> - } - {annotation.type === 'image_carousel' && - <AnnotationFormImageCarousel annotation={annotation} media={media} handleSettingsSelect={this.handleSettingsSelect} /> - } - </div> - ) - } - renderButtons() { - const { annotation } = this.props - return ( - <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> - <div> - {annotation.id !== 'new' && <button onClick={this.handleDestroy}>Delete</button>} - <button onClick={this.handleSubmit}>Save</button> - </div> - </div> - ) - } - renderTextarea() { - const { annotation } = this.props - return ( - <div> - <textarea - name='text' - value={annotation.text} - onKeyDown={this.handleKeyDown} - onChange={this.handleChange} - ref={this.textareaRef} - /> - </div> - ) - } -} - -const mapStateToProps = state => ({ - annotation: state.align.annotation, - timeline: state.align.timeline, - media: state.media.index, -}) - -const mapDispatchToProps = dispatch => ({ -}) - -export default connect(mapStateToProps, mapDispatchToProps)(AnnotationForm) diff --git a/animism-align/frontend/views/align/components/annotations/annotation.index.js b/animism-align/frontend/views/align/components/annotations/annotation.index.js deleted file mode 100644 index 30dc7c0..0000000 --- a/animism-align/frontend/views/align/components/annotations/annotation.index.js +++ /dev/null @@ -1,126 +0,0 @@ -import React, { Component } from 'react' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import actions from '../../../../actions' - -import { ZOOM_STEPS, INNER_HEIGHT } from '../../constants' -import { clamp } from '../../../../util' -import { positionToTime, timeToPosition } from '../../align.util' - -import { AnnotationElementLookup } from './annotationTypes' - -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 = INNER_HEIGHT * secondsPerPixel // secs per pixel - - let timeMin = start_ts - 50.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]).reverse() - this.setState({ items }) - } - handleClick(e, annotation) { - e.stopPropagation() - if (e.shiftKey) { - e.preventDefault() - this.handleParagraphSelection(annotation, e.metaKey) - } - actions.audio.seek(annotation.start_ts) - actions.align.setSelectedAnnotation(annotation) - } - handleParagraphSelection(annotation, shouldClear) { - const { selected_paragraph_id } = this.props.timeline - if (!selected_paragraph_id || selected_paragraph_id === -1 || shouldClear) { - if (annotation.paragraph_id && !shouldClear) { - actions.align.setSelectedParagraph(annotation.paragraph_id) - } else { - actions.paragraph.create({ - type: 'paragraph', - start_ts: annotation.start_ts, - }).then(data => { - 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) - } - render() { - const { timeline, media, annotationInForm, selectedAnnotation } = this.props - const { start_ts, zoom, selected_annotation_id } = timeline - const { items } = this.state - const className = (zoom < 2) - ? 'annotationIndex' - : (zoom < 3) - ? 'annotationIndex condensed' - : 'annotationIndex collapsed' - return ( - <div className={className}> - {items.map(annotation => { - if (annotationInForm && annotation.id === annotationInForm.id) { - return null - } - if (annotation.id === selected_annotation_id) { - annotation = selectedAnnotation - } - const { id, type, start_ts } = annotation - const AnnotationElement = AnnotationElementLookup[type] - const y = timeToPosition(start_ts, timeline) - return ( - <AnnotationElement - key={id} - y={y} - selected={annotation.id === selected_annotation_id} - annotation={annotation} - media={media} - onClick={this.handleClick} - onDoubleClick={this.handleDoubleClick} - /> - ) - })} - </div> - ) - } -} - -const mapStateToProps = state => ({ - timeline: state.align.timeline, - annotationInForm: state.align.annotation, - selectedAnnotation: state.align.selectedAnnotation, - index: state.annotation.index, - media: state.media.index.lookup, -}) - -const mapDispatchToProps = dispatch => ({ -}) - -export default connect(mapStateToProps, mapDispatchToProps)(AnnotationIndex) diff --git a/animism-align/frontend/views/align/components/annotations/annotationForms/annotationForm.image.js b/animism-align/frontend/views/align/components/annotations/annotationForms/annotationForm.image.js deleted file mode 100644 index e2df98b..0000000 --- a/animism-align/frontend/views/align/components/annotations/annotationForms/annotationForm.image.js +++ /dev/null @@ -1,27 +0,0 @@ -import React, { Component } from 'react' - -import { Select } from '../../../../../common' - -export const AnnotationFormImage = ({ annotation, media, handleSettingsSelect }) => { - if (!media.lookup) return <div /> - const { lookup, order } = media - const image_list_items = order.filter(id => lookup[id].type === 'image').map(id => { - const image = lookup[id] - return { - name: image.id, - label: image.author + ' - ' + image.title - } - }) - return ( - <div> - <Select - name='media_id' - className="media_id" - selected={annotation.settings.media_id} - options={image_list_items} - defaultOption='Choose an image' - onChange={handleSettingsSelect} - /> - </div> - ) -} diff --git a/animism-align/frontend/views/align/components/annotations/annotationForms/annotationForm.video.js b/animism-align/frontend/views/align/components/annotations/annotationForms/annotationForm.video.js deleted file mode 100644 index 9302ba4..0000000 --- a/animism-align/frontend/views/align/components/annotations/annotationForms/annotationForm.video.js +++ /dev/null @@ -1,27 +0,0 @@ -import React, { Component } from 'react' - -import { Select } from '../../../../../common' - -export const AnnotationFormVideo = ({ annotation, media, handleSettingsSelect }) => { - if (!media.lookup) return <div /> - const { lookup, order } = media - const video_list_items = order.filter(id => lookup[id].type === 'video').map(id => { - const video = lookup[id] - return { - name: video.id, - label: video.author + ' - ' + video.title - } - }) - return ( - <div> - <Select - name='media_id' - className="media_id" - selected={annotation.settings.media_id} - options={video_list_items} - defaultOption='Choose a video' - onChange={handleSettingsSelect} - /> - </div> - ) -} diff --git a/animism-align/frontend/views/align/components/annotations/annotationForms/index.js b/animism-align/frontend/views/align/components/annotations/annotationForms/index.js deleted file mode 100644 index 1411efc..0000000 --- a/animism-align/frontend/views/align/components/annotations/annotationForms/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import { - AnnotationFormVideo, -} from './annotationForm.video' - -import { - AnnotationFormImage, -} from './annotationForm.image' - -export { - AnnotationFormImage, - AnnotationFormVideo, -} diff --git a/animism-align/frontend/views/align/components/annotations/annotationTypes/annotationTypes.image.js b/animism-align/frontend/views/align/components/annotations/annotationTypes/annotationTypes.image.js deleted file mode 100644 index c3e0c22..0000000 --- a/animism-align/frontend/views/align/components/annotations/annotationTypes/annotationTypes.image.js +++ /dev/null @@ -1,33 +0,0 @@ -import React, { Component } from 'react' - -import { thumbnailURL } from '../../../align.util' - -import { checkAnnotationMediaNotReady, AnnotationMediaLoading } from './annotationTypes.util' - -export const AnnotationImage = ({ y, annotation, media, selected, onClick, onDoubleClick }) => { - const { start_ts, text } = annotation - const className = selected ? 'annotation media image selected' : 'annotation media image' - if (checkAnnotationMediaNotReady(annotation, media)) { - return <AnnotationMediaLoading y={y} className={className} onClick={onClick} onDoubleClick={onDoubleClick} /> - } - const data = media[annotation.settings.media_id] - return ( - <div - className={className} - style={{ top: y }} - onClick={e => onClick(e, annotation)} - onDoubleClick={e => onDoubleClick(e, annotation)} - > - <div className='img'> - <img src={thumbnailURL(data)} alt={data.title} /> - </div> - <div className='meta center'> - <div> - <i>{data.title}</i><br /> - {data.author}<br /> - {data.date} - </div> - </div> - </div> - ) -} diff --git a/animism-align/frontend/views/align/components/annotations/annotationTypes/annotationTypes.text.js b/animism-align/frontend/views/align/components/annotations/annotationTypes/annotationTypes.text.js deleted file mode 100644 index be4674f..0000000 --- a/animism-align/frontend/views/align/components/annotations/annotationTypes/annotationTypes.text.js +++ /dev/null @@ -1,49 +0,0 @@ -import React, { Component } from 'react' - -export const AnnotationSentence = ({ y, annotation, selected, onClick, onDoubleClick }) => { - const { start_ts, text, paragraph_id } = annotation - let className = !paragraph_id - ? 'annotation sentence' - : (paragraph_id % 2) - ? 'annotation sentence odd' - : 'annotation sentence even' - if (selected) className += ' selected' - return ( - <div - className={className} - style={{ top: y }} - onClick={e => onClick(e, annotation)} - onDoubleClick={e => onDoubleClick(e, annotation)} - dangerouslySetInnerHTML={{ __html: text }} - /> - ) -} - -export const AnnotationHeader = ({ y, annotation, selected, onClick, onDoubleClick }) => { - const { start_ts, text } = annotation - const className = selected ? 'annotation header selected' : 'annotation header' - return ( - <div - className={className} - style={{ top: y }} - onClick={e => onClick(e, annotation)} - onDoubleClick={e => onDoubleClick(e, annotation)} - > - {text} - </div> - ) -} - -export const AnnotationParagraphEnd = ({ y, annotation, selected, onClick, onDoubleClick }) => { - const { start_ts, text } = annotation - const className = selected ? 'annotation paragraph_end selected' : 'annotation paragraph_end' - return ( - <div - className={className} - style={{ top: y }} - onClick={e => onClick(e, annotation)} - onDoubleClick={e => onDoubleClick(e, annotation)} - > - </div> - ) -} diff --git a/animism-align/frontend/views/align/components/annotations/annotationTypes/annotationTypes.util.js b/animism-align/frontend/views/align/components/annotations/annotationTypes/annotationTypes.util.js deleted file mode 100644 index 17abebd..0000000 --- a/animism-align/frontend/views/align/components/annotations/annotationTypes/annotationTypes.util.js +++ /dev/null @@ -1,28 +0,0 @@ -import React, { Component } from 'react' - -export const checkAnnotationMediaNotReady = (annotation, media) => { - return (!media) || (!(annotation.settings.media_id in media)) -} - -export const AnnotationMediaLoading = ({ y, className, onClick, onDoubleClick }) => { - if (!media) { - return ( - <div - className={className} - style={{ top: y }} - onClick={e => onClick(e, annotation)} - onDoubleClick={e => onDoubleClick(e, annotation)} - >LOADING...</div> - ) - } - if (!(annotation.settings.media_id in media)) { - return ( - <div - className={className} - style={{ top: y }} - onClick={e => onClick(e, annotation)} - onDoubleClick={e => onDoubleClick(e, annotation)} - >MEDIA NOT FOUND</div> - ) - } -} diff --git a/animism-align/frontend/views/align/components/annotations/annotationTypes/annotationTypes.video.js b/animism-align/frontend/views/align/components/annotations/annotationTypes/annotationTypes.video.js deleted file mode 100644 index dc4f469..0000000 --- a/animism-align/frontend/views/align/components/annotations/annotationTypes/annotationTypes.video.js +++ /dev/null @@ -1,34 +0,0 @@ -import React, { Component } from 'react' - -import { thumbnailURL } from '../../../align.util' - -import { checkAnnotationMediaNotReady, AnnotationMediaLoading } from './annotationTypes.util' - -export const AnnotationVideo = ({ y, annotation, media, selected, onClick, onDoubleClick }) => { - const { start_ts, text } = annotation - const className = selected ? 'annotation media video selected' : 'annotation media video' - if (checkAnnotationMediaNotReady(annotation, media)) { - return <AnnotationMediaLoading y={y} className={className} onClick={onClick} onDoubleClick={onDoubleClick} /> - } - const data = media[annotation.settings.media_id] - return ( - <div - className={className} - style={{ top: y }} - onClick={e => onClick(e, annotation)} - onDoubleClick={e => onDoubleClick(e, annotation)} - > - <div className='img'> - <img src={thumbnailURL(data)} alt={data.title} /> - </div> - <div className='meta center'> - <div> - <i>{data.title}</i><br /> - {data.author}<br /> - {data.date} - </div> - </div> - </div> - ) -} - diff --git a/animism-align/frontend/views/align/components/annotations/annotationTypes/index.js b/animism-align/frontend/views/align/components/annotations/annotationTypes/index.js deleted file mode 100644 index 560063b..0000000 --- a/animism-align/frontend/views/align/components/annotations/annotationTypes/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' - -import { - AnnotationSentence, AnnotationHeader, - AnnotationParagraphEnd, -} from './annotationTypes.text' - -import { - AnnotationVideo, -} from './annotationTypes.video' - -import { - AnnotationImage, -} from './annotationTypes.image' - -export const AnnotationElementLookup = { - sentence: React.memo(AnnotationSentence), - header: React.memo(AnnotationHeader), - paragraph_end: React.memo(AnnotationParagraphEnd), - video: React.memo(AnnotationVideo), - image: React.memo(AnnotationImage), -} diff --git a/animism-align/frontend/views/align/components/player/playButton.component.js b/animism-align/frontend/views/align/components/player/playButton.component.js deleted file mode 100644 index 486eaee..0000000 --- a/animism-align/frontend/views/align/components/player/playButton.component.js +++ /dev/null @@ -1,31 +0,0 @@ -import React, { Component } from 'react' -// import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import actions from '../../../../actions' -// import * as alignActions from '../align.actions' - -import { ZOOM_STEPS } from '../../constants' -import { clamp } from '../../../../util' - -const PlayButton = ({ audio }) => { - return ( - <div - className={audio.playing ? 'playButton playing' : 'playButton paused'} - onClick={() => { - audio.playing ? actions.audio.pause() : actions.audio.play() - }} - /> - ) -} - -const mapStateToProps = state => ({ - audio: state.audio, -}) - -const mapDispatchToProps = dispatch => ({ - // alignActions: bindActionCreators({ ...alignActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(PlayButton) diff --git a/animism-align/frontend/views/align/components/timeline/cursor.component.js b/animism-align/frontend/views/align/components/timeline/cursor.component.js deleted file mode 100644 index f621b37..0000000 --- a/animism-align/frontend/views/align/components/timeline/cursor.component.js +++ /dev/null @@ -1,26 +0,0 @@ -import React, { Component } from 'react' - -import { ZOOM_STEPS } from '../../constants' -import { timestamp } from '../../../../util' - -const Cursor = ({ timeline, annotation }) => { - const { start_ts, zoom, cursor_ts, duration } = timeline - const ts = annotation.start_ts || cursor_ts - const secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 - const y = (ts - start_ts) / secondsPerPixel - return ( - <div - className='cursor' - style={{ - top: y, - }} - > - <div className='line' /> - <div className='tickLabel'> - {timestamp(ts, 1)} - </div> - </div> - ) -} - -export default Cursor
\ No newline at end of file diff --git a/animism-align/frontend/views/align/components/timeline/playCursor.component.js b/animism-align/frontend/views/align/components/timeline/playCursor.component.js deleted file mode 100644 index e03d212..0000000 --- a/animism-align/frontend/views/align/components/timeline/playCursor.component.js +++ /dev/null @@ -1,36 +0,0 @@ -import React, { Component } from 'react' -// import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import { ZOOM_STEPS } from '../../constants' -import { timestamp } from '../../../../util' - -const PlayCursor = ({ timeline, audio }) => { - const { start_ts, zoom, duration } = timeline - const { play_ts } = audio - const secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 - const y = (play_ts - start_ts) / secondsPerPixel - // console.log(play_ts, y) - return ( - <div - className='cursor playCursor' - style={{ - top: y, - }} - > - <div className='line' /> - </div> - ) -} - -const mapStateToProps = state => ({ - timeline: state.align.timeline, - audio: state.audio, -}) - -const mapDispatchToProps = dispatch => ({ - // alignActions: bindActionCreators({ ...alignActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(PlayCursor) diff --git a/animism-align/frontend/views/align/components/timeline/ticks.component.js b/animism-align/frontend/views/align/components/timeline/ticks.component.js deleted file mode 100644 index 747fb7a..0000000 --- a/animism-align/frontend/views/align/components/timeline/ticks.component.js +++ /dev/null @@ -1,88 +0,0 @@ -import React, { Component } from 'react' - -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 - - let secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 // 0.1 sec / step - - let widthTimeDuration = INNER_HEIGHT * secondsPerPixel // secs per pixel - - let timeMin = start_ts - let timeMax = Math.min(start_ts + widthTimeDuration, duration) - let timeWidth = timeMax - timeMin - - let pixelMin = timeMin / secondsPerPixel - - let secondsPerLabel = ZOOM_LABEL_STEPS[zoom] // secs - let pixelsPerLabel = secondsPerLabel / secondsPerPixel - let secondsPerTick = ZOOM_TICK_STEPS[zoom] - let pixelsPerTick = secondsPerTick / secondsPerPixel - - let startOffset = pixelsPerLabel - (pixelMin % pixelsPerLabel) - let startTiming = (pixelMin + startOffset) * secondsPerPixel - - 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 > INNER_HEIGHT) continue - timing = i * secondsPerLabel + startTiming - if (timing > duration) { - break - } - tickLabels.push( - <div className='tickLabel' key={"tickLabel_" + i} - style={{ - top: Math.floor(offset) - }}> - {timestamp(timing)} - </div> - ) - } - - let durationOffset = duration / secondsPerPixel - pixelMin - if (timing > duration) { - tickLabels.push( - <div className='tickLabel tickLabelTotal' key={"tickLabel_total"} - style={{ - top: durationOffset - }}> - {timestamp(duration, 1)} - </div> - ) - ticks.push( - <div className='tick' key={"tick_total"} - style={{ - top: Math.floor(durationOffset), - }} - /> - ) - } - let tickCount = Math.ceil(INNER_HEIGHT / pixelsPerTick) + 6 - for (var i = 0; i < tickCount; i += 1) { - offset = i * pixelsPerTick + startOffset - pixelsPerLabel - if (offset > durationOffset) { - break - } - ticks.push( - <div className='tick' key={"tick_" + i} - style={{ - top: Math.floor(offset), - }} - /> - ) - } - // console.log(ticks.length) - - return ( - <div className='ticks'> - {ticks} - {tickLabels} - </div> - ) - } -} diff --git a/animism-align/frontend/views/align/components/timeline/waveform.component.js b/animism-align/frontend/views/align/components/timeline/waveform.component.js deleted file mode 100644 index 16ceaf6..0000000 --- a/animism-align/frontend/views/align/components/timeline/waveform.component.js +++ /dev/null @@ -1,99 +0,0 @@ -import React, { Component } from 'react' -// import { Link } from 'react-router-dom' -// import { bindActionCreators } from 'redux' -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, INNER_HEIGHT } from '../../constants' - -class Waveform extends Component { - constructor(props){ - super(props) - this.canvasRef = React.createRef() - } - componentDidMount() { - this.resize() - this.draw() - } - componentDidUpdate() { - this.draw() - } - resize() { - const canvas = this.canvasRef.current - canvas.width = WAVEFORM_SIZE - canvas.height = INNER_HEIGHT - } - draw() { - const canvas = this.canvasRef.current - const ctx = canvas.getContext('2d') - const h = INNER_HEIGHT - this.clearCanvas(ctx, h) - this.drawCurve(ctx, h) - } - clearCanvas(ctx, h) { - const w = WAVEFORM_SIZE - ctx.clearRect(0, 0, w, h) - ctx.fillStyle = 'rgba(0,0,0,0.5)' - ctx.fillRect(0, 0, w, h) - ctx.fillStyle = 'rgba(64,128,192,0.5)' - } - drawCurve(ctx, h) { - let { peaks, timeline } = this.props - let { start_ts, zoom, duration } = timeline - - let secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 // 0.1 sec / step - let stepsPerPixel = ZOOM_STEPS[zoom] // 0.1 sec / step - - let widthTimeDuration = h * secondsPerPixel // secs per pixel - - let timeMin = Math.round(start_ts / secondsPerPixel) * secondsPerPixel - let timeMax = Math.min(timeMin + widthTimeDuration, duration) - let timeWidth = timeMax - timeMin - - let stepMin = Math.floor(timeMin * 10) - let pixelWidth = Math.ceil(timeWidth / secondsPerPixel) - - let i = 0 - let step = stepMin - let waveformPeak = WAVEFORM_SIZE / 2 - let origin = (1 - peaks[step]) * waveformPeak - let y - let peak - // console.log(stepMin, pixelWidth * stepsPerPixel + stepMin) - ctx.beginPath() - ctx.moveTo(origin, 0) - for (i = 0; i < pixelWidth; i++) { - step = i * stepsPerPixel + stepMin - peak = peaks[step] - y = (1 - peak) * waveformPeak - ctx.lineTo(y, i) - } - for (i = pixelWidth - 1; i > 0; i--) { - step = i * stepsPerPixel + stepMin - peak = peaks[step] - y = (1 + peak) * waveformPeak - ctx.lineTo(y, i) - } - ctx.lineTo(origin, 0) - ctx.fillStyle = 'rgba(255,255,255,0.8)' - ctx.fill() - } - render() { - return ( - <canvas ref={this.canvasRef} onClick={this.props.onClick} /> - ) - } -} - -const mapStateToProps = state => ({ - timeline: state.align.timeline, - peaks: state.site.peaks, -}) - -const mapDispatchToProps = dispatch => ({ - // uploadActions: bindActionCreators({ ...uploadActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(Waveform) diff --git a/animism-align/frontend/views/align/constants.js b/animism-align/frontend/views/align/constants.js deleted file mode 100644 index cf504d3..0000000 --- a/animism-align/frontend/views/align/constants.js +++ /dev/null @@ -1,34 +0,0 @@ -export const WAVEFORM_SIZE = 300 - -export const ZOOM_STEPS = [ - 1, - 2, - 3, - 10, - 20, - 30, - 60, -] - -export const ZOOM_LABEL_STEPS = [ - 20, - 60, - 60, - 300, - 600, - 600, - 1200, -] - -export const ZOOM_TICK_STEPS = [ - 5, - 10, - 30, - 60, - 60, - 60, - 600, -] - -export const HEADER_MARGIN = 50 -export const INNER_HEIGHT = window.innerHeight - HEADER_MARGIN diff --git a/animism-align/frontend/views/align/containers/annotations.container.js b/animism-align/frontend/views/align/containers/annotations.container.js deleted file mode 100644 index b6cdace..0000000 --- a/animism-align/frontend/views/align/containers/annotations.container.js +++ /dev/null @@ -1,40 +0,0 @@ -import React, { Component } from 'react' -// import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import actions from '../../../actions' -// import * as alignActions from '../align.actions' - -import { ZOOM_STEPS } from '../constants' -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) - } - render() { - return ( - <div className='annotations'> - <AnnotationIndex /> - {this.props.annotation.start_ts && - <AnnotationForm /> - } - </div> - ) - } -} - -const mapStateToProps = state => ({ - timeline: state.align.timeline, - annotation: state.align.annotation, -}) - -const mapDispatchToProps = dispatch => ({ -}) - -export default connect(mapStateToProps, mapDispatchToProps)(Annotations) diff --git a/animism-align/frontend/views/align/containers/script.container.js b/animism-align/frontend/views/align/containers/script.container.js deleted file mode 100644 index ce3dee8..0000000 --- a/animism-align/frontend/views/align/containers/script.container.js +++ /dev/null @@ -1,34 +0,0 @@ -import React, { Component } from 'react' -// import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import actions from '../../../actions' -// import * as alignActions from '../align.actions' - -class Timeline extends Component { - constructor(props){ - super(props) - } - render() { - if (this.props.text.loading) return <div /> - return ( - <textarea - className='script' - onChange={e => actions.site.updateText(e.target.value)} - value={this.props.text} - /> - ) - } -} - - -const mapStateToProps = state => ({ - text: state.site.text, -}) - -const mapDispatchToProps = dispatch => ({ - // alignActions: bindActionCreators({ ...alignActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(Timeline) diff --git a/animism-align/frontend/views/align/containers/timeline.container.js b/animism-align/frontend/views/align/containers/timeline.container.js deleted file mode 100644 index a924eaf..0000000 --- a/animism-align/frontend/views/align/containers/timeline.container.js +++ /dev/null @@ -1,171 +0,0 @@ -import React, { Component } from 'react' -// import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import actions from '../../../actions' -// import * as alignActions from '../align.actions' - -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/player/playButton.component' -import PlayCursor from '../components/timeline/playCursor.component' - -import { WAVEFORM_SIZE, ZOOM_STEPS, INNER_HEIGHT } from '../constants' -import { clamp } from '../../../util' -import { positionToTime } from '../align.util' - -class Timeline extends Component { - constructor(props){ - super(props) - this.handleKeydown = this.handleKeydown.bind(this) - this.handleMouseMove = this.handleMouseMove.bind(this) - this.handleWheel = this.handleWheel.bind(this) - this.handleContainerClick = this.handleContainerClick.bind(this) - this.handleTimelineClick = this.handleTimelineClick.bind(this) - } - componentDidMount() { - this.bind() - } - componentWillUnmount() { - this.unbind() - } - shouldComponentUpdate(nextProps) { - return ( - nextProps.timeline !== this.props.timeline || - nextProps.annotation !== this.props.annotation - ) - } - bind() { - document.addEventListener('keydown', this.handleKeydown) - } - unbind() { - document.removeEventListener('keydown', this.handleKeydown) - } - handleKeydown(e) { - if (document.activeElement !== document.body) { - return - } - // console.log(e.keyCode) - if (e.metaKey && this.props.selectedAnnotation.id) { - const { selectedAnnotation } = this.props - switch (e.keyCode) { - case 38: // up - e.preventDefault() - selectedAnnotation.start_ts = Math.max(selectedAnnotation.start_ts - (e.shiftKey ? 1 : 0.1), 0) - actions.align.updateSelectedAnnotation(selectedAnnotation) - actions.audio.seek(selectedAnnotation.start_ts) - actions.align.setCursor(selectedAnnotation.start_ts) - break - case 40: // down - e.preventDefault() - selectedAnnotation.start_ts += e.shiftKey ? 1 : 0.1 - actions.align.updateSelectedAnnotation(selectedAnnotation) - actions.audio.seek(selectedAnnotation.start_ts) - actions.align.setCursor(selectedAnnotation.start_ts) - break - } - return - } - if (e.shiftKey) { - switch (e.keyCode) { - case 187: // plus - actions.align.setZoom(this.props.timeline.zoom - 1) - break - case 189: // minus - actions.align.setZoom(this.props.timeline.zoom + 1) - break - } - } else { - // console.log(e.keyCode) - switch (e.keyCode) { - case 27: // escape - actions.align.hideAnnotationForm() - break - case 65: // A - add - e.preventDefault() - actions.align.showNewAnnotationForm(this.props.audio.play_ts, this.props.text) - break - case 32: // spacebar - actions.audio.toggle() - break - case 38: // up - actions.audio.jump(- ZOOM_STEPS[this.props.timeline.zoom] * 0.1) - break - case 40: // down - actions.audio.jump(ZOOM_STEPS[this.props.timeline.zoom] * 0.1) - break - } - } - } - handleWheel(e) { - let { start_ts, zoom, duration } = this.props.timeline - let { deltaY } = e - let secondsPerPixel = ZOOM_STEPS[zoom] * 0.1 // 0.1 sec / step - let widthTimeDuration = INNER_HEIGHT * secondsPerPixel // secs per pixel - start_ts += Math.round((deltaY) * ZOOM_STEPS[zoom]) - start_ts = clamp(start_ts, 0, Math.max(0, duration - widthTimeDuration / 2)) - if (e.shiftKey) { - if (Math.abs(deltaY) < 2) return - if (e.deltaY > 0) { - actions.align.throttledSetZoom(this.props.timeline.zoom + 1) - } else { - actions.align.throttledSetZoom(this.props.timeline.zoom - 1) - } - } else if (e.altKey) { - actions.audio.jump(e.deltaY * ZOOM_STEPS[zoom]) - } else { - actions.align.setScrollPosition(start_ts) - } - } - handleMouseMove(e) { - const cursor_ts = positionToTime(e.pageY, this.props.timeline) - actions.align.setCursor(cursor_ts) - } - handleContainerClick(e) { - actions.align.clearSelectedAnnotation() - actions.align.clearSelectedParagraph() - } - handleTimelineClick(e) { - const play_ts = positionToTime(e.pageY, this.props.timeline) - if (e.pageX < WAVEFORM_SIZE * 0.67) { - actions.audio.seek(play_ts) - } else { - actions.align.showNewAnnotationForm(play_ts, this.props.text) - } - } - render() { - return ( - <div - className='timeline' - onClick={this.handleContainerClick} - onWheel={this.handleWheel} - onMouseMove={this.handleMouseMove} - > - <div className='timelineColumn'> - <Waveform onClick={this.handleTimelineClick} /> - <Ticks timeline={this.props.timeline} /> - <Cursor timeline={this.props.timeline} annotation={this.props.annotation} /> - </div> - <Annotations timeline={this.props.timeline} /> - <PlayCursor /> - </div> - ) - } -} - -const mapStateToProps = state => ({ - timeline: state.align.timeline, - annotation: state.align.annotation, - selectedAnnotation: state.align.selectedAnnotation, - audio: state.audio, - text: state.site.text, -}) - -const mapDispatchToProps = dispatch => ({ - // alignActions: bindActionCreators({ ...alignActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(Timeline) diff --git a/animism-align/frontend/views/annotation/annotation.reducer.js b/animism-align/frontend/views/annotation/annotation.reducer.js deleted file mode 100644 index 98b785e..0000000 --- a/animism-align/frontend/views/annotation/annotation.reducer.js +++ /dev/null @@ -1,21 +0,0 @@ -import * as types from '../../types' -import { session, getDefault, getDefaultInt } from '../../session' - -import { crudState, crudReducer } from '../../api/crud.reducer' - -const initialState = crudState('annotation', { - options: { - sort: 'start_ts asc', - } -}) - -const reducer = crudReducer('annotation') - -export default function annotationReducer(state = initialState, action) { - // console.log(action.type, action) - state = reducer(state, action) - switch (action.type) { - default: - return state - } -} diff --git a/animism-align/frontend/views/audio/audio.actions.js b/animism-align/frontend/views/audio/audio.actions.js deleted file mode 100644 index 64c8215..0000000 --- a/animism-align/frontend/views/audio/audio.actions.js +++ /dev/null @@ -1,40 +0,0 @@ -import * as types from '../../types' -import { store, history, dispatch } from '../../store' -import actions from '../../actions' -import { session } from '../../session' - -const audioPlayer = document.createElement('audio') -audioPlayer.src = '/static/data_store/peaks/animismA080720.mp3' -audioPlayer.addEventListener('loadedmetadata', () => { - console.log('audio duration:', audioPlayer.duration) - dispatch({ type: types.align.set_display_setting, key: 'duration', value: audioPlayer.duration }) -}) -audioPlayer.addEventListener('play', () => { - dispatch({ type: types.audio.play }) -}) -audioPlayer.addEventListener('pause', () => { - dispatch({ type: types.audio.pause }) -}) -audioPlayer.addEventListener('timeupdate', () => { - dispatch({ type: types.audio.update_time, play_ts: audioPlayer.currentTime }) -}) - -export const play = () => dispatch => { - audioPlayer.play() -} -export const pause = () => dispatch => { - audioPlayer.pause() -} -export const seek = play_ts => dispatch => { - audioPlayer.currentTime = play_ts -} -export const jump = delta_ts => dispatch => { - audioPlayer.currentTime += delta_ts -} -export const toggle = () => dispatch => { - if (store.getState().audio.playing) { - pause()(dispatch) - } else { - play()(dispatch) - } -} diff --git a/animism-align/frontend/views/audio/audio.reducer.js b/animism-align/frontend/views/audio/audio.reducer.js deleted file mode 100644 index e37933d..0000000 --- a/animism-align/frontend/views/audio/audio.reducer.js +++ /dev/null @@ -1,30 +0,0 @@ -import * as types from '../../types' -import { session, getDefault, getDefaultInt } from '../../session' - -const initialState = { - playing: false, - play_ts: 0, -} - -export default function alignReducer(state = initialState, action) { - // console.log(action.type, action) - switch (action.type) { - case types.audio.play: - return { - ...state, - playing: true, - } - case types.audio.pause: - return { - ...state, - playing: false, - } - case types.audio.update_time: - return { - ...state, - play_ts: action.play_ts, - } - default: - return state - } -} diff --git a/animism-align/frontend/views/index.js b/animism-align/frontend/views/index.js deleted file mode 100644 index 2c9ee78..0000000 --- a/animism-align/frontend/views/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export { default as align } from './align/align.container' -export { default as paragraph } from './paragraph/paragraph.container' -export { default as upload } from './upload/upload.container' -export { default as media } from './media/media.container' diff --git a/animism-align/frontend/views/media/components/media.form.js b/animism-align/frontend/views/media/components/media.form.js deleted file mode 100644 index 1463041..0000000 --- a/animism-align/frontend/views/media/components/media.form.js +++ /dev/null @@ -1,272 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' - -import { session } from '../../../session' -import { capitalize } from '../../../util' - -import { TextInput, LabelDescription, Select, TextArea, Checkbox, SubmitButton, Loader } from '../../../common' - -import MediaImageForm from './media.formImage' -import MediaVideoForm from './media.formVideo' - -const newMedia = () => ({ - type: 'image', - tag: 'media', - url: '', - title: '', - author: '', - pre_title: '', - post_title: '', - translated_title: '', - date: '', - source: '', - medium: '', - start_ts: 0, - settings: {}, -}) - -const MEDIA_TYPES = [ - 'image', 'video' -].map(name => ({ name, label: capitalize(name) })) - -export default class MediaForm extends Component { - state = { - title: "", - submitTitle: "", - data: { ...newMedia() }, - errorFields: new Set([]), - } - - constructor(props) { - super(props) - this.handleKeyDown = this.handleKeyDown.bind(this) - this.handleSelect = this.handleSelect.bind(this) - this.handleChange = this.handleChange.bind(this) - this.handleSettingsChange = this.handleSettingsChange.bind(this) - this.handleSettingsChangeEvent = this.handleSettingsChangeEvent.bind(this) - this.handleSubmit = this.handleSubmit.bind(this) - } - - componentDidMount() { - const { data, isNew } = this.props - const title = isNew ? 'New media' : 'Editing ' + data.title - const submitTitle = isNew ? "Add Media" : "Save Changes" - this.setState({ - title, - submitTitle, - errorFields: new Set([]), - data: { - ...newMedia(), - ...data - }, - }) - window.addEventListener('keydown', this.handleKeyDown) - } - - componentWillUnmount() { - window.removeEventListener('keydown', this.handleKeyDown) - } - - handleKeyDown(e) { - // console.log(e, e.keyCode) - if ((e.ctrlKey || e.metaKey) && e.keyCode === 83) { - if (e) { - e.preventDefault() - } - this.handleSubmit() - } - } - - handleChange(e) { - const { name, value } = e.target - 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, - } - }) - } - - handleSettingsChangeEvent(e) { - const { name, value } = e.target - this.handleSettingsChange(name, value) - } - - handleSettingsChange(name, value) { - console.log(name, value) - if (name !== 'multiple') { - value = { [name]: value } - } - this.setState({ - data: { - ...this.state.data, - settings: { - ...this.state.data.settings, - ...value, - } - } - }) - } - - handleSubmit(e) { - if (e) { - e.preventDefault() - } - const { isNew, onSubmit } = this.props - const { data } = this.state - const requiredKeys = "author title date".split(" ") - const validKeys = "type tag url title author pre_title post_title translated_title date source medium start_ts settings".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 media - // session.set('username', data.username) - } else { - validData.id = data.id - } - console.log('submit', validData) - onSubmit(validData) - } - } - - render() { - const { isNew } = this.props - const { title, submitTitle, errorFields, data } = this.state - // console.log(data) - return ( - <div className='form'> - <h1>{title}</h1> - <form onSubmit={this.handleSubmit}> - <Select - title='Media Type' - name='type' - selected={data.type} - options={MEDIA_TYPES} - onChange={this.handleSelect} - /> - - {data.type === 'image' && - <MediaImageForm - data={data} - onChange={this.handleSelect} - onSettingsChange={this.handleSettingsChange} - /> - } - - {data.type === 'video' && - <MediaVideoForm - data={data} - onChange={this.handleSelect} - onSettingsChange={this.handleSettingsChange} - /> - } - - <TextInput - title="Author" - name="author" - required - data={data} - onChange={this.handleChange} - autoComplete="off" - /> - <TextInput - title="Title" - name="title" - required - data={data} - onChange={this.handleChange} - autoComplete="off" - /> - <TextInput - title="Title Prefix" - name="pre_title" - data={data} - onChange={this.handleChange} - autoComplete="off" - /> - <TextInput - title="Title Suffix" - name="post_title" - data={data} - onChange={this.handleChange} - autoComplete="off" - /> - <TextInput - title="Translated Title" - name="translated_title" - data={data} - onChange={this.handleChange} - autoComplete="off" - /> - <TextInput - title="Date" - name="date" - required - data={data} - onChange={this.handleChange} - autoComplete="off" - /> - <TextInput - title="Medium" - name="medium" - required - data={data} - onChange={this.handleChange} - /> - <TextInput - title="Source" - name="source" - placeholder="Courtesy of / Copyright" - required - data={data} - onChange={this.handleChange} - autoComplete="off" - /> - <TextArea - title="Citation" - name="bibliography" - placeholder="Use if special HTML formatting needed" - data={data.settings} - onChange={this.handleSettingsChangeEvent} - /> - <Checkbox - label="Hide in list of works" - name="hide_in_bibliography" - checked={data.settings.hide_in_bibliography} - onChange={this.handleSettingsChange} - autoComplete="off" - /> - <TextArea - title="Description" - name="description" - data={data} - onChange={this.handleChange} - /> - <SubmitButton - title={submitTitle} - onClick={this.handleSubmit} - /> - {!!errorFields.size && - <label> - <span></span> - <span>Please complete the required fields</span> - </label> - } - </form> - </div> - ) - } -} diff --git a/animism-align/frontend/views/media/components/media.formImage.js b/animism-align/frontend/views/media/components/media.formImage.js deleted file mode 100644 index 4a97112..0000000 --- a/animism-align/frontend/views/media/components/media.formImage.js +++ /dev/null @@ -1,172 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' - -import { session } from '../../../session' -import actions from '../../../actions' -import { capitalize, preloadImage, cropImage } from '../../../util' - -import { TextInput, LabelDescription, UploadImage, Select, TextArea, Checkbox, SubmitButton, Loader } from '../../../common' -import { renderThumbnail } from '../../../common/upload.helpers' - -import ImageSelection from './media.formImageSelection' - -const DISPLAY_SIZE = 1024 -const DISPLAY_QUALITY= 80 -const THUMBNAIL_SIZE = 320 -const THUMBNAIL_QUALITY = 80 - -export default class MediaImageForm extends Component { - state = { - img: null, - } - - constructor(props) { - super(props) - this.handleSelect = this.handleSelect.bind(this) - this.handleChange = this.handleChange.bind(this) - this.handleSettingsChange = this.handleSettingsChange.bind(this) - this.handleUpload = this.handleUpload.bind(this) - this.handleCrop = this.handleCrop.bind(this) - this.replaceTaggedSize = this.replaceTaggedSize.bind(this) - this.uploadTaggedSize = this.uploadTaggedSize.bind(this) - } - - componentDidMount() { - // this.setState({ }) - if (this.props.data.settings.fullsize) { - preloadImage(this.props.data.settings.fullsize.url) - .then(img => this.setState({ img })) - } - } - - handleChange(e) { - const { name, value } = e.target - this.handleSelect(name, value) - } - - handleSelect(name, value) { - this.props.onSelect(name, value) - } - - handleSettingsChange(name, value) { - this.props.onSettingsChange(name, value) - } - - handleUpload({ file, img, canvas, blob }) { - // sizes: fullsize, display, thumbnail - this.replaceTaggedSize(file, 'fullsize') - .then(data => { - this.setState({ img }) - this.props.onSettingsChange('multiple', { - fullsize: data, - crop: {}, - }) - return this.replaceTaggedSize(blob, 'display', file.name) - }).then(data => { - this.props.onSettingsChange('multiple', { - display: data, - }) - this.uploadThumbnail(img) - }) - } - - uploadThumbnail(img) { - const { fn } = this.props.data.settings.fullsize - const thumbnailCanvas = renderThumbnail(img, { maxSide: THUMBNAIL_SIZE }) - thumbnailCanvas.toBlob(thumbnail => { - this.replaceTaggedSize(thumbnail, 'thumbnail', fn).then(data => { - this.props.onSettingsChange('multiple', { - thumbnail: data, - }) - }) - }, 'image/jpeg', THUMBNAIL_QUALITY) - } - - replaceTaggedSize(image, tag, fn) { - // when we upload an image, if the image already exists in this "position" - // on the record, we should also delete it - if (this.props.data.settings[tag] && this.props.data.settings[tag].id) { - return new Promise((resolve, reject) => { - actions.upload.destroy(this.props.data.settings[tag]) - .then(() => { - console.log('destroy successful') - this.uploadTaggedSize(image, tag, fn).then(data => resolve(data)) - }) - .catch(() => { - console.log('error deleting the image') - this.uploadTaggedSize(image, tag, fn).then(data => resolve(data)) - }) - }) - } - return this.uploadTaggedSize(image, tag, fn) - } - - uploadTaggedSize(image, tag, fn) { - console.log('uploading size', tag) - const uploadData = { - image, - tag, - username: 'animism', - } - if (fn) { - uploadData['__image_filename'] = fn - } - // console.log(uploadData) - return actions.upload.upload(uploadData).then(data => { - // console.log(data) - return data.res - }) - } - - handleCrop(crop) { - // when cropping an image, re-upload the display image and thumbnail - // console.log(crop) - cropImage(this.state.img, crop, DISPLAY_SIZE) - .then(canvas => { - canvas.toBlob(blob => { - // console.log(canvas, canvas.width, canvas.height, blob) - this.replaceTaggedSize(blob, 'display', this.props.data.settings.fullsize.fn) - .then(data => { - this.props.onSettingsChange('multiple', { - crop, - display: data, - }) - this.uploadThumbnail(canvas) - }) - }, 'image/jpeg', DISPLAY_QUALITY) - }) - } - - render() { - const { data } = this.props - // console.log(data) - return ( - <div className='imageForm'> - {!data.url && - <label className={'text fileInput'}> - <span>{"Upload image"}</span> - <div className="row"> - <button> - {"Choose image"} - </button> - <UploadImage - onUpload={this.handleUpload} - maxSide={DISPLAY_SIZE} - quality={DISPLAY_QUALITY} - /> - </div> - </label> - } - {data.settings.fullsize && - <div> - <ImageSelection - url={data.settings.fullsize.url} - crop={data.settings.crop} - onCrop={this.handleCrop} - /> - </div> - } - </div> - ) - } -} diff --git a/animism-align/frontend/views/media/components/media.formImageSelection.js b/animism-align/frontend/views/media/components/media.formImageSelection.js deleted file mode 100644 index 5572793..0000000 --- a/animism-align/frontend/views/media/components/media.formImageSelection.js +++ /dev/null @@ -1,213 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import toBlob from 'data-uri-to-blob' - -import { clamp } from '../../../util' -import { Loader } from '../../../common' - -const defaultState = { - dragging: false, - draggingBox: false, - bounds: null, - mouseX: 0, - mouseY: 0, - box: { - x: 0, - y: 0, - w: 0, - h: 0, - } -} - -export default class ImageSelection extends Component { - state = { - ...defaultState - } - - constructor() { - super() - // bind these events in the constructor, so we can remove event listeners later - this.handleMouseDown = this.handleMouseDown.bind(this) - this.handleMouseDownOnBox = this.handleMouseDownOnBox.bind(this) - this.handleMouseMove = this.handleMouseMove.bind(this) - this.handleMouseUp = this.handleMouseUp.bind(this) - this.handleWindowResize = this.handleWindowResize.bind(this) - } - - componentDidMount() { - document.body.addEventListener('mousemove', this.handleMouseMove) - document.body.addEventListener('mouseup', this.handleMouseUp) - window.addEventListener('resize', this.handleWindowResize) - } - - componentDidUpdate(prevProps) { - if (this.state.bounds && this.props.url !== prevProps.url) { - this.setState({ - ...defaultState, - bounds: this.getBoundingClientRect(), - box: this.props.crop || defaultState.box, - }) - } - } - - componentWillUnmount() { - document.body.removeEventListener('mousemove', this.handleMouseMove) - document.body.removeEventListener('mouseup', this.handleMouseUp) - window.removeEventListener('resize', this.handleWindowResize) - } - - getBoundingClientRect() { - if (!this.imgRef) return null - const rect = this.imgRef.getBoundingClientRect() - const scrollTop = document.body.scrollTop || document.body.parentNode.scrollTop - const scrollLeft = document.body.scrollLeft || document.body.parentNode.scrollLeft - const bounds = { - top: rect.top + scrollTop, - left: rect.left + scrollLeft, - width: rect.width, - height: rect.height, - } - return bounds - } - - handleLoad() { - const bounds = this.getBoundingClientRect() - const box = this.props.crop || defaultState.box - this.setState({ bounds, box }) - } - - handleWindowResize() { - if (!this.imgRef) return - const bounds = this.getBoundingClientRect() - this.setState({ bounds }) - } - - handleMouseDown(e) { - e.preventDefault() - const bounds = this.getBoundingClientRect() - const mouseX = e.pageX - const mouseY = e.pageY - const x = (mouseX - bounds.left) / bounds.width - const y = (mouseY - bounds.top) / bounds.height - const w = 1 / bounds.width - const h = 1 / bounds.height - this.setState({ - dragging: true, - bounds, - mouseX, - mouseY, - box: { - x, y, w, h, - } - }) - } - - handleMouseDownOnBox(e) { - const bounds = this.getBoundingClientRect() - const mouseX = e.pageX - const mouseY = e.pageY - this.setState({ - draggingBox: true, - bounds, - mouseX, - mouseY, - initialBox: { - ...this.state.box - }, - box: { - ...this.state.box - } - }) - } - - handleMouseMove(e) { - const { - dragging, draggingBox, - bounds, mouseX, mouseY, initialBox, box - } = this.state - if (dragging) { - e.preventDefault() - let { x, y } = box - let dx = (e.pageX - mouseX) / bounds.width - let dy = (e.pageY - mouseY) / bounds.height - let w = clamp(dx, 0.0, 1.0 - x) - let h = clamp(dy, 0.0, 1.0 - y) - this.setState({ - box: { - x, y, w, h, - } - }) - } else if (draggingBox) { - e.preventDefault() - let { x, y, w, h } = initialBox - let dx = (e.pageX - mouseX) / bounds.width - let dy = (e.pageY - mouseY) / bounds.height - this.setState({ - box: { - x: clamp(x + dx, 0, 1.0 - w), - y: clamp(y + dy, 0, 1.0 - h), - w, - h, - } - }) - } - } - - handleMouseUp(e) { - const { onCrop } = this.props - const { dragging, draggingBox, bounds, box } = this.state - if (!dragging && !draggingBox) return - e.preventDefault() - const { x, y, w, h } = box - let url = window.location.pathname - this.setState({ - dragging: false, - draggingBox: false, - }) - if (w < 10 / bounds.width || h < 10 / bounds.height) { - this.setState({ box: { ...defaultState.box }}) - onCrop({}) - } else { - // pass the box dimensions up - do the search again - onCrop(box) - } - } - - render() { - const { url } = this.props - const { bounds, box } = this.state - const { x, y, w, h } = box - return ( - <div className="imageSelection"> - <img - src={url} - ref={ref => this.imgRef = ref} - onMouseDown={this.handleMouseDown} - onLoad={this.handleLoad.bind(this)} - crossOrigin='anonymous' - /> - {!!w && - <div - className="box" - style={{ - left: x * bounds.width, - top: y * bounds.height, - width: w * bounds.width, - height: h * bounds.height, - }} - onMouseDown={this.handleMouseDownOnBox} - /> - } - </div> - ) - } -} - -const boxToFixed = ({ x, y, w, h }) => ({ - x: x.toFixed(3), - y: y.toFixed(3), - w: w.toFixed(3), - h: h.toFixed(3), -}) diff --git a/animism-align/frontend/views/media/components/media.formVideo.js b/animism-align/frontend/views/media/components/media.formVideo.js deleted file mode 100644 index 89954b9..0000000 --- a/animism-align/frontend/views/media/components/media.formVideo.js +++ /dev/null @@ -1,111 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import VimeoPlayer from '@u-wave/react-vimeo' - -import { capitalize } from '../../../util' -import { TextInput, LabelDescription, Select, TextArea, Checkbox, SubmitButton, Loader } from '../../../common' - -import { getVimeoMetadata } from '../media.actions' - -export default class MediaVideoForm extends Component { - state = { - } - - constructor(props) { - super(props) - this.handleSelect = this.handleSelect.bind(this) - this.handleChange = this.handleChange.bind(this) - this.handleSettingsChange = this.handleSettingsChange.bind(this) - } - - handleChange(e) { - let { name, value } = e.target - return this.handleSelect(name, value) - } - - handleSelect(name, value) { - value = value.trim() - if (name === 'url') { - getVimeoMetadata(value) - .then(data => { - console.log('video metadata', data) - this.props.onChange(name, value) - setTimeout(() => { - this.props.onSettingsChange('video', { - thumbnail_url: data.thumbnail_url, - duration: data.duration, - video_id: data.video_id, - }) - }, 20) - }) - } else { - this.props.onChange(name, value) - } - } - - handleSettingsChange(e) { - let { name, value } = e.target - this.props.onSettingsChange(name, value) - } - - handleSettingsSelect(name, value) { - this.props.onSettingsChange(name, value) - } - - render() { - const { data } = this.props - return ( - <div className='videoForm'> - <TextInput - title="Video URL" - name="url" - required - data={data} - onChange={this.handleChange} - autoComplete="off" - /> - - {data.url && - <div> - <LabelDescription className='video'> - <VimeoPlayer video={data.url} /> - </LabelDescription> - - {data.settings.video && data.settings.video.thumbnail && - <LabelDescription className='thumbnail'> - <img src={data.settings.video.thumbnail} /> - </LabelDescription> - } - - <TextInput - title="Start time" - name="video_start_time" - data={data.settings} - placeholder="0:00" - onChange={this.handleSettingsChange} - autoComplete="off" - /> - - <TextInput - title="End time" - name="video_end_time" - data={data.settings} - placeholder="0:00" - onChange={this.handleSettingsChange} - autoComplete="off" - /> - - <TextInput - title="Original duration" - name="original_duration" - data={data.settings} - placeholder="0:00" - onChange={this.handleSettingsChange} - autoComplete="off" - /> - </div> - } - </div> - ) - } -} diff --git a/animism-align/frontend/views/media/components/media.indexOptions.js b/animism-align/frontend/views/media/components/media.indexOptions.js deleted file mode 100644 index 5dbc415..0000000 --- a/animism-align/frontend/views/media/components/media.indexOptions.js +++ /dev/null @@ -1,65 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import actions from '../../../actions' - -import { Select, Checkbox } from '../../../common' - -const thumbnailOptions = [ - { name: 'th', label: 'Thumbnails', }, - { name: 'sm', label: 'Small', }, - { name: 'md', label: 'Medium', }, - { name: 'lg', label: 'Large', }, - { name: 'orig', label: 'Original', }, -] - -const sortOptions = [ - { name: 'id-asc', label: 'Most recent' }, - { name: 'id-desc', label: 'Oldest first' }, - { name: 'username-asc', label: 'Username (A-Z)' }, - { name: 'username-desc', label: 'Username (Z-A)' }, - { name: 'author-asc', label: 'Author (A-Z)' }, - { name: 'author-desc', label: 'Author (Z-A)' }, - { name: 'title-asc', label: 'Title (A-Z)' }, - { name: 'title-desc', label: 'Title (Z-A)' }, - // { name: '-asc', label: '' }, - // { name: '-desc', label: '' }, - // { name: '-asc', label: '' }, - // { name: '-desc', label: '' }, - // { name: '-asc', label: '' }, - // { name: '-desc', label: '' }, -] - -class IndexOptions extends Component { - render() { - const { options } = this.props - return ( - <div className='row menubar'> - <div /> - <Select - name={'sort'} - options={sortOptions} - selected={options.sort} - onChange={actions.upload.updateOption} - /> - <Select - name={'thumbnailSize'} - options={thumbnailOptions} - selected={options.thumbnailSize} - onChange={actions.upload.updateOption} - /> - </div> - ) - } -} - -const mapStateToProps = state => ({ - options: state.upload.options, -}) - -const mapDispatchToProps = dispatch => ({ -}) - -export default connect(mapStateToProps, mapDispatchToProps)(IndexOptions) diff --git a/animism-align/frontend/views/media/components/media.menu.js b/animism-align/frontend/views/media/components/media.menu.js deleted file mode 100644 index 153a5c1..0000000 --- a/animism-align/frontend/views/media/components/media.menu.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, { Component } from 'react' -import { Route, Link } from 'react-router-dom' -import { connect } from 'react-redux' - -import { history } from '../../../store' -import actions from '../../../actions' -import { MenuButton, FileInput } from '../../../common' - -const mapStateToProps = state => ({ - media: state.media, -}) - -export default class MediaMenu extends Component { - render() { - return ( - <div className='menuButtons'> - <Route exact path='/media/:id/show/' component={MediaShowMenu} /> - <Route exact path='/media/:id/edit/' component={MediaEditMenu} /> - <Route exact path='/media/new/' component={MediaNewMenu} /> - <Route exact path='/media/' component={MediaIndexMenu} /> - </div> - ) - } -} - -const MediaIndexMenu = () => ([ - <MenuButton key='new' name="new" href="/media/new/" />, -]) - -const MediaShowMenu = connect(mapStateToProps)((props) => ([ - <MenuButton key='back' name="back" href="/media/" />, - <MenuButton key='edit' name="edit" href={"/media/" + props.match.params.id + "/edit/"} />, - <MenuButton key='delete' name="delete" onClick={() => { - const { res: media } = props.media.show - if (confirm("Really delete this media?")) { - actions.media.destroy(media).then(() => { - history.push('/media/') - }) - } - }} />, -])) - -const MediaNewMenu = (props) => ([ - <MenuButton key='back' name="back" href="/media/" />, -]) - -const MediaEditMenu = connect(mapStateToProps)((props) => ([ - <MenuButton key='back' name="back" href="/media/" />, - <MenuButton key='copy' name="copy" href={"/media/" + props.match.params.id + '/copy/'} label="Make a copy" />, - <MenuButton key='delete' name="delete" onClick={() => { - const { res: media } = props.media.show - if (confirm("Really delete this media?")) { - actions.media.destroy(media).then(() => { - history.push('/media/') - }) - } - }} />, -])) diff --git a/animism-align/frontend/views/media/containers/media.edit.js b/animism-align/frontend/views/media/containers/media.edit.js deleted file mode 100644 index 143cdfe..0000000 --- a/animism-align/frontend/views/media/containers/media.edit.js +++ /dev/null @@ -1,57 +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 MediaForm from '../components/media.form' -import MediaMenu from '../components/media.menu' - -class MediaEdit extends Component { - componentDidMount() { - console.log(this.props.match.params.id) - actions.media.show(this.props.match.params.id) - } - - handleSubmit(data) { - actions.media.update(data) - .then(response => { - // response - console.log(response) - history.push('/media/') - }) - } - - render() { - const { show } = this.props.media - if (show.loading || !show.res) { - return ( - <div className='form'> - <Loader /> - </div> - ) - } - return ( - <div className='row formContainer'> - <MediaMenu mediaActions={this.props.mediaActions} /> - <MediaForm - data={show.res} - onSubmit={this.handleSubmit.bind(this)} - /> - </div> - ) - } -} - -const mapStateToProps = state => ({ - media: state.media, -}) - -const mapDispatchToProps = dispatch => ({ - // mediaActions: bindActionCreators({ ...mediaActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(MediaEdit) diff --git a/animism-align/frontend/views/media/containers/media.index.js b/animism-align/frontend/views/media/containers/media.index.js deleted file mode 100644 index a865522..0000000 --- a/animism-align/frontend/views/media/containers/media.index.js +++ /dev/null @@ -1,115 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import { formatDateTime } from '../../../util' -import { MenuButton, SmallMenuButton, Loader } from '../../../common' -import actions from '../../../actions' - -import { thumbnailURL } from '../../align/align.util' - -import MediaIndexOptions from '../components/media.indexOptions' -import MediaMenu from '../components/media.menu' - -// const { result, collectionLookup } = this.props - -class MediaIndex extends Component { - componentDidMount() { - // this.fetch(false) - } - - componentDidUpdate(prevProps) { - if (this.props.media.options.sort !== prevProps.media.options.sort) { - this.fetch(false) - } - } - - fetch(load_more) { - const { options, index } = this.props.media - const { order: index_order } = index - const [ sort, order ] = options.sort.split(' ') - actions.media.index({ - sort, order, limit: 5000, // offset: load_more ? index_order.length : 0, - }, load_more) - } - - render() { - const { mediaActions } = this.props - const { options } = this.props.media - const { loading, lookup, order } = this.props.media.index - if (loading) { - return ( - <section> - <MediaIndexOptions /> - <div className="row"> - {order && !!order.length && - <div className={'results ' + options.thumbnailSize}> - {order.map(id => <MediaItem key={id} data={lookup[id]} />)} - </div> - } - </div> - <Loader /> - </section> - ) - } - if (!lookup || !order.length) { - return ( - <section> - <MediaIndexOptions /> - <div className="row"> - <MediaMenu /> - <p className='gray'> - {"No media"} - </p> - </div> - </section> - ) - } - return ( - <section> - <MediaIndexOptions /> - <div className="row"> - <MediaMenu /> - <div className={'results ' + options.thumbnailSize}> - <h2>Images</h2> - {order.filter(id => lookup[id].type === 'image').map(id => <MediaItem key={id} data={lookup[id]} />)} - <h2>Video</h2> - {order.filter(id => lookup[id].type === 'video').map(id => <MediaItem key={id} data={lookup[id]} />)} - </div> - </div> - {order.length >= 50 && <button className='loadMore' onClick={() => this.fetch(true)}>Load More</button>} - </section> - ) - } -} - -const MediaItem = ({ data }) => { - // console.log(data) - return ( - <div className='cell'> - <div className='img'> - <Link to={"/media/" + data.id + "/edit/"}> - <img src={thumbnailURL(data)} alt={data.title} /> - </Link> - </div> - <div className='meta center'> - <div> - <i>{data.title}</i><br /> - {data.author}<br /> - {data.date} - </div> - </div> - </div> - ) -} - -const mapStateToProps = state => ({ - media: state.media, -}) - -const mapDispatchToProps = dispatch => ({ - // uploadActions: bindActionCreators({ ...uploadActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(MediaIndex) diff --git a/animism-align/frontend/views/media/containers/media.new.js b/animism-align/frontend/views/media/containers/media.new.js deleted file mode 100644 index 80879cb..0000000 --- a/animism-align/frontend/views/media/containers/media.new.js +++ /dev/null @@ -1,81 +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 MediaForm from '../components/media.form' -import MediaMenu from '../components/media.menu' - -class MediaNew extends Component { - state = { - loading: true, - initialData: {}, - } - - componentDidMount() { - // console.log(this.props.match.params.id) - if (this.props.match.params && this.props.match.params.id) { - actions.media.show(this.props.match.params.id) - .then(data => { - const { id, ...initialData } = data.res - delete initialData.settings.video - delete initialData.settings.crop - delete initialData.settings.display - delete initialData.settings.fullsize - delete initialData.settings.thumbnail - delete initialData.settings.bibliography - console.log("copying", id) - this.setState({ - loading: false, - initialData, - }) - }) - } else { - this.setState({ loading: false }) - } - } - - handleSubmit(data) { - console.log(data) - actions.media.create(data) - .then(res => { - console.log(res) - if (res.res && res.res.id) { - history.push('/media/') - } - }) - .catch(err => { - console.error('error') - }) - } - - render() { - if (this.state.loading) { - return ( - <div className='row formContainer' /> - ) - } - return ( - <div className='row formContainer'> - <MediaMenu /> - <MediaForm - isNew - data={this.state.initialData} - onSubmit={this.handleSubmit.bind(this)} - /> - </div> - ) - } -} - -const mapStateToProps = state => ({ - media: state.media, -}) - -const mapDispatchToProps = dispatch => ({ - // uploadActions: bindActionCreators({ ...uploadActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(MediaNew) diff --git a/animism-align/frontend/views/media/media.actions.js b/animism-align/frontend/views/media/media.actions.js deleted file mode 100644 index 1f1ab01..0000000 --- a/animism-align/frontend/views/media/media.actions.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as types from '../../types' -import { capitalize, api } from '../../util' - -export const getVimeoMetadata = url => { - return api(() => {}, types.vimeo, 'vimeo', 'https://vimeo.com/api/oembed.json', { url }) - .then(data => { - return data - }) -} diff --git a/animism-align/frontend/views/media/media.container.js b/animism-align/frontend/views/media/media.container.js deleted file mode 100644 index 97a5b08..0000000 --- a/animism-align/frontend/views/media/media.container.js +++ /dev/null @@ -1,38 +0,0 @@ -import React, { Component } from 'react' -import { Route, Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import './media.css' - -import actions from '../../actions' - -import MediaIndex from './containers/media.index' -// import MediaShow from './containers/media.show' -import MediaNew from './containers/media.new' -import MediaEdit from './containers/media.edit' - -class Container extends Component { - render() { - return ( - <div className='media'> - <Route exact path='/media/:id/copy/' component={MediaNew} /> - <Route exact path='/media/:id/edit/' component={MediaEdit} /> - <Route exact path='/media/new/' component={MediaNew} /> - <Route exact path='/media/' component={MediaIndex} /> - </div> - ) - } -} -/* - <Route exact path='/media/:id/show/' component={MediaShow} /> -*/ -const mapStateToProps = state => ({ - media: state.media, -}) - -const mapDispatchToProps = dispatch => ({ - // uploadActions: bindActionCreators({ ...uploadActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(Container) diff --git a/animism-align/frontend/views/media/media.css b/animism-align/frontend/views/media/media.css deleted file mode 100644 index a2d95c8..0000000 --- a/animism-align/frontend/views/media/media.css +++ /dev/null @@ -1,70 +0,0 @@ -.app > .media { - width: 100%; - height: calc(100% - 3.125rem); - overflow: scroll; -} - -.results .cell { - margin-bottom: 1rem; - margin-right: 1rem; -} -.results h2 { - display: block; - width: 100%; -} -.media .results .meta > div { - max-width: 100%; -} - -/* new / edit media forms */ - -.formContainer { - padding-top: 1rem; -} - -.imageForm, -.videoForm { - padding: 1rem 1rem 0.5rem 1rem; - margin: 1rem 0; - position: relative; - left: -1rem; - border-radius: 10px; -} - -/* image form */ - -.imageForm { - background: #315; -} -.imageForm .fileInput .row { - position: relative; -} - -/* video form */ - -.videoForm { - background: #314; -} -.videoForm .thumbnail img { - max-height: 200px; -} - -/* image crop */ - -.imageSelection { - width: 30rem; - position: relative; -} -.imageSelection img { - display: block; - max-width: 100%; - max-height: 20rem; -} -.imageSelection img.loading { - opacity: 0.5; -} -.imageSelection .box { - position: absolute; - background: rgba(255,32,64,0.05); - border: 1px solid #f24; -} diff --git a/animism-align/frontend/views/media/media.reducer.js b/animism-align/frontend/views/media/media.reducer.js deleted file mode 100644 index cb9b91d..0000000 --- a/animism-align/frontend/views/media/media.reducer.js +++ /dev/null @@ -1,22 +0,0 @@ -import * as types from '../../types' -import { session, getDefault, getDefaultInt } from '../../session' - -import { crudState, crudReducer } from '../../api/crud.reducer' - -const initialState = crudState('media', { - options: { - sort: 'author asc', - thumbnailSize: getDefault('upload.thumbnailSize', 'small'), - } -}) - -const reducer = crudReducer('media') - -export default function mediaReducer(state = initialState, action) { - // console.log(action.type, action) - state = reducer(state, action) - switch (action.type) { - default: - return state - } -} diff --git a/animism-align/frontend/views/nav/header.component.js b/animism-align/frontend/views/nav/header.component.js deleted file mode 100644 index efbaf08..0000000 --- a/animism-align/frontend/views/nav/header.component.js +++ /dev/null @@ -1,42 +0,0 @@ -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">Transcript</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 deleted file mode 100644 index 485ace2..0000000 --- a/animism-align/frontend/views/nav/nav.css +++ /dev/null @@ -1,73 +0,0 @@ -/* 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.form.js b/animism-align/frontend/views/paragraph/components/paragraph.form.js deleted file mode 100644 index de3114c..0000000 --- a/animism-align/frontend/views/paragraph/components/paragraph.form.js +++ /dev/null @@ -1,87 +0,0 @@ -import React, { Component } from 'react' -// import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import actions from '../../../actions' - -import { clamp, timestamp, capitalize } from '../../../util' -import { Select } from '../../../common' - -const PARAGRAPH_TYPES = [ - 'paragraph', 'blockquote', 'hidden', -].map(name => ({ name, label: capitalize(name.replace('_', ' ')) })) - -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() { - if (this.textareaRef && this.textareaRef.current) { - this.textareaRef.current.focus() - } - } - handleChange(e) { - const { name, value } = e.target - this.handleSelect(name, value) - } - handleSelect(name, value) { - const { onUpdate, paragraph } = this.props - onUpdate({ - ...paragraph, - [name]: value, - }) - } - handleSubmit() { - const { paragraph, onClose } = this.props - actions.paragraph.update(paragraph) - .then(response => { - console.log(response) - onClose() - }) - } - render() { - const { paragraph, y } = this.props - return ( - <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} - /> - <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 deleted file mode 100644 index 62b4a49..0000000 --- a/animism-align/frontend/views/paragraph/components/paragraphTypes/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' - -import { - Paragraph, ParagraphHeader -} from './paragraphTypes.text' - -import { - MediaVideo -} from './paragraphTypes.video' - -import { - MediaImage -} from './paragraphTypes.image' - -export const paragraphElementLookup = { - paragraph: React.memo(Paragraph), - hidden: React.memo(Paragraph), - blockquote: React.memo(Paragraph), - header: React.memo(ParagraphHeader), - video: React.memo(MediaVideo), - image: React.memo(MediaImage), -} diff --git a/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.image.js b/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.image.js deleted file mode 100644 index 36c72e9..0000000 --- a/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.image.js +++ /dev/null @@ -1,17 +0,0 @@ -import React, { Component } from 'react' - -export const MediaImage = ({ paragraph, media, currentParagraph, currentAnnotation, onAnnotationClick, onDoubleClick }) => { - if (!media.lookup) return <div /> - const className = currentParagraph ? 'media image current' : 'media image' - 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> - return ( - <div - className={className} - onDoubleClick={e => onDoubleClick(e, paragraph)} - > - <img src={item.settings.display.url} /> - </div> - ) -} diff --git a/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.text.js b/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.text.js deleted file mode 100644 index c2ebcd7..0000000 --- a/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.text.js +++ /dev/null @@ -1,35 +0,0 @@ -import React, { Component } from 'react' - -export const Paragraph = ({ paragraph, currentParagraph, currentAnnotation, onAnnotationClick, onDoubleClick }) => { - let className = paragraph.type - if (className !== 'paragraph') className += ' paragraph' - if (currentParagraph) className += ' current' - return ( - <div - className={className} - onDoubleClick={e => onDoubleClick(e, paragraph)} - > - {paragraph.annotations.map(annotation => ( - <span - key={annotation.id} - className={annotation.id === currentAnnotation ? 'current' : ''} - onClick={e => onAnnotationClick(e, paragraph, annotation)} - dangerouslySetInnerHTML={{ __html: ' ' + annotation.text + ' ' }} - /> - ))} - </div> - ) -} - -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 - className={className} - onDoubleClick={e => onDoubleClick(e, paragraph)} - > - {text} - </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 deleted file mode 100644 index 423864b..0000000 --- a/animism-align/frontend/views/paragraph/components/paragraphTypes/paragraphTypes.video.js +++ /dev/null @@ -1,19 +0,0 @@ -import React, { Component } from 'react' - -import VimeoPlayer from '@u-wave/react-vimeo' - -export const MediaVideo = ({ paragraph, media, currentParagraph, currentAnnotation, onAnnotationClick, onDoubleClick }) => { - if (!media.lookup) return <div /> - 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> - return ( - <div - className={className} - onDoubleClick={e => onDoubleClick(e, paragraph)} - > - <VimeoPlayer video={item.url} muted width="650" /> - </div> - ) -} diff --git a/animism-align/frontend/views/paragraph/containers/paragraphList.container.js b/animism-align/frontend/views/paragraph/containers/paragraphList.container.js deleted file mode 100644 index 4c54808..0000000 --- a/animism-align/frontend/views/paragraph/containers/paragraphList.container.js +++ /dev/null @@ -1,206 +0,0 @@ -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 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)) - -const MEDIA_TYPES = new Set(['image', 'gallery', 'vitrine', 'video']) - -class ParagraphList extends Component { - state = { - paragraphs: [], - currentParagraph: -1, - currentAnnotation: -1, - selectedParagraph: null, - selectedParagraphOffset: 0, - } - - constructor(props) { - super(props) - 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 => { - if (floatLTE(paragraph.start_ts, play_ts) && floatLT(play_ts, paragraph.end_ts)) { - this.setCurrentAnnotation(paragraph, play_ts) - return true - } - return false - }) - if (!insideParagraph) { - this.setState({ - currentParagraph: -1, - currentAnnotation: -1, - }) - } - } - - setCurrentAnnotation(paragraph, play_ts) { - const { id: currentParagraph, annotations } = paragraph - let currentAnnotation - let annotation - let i = 0 - let len = annotations.length - for (let i = 0; i < len - 1; i++) { - if (floatLT(play_ts, annotations[i+1].start_ts)) { - currentAnnotation = annotations[i].id - break - } - } - if (!currentAnnotation) { - currentAnnotation = annotations[len-1].id - } - this.setState({ currentParagraph, currentAnnotation }) - } - - build() { - const { order: annotationOrder, lookup: annotationLookup } = this.props.annotation - const { lookup: paragraphLookup } = this.props.paragraph - let currentParagraph = {} - const paragraphs = [] - // loop over the annotations in time order - annotationOrder.forEach((annotation_id, i) => { - const annotation = annotationLookup[annotation_id] - const paragraph = paragraphLookup[annotation.paragraph_id] - // if this annotation is media, insert it after the current paragraph - if (MEDIA_TYPES.has(annotation.type)) { - paragraphs.push({ - id: ('index_' + i), - type: annotation.type, - start_ts: annotation.start_ts, - end_ts: 0, - annotations: [annotation], - }) - return - } - // if this annotation is from a different paragraph, make a new paragraph - if (annotation.paragraph_id !== currentParagraph.id) { - const paragraph_type = getParagraphType(annotation, paragraph) - currentParagraph = { - id: annotation.paragraph_id || ('index_' + i), - type: paragraph_type, - start_ts: annotation.start_ts, - end_ts: 0, - annotations: [], - } - paragraphs.push(currentParagraph) - } - // if this annotation is a paragraph_end, set the end timestamp - if (annotation.type === 'paragraph_end') { - currentParagraph.end_ts = annotation.start_ts - } - // otherwise, just append this annotation to the paragraph - else { - currentParagraph.annotations.push(annotation) - } - }) - for (let i = 0; i < (paragraphs.length - 1); i++) { - if (!paragraphs[i].end_ts) { - paragraphs[i].end_ts = paragraphs[i+1].start_ts - 0.1 - } - } - this.setState({ paragraphs }) - } - - handleAnnotationClick(e, paragraph, annotation){ - actions.audio.seek(annotation.start_ts) - } - 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, paragraphElementLookup } = this.props - 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 ( - <ParagraphElement - key={paragraph.id} - paragraph={paragraph} - media={media} - 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> - ) - } -} - -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, - media: state.media.index, -}) - -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 deleted file mode 100644 index 6035be8..0000000 --- a/animism-align/frontend/views/paragraph/paragraph.container.js +++ /dev/null @@ -1,70 +0,0 @@ -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' -import { paragraphElementLookup } from '../components/paragraphTypes' - -class ParagraphContainer extends Component { - componentDidMount() { - this.bind() - } - componentWillUnmount() { - this.unbind() - } - bind() { - document.addEventListener('keydown', this.handleKeydown) - } - unbind() { - document.removeEventListener('keydown', this.handleKeydown) - } - handleKeydown(e) { - if (document.activeElement !== document.body) { - return - } - // console.log(e.keyCode) - switch (e.keyCode) { - case 32: // spacebar - e.preventDefault() - actions.audio.toggle() - break - case 37: // left - case 38: // up - e.preventDefault() - actions.audio.jump(-5.0) - break - case 39: // right - case 40: // down - e.preventDefault() - actions.audio.jump(5.0) - break - } - } - render() { - if (!this.props.annotation.lookup || !this.props.paragraph.lookup) { - return <div className='body loading'><Loader /></div> - } - return ( - <div className='body'> - <ParagraphList paragraphElementLookup={paragraphElementLookup} /> - </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 deleted file mode 100644 index 8cd502c..0000000 --- a/animism-align/frontend/views/paragraph/paragraph.css +++ /dev/null @@ -1,93 +0,0 @@ -.paragraphs { - width: 100%; - height: calc(100% - 3.125rem); - overflow: scroll; - background: white; - color: black; - padding: 1rem; -} - -/* general paragraph styles */ - -.paragraphs .content { - font-family: 'Georgia', serif; - width: 650px; - margin: 0 auto; - padding-bottom: 6rem; - position: relative; -} - -.paragraphs .content > div { - margin-bottom: 16px; -} - -/* paragraph subtypes */ - -.paragraphs .header { - font-size: 32px; -} - -.paragraphs .paragraph { - font-size: 16px; - line-height: 24px; -} - -.paragraphs .blockquote { - padding-left: 3rem; -} - -.paragraphs .hidden { - opacity: 0.5; -} - -/* media image */ - -.paragraphs .media.image img { - width: 100%; -} - -/* current paragraph */ - -.paragraphs .paragraph.current { - background: rgba(0,0,0,0.0); -} - -/* sentences */ - -.paragraphs span { - margin-right: 4px; - cursor: pointer; -} - -.paragraphs .paragraph .current { - box-shadow: -2px -3px 0 #fff, - 2px -3px 0 #fff, - -2px 3px 0 #fff, - 2px 3px 0 #fff; - box-decoration-break: clone; - background: black; - color: white; -} - -/* 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; -} diff --git a/animism-align/frontend/views/paragraph/paragraph.reducer.js b/animism-align/frontend/views/paragraph/paragraph.reducer.js deleted file mode 100644 index 263aa07..0000000 --- a/animism-align/frontend/views/paragraph/paragraph.reducer.js +++ /dev/null @@ -1,20 +0,0 @@ -import * as types from '../../types' -import { session, getDefault, getDefaultInt } from '../../session' - -import { crudState, crudReducer } from '../../api/crud.reducer' - -const initialState = crudState('paragraph', { - options: { - } -}) - -const reducer = crudReducer('paragraph') - -export default function paragraphReducer(state = initialState, action) { - // console.log(action.type, action) - state = reducer(state, action) - switch (action.type) { - default: - return state - } -} diff --git a/animism-align/frontend/views/site/component.template.js b/animism-align/frontend/views/site/component.template.js deleted file mode 100644 index a22d582..0000000 --- a/animism-align/frontend/views/site/component.template.js +++ /dev/null @@ -1,28 +0,0 @@ -import React, { Component } from 'react' -// import { Link } from 'react-router-dom' -// import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import actions from '../../../actions' -// import * as uploadActions from './upload.actions' - -class ComponentTemplate extends Component { - componentDidMount() { - } - render() { - const { } = this.props - return ( - <div className=""> - </div> - ) - } -} - -const mapStateToProps = state => ({ -}) - -const mapDispatchToProps = dispatch => ({ - // uploadActions: bindActionCreators({ ...uploadActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(ComponentTemplate) diff --git a/animism-align/frontend/views/site/site.actions.js b/animism-align/frontend/views/site/site.actions.js deleted file mode 100644 index c7c228e..0000000 --- a/animism-align/frontend/views/site/site.actions.js +++ /dev/null @@ -1,16 +0,0 @@ -import * as types from '../../types' -// import actions from '../../actions' -// import { session } from '../../session' -import { api, post, pad, preloadImage } from '../../util' - -export const loadPeaks = (asdf) => dispatch => { - api(dispatch, types.peaks, 'peaks', '/static/data_store/peaks/peaks.json') -} - -export const loadText = (asdf) => dispatch => { - api(dispatch, types.text, 'text', '/static/data_store/peaks/text.txt') -} - -export const updateText = text => dispatch => { - dispatch({ type: types.text.loaded, data: text }) -}
\ No newline at end of file diff --git a/animism-align/frontend/views/site/site.reducer.js b/animism-align/frontend/views/site/site.reducer.js deleted file mode 100644 index e80033f..0000000 --- a/animism-align/frontend/views/site/site.reducer.js +++ /dev/null @@ -1,26 +0,0 @@ -import * as types from '../../types' -// import { session, getDefault, getDefaultInt } from '../../session' - -const initialState = { - peaks: { loading: true }, - text: { loading: true }, -} - -export default function siteReducer(state = initialState, action) { - // console.log(action.type, action) - // console.log(action.data) - switch (action.type) { - case types.peaks.loaded: - return { - ...state, - peaks: action.data, - } - case types.text.loaded: - return { - ...state, - text: action.data, - } - default: - return state - } -} diff --git a/animism-align/frontend/views/upload/components/upload.form.js b/animism-align/frontend/views/upload/components/upload.form.js deleted file mode 100644 index 2010088..0000000 --- a/animism-align/frontend/views/upload/components/upload.form.js +++ /dev/null @@ -1,16 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' - -import { MenuButton, FileInput } from '../../../common' - -export default class UploadForm extends Component { - render() { - return ( - <div className='uploadForm'> - <MenuButton name="upload" label={false}> - <FileInput onChange={this.props.uploadActions.upload} /> - </MenuButton> - </div> - ) - } -} diff --git a/animism-align/frontend/views/upload/components/upload.index.js b/animism-align/frontend/views/upload/components/upload.index.js deleted file mode 100644 index 3a7ae4b..0000000 --- a/animism-align/frontend/views/upload/components/upload.index.js +++ /dev/null @@ -1,98 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' - -import { uploadUri, formatDateTime } from '../../../util' -import { MenuButton, SmallMenuButton, Loader } from '../../../common' -import actions from '../../../actions' - -import UploadIndexOptions from './upload.indexOptions' -import UploadMenu from './upload.menu' - -// const { result, collectionLookup } = this.props - -export default class UploadIndex extends Component { - componentDidMount() { - this.fetch(false) - } - - componentDidUpdate(prevProps) { - if (this.props.upload.options.sort !== prevProps.upload.options.sort) { - this.fetch(false) - } - } - - fetch(load_more) { - const { options, index } = this.props.upload - const { order: index_order } = index - const [ sort, order ] = options.sort.split('-') - actions.upload.index({ - sort, order, limit: 50, offset: load_more ? index_order.length : 0, - }, load_more) - } - - render() { - const { uploadActions } = this.props - const { options } = this.props.upload - const { loading, lookup, order } = this.props.upload.index - if (loading) { - return ( - <section> - <UploadIndexOptions /> - <div className="row"> - {order && !!order.length && - <div className={'results ' + options.thumbnailSize}> - {order.map(id => <UploadItem key={id} data={lookup[id]} />)} - </div> - } - </div> - <Loader /> - </section> - ) - } - if (!lookup || !order.length) { - return ( - <section> - <UploadIndexOptions /> - <div className="row"> - <UploadMenu uploadActions={uploadActions} /> - <p className='gray'> - {"No uploads"} - </p> - </div> - </section> - ) - } - return ( - <section> - <UploadIndexOptions /> - <div className="row"> - <UploadMenu uploadActions={uploadActions} /> - <div className={'results ' + options.thumbnailSize}> - {order.map(id => <UploadItem key={id} data={lookup[id]} />)} - </div> - </div> - {order.length >= 50 && <button className='loadMore' onClick={() => this.fetch(true)}>Load More</button>} - </section> - ) - } -} - -const UploadItem = ({ data }) => { - // console.log(data) - // const imageUri = uploadUri(data) - return ( - <div className='cell'> - <div className='img'> - <Link to={"/upload/" + data.id + "/show/"}> - <img src={data.url} alt={"Uploaded image"} /> - </Link> - </div> - <div className='meta center'> - <div> - {formatDateTime(data.created_at)} - </div> - </div> - </div> - ) -} - diff --git a/animism-align/frontend/views/upload/components/upload.indexOptions.js b/animism-align/frontend/views/upload/components/upload.indexOptions.js deleted file mode 100644 index 774bf22..0000000 --- a/animism-align/frontend/views/upload/components/upload.indexOptions.js +++ /dev/null @@ -1,61 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import actions from '../../../actions' - -import { Select, Checkbox } from '../../../common' - -const thumbnailOptions = [ - { name: 'th', label: 'Thumbnails', }, - { name: 'sm', label: 'Small', }, - { name: 'md', label: 'Medium', }, - { name: 'lg', label: 'Large', }, - { name: 'orig', label: 'Original', }, -] - -const sortOptions = [ - { name: 'id-asc', label: 'Most recent' }, - { name: 'id-desc', label: 'Oldest first' }, - { name: 'username-asc', label: 'Username (A-Z)' }, - { name: 'username-desc', label: 'Username (Z-A)' }, - // { name: '-asc', label: '' }, - // { name: '-desc', label: '' }, - // { name: '-asc', label: '' }, - // { name: '-desc', label: '' }, - // { name: '-asc', label: '' }, - // { name: '-desc', label: '' }, -] - -class IndexOptions extends Component { - render() { - const { options } = this.props - return ( - <div className='row menubar'> - <div /> - <Select - name={'sort'} - options={sortOptions} - selected={options.sort} - onChange={actions.upload.updateOption} - /> - <Select - name={'thumbnailSize'} - options={thumbnailOptions} - selected={options.thumbnailSize} - onChange={actions.upload.updateOption} - /> - </div> - ) - } -} - -const mapStateToProps = state => ({ - options: state.upload.options, -}) - -const mapDispatchToProps = dispatch => ({ -}) - -export default connect(mapStateToProps, mapDispatchToProps)(IndexOptions) diff --git a/animism-align/frontend/views/upload/components/upload.menu.js b/animism-align/frontend/views/upload/components/upload.menu.js deleted file mode 100644 index 37c7f0b..0000000 --- a/animism-align/frontend/views/upload/components/upload.menu.js +++ /dev/null @@ -1,18 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' - -import { MenuButton, FileInput } from '../../../common' - -import actions from '../../../actions' - -export default class UploadMenu extends Component { - render() { - return ( - <div className='menuButtons'> - <MenuButton name="upload"> - <FileInput onChange={this.props.uploadActions.upload} /> - </MenuButton> - </div> - ) - } -} diff --git a/animism-align/frontend/views/upload/components/upload.show.js b/animism-align/frontend/views/upload/components/upload.show.js deleted file mode 100644 index 352caf3..0000000 --- a/animism-align/frontend/views/upload/components/upload.show.js +++ /dev/null @@ -1,69 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { connect } from 'react-redux' - -import actions from '../../../actions' -import { formatDate, formatTime, formatAge, uploadUri } from '../../../util' -import { history } from '../../../store' -import { Loader, MenuButton } from '../../../common' - -class UploadShow extends Component { - componentDidMount() { - actions.upload.show(this.props.match.params.id) - } - - componentDidUpdate(prevProps) { - if (prevProps.match.params.id !== this.props.match.params.id) { - actions.upload.show(this.props.match.params.id) - } - } - - handleDestroy() { - const { res: data } = this.props.upload.show - if (confirm("Really delete this upload?")) { - actions.upload.destroy(data).then(() => { - history.push('/upload/') - }) - } - } - - render() { - const { show, destroy } = this.props.upload - if (show.loading || destroy.loading) { - return <Loader /> - } - if (!show.loading && !show.res || show.not_found) { - return <div className='gray'>Upload {this.props.match.params.id} not found</div> - } - const { res: data } = show - return ( - <section className="row uploadShow"> - <div className="menuButtons"> - <MenuButton name="delete" onClick={this.handleDestroy.bind(this)} /> - </div> - <div> - <img src={data.url} /> - <div className='byline'> - {'Uploaded by '} - {data.username} - {' on '} - {formatDate(data.created_at)} - {' at '} - {formatTime(data.created_at)} - {'. '} - </div> - </div> - </section> - ) - } -} - -const mapStateToProps = state => ({ - upload: state.upload, -}) - -const mapDispatchToProps = dispatch => ({ - // searchActions: bindActionCreators({ ...searchActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(UploadShow) diff --git a/animism-align/frontend/views/upload/upload.actions.js b/animism-align/frontend/views/upload/upload.actions.js deleted file mode 100644 index c5d12e3..0000000 --- a/animism-align/frontend/views/upload/upload.actions.js +++ /dev/null @@ -1,18 +0,0 @@ -import * as types from '../../types' -import { store, history } from '../../store' -import { api, post, pad, preloadImage } from '../../util' -import actions from '../../actions' -import { session } from '../../session' - -export const upload = (image, tag='upload') => dispatch => { - const formData = { - image, - tag, - username: 'animism', // session('username'), - } - // console.log(formData) - return actions.upload.upload(formData).then(data => { - // console.log(data.res) - return data.res - }) -} diff --git a/animism-align/frontend/views/upload/upload.container.js b/animism-align/frontend/views/upload/upload.container.js deleted file mode 100644 index 0ad76c9..0000000 --- a/animism-align/frontend/views/upload/upload.container.js +++ /dev/null @@ -1,36 +0,0 @@ -import React, { Component } from 'react' -import { Route, Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import './upload.css' - -import actions from '../../actions' -import * as uploadActions from './upload.actions' - -import UploadMenu from './components/upload.menu' -import UploadIndex from './components/upload.index' -import UploadShow from './components/upload.show' - -class Container extends Component { - render() { - return ( - <div className='row upload'> - <div> - <Route exact path='/upload/:id/show/' component={UploadShow} /> - <UploadIndex {...this.props} /> - </div> - </div> - ) - } -} - -const mapStateToProps = state => ({ - upload: state.upload, -}) - -const mapDispatchToProps = dispatch => ({ - uploadActions: bindActionCreators({ ...uploadActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(Container) diff --git a/animism-align/frontend/views/upload/upload.css b/animism-align/frontend/views/upload/upload.css deleted file mode 100644 index 28ce33d..0000000 --- a/animism-align/frontend/views/upload/upload.css +++ /dev/null @@ -1,182 +0,0 @@ -.uploadShow { - margin-top: 1rem; -} -.uploadShow img { - max-width: 30rem; - max-height: 20rem; -} -.upload { - height: 100%; -} -.upload > div:last-child { - flex: 1; - width: 100%; -} - -/* results */ - -.resultsContainer { -} -.results { - display: flex; - flex-flow: row wrap; - justify-content: flex-start; - align-items: flex-end; -} -.results .result { - display: inline-block; - margin-right: 1.125rem; - margin-bottom: 1.125rem; -} -.result > a { - display: block; -} -.result > a > div { - position: relative; -} -.result img { - max-width: 100%; - display: block; - cursor: pointer; -} -.result > a { - border: 2px solid transparent; -} -.results .active img, -.desktop .result > a:hover { - border-color: #11f; -} -.results.th .result { - max-width: 10rem; -} -.results.sm .result { - max-width: 20rem; -} -.results.md .result { - max-width: 40rem; -} -.results.lg .result { - max-width: 80rem; -} -.results.orig .result { - max-width: 100%; -} -.results.th img { - max-height: 120px; -} -.results.sm img { - max-height: 160px; -} -.results.md img { - max-height: 480px; -} -.results.lg img { - max-height: 960px; -} -.results.orig img { - max-width: none; - max-height: none; -} -.results .img { - width: 100%; -} -.results .img > a { - display: inline-block; - position: relative; -} -.results .meta { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: baseline; - font-size: 0.75rem; - color: #888; - padding: 0.125rem; -} -.results .meta.center, -.row.center { - align-items: center; -} -.results .meta > div { - overflow: hidden; - max-width: 75%; - text-overflow: ellipsis; - white-space: nowrap; -} -.results .meta > span { - padding-right: 0.125rem; -} -.results .meta .buttons { -} -.score.good { - color: #11f; - font-weight: bold; -} -.score.ok { - color: #44d; - font-weight: bold; -} -.score.poor { - color: #66b; -} -.score { - color: #888; -} -.resultGroup { - display: flex; - flex-direction: row; - flex-wrap: wrap; - position: relative; - border: 2px solid #888; - max-width: 20rem; - margin-right: 1.5rem; - margin-bottom: 1.5rem; - background: #fff; - box-shadow: 0 2px 4px #888; -} -.resultGroup .sha256 { - position: absolute; - background: white; - padding: 0.25rem 0.25rem 0rem 0.25rem; - left: 0.25rem; - top: -0.75rem; - font-size: 0.75rem; - color: #333; - text-transform: uppercase; - max-width: 95%; - overflow: hidden; - text-overflow: ellipsis; -} -.results.grouped { - background: #fff; -} -.results.grouped.sm, -.results.grouped.md, -.results.grouped.lg, -.results.grouped.orig { - flex-flow: column nowrap; -} -.results.th .resultGroup { - max-width: 33.5rem; -} -.results.sm .resultGroup { - max-width: 56rem; -} -.results.md .resultGroup { - max-width: 79rem; -} -.results.lg .resultGroup { - max-width: 100%; -} -.results.orig .resultGroup { - max-width: 100%; -} -.results .resultGroup .result { - margin: 0.5rem; -} -.loadMore { - width: 100%; -} -.loadMore button { - width: 100%; -} diff --git a/animism-align/frontend/views/upload/upload.reducer.js b/animism-align/frontend/views/upload/upload.reducer.js deleted file mode 100644 index db12819..0000000 --- a/animism-align/frontend/views/upload/upload.reducer.js +++ /dev/null @@ -1,22 +0,0 @@ -import * as types from '../../types' -import { session, getDefault, getDefaultInt } from '../../session' - -import { crudState, crudReducer } from '../../api/crud.reducer' - -const initialState = crudState('upload', { - options: { - sort: getDefault('upload.sort', 'id-desc'), - thumbnailSize: getDefault('upload.thumbnailSize', 'small'), - } -}) - -const reducer = crudReducer('upload') - -export default function uploadReducer(state = initialState, action) { - // console.log(action.type, action) - state = reducer(state, action) - switch (action.type) { - default: - return state - } -} |
