Downloadable image, wowie!!
This commit is contained in:
parent
66c6998c58
commit
4f7c8b1332
3 changed files with 103 additions and 8 deletions
|
@ -1,3 +1,4 @@
|
|||
* Use accessible click targets for item lists! Honestly, can they be checkboxes?
|
||||
* Pagination for search queries, right now we LIMIT 30
|
||||
* Search needs to restrict by fit!
|
||||
* Undo the local linking we did for @chakra-ui/core, react, and react-dom on Matchu's machine 😅
|
||||
|
|
|
@ -2,7 +2,17 @@ import React from "react";
|
|||
import { CSSTransition, TransitionGroup } from "react-transition-group";
|
||||
import gql from "graphql-tag";
|
||||
import { useQuery } from "@apollo/react-hooks";
|
||||
import { Flex, Image, Spinner, Text, Icon, Box } from "@chakra-ui/core";
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Icon,
|
||||
IconButton,
|
||||
Image,
|
||||
PseudoBox,
|
||||
Spinner,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@chakra-ui/core";
|
||||
|
||||
import { Delay } from "./util";
|
||||
|
||||
|
@ -49,6 +59,11 @@ function OutfitPreview({ outfitState }) {
|
|||
}
|
||||
);
|
||||
|
||||
const visibleLayers = getVisibleLayers(data);
|
||||
const [downloadImageUrl, prepareDownload] = useDownloadableImage(
|
||||
visibleLayers
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<FullScreenCenter>
|
||||
|
@ -62,9 +77,9 @@ function OutfitPreview({ outfitState }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Box pos="relative" height="100%" width="100%">
|
||||
<PseudoBox role="group" pos="relative" height="100%" width="100%">
|
||||
<TransitionGroup>
|
||||
{getVisibleLayers(data).map((layer) => (
|
||||
{visibleLayers.map((layer) => (
|
||||
<CSSTransition
|
||||
key={layer.id}
|
||||
classNames={{
|
||||
|
@ -80,7 +95,9 @@ function OutfitPreview({ outfitState }) {
|
|||
maxWidth="100%"
|
||||
maxHeight="100%"
|
||||
className="outfit-preview-layer-image"
|
||||
crossOrigin="anonymous"
|
||||
// This sets up the cache to not need to reload images during
|
||||
// download!
|
||||
crossOrigin="Anonymous"
|
||||
/>
|
||||
</FullScreenCenter>
|
||||
</CSSTransition>
|
||||
|
@ -101,7 +118,39 @@ function OutfitPreview({ outfitState }) {
|
|||
</FullScreenCenter>
|
||||
</Delay>
|
||||
)}
|
||||
</Box>
|
||||
<Tooltip label="Download" placement="left" showDelay={200}>
|
||||
<IconButton
|
||||
icon="download"
|
||||
aria-label="Download"
|
||||
as="a"
|
||||
// eslint-disable-next-line no-script-url
|
||||
href={downloadImageUrl || "javascript:void 0"}
|
||||
download="Outfit.png"
|
||||
onMouseEnter={prepareDownload}
|
||||
onFocus={prepareDownload}
|
||||
variant="unstyled"
|
||||
color="gray.50"
|
||||
d="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
opacity="0"
|
||||
transition="all 0.2s"
|
||||
_hover={{
|
||||
backgroundColor: "gray.600",
|
||||
}}
|
||||
_focus={{
|
||||
opacity: 1,
|
||||
backgroundColor: "gray.600",
|
||||
}}
|
||||
_groupHover={{
|
||||
opacity: 1,
|
||||
}}
|
||||
pos="absolute"
|
||||
right="1"
|
||||
top="1"
|
||||
/>
|
||||
</Tooltip>
|
||||
</PseudoBox>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -152,4 +201,49 @@ function FullScreenCenter({ children }) {
|
|||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
})
|
||||
);
|
||||
|
||||
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 OutfitPreview;
|
||||
|
|
|
@ -5403,9 +5403,9 @@ flush-write-stream@^1.0.0:
|
|||
readable-stream "^2.3.6"
|
||||
|
||||
focus-lock@^0.6.7:
|
||||
version "0.6.7"
|
||||
resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.7.tgz#65e298f2ba2a3372ab57a4e4c4bdc19e1e32a4e5"
|
||||
integrity sha512-KRo93U/afEqt7w5tBm4t0FHf/Li8tEYav3n4GUiZdeRlRfrtMbL8yQg0xRVnY/kmBRmQ4xkqIlbaMvuqlu53kg==
|
||||
version "0.6.8"
|
||||
resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.8.tgz#61985fadfa92f02f2ee1d90bc738efaf7f3c9f46"
|
||||
integrity sha512-vkHTluRCoq9FcsrldC0ulQHiyBYgVJB2CX53I8r0nTC6KnEij7Of0jpBspjt3/CuNb6fyoj3aOh9J2HgQUM0og==
|
||||
|
||||
follow-redirects@^1.0.0:
|
||||
version "1.10.0"
|
||||
|
|
Loading…
Reference in a new issue