diff options
Diffstat (limited to 'animism-align/frontend/app')
7 files changed, 136 insertions, 84 deletions
diff --git a/animism-align/frontend/app/common/imageCrop.component.js b/animism-align/frontend/app/common/imageCrop.component.js index f139dce..6ba9606 100644 --- a/animism-align/frontend/app/common/imageCrop.component.js +++ b/animism-align/frontend/app/common/imageCrop.component.js @@ -1,5 +1,5 @@ import React, { Component } from 'react' -import { cropImage } from 'app/utils' +import { cropImage } from 'app/utils/image.utils' export default class ImageCrop extends Component { state = { diff --git a/animism-align/frontend/app/utils/annotation.utils.js b/animism-align/frontend/app/utils/annotation.utils.js index 76175c9..a055d91 100644 --- a/animism-align/frontend/app/utils/annotation.utils.js +++ b/animism-align/frontend/app/utils/annotation.utils.js @@ -45,6 +45,7 @@ export const thumbnailURL = media => { } export const displayThumbnailURL = media => { + if (!media) return null let image_id switch (media.type) { case 'video': diff --git a/animism-align/frontend/app/utils/image.utils.js b/animism-align/frontend/app/utils/image.utils.js new file mode 100644 index 0000000..dced2d8 --- /dev/null +++ b/animism-align/frontend/app/utils/image.utils.js @@ -0,0 +1,118 @@ +const preloadedImages = {} + +export const preloadImages = urls => batchPromise(urls, 4, preloadImage) + +export const batchPromise = (list, count, fn) => { + // console.log(list, count, fn) + return new Promise((resolve, reject) => { + let index = 0 + let len = list.length + const worker = j => ( + new Promise(resolveWorker => { + const next = () => { + const i = index++ + if (i >= len) { + return resolveWorker() + } + const item = list[i] + // console.log(['worker', j, '=>', i, item].join(' ')) + fn(item) + .then(next) + .catch(err => { + console.error(err, item) + next() + }) + } + next() + }) + ) + const workers = [] + for (let j = 0; j < count; j++) { + workers.push(worker(j)) + } + Promise.all(workers) + .then(resolve) + .catch(reject) + }) +} + +export const preloadImage = (url, anonymous=false) => ( + new Promise((resolve, reject) => { + if (preloadedImages[url] || typeof url === 'object' && url instanceof Image) { + return resolve(url) + } + preloadedImages[url] = true + const image = new Image() + let loaded = false + image.onload = () => { + if (loaded) return + loaded = true + image.onload = null + image.onerror = null + resolve(image) + } + image.onerror = () => { + if (loaded) return + image.onload = null + image.onerror = null + reject(image) + } + // console.log(img.src) + if (anonymous) { + image.crossOrigin = 'anonymous' + } + image.src = url + if (image.complete) { + image.onload() + } + }) +) + +export const cropImage = (url, crop, maxSide) => { + return new Promise((resolve, reject) => { + preloadImage(url, true) + .then(image => { + let { x, y, w, h } = crop + x = parseFloat(x) + y = parseFloat(y) + w = parseFloat(w) + h = parseFloat(h) + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + const { naturalWidth, naturalHeight } = image + + let width, height + let cropWidth = naturalWidth * w + let cropHeight = naturalHeight * h + + if (maxSide > 0) { + if (cropWidth > cropHeight) { + width = Math.min(maxSide, cropWidth) + height = cropHeight * width / cropWidth + } else { + height = Math.min(maxSide, cropHeight) + width = cropWidth * height / cropHeight + } + } else { + width = cropWidth + height = cropHeight + } + + canvas.width = width + canvas.height = height + + ctx.drawImage( + image, + Math.round(x * naturalWidth), + Math.round(y * naturalHeight), + Math.round(w * naturalWidth), + Math.round(h * naturalHeight), + 0, 0, canvas.width, canvas.height + ) + // console.log(x, y, w, h) + // console.log(naturalWidth, naturalHeight) + // console.log(width, height) + resolve(canvas) + }) + }) +} diff --git a/animism-align/frontend/app/utils/index.js b/animism-align/frontend/app/utils/index.js index d6c1a16..ddbfb7e 100644 --- a/animism-align/frontend/app/utils/index.js +++ b/animism-align/frontend/app/utils/index.js @@ -114,85 +114,6 @@ export const sha256_tree = (sha256, branch_size=2, tree_depth=2) => { return tree } -export const preloadImage = (url, anonymous=false) => ( - new Promise((resolve, reject) => { - if (typeof url === 'object' && url instanceof Image) { - return resolve(url) - } - const image = new Image() - let loaded = false - image.onload = () => { - if (loaded) return - loaded = true - image.onload = null - image.onerror = null - resolve(image) - } - image.onerror = () => { - if (loaded) return - image.onload = null - image.onerror = null - resolve(image) - } - // console.log(img.src) - if (anonymous) { - image.crossOrigin = 'anonymous' - } - image.src = url - if (image.complete) { - image.onload() - } - }) -) - -export const cropImage = (url, crop, maxSide) => { - return new Promise((resolve, reject) => { - preloadImage(url, true) - .then(image => { - let { x, y, w, h } = crop - x = parseFloat(x) - y = parseFloat(y) - w = parseFloat(w) - h = parseFloat(h) - const canvas = document.createElement('canvas') - const ctx = canvas.getContext('2d') - const { naturalWidth, naturalHeight } = image - - let width, height - let cropWidth = naturalWidth * w - let cropHeight = naturalHeight * h - - if (maxSide > 0) { - if (cropWidth > cropHeight) { - width = Math.min(maxSide, cropWidth) - height = cropHeight * width / cropWidth - } else { - height = Math.min(maxSide, cropHeight) - width = cropWidth * height / cropHeight - } - } else { - width = cropWidth - height = cropHeight - } - - canvas.width = width - canvas.height = height - - ctx.drawImage( - image, - Math.round(x * naturalWidth), - Math.round(y * naturalHeight), - Math.round(w * naturalWidth), - Math.round(h * naturalHeight), - 0, 0, canvas.width, canvas.height - ) - // console.log(x, y, w, h) - // console.log(naturalWidth, naturalHeight) - // console.log(width, height) - resolve(canvas) - }) - }) -} export const urlSearchParamsToDict = search => { const params = new URLSearchParams(search) const dict = {} 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 a62a4dc..ab7c8ed 100644 --- a/animism-align/frontend/app/views/media/components/media.formGallery.js +++ b/animism-align/frontend/app/views/media/components/media.formGallery.js @@ -3,7 +3,8 @@ import { Link } from 'react-router-dom' import { ReactSortable } from "react-sortablejs" import actions from 'app/actions' -import { capitalize, preloadImage, simpleArraysEqual } from 'app/utils' +import { capitalize, simpleArraysEqual } from 'app/utils' +import { preloadImage } from 'app/utils/image.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' 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 7f22af0..dbbf69f 100644 --- a/animism-align/frontend/app/views/media/components/media.formImage.js +++ b/animism-align/frontend/app/views/media/components/media.formImage.js @@ -3,7 +3,8 @@ import { Link } from 'react-router-dom' import { session } from 'app/session' import actions from 'app/actions' -import { capitalize, preloadImage, cropImage } from 'app/utils' +import { capitalize } from 'app/utils' +import { preloadImage, cropImage } from 'app/utils/image.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' diff --git a/animism-align/frontend/app/views/viewer/viewer.actions.js b/animism-align/frontend/app/views/viewer/viewer.actions.js index 71dfa46..1113680 100644 --- a/animism-align/frontend/app/views/viewer/viewer.actions.js +++ b/animism-align/frontend/app/views/viewer/viewer.actions.js @@ -12,8 +12,9 @@ import { } from 'app/constants' import { floatInRange, timestampToSeconds } from 'app/utils' import { buildParagraphs } from 'app/utils/transcript.utils' -import { annotationFadeTimings } from 'app/utils/annotation.utils' +import { annotationFadeTimings, displayThumbnailURL } from 'app/utils/annotation.utils' import { getNextSection } from 'app/utils/viewer.utils' +import { preloadImages } from 'app/utils/image.utils' /* building the list of sections from the raw annotation list */ @@ -112,6 +113,7 @@ export const loadSections = () => dispatch => { } // build timeline of gallery / carousel advance instructions. this is in reverse so we can step thru it + // TODO: modify this to append these instructions to a list based on media_id, so we can grab it for the gallery if (GALLERY_UTILITY_ANNOTATION_TYPES.has(annotation.type) && currentSection.fullscreenTimeline.length) { const lastTimelineEvent = currentSection.fullscreenTimeline[currentSection.fullscreenTimeline.length - 1] annotation.settings.frame_index = parseInt(annotation.settings.frame_index) @@ -178,7 +180,7 @@ export const loadSections = () => dispatch => { } } if ((!currentSection.fullscreenTimeline.length || time_to_first_fullscreen_element > 0.0) && currentSection.index !== 0) { - // here we should create a dummy curtain event + // this section has no initial fullscreen event, so we should create a blank dummy curtain event sectionColor = currentSection.paragraphs[0].annotations[0].settings.color || 'white' initial_curtain_event = makeFullscreenEvent(0, { start_ts: currentSection.start_ts, @@ -206,6 +208,14 @@ export const loadSections = () => dispatch => { currentSection.inlineParagraphCount = currentSection.paragraphs.filter(p => !p.hidden).length // console.log(i, currentSection.inlineParagraphCount) }) + + // Preload chapter cover URLs + const chapterCurtainImages = sections + .map(section => section.fullscreenTimeline.length && section.fullscreenTimeline[0].mediaItem) + .map(section => displayThumbnailURL(section)) + .filter(s => !!s) + preloadImages(chapterCurtainImages) + // console.log(sections) // console.log(footnoteList) dispatch({ type: types.viewer.load_sections, sections, footnoteList }) |
