import React from "react"; import { css, cx } from "emotion"; import { Box, Flex, IconButton, PseudoBox, Stack, Tooltip, useClipboard, } from "@chakra-ui/core"; import OutfitResetModal from "./OutfitResetModal"; import PosePicker from "./PosePicker"; import SpeciesColorPicker from "./SpeciesColorPicker"; import useOutfitAppearance from "./useOutfitAppearance"; /** * OutfitControls is the set of controls layered over the outfit preview, to * control things like species/color and sharing links! */ function OutfitControls({ outfitState, dispatchToOutfit }) { const [focusIsLocked, setFocusIsLocked] = React.useState(false); return ( {/** * We try to center the species/color picker, but the left spacer will * shrink more than the pose picker container if we run out of space! */} setFocusIsLocked(true)} onUnlockFocus={() => setFocusIsLocked(false)} /> ); } /** * DownloadButton downloads the outfit as an image! */ function DownloadButton({ outfitState }) { const { visibleLayers } = useOutfitAppearance(outfitState); const [downloadImageUrl, prepareDownload] = useDownloadableImage( visibleLayers ); return ( ); } /** * CopyLinkButton copies the outfit URL to the clipboard! */ function CopyLinkButton({ outfitState }) { const { onCopy, hasCopied } = useClipboard(outfitState.url); return ( ); } /** * BackButton opens a reset modal to let you clear the outfit or enter a new * pet's name to start from! */ function BackButton({ dispatchToOutfit }) { const [showResetModal, setShowResetModal] = React.useState(false); return ( <> setShowResetModal(true)} /> setShowResetModal(false)} dispatchToOutfit={dispatchToOutfit} /> ); } /** * ControlButton is a UI helper to render the cute round buttons we use in * OutfitControls! */ function ControlButton({ icon, "aria-label": ariaLabel, ...props }) { return ( ); } /** * useDownloadableImage loads the image data and generates the downloadable * image URL. */ function useDownloadableImage(visibleLayers) { const [downloadImageUrl, setDownloadImageUrl] = React.useState(null); const [preparedForLayerIds, setPreparedForLayerIds] = React.useState([]); const prepareDownload = React.useCallback(async () => { // Skip if the current image URL is already correct for these layers. const layerIds = visibleLayers.map((l) => l.id); if (layerIds.join(",") === preparedForLayerIds.join(",")) { return; } // Skip if there are no layers. (This probably means we're still loading!) if (layerIds.length === 0) { return; } 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 images = await Promise.all(imagePromises); const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); canvas.width = 600; canvas.height = 600; for (const image of images) { context.drawImage(image, 0, 0); } console.log( "Generated image for download", layerIds, canvas.toDataURL("image/png") ); setDownloadImageUrl(canvas.toDataURL("image/png")); setPreparedForLayerIds(layerIds); }, [preparedForLayerIds, visibleLayers]); return [downloadImageUrl, prepareDownload]; } export default OutfitControls;