From 24661f00461f0ea56c290770c698f8671e66bb37 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Thu, 13 Aug 2020 18:35:58 +0200 Subject: scrolling viewer sections --- animism-align/frontend/app/constants.js | 5 ++ animism-align/frontend/app/utils/index.js | 1 + .../views/media/components/media.formGallery.js | 93 +++++++++++++--------- .../app/views/media/components/media.formImage.js | 6 +- .../components.fullscreen/fullscreen.video.js | 2 +- .../player/components.utility/media.citation.js | 8 +- .../app/views/viewer/player/player.container.js | 1 + .../app/views/viewer/player/player.fullscreen.js | 1 + .../app/views/viewer/sections/sections.css | 30 ++++++- .../app/views/viewer/sections/viewer.sections.js | 44 +++++----- .../frontend/app/views/viewer/viewer.actions.js | 12 +-- 11 files changed, 130 insertions(+), 73 deletions(-) (limited to 'animism-align') diff --git a/animism-align/frontend/app/constants.js b/animism-align/frontend/app/constants.js index 69f2b44..16548ee 100644 --- a/animism-align/frontend/app/constants.js +++ b/animism-align/frontend/app/constants.js @@ -81,3 +81,8 @@ export const CURTAIN_COLOR_LOOKUP = CURTAIN_COLORS.reduce((a,b) => { a[b.label] = b return a }, {}) + +export const DISPLAY_SIZE = 2000 +export const DISPLAY_QUALITY= 80 +export const THUMBNAIL_SIZE = 320 +export const THUMBNAIL_QUALITY = 80 diff --git a/animism-align/frontend/app/utils/index.js b/animism-align/frontend/app/utils/index.js index b7c05da..9a47df3 100644 --- a/animism-align/frontend/app/utils/index.js +++ b/animism-align/frontend/app/utils/index.js @@ -92,6 +92,7 @@ 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 floatInRange = (a,b,c) => floatLTE(a, b) && floatLT(b, c) +export const simpleArraysEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b) /* URLs */ diff --git a/animism-align/frontend/app/views/media/components/media.formGallery.js b/animism-align/frontend/app/views/media/components/media.formGallery.js index e5311cb..f785b40 100644 --- a/animism-align/frontend/app/views/media/components/media.formGallery.js +++ b/animism-align/frontend/app/views/media/components/media.formGallery.js @@ -4,16 +4,12 @@ import { ReactSortable } from "react-sortablejs" import { session } from 'app/session' import actions from 'app/actions' -import { capitalize, preloadImage } from 'app/utils' +import { capitalize, preloadImage, simpleArraysEqual } from 'app/utils' +import { DISPLAY_SIZE, DISPLAY_QUALITY, THUMBNAIL_SIZE, THUMBNAIL_QUALITY } from 'app/constants' import { TextInput, LabelDescription, FileInputField, Select, TextArea, Checkbox, SubmitButton, Loader } from 'app/common' import { renderThumbnail } from 'app/common/upload.helpers' -const DISPLAY_SIZE = 2000 -const DISPLAY_QUALITY= 80 -const THUMBNAIL_SIZE = 320 -const THUMBNAIL_QUALITY = 80 - export default class MediaGalleryForm extends Component { state = { loading: false, @@ -44,13 +40,20 @@ export default class MediaGalleryForm extends Component { handleUpload(files) { const { data } = this.props this.setState({ loading: true }) + this.uploadFullsize(files) + .then(() => { + this.setState({ loading: false }) + }) + } + + uploadFullsize(files) { + const { data } = this.props // first, upload all the fullsize files let fullsizeUploadPromises = files.map(file => { return this.uploadSize(file, 'fullsize') }) // when these are done - Promise.all(fullsizeUploadPromises) - .then(results => { + return Promise.all(fullsizeUploadPromises).then(results => { // get the added IDs in order const added_image_order = results.map(result => result.id) // append the new IDs to the image order @@ -66,29 +69,44 @@ export default class MediaGalleryForm extends Component { image_lookup: image_lookup, thumbnail_lookup: data.settings.thumbnail_lookup || {}, }) - // construct thumbnails and upload these - const thumbnailUploadPromises = files.map(file => { - return this.uploadThumbnail(file) + return this.uploadResizedFiles(files, added_image_order) + }) + } + + uploadResizedFiles(files, added_image_order) { + return ( + this.uploadThumbnails(files, added_image_order, 'thumbnail', THUMBNAIL_SIZE, THUMBNAIL_QUALITY) + .then(() => { + return this.uploadThumbnails(files, added_image_order, 'display', DISPLAY_SIZE, DISPLAY_QUALITY) }) - // once the thumbnails are done uploading - Promise.all(thumbnailUploadPromises) - .then(thumbnail_results => { - // add them to the thumbnail lookup, keyed off the ID of the fullsize image - const thumbnail_lookup = thumbnail_results.reduce((a, b, i) => { - const id = added_image_order[i] - a[id] = b - return a - }, (data.settings.thumbnail_lookup || {})) - // update the settings object - this.handleSettingsChange('multiple', { - thumbnail_lookup: thumbnail_lookup, - }) - this.setState({ loading: false }) + ) + } + + uploadThumbnails(files, added_image_order, tag, maxSide, quality) { + const { data } = this.props + // construct thumbnails and upload these + const thumbnailUploadPromises = files.map(file => { + return this.uploadThumbnail(file, tag, maxSide, quality) + }) + // once the thumbnails are done uploading... + return Promise.all(thumbnailUploadPromises).then(thumbnail_results => { + // decide which lookup we're adding to + const tag_lookup_name = tag + '_lookup' + const tag_lookup = data.settings[tag_lookup_name] || {} + // add them to the thumbnail lookup, keyed off the ID of the fullsize image + const thumbnail_lookup = thumbnail_results.reduce((a, b, i) => { + const id = added_image_order[i] + a[id] = b + return a + }, tag_lookup) + // update the settings object + this.handleSettingsChange('multiple', { + [tag_lookup_name]: thumbnail_lookup, }) }) } - uploadThumbnail(file) { + uploadThumbnail(file, tag, maxSide, quality) { return new Promise((resolve, reject) => { const type = (file.name.match('.png') !== -1) ? 'image/png' : 'image/jpg' const fr = new FileReader() @@ -97,16 +115,16 @@ export default class MediaGalleryForm extends Component { const image = new Image() image.onload = () => { image.onload = null - const thumbnailCanvas = renderThumbnail(image, { maxSide: THUMBNAIL_SIZE }) + const thumbnailCanvas = renderThumbnail(image, { maxSide }) thumbnailCanvas.toBlob(thumbnail => { - this.uploadSize(thumbnail, 'thumbnail', file.name) + this.uploadSize(thumbnail, tag, file.name) .then(res => { resolve(res) }) .catch(err => { reject(err) }) - }, type, THUMBNAIL_QUALITY) + }, type, quality) } image.src = fileReaderEvent.target.result } @@ -115,7 +133,7 @@ export default class MediaGalleryForm extends Component { } uploadSize(image, tag, fn) { - console.log('uploading size', tag) + // console.log('uploading size', tag) const uploadData = { image, tag, @@ -131,12 +149,13 @@ export default class MediaGalleryForm extends Component { }) } - handleOrderChanged(image_order) { - console.log(image_order) - // if (image_order !== this.props.data.settings.image_order) { - // console.log('handle order chagned', image_order) - // this.handleSettingsChange('image_order', image_order) - // } + handleOrderChanged(new_image_order) { + // console.log(new_image_order) + const image_order = new_image_order.map(el => el.id) + if (!simpleArraysEqual(image_order, this.props.data.settings.image_order)) { + console.log('order changed', image_order) + this.handleSettingsChange('image_order', image_order) + } } render() { @@ -173,7 +192,7 @@ export default class MediaGalleryForm extends Component { } const GalleryImageForm = ({ id, key, image, thumbnail }) => { - console.log(image, thumbnail) + // console.log(image, thumbnail) return (
{thumbnail diff --git a/animism-align/frontend/app/views/media/components/media.formImage.js b/animism-align/frontend/app/views/media/components/media.formImage.js index 04b821f..7f22af0 100644 --- a/animism-align/frontend/app/views/media/components/media.formImage.js +++ b/animism-align/frontend/app/views/media/components/media.formImage.js @@ -4,17 +4,13 @@ import { Link } from 'react-router-dom' import { session } from 'app/session' import actions from 'app/actions' import { capitalize, preloadImage, cropImage } from 'app/utils' +import { DISPLAY_SIZE, DISPLAY_QUALITY, THUMBNAIL_SIZE, THUMBNAIL_QUALITY } from 'app/constants' import { TextInput, LabelDescription, UploadImage, Select, TextArea, Checkbox, SubmitButton, Loader } from 'app/common' import { renderThumbnail } from 'app/common/upload.helpers' import ImageSelection from './media.formImageSelection' -const DISPLAY_SIZE = 2000 -const DISPLAY_QUALITY= 80 -const THUMBNAIL_SIZE = 320 -const THUMBNAIL_QUALITY = 80 - export default class MediaImageForm extends Component { state = { img: null, diff --git a/animism-align/frontend/app/views/viewer/player/components.fullscreen/fullscreen.video.js b/animism-align/frontend/app/views/viewer/player/components.fullscreen/fullscreen.video.js index 6ca7ec8..a048fa1 100644 --- a/animism-align/frontend/app/views/viewer/player/components.fullscreen/fullscreen.video.js +++ b/animism-align/frontend/app/views/viewer/player/components.fullscreen/fullscreen.video.js @@ -9,7 +9,7 @@ export const FullscreenVideo = ({ element, media, transitionDuration }) => { color: color.textColor, transitionDuration, } - console.log(item, window.innerWidth, window.innerHeight) + console.log(item) return (
{
{media.author} {', '} - {media.pre_title} + {media.pre_title && ( + media.pre_title + ' ' + )} {media.title} - {media.post_title} + {media.post_title && ( + ' ' + media.post_title + )} {'. '} {media.date && ( ' ' + media.date + '.' diff --git a/animism-align/frontend/app/views/viewer/player/player.container.js b/animism-align/frontend/app/views/viewer/player/player.container.js index 0ac5662..8a23720 100644 --- a/animism-align/frontend/app/views/viewer/player/player.container.js +++ b/animism-align/frontend/app/views/viewer/player/player.container.js @@ -36,6 +36,7 @@ class PlayerContainer extends Component { } return false }) + if (!insideSection) { actions.viewer.setCurrentSection(sections[sections.length-1], null) } diff --git a/animism-align/frontend/app/views/viewer/player/player.fullscreen.js b/animism-align/frontend/app/views/viewer/player/player.fullscreen.js index a4acef9..133653d 100644 --- a/animism-align/frontend/app/views/viewer/player/player.fullscreen.js +++ b/animism-align/frontend/app/views/viewer/player/player.fullscreen.js @@ -27,6 +27,7 @@ class PlayerFullscreen extends Component { const elements = timeline.filter(element => ( floatInRange(element.start_ts, play_ts, element.fade_out_start_ts + 0.1) )) + console.log(elements) if (elements.length) { const lastElement = elements[elements.length - 1] if (lastElement.color && lastElement.color.textColor === '#ffffff') { diff --git a/animism-align/frontend/app/views/viewer/sections/sections.css b/animism-align/frontend/app/views/viewer/sections/sections.css index a94b712..0ca84a2 100644 --- a/animism-align/frontend/app/views/viewer/sections/sections.css +++ b/animism-align/frontend/app/views/viewer/sections/sections.css @@ -8,7 +8,6 @@ background: black; color: white; display: block; - white-space: nowrap; transition: transform 0.2s; transform: translateZ(0) translateY(25rem); border-top: 1px solid #fff; @@ -17,10 +16,37 @@ transform: translateZ(0) translateY(0); } .checklist-open .viewer-sections { - transform: translateZ(0) translateY(calc(3rem - 100vh)); + transform: translateZ(0) translateY(calc(3rem - 100vh - 4px)); z-index: 20; } +/* scrolling part */ + +.viewer-sections .viewer-sections-scroll { + width: 100%; + white-space: nowrap; + overflow-y: auto; +} +.viewer-sections-scroll::-webkit-scrollbar { + cursor: pointer; + user-select: none; + height: 4px +} +.viewer-sections-scroll::-webkit-scrollbar-button { + display: block; + width: 0; + height: 0; +} +.viewer-sections-scroll::-webkit-scrollbar-track-piece { + background:rgba(211,211,211,0.8); +} +.viewer-sections-scroll::-webkit-scrollbar-thumb { + display: block; + background: #000; +} + +/* clickable section indicators */ + .viewer-sections .viewer-section { display: inline-flex; white-space: normal; 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 f4abbd6..4bb61ec 100644 --- a/animism-align/frontend/app/views/viewer/sections/viewer.sections.js +++ b/animism-align/frontend/app/views/viewer/sections/viewer.sections.js @@ -12,29 +12,31 @@ class ViewerSections extends Component { const { sections } = this.props return (
- {sections.map(section => { - // console.log(section) - return ( -
actions.viewer.seekToSection(section)} - > -
-
-
- {ROMAN_NUMERALS[section.index]}
- {section.title} -
-
- {section.mediaLabels} +
+ {sections.map(section => { + // console.log(section) + return ( +
actions.viewer.seekToSection(section)} + > +
+
+
+ {ROMAN_NUMERALS[section.index]}
+ {section.title} +
+
+ {section.mediaLabels} +
-
- ) - })} + ) + })} +
) diff --git a/animism-align/frontend/app/views/viewer/viewer.actions.js b/animism-align/frontend/app/views/viewer/viewer.actions.js index 1547606..ae2bf5b 100644 --- a/animism-align/frontend/app/views/viewer/viewer.actions.js +++ b/animism-align/frontend/app/views/viewer/viewer.actions.js @@ -80,10 +80,12 @@ export const loadSections = () => dispatch => { if (MEDIA_ANNOTATION_TYPES.has(annotation.type)) { // fetch the media and add it to the list of media (TODO: handle carousels) const media = mediaLookup[annotation.settings.media_id] - currentSection.media.push({ - start_ts: annotation.start_ts, - media - }) + if (!media.settings.hide_in_bibliography) { + currentSection.media.push({ + start_ts: annotation.start_ts, + media + }) + } // get the display string for this media type if (media.type in MEDIA_LABEL_TYPES) { @@ -93,7 +95,7 @@ export const loadSections = () => dispatch => { // increment the media tally mediaIndex += 1 - // non-fullscreen, inline media should be displayed in the transcript. + // non-fullscreen (or fullscreen-inline) media should be displayed in the transcript. if (!annotation.settings.fullscreen || annotation.settings.inline) { sectionTextAnnotationOrder.push(annotation.id) } -- cgit v1.2.3-70-g09d2