summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--animism-align/frontend/app/constants.js5
-rw-r--r--animism-align/frontend/app/utils/index.js12
-rw-r--r--animism-align/frontend/app/views/align/components/annotations/annotationForms/annotationForm.text.js38
-rw-r--r--animism-align/frontend/app/views/viewer/nav/viewer.icons.js7
-rw-r--r--animism-align/frontend/app/views/viewer/sections/sections.css48
-rw-r--r--animism-align/frontend/app/views/viewer/sections/viewer.sections.js42
-rw-r--r--animism-align/frontend/app/views/viewer/viewer.actions.js5
7 files changed, 144 insertions, 13 deletions
diff --git a/animism-align/frontend/app/constants.js b/animism-align/frontend/app/constants.js
index 6650bee..857c20a 100644
--- a/animism-align/frontend/app/constants.js
+++ b/animism-align/frontend/app/constants.js
@@ -73,6 +73,11 @@ export const CURTAIN_COLORS = [
{ label: 'black', backgroundColor: '#000000', textColor: '#ffffff' },
]
+export const BLACK_WHITE_SELECT_OPTIONS = [
+ { label: 'white', name: 'white'},
+ { label: 'black', name: 'black'},
+]
+
export const CURTAIN_COLOR_SELECT_OPTIONS = CURTAIN_COLORS.map(color => ({
label: color.label,
name: color.label,
diff --git a/animism-align/frontend/app/utils/index.js b/animism-align/frontend/app/utils/index.js
index 7f9100c..0f5a1dd 100644
--- a/animism-align/frontend/app/utils/index.js
+++ b/animism-align/frontend/app/utils/index.js
@@ -53,9 +53,9 @@ export const capitalize = s => s.split(' ').map(capitalizeWord).join(' ')
export const capitalizeWord = s => s.substr(0, 1).toUpperCase() + s.substr(1)
export const padSeconds = n => n < 10 ? '0' + n : n
-export const timestamp = (n = 0, fps = 1, ms = false) => {
+export const timestamp = (n = 0, fps = 1, ms = false, h_label = ':', m_label = ':', s_label = '') => {
if (n < 0) {
- return '0:00'
+ return '0' + m_label + '00' + s_label
}
let s = ''
n /= fps
@@ -67,11 +67,13 @@ export const timestamp = (n = 0, fps = 1, ms = false) => {
s = padSeconds(n % 60) + s
n = Math.floor(n / 60)
if (n > 60) {
- return Math.floor(n / 60) + ':' + padSeconds(n % 60) + ':' + s
+ return Math.floor(n / 60) + h_label + padSeconds(n % 60) + m_label + s + s_label
}
- return (n % 60) + ':' + s
+ return (n % 60) + m_label + s + s_label
}
+export const timestampHMS = n => timestamp(n, 1, false, 'h', 'm', 's')
+
export const timestampToSeconds = time_str => {
const time_str_parts = (time_str || "").trim().split(":").map(s => parseFloat(s))
if (time_str_parts.length === 3) {
@@ -93,6 +95,8 @@ export const mod = (n, m) => n - (m * Math.floor(n / m))
export const angle = (x1, y1, x2, y2) => Math.atan2(y2 - y1, x2 - x1)
export const floatLT = (a,b) => ((a*10|0) < (b*10|0))
export const floatLTE = (a,b) => ((a*10|0) === (b*10|0) || floatLT(a,b))
+export const floatGT = (a,b) => ((a*10|0) > (b*10|0))
+export const floatGTE = (a,b) => ((a*10|0) === (b*10|0) || floatGT(a,b))
export const floatInRange = (a,b,c) => floatLTE(a, b) && floatLT(b, c)
export const simpleArraysEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b)
diff --git a/animism-align/frontend/app/views/align/components/annotations/annotationForms/annotationForm.text.js b/animism-align/frontend/app/views/align/components/annotations/annotationForms/annotationForm.text.js
index a632e70..d3009d8 100644
--- a/animism-align/frontend/app/views/align/components/annotations/annotationForms/annotationForm.text.js
+++ b/animism-align/frontend/app/views/align/components/annotations/annotationForms/annotationForm.text.js
@@ -1,6 +1,7 @@
import React, { Component } from 'react'
-import { Select, Checkbox } from 'app/common'
+import { CURTAIN_COLOR_SELECT_OPTIONS, BLACK_WHITE_SELECT_OPTIONS } from 'app/constants'
+import { Select, Checkbox, LabelDescription } from 'app/common'
export const AnnotationFormSectionHeading = ({ annotation, handleSettingsSelect, handleSettingsChange }) => {
return (
@@ -11,6 +12,41 @@ export const AnnotationFormSectionHeading = ({ annotation, handleSettingsSelect,
checked={annotation.settings.hidden}
onChange={handleSettingsSelect}
/>
+ <Select
+ title='Transition Color'
+ name='transition_color'
+ selected={annotation.settings.transition_color}
+ options={CURTAIN_COLOR_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+ <LabelDescription>
+ {'Player will fade from this color when section begins'}
+ </LabelDescription>
+ <Checkbox
+ label="Section does not have audio"
+ name="no_audio"
+ checked={annotation.settings.no_audio}
+ onChange={handleSettingsSelect}
+ />
+ <LabelDescription>
+ {'Check if this is a text-only section'}
+ </LabelDescription>
+ {!annotation.settings.no_audio &&
+ <div>
+ <Select
+ title='Section Nav Color'
+ name='section_nav_color'
+ selected={annotation.settings.section_nav_color}
+ options={BLACK_WHITE_SELECT_OPTIONS}
+ defaultOption='Pick a color'
+ onChange={handleSettingsSelect}
+ />
+ <LabelDescription>
+ {'Set the color of the duration icon on the section navigation'}
+ </LabelDescription>
+ </div>
+ }
</div>
)
}
diff --git a/animism-align/frontend/app/views/viewer/nav/viewer.icons.js b/animism-align/frontend/app/views/viewer/nav/viewer.icons.js
index 7bc0219..059f0c0 100644
--- a/animism-align/frontend/app/views/viewer/nav/viewer.icons.js
+++ b/animism-align/frontend/app/views/viewer/nav/viewer.icons.js
@@ -92,3 +92,10 @@ export const ZoomPlus = (
<path className='cross' d="M27.5,19.5h-7v-7h-1v7h-7v1h7v7h1v-7h7V19.5z"/>
</svg>
)
+
+export const SpeakerIcon = (
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
+ <path d="M29.11,20.37c0,2.48-1.21,4.74-3.23,6.04l-0.57-0.89c1.71-1.1,2.74-3.03,2.74-5.15c0-2.11-1.06-4.09-2.77-5.18l0.57-0.9
+ C27.89,15.59,29.11,17.86,29.11,20.37z M10.89,15.5v9h5.02l5.4,4.38V11.12l-5.4,4.38H10.89z"/>
+ </svg>
+) \ No newline at end of file
diff --git a/animism-align/frontend/app/views/viewer/sections/sections.css b/animism-align/frontend/app/views/viewer/sections/sections.css
index 1ddb9d2..a60a063 100644
--- a/animism-align/frontend/app/views/viewer/sections/sections.css
+++ b/animism-align/frontend/app/views/viewer/sections/sections.css
@@ -58,6 +58,14 @@
margin: 1rem 0 1rem 1rem;
font-size: 16px;
cursor: pointer;
+ opacity: 0.6;
+ transition: opacity 0.2s;
+}
+.viewer-sections .viewer-section:hover {
+ opacity: 1.0;
+}
+.viewer-sections .viewer-section.current-section {
+ opacity: 1.0;
}
.viewer-sections .viewer-section:last-child {
margin-right: 1rem;
@@ -66,7 +74,7 @@
height: calc(20rem - 4px);
width: 12rem;
}
-.viewer-sections .section-thumbnail {
+.viewer-section .section-thumbnail {
display: block;
border-radius: 1rem;
width: 12rem;
@@ -74,8 +82,44 @@
margin-bottom: 0.5rem;
background-size: cover;
background-position: center center;
+ position: relative;
+ overflow: hidden;
}
-.viewer-sections .section-media {
+.viewer-section .section-duration {
+ position: absolute;
+ bottom: 0.5rem;
+ left: 0.5rem;
+ font-size: 0.75rem;
+}
+.section-duration-white { color: white; }
+.section-duration-black { color: black; }
+.section-duration-white svg path { fill: white; }
+.section-duration-black svg path { fill: black; }
+.viewer-section .section-media {
margin-top: 0.75rem;
font-size: 12px;
}
+.viewer-section .section-has-audio {
+ position: absolute;
+ bottom: -0.25rem;
+ right: 0.25rem;
+}
+.viewer-section .section-has-audio svg {
+ width: 2.5rem;
+ height: 2.5rem;
+}
+.viewer-section .section-progress-bar {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 3px;
+ background: #333;
+}
+.viewer-section .section-progress {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 3px;
+ background: #fff;
+} \ No newline at end of file
diff --git a/animism-align/frontend/app/views/viewer/sections/viewer.sections.js b/animism-align/frontend/app/views/viewer/sections/viewer.sections.js
index 4bb61ec..05eef9c 100644
--- a/animism-align/frontend/app/views/viewer/sections/viewer.sections.js
+++ b/animism-align/frontend/app/views/viewer/sections/viewer.sections.js
@@ -4,27 +4,52 @@ import { connect } from 'react-redux'
import actions from 'app/actions'
import ViewerSectionsNav from './viewer.sections.nav'
-import { ROMAN_NUMERALS } from 'app/constants'
+import { ROMAN_NUMERALS, CURTAIN_COLOR_LOOKUP } from 'app/constants'
+import { clamp, timestamp, floatInRange, floatLT } from 'app/utils'
import { thumbnailURL } from 'app/utils/annotation.utils'
+import { SpeakerIcon } from '../nav/viewer.icons'
class ViewerSections extends Component {
+ shouldComponentUpdate(nextProps) {
+ if (nextProps.nav !== this.props.nav) return true
+ return nextProps.nav
+ }
render() {
- const { sections } = this.props
+ const { play_ts, sections, currentSection } = this.props
return (
<div className="viewer-sections">
<div className="viewer-sections-scroll">
{sections.map(section => {
// console.log(section)
+ const media = section.media.length ? section.media[0].media : null
+ const { no_audio, section_nav_color } = section
+ const progress = Math.round(sectionProgress(section, play_ts) * 100)
return (
<div
- className="viewer-section"
+ className={(!currentSection || section.index === currentSection.index) ? "viewer-section current-section" : "viewer-section"}
key={section.index}
onClick={() => actions.viewer.seekToSection(section)}
>
<div>
<div className="section-thumbnail" style={{
- backgroundImage: section.media.length && 'url(' + thumbnailURL(section.media[0].media) + ')',
- }}/>
+ backgroundImage: media && 'url(' + thumbnailURL(media) + ')',
+ }}>
+ {!no_audio &&
+ <div className={"section-duration-" + section_nav_color}>
+ <div className="section-duration">
+ {timestamp(section.duration)}
+ </div>
+ <div className="section-has-audio">
+ {SpeakerIcon}
+ </div>
+ <div className="section-progress-bar">
+ <div className="section-progress"
+ style={{ width: progress + '%' }}
+ />
+ </div>
+ </div>
+ }
+ </div>
<div className="section-title">
{ROMAN_NUMERALS[section.index]}<br />
{section.title}
@@ -43,8 +68,15 @@ class ViewerSections extends Component {
}
}
+const sectionProgress = (section, play_ts) => {
+ return (clamp(play_ts, section.start_ts, section.end_ts) - section.start_ts) / section.duration
+}
+
const mapStateToProps = state => ({
+ nav: state.viewer.nav,
+ play_ts: state.audio.play_ts,
sections: state.viewer.sections,
+ currentSection: state.viewer.currentSection,
})
export default connect(mapStateToProps)(ViewerSections)
diff --git a/animism-align/frontend/app/views/viewer/viewer.actions.js b/animism-align/frontend/app/views/viewer/viewer.actions.js
index 097f01b..f5f0630 100644
--- a/animism-align/frontend/app/views/viewer/viewer.actions.js
+++ b/animism-align/frontend/app/views/viewer/viewer.actions.js
@@ -18,6 +18,8 @@ const newSection = (annotation, index, mediaIndex) => ({
media: [],
index,
mediaIndex,
+ no_audio: !!annotation.settings.no_audio,
+ section_nav_color: annotation.settings.section_nav_color || 'white',
})
// build the list of sections from the raw annotation list.
@@ -125,7 +127,7 @@ export const loadSections = () => dispatch => {
if (currentSection) {
currentSection.mediaLabels = Object.keys(currentMediaLabels).sort().join(', ')
currentSection.paragraphs = buildParagraphs(sectionTextAnnotationOrder, currentSection.index)
- currentSection.end_ts = timeline.duration
+ currentSection.duration = timeline.duration
}
// set the end_ts for each section (i.e. just before the next section starts)
@@ -134,6 +136,7 @@ export const loadSections = () => dispatch => {
if (currentSection.end_ts === 0) {
currentSection.end_ts = sections[i+1].start_ts - 1
}
+ currentSection.duration = currentSection.end_ts - currentSection.start_ts
}
// console.log(sections)