import React from "react";
import { css, cx } from "emotion";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import { Box, Flex, Spinner, Text } from "@chakra-ui/core";
import { WarningIcon } from "@chakra-ui/icons";
import useOutfitAppearance from "./useOutfitAppearance";
/**
* OutfitPreview renders the actual image layers for the outfit we're viewing!
*/
function OutfitPreview({ outfitState }) {
const {
loading: loading1,
error: error1,
visibleLayers,
} = useOutfitAppearance(outfitState);
const { loading: loading2, error: error2, loadedLayers } = usePreloadLayers(
visibleLayers
);
if (error1 || error2) {
return (
Could not load preview. Try again?
);
}
return (
);
}
/**
* OutfitLayers is the raw UI component for rendering outfit layers. It's
* used both in the main outfit preview, and in other minor UIs!
*/
export function OutfitLayers({ loading, visibleLayers, doAnimations = false }) {
return (
{visibleLayers.map((layer) => (
finishes preloading and
// applies the src to the underlying .
className={cx(
css`
object-fit: contain;
max-width: 100%;
max-height: 100%;
&.do-animations {
animation: fade-in 0.2s;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
`,
doAnimations && "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"
/>
))}
);
}
function FullScreenCenter({ children }) {
return (
{children}
);
}
function getBestImageUrlForLayer(layer) {
if (layer.svgUrl) {
return `/api/assetProxy?url=${encodeURIComponent(layer.svgUrl)}`;
} else {
return layer.imageUrl;
}
}
function loadImage(url) {
const image = new Image();
const promise = new Promise((resolve, reject) => {
image.onload = () => resolve();
image.onerror = () => reject();
image.src = url;
});
promise.cancel = () => {
image.src = "";
};
return promise;
}
/**
* 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
* all the new layers are ready, then show them all at once!
*/
function usePreloadLayers(layers) {
const [error, setError] = React.useState(null);
const [loadedLayers, setLoadedLayers] = React.useState([]);
React.useEffect(() => {
let canceled = false;
setError(null);
const loadImages = async () => {
const imagePromises = layers.map(getBestImageUrlForLayer).map(loadImage);
try {
// TODO: Load in one at a time, under a loading spinner & delay?
await Promise.all(imagePromises);
} catch (e) {
if (canceled) return;
console.error("Error preloading outfit layers", e);
imagePromises.forEach((p) => p.cancel());
setError(e);
return;
}
if (canceled) return;
setLoadedLayers(layers);
};
loadImages();
return () => {
canceled = true;
};
}, [layers]);
// NOTE: This condition would need to change if we started loading one at a
// time, or if the error case would need to show a partial state!
const loading = loadedLayers !== layers;
return { loading, error, loadedLayers };
}
export default OutfitPreview;