From ad43f58a079342482564acd31257555bdb0d55f6 Mon Sep 17 00:00:00 2001 From: Matchu Date: Sat, 10 Oct 2020 01:19:59 -0700 Subject: [PATCH] fix Download button to use better caching So I broke the Download button when we switched to impress-2020.openneo.net, and I forgot to update the Amazon S3 config. But in addition to that, I'm making some code changes here, to make downloads faster: we now use exactly the same URL and crossOrigin configuration between the tag on the page, and the image that the Download button requests, which ensures that it can use the cached copy instead of loading new stuff. (There were two main cases: 1. it always loaded the PNGs instead of the SVG, which doesn't matter for quality if we're rendering a 600x600 bitmap anyway, but is good caching, and 2. send `crossOrigin` on the tag, which isn't necessary there, but is necessary for Download, and having them match means we can use the cached copy.) --- src/app/WardrobePage/OutfitControls.js | 14 ++++---------- src/app/components/OutfitMovieLayer.js | 17 ++--------------- src/app/components/OutfitPreview.js | 23 +++++++++++------------ src/app/util.js | 16 ++++++++++++++++ 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/app/WardrobePage/OutfitControls.js b/src/app/WardrobePage/OutfitControls.js index 3ea60be..9310b44 100644 --- a/src/app/WardrobePage/OutfitControls.js +++ b/src/app/WardrobePage/OutfitControls.js @@ -21,9 +21,10 @@ import { import { MdPause, MdPlayArrow } from "react-icons/md"; import { Link } from "react-router-dom"; +import { getBestImageUrlForLayer } from "../components/OutfitPreview"; import PosePicker from "./PosePicker"; import SpeciesColorPicker from "../components/SpeciesColorPicker"; -import { useLocalStorage } from "../util"; +import { loadImage, useLocalStorage } from "../util"; import useOutfitAppearance from "../components/useOutfitAppearance"; /** @@ -409,15 +410,8 @@ function useDownloadableImage(visibleLayers) { setDownloadImageUrl(null); - const imagePromises = visibleLayers.map( - (layer) => - new Promise((resolve, reject) => { - const image = new window.Image(); - image.crossOrigin = "Anonymous"; // Requires S3 CORS config! - image.addEventListener("load", () => resolve(image), false); - image.addEventListener("error", (e) => reject(e), false); - image.src = layer.imageUrl + "&xoxo"; - }) + const imagePromises = visibleLayers.map((layer) => + loadImage(getBestImageUrlForLayer(layer)) ); const images = await Promise.all(imagePromises); diff --git a/src/app/components/OutfitMovieLayer.js b/src/app/components/OutfitMovieLayer.js index aa807d3..5b80ccb 100644 --- a/src/app/components/OutfitMovieLayer.js +++ b/src/app/components/OutfitMovieLayer.js @@ -1,6 +1,6 @@ import React from "react"; -import { safeImageUrl } from "../util"; +import { loadImage, safeImageUrl } from "../util"; function OutfitMovieLayer({ libraryUrl, width, height, isPaused = false }) { const [stage, setStage] = React.useState(null); @@ -221,7 +221,7 @@ export async function loadMovieLibrary(librarySrc) { const manifestImages = new Map( library.properties.manifest.map(({ id, src }) => [ id, - loadImage(safeImageUrl(librarySrcDir + "/" + src)), + loadImage({src: safeImageUrl(librarySrcDir + "/" + src)}), ]) ); await Promise.all(manifestImages.values()); @@ -242,19 +242,6 @@ export async function loadMovieLibrary(librarySrc) { return library; } -export function loadImage(url) { - const image = new Image(); - const promise = new Promise((resolve, reject) => { - image.onload = () => resolve(image); - image.onerror = (e) => reject(e); - image.src = url; - }); - promise.cancel = () => { - image.src = ""; - }; - return promise; -} - export function buildMovieClip(library, libraryUrl) { let constructorName; try { diff --git a/src/app/components/OutfitPreview.js b/src/app/components/OutfitPreview.js index 75dcfeb..14e6055 100644 --- a/src/app/components/OutfitPreview.js +++ b/src/app/components/OutfitPreview.js @@ -7,12 +7,11 @@ import { CSSTransition, TransitionGroup } from "react-transition-group"; import OutfitMovieLayer, { buildMovieClip, hasAnimations, - loadImage, loadMovieLibrary, useEaselDependenciesLoader, } from "./OutfitMovieLayer"; import HangerSpinner from "./HangerSpinner"; -import { safeImageUrl, useLocalStorage } from "../util"; +import { loadImage, safeImageUrl, useLocalStorage } from "../util"; import useOutfitAppearance from "./useOutfitAppearance"; /** @@ -190,7 +189,13 @@ export function OutfitLayers({ /> ) : ( tags are always allowed through CORS), but + // this means we make the same request that the Download + // button makes, so it can use the cached version of this + // image instead of requesting it again with crossOrigin! + crossOrigin={getBestImageUrlForLayer(layer).crossOrigin} alt="" // We manage the fade-in and fade-out separately! The fade-in // happens here, when the finishes preloading and @@ -216,12 +221,6 @@ export function OutfitLayers({ `, doTransitions && "do-animations" )} - // This sets up the cache to not need to reload images during - // download! - // TODO: Re-enable this once we get our change into Chakra - // main. For now, this will make Downloads a bit slower, which - // is fine! - // crossOrigin="Anonymous" /> )} @@ -280,11 +279,11 @@ export function FullScreenCenter({ children, ...otherProps }) { ); } -function getBestImageUrlForLayer(layer) { +export function getBestImageUrlForLayer(layer) { if (layer.svgUrl) { - return safeImageUrl(layer.svgUrl); + return { src: safeImageUrl(layer.svgUrl) }; } else { - return layer.imageUrl; + return { src: layer.imageUrl, crossOrigin: "anonymous" }; } } diff --git a/src/app/util.js b/src/app/util.js index a150835..6d95951 100644 --- a/src/app/util.js +++ b/src/app/util.js @@ -225,3 +225,19 @@ export function useLocalStorage(key, initialValue) { return [storedValue, setValue]; } + +export function loadImage({ src, crossOrigin = null }) { + const image = new Image(); + const promise = new Promise((resolve, reject) => { + image.onload = () => resolve(image); + image.onerror = (e) => reject(e); + if (crossOrigin) { + image.crossOrigin = crossOrigin; + } + image.src = src; + }); + promise.cancel = () => { + image.src = ""; + }; + return promise; +}