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?
|
* Use accessible click targets for item lists! Honestly, can they be checkboxes?
|
||||||
* Pagination for search queries, right now we LIMIT 30
|
* Pagination for search queries, right now we LIMIT 30
|
||||||
* Search needs to restrict by fit!
|
* 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 { CSSTransition, TransitionGroup } from "react-transition-group";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
import { useQuery } from "@apollo/react-hooks";
|
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";
|
import { Delay } from "./util";
|
||||||
|
|
||||||
|
@ -49,6 +59,11 @@ function OutfitPreview({ outfitState }) {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const visibleLayers = getVisibleLayers(data);
|
||||||
|
const [downloadImageUrl, prepareDownload] = useDownloadableImage(
|
||||||
|
visibleLayers
|
||||||
|
);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<FullScreenCenter>
|
<FullScreenCenter>
|
||||||
|
@ -62,9 +77,9 @@ function OutfitPreview({ outfitState }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box pos="relative" height="100%" width="100%">
|
<PseudoBox role="group" pos="relative" height="100%" width="100%">
|
||||||
<TransitionGroup>
|
<TransitionGroup>
|
||||||
{getVisibleLayers(data).map((layer) => (
|
{visibleLayers.map((layer) => (
|
||||||
<CSSTransition
|
<CSSTransition
|
||||||
key={layer.id}
|
key={layer.id}
|
||||||
classNames={{
|
classNames={{
|
||||||
|
@ -80,7 +95,9 @@ function OutfitPreview({ outfitState }) {
|
||||||
maxWidth="100%"
|
maxWidth="100%"
|
||||||
maxHeight="100%"
|
maxHeight="100%"
|
||||||
className="outfit-preview-layer-image"
|
className="outfit-preview-layer-image"
|
||||||
crossOrigin="anonymous"
|
// This sets up the cache to not need to reload images during
|
||||||
|
// download!
|
||||||
|
crossOrigin="Anonymous"
|
||||||
/>
|
/>
|
||||||
</FullScreenCenter>
|
</FullScreenCenter>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
|
@ -101,7 +118,39 @@ function OutfitPreview({ outfitState }) {
|
||||||
</FullScreenCenter>
|
</FullScreenCenter>
|
||||||
</Delay>
|
</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;
|
export default OutfitPreview;
|
||||||
|
|
|
@ -5403,9 +5403,9 @@ flush-write-stream@^1.0.0:
|
||||||
readable-stream "^2.3.6"
|
readable-stream "^2.3.6"
|
||||||
|
|
||||||
focus-lock@^0.6.7:
|
focus-lock@^0.6.7:
|
||||||
version "0.6.7"
|
version "0.6.8"
|
||||||
resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.7.tgz#65e298f2ba2a3372ab57a4e4c4bdc19e1e32a4e5"
|
resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.8.tgz#61985fadfa92f02f2ee1d90bc738efaf7f3c9f46"
|
||||||
integrity sha512-KRo93U/afEqt7w5tBm4t0FHf/Li8tEYav3n4GUiZdeRlRfrtMbL8yQg0xRVnY/kmBRmQ4xkqIlbaMvuqlu53kg==
|
integrity sha512-vkHTluRCoq9FcsrldC0ulQHiyBYgVJB2CX53I8r0nTC6KnEij7Of0jpBspjt3/CuNb6fyoj3aOh9J2HgQUM0og==
|
||||||
|
|
||||||
follow-redirects@^1.0.0:
|
follow-redirects@^1.0.0:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
|
|
Loading…
Reference in a new issue