extract EaselCanvas to its own file, basic stories
249
src/app/components/EaselCanvas.js
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const EaselContext = React.createContext({
|
||||||
|
stage: null,
|
||||||
|
addResizeListener: () => {},
|
||||||
|
removeResizeListener: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
function EaselCanvas({ children, width, height }) {
|
||||||
|
const [stage, setStage] = React.useState(null);
|
||||||
|
const resizeListenersRef = React.useRef([]);
|
||||||
|
const canvasRef = React.useRef(null);
|
||||||
|
|
||||||
|
const { loading } = useEaselDependenciesLoader();
|
||||||
|
|
||||||
|
React.useLayoutEffect(() => {
|
||||||
|
if (loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stage = new window.createjs.Stage(canvasRef.current);
|
||||||
|
setStage(stage);
|
||||||
|
|
||||||
|
function onTick(event) {
|
||||||
|
stage.update(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.createjs.Ticker.timingMode = window.createjs.Ticker.RAF;
|
||||||
|
window.createjs.Ticker.on("tick", onTick);
|
||||||
|
|
||||||
|
return () => window.createjs.Ticker.off("tick", onTick);
|
||||||
|
}, [loading]);
|
||||||
|
|
||||||
|
const addChild = React.useCallback(
|
||||||
|
(child, zIndex, { afterFirstDraw = null } = {}) => {
|
||||||
|
// Save this child's z-index for future sorting.
|
||||||
|
child.DTI_zIndex = zIndex;
|
||||||
|
// Add the child, then slot it into the right place in the order.
|
||||||
|
stage.addChild(child);
|
||||||
|
stage.sortChildren((a, b) => a.DTI_zIndex - b.DTI_zIndex);
|
||||||
|
// Then update in bulk!
|
||||||
|
stage.update();
|
||||||
|
if (afterFirstDraw) {
|
||||||
|
stage.on("drawend", afterFirstDraw, null, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[stage]
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeChild = React.useCallback(
|
||||||
|
(child) => {
|
||||||
|
stage.removeChild(child);
|
||||||
|
stage.update();
|
||||||
|
},
|
||||||
|
[stage]
|
||||||
|
);
|
||||||
|
|
||||||
|
const addResizeListener = React.useCallback((handler) => {
|
||||||
|
resizeListenersRef.current.push(handler);
|
||||||
|
}, []);
|
||||||
|
const removeResizeListener = React.useCallback((handler) => {
|
||||||
|
resizeListenersRef.current = resizeListenersRef.current.filter(
|
||||||
|
(h) => h !== handler
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// When the canvas resizes, resize all the layers, then a single bulk update.
|
||||||
|
React.useEffect(() => {
|
||||||
|
for (const handler of resizeListenersRef.current) {
|
||||||
|
handler();
|
||||||
|
}
|
||||||
|
if (stage) {
|
||||||
|
stage.update();
|
||||||
|
}
|
||||||
|
}, [stage, width, height]);
|
||||||
|
|
||||||
|
// Set the canvas's internal dimensions to be higher, if the device has high
|
||||||
|
// DPI like retina. But we'll keep the layout width/height as expected!
|
||||||
|
const internalWidth = width * window.devicePixelRatio;
|
||||||
|
const internalHeight = height * window.devicePixelRatio;
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EaselContext.Provider
|
||||||
|
value={{
|
||||||
|
width: internalWidth,
|
||||||
|
height: internalHeight,
|
||||||
|
addChild,
|
||||||
|
removeChild,
|
||||||
|
addResizeListener,
|
||||||
|
removeResizeListener,
|
||||||
|
stage, // Not used, but available for debugging.
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
width={internalWidth}
|
||||||
|
height={internalHeight}
|
||||||
|
style={{
|
||||||
|
width: width + "px",
|
||||||
|
height: height + "px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{stage && children}
|
||||||
|
</EaselContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EaselBitmap({ src, zIndex }) {
|
||||||
|
const {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
addChild,
|
||||||
|
removeChild,
|
||||||
|
addResizeListener,
|
||||||
|
removeResizeListener,
|
||||||
|
} = React.useContext(EaselContext);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
let image;
|
||||||
|
let bitmap;
|
||||||
|
let tween;
|
||||||
|
|
||||||
|
function setBitmapSize() {
|
||||||
|
bitmap.scaleX = width / image.width;
|
||||||
|
bitmap.scaleY = height / image.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addBitmap() {
|
||||||
|
image = await loadImage(src);
|
||||||
|
bitmap = new window.createjs.Bitmap(image);
|
||||||
|
|
||||||
|
// We're gonna fade in! Wait for the first frame to draw, to make the
|
||||||
|
// timing smooth, but yeah here we go!
|
||||||
|
bitmap.alpha = 0;
|
||||||
|
tween = window.createjs.Tween.get(bitmap, { paused: true }).to(
|
||||||
|
{ alpha: 1 },
|
||||||
|
200
|
||||||
|
);
|
||||||
|
const startFadeIn = () => {
|
||||||
|
// NOTE: You must cache bitmaps to apply filters to them, and caching
|
||||||
|
// doesn't work until the first draw.
|
||||||
|
bitmap.cache(0, 0, image.width, image.height);
|
||||||
|
tween.paused = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
setBitmapSize();
|
||||||
|
addChild(bitmap, zIndex, { afterFirstDraw: startFadeIn });
|
||||||
|
addResizeListener(setBitmapSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBitmap() {
|
||||||
|
removeResizeListener(setBitmapSize);
|
||||||
|
removeChild(bitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
addBitmap();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (bitmap) {
|
||||||
|
// Reverse the fade-in into a fade-out, then remove the bitmap.
|
||||||
|
tween.reversed = true;
|
||||||
|
tween.setPosition(0);
|
||||||
|
tween.paused = false;
|
||||||
|
tween.on("complete", removeBitmap, null, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
src,
|
||||||
|
zIndex,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
addChild,
|
||||||
|
removeChild,
|
||||||
|
addResizeListener,
|
||||||
|
removeResizeListener,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* useEaselDependenciesLoader loads the CreateJS scripts we use in EaselCanvas.
|
||||||
|
* We load it as part of EaselCanvas, but callers can also use this to preload
|
||||||
|
* the scripts and track loading progress.
|
||||||
|
*/
|
||||||
|
export function useEaselDependenciesLoader() {
|
||||||
|
// NOTE: I couldn't find an official NPM source for this that worked with
|
||||||
|
// Webpack, and I didn't want to rely on random people's ports, and I
|
||||||
|
// couldn't get a bundled version to work quite right. So we load
|
||||||
|
// createjs async!
|
||||||
|
const easelLoading = useScriptTag(
|
||||||
|
"https://code.createjs.com/1.0.0/easeljs.min.js"
|
||||||
|
);
|
||||||
|
const tweenLoading = useScriptTag(
|
||||||
|
"https://code.createjs.com/1.0.0/tweenjs.min.js"
|
||||||
|
);
|
||||||
|
|
||||||
|
return { loading: easelLoading || tweenLoading };
|
||||||
|
}
|
||||||
|
|
||||||
|
function useScriptTag(src) {
|
||||||
|
const [loading, setLoading] = React.useState(true);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const existingScript = document.querySelector(
|
||||||
|
`script[src=${CSS.escape(src)}]`
|
||||||
|
);
|
||||||
|
if (existingScript) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let canceled = false;
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.onload = () => {
|
||||||
|
if (!canceled) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
script.src = src;
|
||||||
|
document.body.appendChild(script);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canceled = true;
|
||||||
|
setLoading(true);
|
||||||
|
};
|
||||||
|
}, [src, setLoading]);
|
||||||
|
|
||||||
|
return loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 default EaselCanvas;
|
|
@ -4,6 +4,11 @@ import { WarningIcon } from "@chakra-ui/icons";
|
||||||
import { css, cx } from "emotion";
|
import { css, cx } from "emotion";
|
||||||
import { CSSTransition, TransitionGroup } from "react-transition-group";
|
import { CSSTransition, TransitionGroup } from "react-transition-group";
|
||||||
|
|
||||||
|
import EaselCanvas, {
|
||||||
|
EaselBitmap,
|
||||||
|
loadImage,
|
||||||
|
useEaselDependenciesLoader,
|
||||||
|
} from "./EaselCanvas";
|
||||||
import HangerSpinner from "./HangerSpinner";
|
import HangerSpinner from "./HangerSpinner";
|
||||||
import useOutfitAppearance from "./useOutfitAppearance";
|
import useOutfitAppearance from "./useOutfitAppearance";
|
||||||
|
|
||||||
|
@ -83,29 +88,20 @@ export function OutfitLayers({
|
||||||
doTransitions = false,
|
doTransitions = false,
|
||||||
engine = "images",
|
engine = "images",
|
||||||
}) {
|
}) {
|
||||||
// NOTE: I couldn't find an official NPM source for this that worked with
|
|
||||||
// Webpack, and I didn't want to rely on random people's ports, and I
|
|
||||||
// couldn't get a bundled version to work quite right. So we load
|
|
||||||
// createjs async!
|
|
||||||
const easelLoading = useScriptTag(
|
|
||||||
"https://code.createjs.com/1.0.0/easeljs.min.js"
|
|
||||||
);
|
|
||||||
const tweenLoading = useScriptTag(
|
|
||||||
"https://code.createjs.com/1.0.0/tweenjs.min.js"
|
|
||||||
);
|
|
||||||
const scriptsLoading = easelLoading || tweenLoading;
|
|
||||||
|
|
||||||
const containerRef = React.useRef(null);
|
const containerRef = React.useRef(null);
|
||||||
const [canvasSize, setCanvasSize] = React.useState(0);
|
const [canvasSize, setCanvasSize] = React.useState(0);
|
||||||
|
|
||||||
const [loadingDelayHasPassed, setLoadingDelayHasPassed] = React.useState(
|
const [loadingDelayHasPassed, setLoadingDelayHasPassed] = React.useState(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { loading: loadingEasel } = useEaselDependenciesLoader();
|
||||||
|
|
||||||
|
const loadingAnything = loading || loadingEasel;
|
||||||
|
|
||||||
// When we start in a loading state, or re-enter a loading state, start the
|
// When we start in a loading state, or re-enter a loading state, start the
|
||||||
// loading delay timer.
|
// loading delay timer.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (loading) {
|
if (loadingAnything) {
|
||||||
setLoadingDelayHasPassed(false);
|
setLoadingDelayHasPassed(false);
|
||||||
const t = setTimeout(
|
const t = setTimeout(
|
||||||
() => setLoadingDelayHasPassed(true),
|
() => setLoadingDelayHasPassed(true),
|
||||||
|
@ -113,7 +109,7 @@ export function OutfitLayers({
|
||||||
);
|
);
|
||||||
return () => clearTimeout(t);
|
return () => clearTimeout(t);
|
||||||
}
|
}
|
||||||
}, [loadingDelayMs, loading]);
|
}, [loadingDelayMs, loadingAnything]);
|
||||||
|
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
function computeAndSizeCanvasSize() {
|
function computeAndSizeCanvasSize() {
|
||||||
|
@ -129,8 +125,6 @@ export function OutfitLayers({
|
||||||
return () => window.removeEventListener("resize", computeAndSizeCanvasSize);
|
return () => window.removeEventListener("resize", computeAndSizeCanvasSize);
|
||||||
}, [setCanvasSize]);
|
}, [setCanvasSize]);
|
||||||
|
|
||||||
console.log(loading, scriptsLoading);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
pos="relative"
|
pos="relative"
|
||||||
|
@ -155,7 +149,7 @@ export function OutfitLayers({
|
||||||
{
|
{
|
||||||
// TODO: A bit of a mess in here! Extract these out?
|
// TODO: A bit of a mess in here! Extract these out?
|
||||||
engine === "canvas" ? (
|
engine === "canvas" ? (
|
||||||
!scriptsLoading && (
|
!loadingEasel && (
|
||||||
<FullScreenCenter>
|
<FullScreenCenter>
|
||||||
<EaselCanvas width={canvasSize} height={canvasSize}>
|
<EaselCanvas width={canvasSize} height={canvasSize}>
|
||||||
{visibleLayers.map((layer) => (
|
{visibleLayers.map((layer) => (
|
||||||
|
@ -235,7 +229,7 @@ export function OutfitLayers({
|
||||||
// also use a timeout to delay the fade-in by 0.5s, but don't delay the
|
// also use a timeout to delay the fade-in by 0.5s, but don't delay the
|
||||||
// fade-out at all. (The timeout was an awkward choice, it was hard to
|
// fade-out at all. (The timeout was an awkward choice, it was hard to
|
||||||
// find a good CSS way to specify this delay well!)
|
// find a good CSS way to specify this delay well!)
|
||||||
opacity={(loading || scriptsLoading) && loadingDelayHasPassed ? 1 : 0}
|
opacity={loadingAnything && loadingDelayHasPassed ? 1 : 0}
|
||||||
transition="opacity 0.2s"
|
transition="opacity 0.2s"
|
||||||
>
|
>
|
||||||
{spinnerVariant === "overlay" && (
|
{spinnerVariant === "overlay" && (
|
||||||
|
@ -280,209 +274,6 @@ export function FullScreenCenter({ children, ...otherProps }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function useScriptTag(src) {
|
|
||||||
const [loading, setLoading] = React.useState(true);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const existingScript = document.querySelector(
|
|
||||||
`script[src=${CSS.escape(src)}]`
|
|
||||||
);
|
|
||||||
if (existingScript) {
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let canceled = false;
|
|
||||||
const script = document.createElement("script");
|
|
||||||
script.onload = () => {
|
|
||||||
if (!canceled) {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
script.src = src;
|
|
||||||
document.body.appendChild(script);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
canceled = true;
|
|
||||||
setLoading(true);
|
|
||||||
};
|
|
||||||
}, [src, setLoading]);
|
|
||||||
|
|
||||||
return loading;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EaselContext = React.createContext({
|
|
||||||
stage: null,
|
|
||||||
addResizeListener: () => {},
|
|
||||||
removeResizeListener: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
function EaselCanvas({ children, width, height }) {
|
|
||||||
const [stage, setStage] = React.useState(null);
|
|
||||||
const resizeListenersRef = React.useRef([]);
|
|
||||||
const canvasRef = React.useRef(null);
|
|
||||||
|
|
||||||
React.useLayoutEffect(() => {
|
|
||||||
const stage = new window.createjs.Stage(canvasRef.current);
|
|
||||||
setStage(stage);
|
|
||||||
|
|
||||||
function onTick(event) {
|
|
||||||
stage.update(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.createjs.Ticker.timingMode = window.createjs.Ticker.RAF;
|
|
||||||
window.createjs.Ticker.on("tick", onTick);
|
|
||||||
|
|
||||||
return () => window.createjs.Ticker.off("tick", onTick);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const addChild = React.useCallback(
|
|
||||||
(child, zIndex, { afterFirstDraw = null } = {}) => {
|
|
||||||
// Save this child's z-index for future sorting.
|
|
||||||
child.DTI_zIndex = zIndex;
|
|
||||||
// Add the child, then slot it into the right place in the order.
|
|
||||||
stage.addChild(child);
|
|
||||||
stage.sortChildren((a, b) => a.DTI_zIndex - b.DTI_zIndex);
|
|
||||||
// Then update in bulk!
|
|
||||||
stage.update();
|
|
||||||
if (afterFirstDraw) {
|
|
||||||
stage.on("drawend", afterFirstDraw, null, true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[stage]
|
|
||||||
);
|
|
||||||
|
|
||||||
const removeChild = React.useCallback(
|
|
||||||
(child) => {
|
|
||||||
stage.removeChild(child);
|
|
||||||
stage.update();
|
|
||||||
},
|
|
||||||
[stage]
|
|
||||||
);
|
|
||||||
|
|
||||||
const addResizeListener = React.useCallback((handler) => {
|
|
||||||
resizeListenersRef.current.push(handler);
|
|
||||||
}, []);
|
|
||||||
const removeResizeListener = React.useCallback((handler) => {
|
|
||||||
resizeListenersRef.current = resizeListenersRef.current.filter(
|
|
||||||
(h) => h !== handler
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// When the canvas resizes, resize all the layers, then a single bulk update.
|
|
||||||
React.useEffect(() => {
|
|
||||||
for (const handler of resizeListenersRef.current) {
|
|
||||||
handler();
|
|
||||||
}
|
|
||||||
if (stage) {
|
|
||||||
stage.update();
|
|
||||||
}
|
|
||||||
}, [stage, width, height]);
|
|
||||||
|
|
||||||
// Set the canvas's internal dimensions to be higher, if the device has high
|
|
||||||
// DPI like retina. But we'll keep the layout width/height as expected!
|
|
||||||
const internalWidth = width * window.devicePixelRatio;
|
|
||||||
const internalHeight = height * window.devicePixelRatio;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<EaselContext.Provider
|
|
||||||
value={{
|
|
||||||
width: internalWidth,
|
|
||||||
height: internalHeight,
|
|
||||||
addChild,
|
|
||||||
removeChild,
|
|
||||||
addResizeListener,
|
|
||||||
removeResizeListener,
|
|
||||||
stage, // Not used, but available for debugging.
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<canvas
|
|
||||||
ref={canvasRef}
|
|
||||||
width={internalWidth}
|
|
||||||
height={internalHeight}
|
|
||||||
style={{
|
|
||||||
width: width + "px",
|
|
||||||
height: height + "px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{stage && children}
|
|
||||||
</EaselContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function EaselBitmap({ src, zIndex }) {
|
|
||||||
const {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
addChild,
|
|
||||||
removeChild,
|
|
||||||
addResizeListener,
|
|
||||||
removeResizeListener,
|
|
||||||
} = React.useContext(EaselContext);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
let image;
|
|
||||||
let bitmap;
|
|
||||||
let tween;
|
|
||||||
|
|
||||||
function setBitmapSize() {
|
|
||||||
bitmap.scaleX = width / image.width;
|
|
||||||
bitmap.scaleY = height / image.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addBitmap() {
|
|
||||||
image = await loadImage(src);
|
|
||||||
bitmap = new window.createjs.Bitmap(image);
|
|
||||||
|
|
||||||
// We're gonna fade in! Wait for the first frame to draw, to make the
|
|
||||||
// timing smooth, but yeah here we go!
|
|
||||||
bitmap.alpha = 0;
|
|
||||||
tween = window.createjs.Tween.get(bitmap, { paused: true }).to(
|
|
||||||
{ alpha: 1 },
|
|
||||||
200
|
|
||||||
);
|
|
||||||
const startFadeIn = () => {
|
|
||||||
// NOTE: You must cache bitmaps to apply filters to them, and caching
|
|
||||||
// doesn't work until the first draw.
|
|
||||||
bitmap.cache(0, 0, image.width, image.height);
|
|
||||||
tween.paused = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
setBitmapSize();
|
|
||||||
addChild(bitmap, zIndex, { afterFirstDraw: startFadeIn });
|
|
||||||
addResizeListener(setBitmapSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeBitmap() {
|
|
||||||
removeResizeListener(setBitmapSize);
|
|
||||||
removeChild(bitmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
addBitmap();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (bitmap) {
|
|
||||||
// Reverse the fade-in into a fade-out, then remove the bitmap.
|
|
||||||
tween.reversed = true;
|
|
||||||
tween.setPosition(0);
|
|
||||||
tween.paused = false;
|
|
||||||
tween.on("complete", removeBitmap, null, true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [
|
|
||||||
src,
|
|
||||||
zIndex,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
addChild,
|
|
||||||
removeChild,
|
|
||||||
addResizeListener,
|
|
||||||
removeResizeListener,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBestImageUrlForLayer(layer) {
|
function getBestImageUrlForLayer(layer) {
|
||||||
if (layer.svgUrl) {
|
if (layer.svgUrl) {
|
||||||
return `/api/assetProxy?url=${encodeURIComponent(layer.svgUrl)}`;
|
return `/api/assetProxy?url=${encodeURIComponent(layer.svgUrl)}`;
|
||||||
|
@ -491,19 +282,6 @@ function getBestImageUrlForLayer(layer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* usePreloadLayers preloads the images for the given layers, and yields them
|
* usePreloadLayers preloads the images for the given layers, and yields them
|
||||||
* when done. This enables us to keep the old outfit preview on screen until
|
* when done. This enables us to keep the old outfit preview on screen until
|
||||||
|
|
37
src/stories/EaselCanvas.stories.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import EaselCanvas, { EaselBitmap } from "../app/components/EaselCanvas";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Dress to Impress/EaselCanvas",
|
||||||
|
component: EaselCanvas,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BlueAcara = () => (
|
||||||
|
<EaselCanvas width={300} height={300}>
|
||||||
|
<EaselBitmap
|
||||||
|
src="http://images.neopets.com/cp/bio/data/000/000/002/2426_898928db88/2426.svg"
|
||||||
|
zIndex={10}
|
||||||
|
/>
|
||||||
|
<EaselBitmap
|
||||||
|
src="http://images.neopets.com/cp/bio/data/000/000/002/2425_501f596cef/2425.svg"
|
||||||
|
zIndex={20}
|
||||||
|
/>
|
||||||
|
<EaselBitmap
|
||||||
|
src="http://images.neopets.com/cp/bio/data/000/000/002/2427_f12853f18a/2427.svg"
|
||||||
|
zIndex={30}
|
||||||
|
/>
|
||||||
|
<EaselBitmap
|
||||||
|
src="http://images.neopets.com/cp/bio/data/000/000/032/32185_dc8f076ae3/32185.svg"
|
||||||
|
zIndex={40}
|
||||||
|
/>
|
||||||
|
<EaselBitmap
|
||||||
|
src="http://images.neopets.com/cp/bio/data/000/000/002/2428_991dcdedc7/2428.svg"
|
||||||
|
zIndex={50}
|
||||||
|
/>
|
||||||
|
<EaselBitmap
|
||||||
|
src="http://images.neopets.com/cp/bio/data/000/000/002/2430_87edccba4c/2430.svg"
|
||||||
|
zIndex={60}
|
||||||
|
/>
|
||||||
|
</EaselCanvas>
|
||||||
|
);
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |