diff --git a/src/app/WardrobePage/OutfitControls.js b/src/app/WardrobePage/OutfitControls.js index 83667f9..23d7cd8 100644 --- a/src/app/WardrobePage/OutfitControls.js +++ b/src/app/WardrobePage/OutfitControls.js @@ -5,10 +5,20 @@ import { Button, DarkMode, Flex, + FormControl, + FormHelperText, + FormLabel, + HStack, IconButton, ListItem, + Popover, + PopoverArrow, + PopoverBody, + PopoverContent, + PopoverTrigger, Portal, Stack, + Switch, Tooltip, UnorderedList, useClipboard, @@ -17,8 +27,10 @@ import { import { ArrowBackIcon, CheckIcon, + ChevronDownIcon, DownloadIcon, LinkIcon, + SettingsIcon, } from "@chakra-ui/icons"; import { MdPause, MdPlayArrow } from "react-icons/md"; import { Link } from "react-router-dom"; @@ -151,13 +163,20 @@ function OutfitControls({ - {showAnimationControls && ( - - - - - - )} + + + + {showAnimationControls && } + + + {blinkInState.type === "started" && ( @@ -432,36 +450,95 @@ function PlayPauseButton() { const PlayPauseButtonContent = React.forwardRef( ({ isPaused, setIsPaused, ...props }, ref) => { return ( - + ); } ); +function SettingsButton({ onLockFocus, onUnlockFocus }) { + return ( + + + + + + + + + + + + + + + + + + ); +} + +function HiResModeSetting() { + const [hiResMode, setHiResMode] = useLocalStorage("DTIHiResMode", false); + + return ( + + + + + Hi-res mode (SVG) + + + Crisper at higher resolutions, but not always accurate + + + + setHiResMode(e.target.checked)} + /> + + + ); +} + +const TranslucentButton = React.forwardRef(({ children, ...props }, ref) => { + return ( + + ); +}); + /** * ControlButton is a UI helper to render the cute round buttons we use in * OutfitControls! @@ -493,6 +570,8 @@ function ControlButton({ icon, "aria-label": ariaLabel, ...props }) { * image URL. */ function useDownloadableImage(visibleLayers) { + const [hiResMode] = useLocalStorage("DTIHiResMode", false); + const [downloadImageUrl, setDownloadImageUrl] = React.useState(null); const [preparedForLayerIds, setPreparedForLayerIds] = React.useState([]); const toast = useToast(); @@ -511,8 +590,12 @@ function useDownloadableImage(visibleLayers) { setDownloadImageUrl(null); + // NOTE: You could argue that we may as well just always use PNGs here, + // regardless of hi-res modeā€¦ but using the same src will help both + // performance (can use cached SVG), and predictability (image will + // look like what you see here). const imagePromises = visibleLayers.map((layer) => - loadImage(getBestImageUrlForLayer(layer)) + loadImage(getBestImageUrlForLayer(layer, { hiResMode })) ); let images; @@ -545,7 +628,7 @@ function useDownloadableImage(visibleLayers) { ); setDownloadImageUrl(canvas.toDataURL("image/png")); setPreparedForLayerIds(layerIds); - }, [preparedForLayerIds, visibleLayers, toast]); + }, [preparedForLayerIds, visibleLayers, toast, hiResMode]); return [downloadImageUrl, prepareDownload]; } diff --git a/src/app/components/OutfitPreview.js b/src/app/components/OutfitPreview.js index b05ba78..d964ee7 100644 --- a/src/app/components/OutfitPreview.js +++ b/src/app/components/OutfitPreview.js @@ -124,6 +124,8 @@ export function OutfitLayers({ isPaused = true, ...props }) { + const [hiResMode] = useLocalStorage("DTIHiResMode", false); + const containerRef = React.useRef(null); const [canvasSize, setCanvasSize] = React.useState(0); const [loadingDelayHasPassed, setLoadingDelayHasPassed] = React.useState( @@ -230,13 +232,16 @@ export function OutfitLayers({ ) : ( tags are always allowed through CORS), but // this means we make the same request that the Download // button makes, so it can use the cached version of this // image instead of requesting it again with crossOrigin! - crossOrigin={getBestImageUrlForLayer(layer).crossOrigin} + crossOrigin={ + getBestImageUrlForLayer(layer, { hiResMode }) + .crossOrigin + } alt="" objectFit="contain" maxWidth="100%" @@ -306,8 +311,8 @@ export function FullScreenCenter({ children, ...otherProps }) { ); } -export function getBestImageUrlForLayer(layer) { - if (layer.svgUrl) { +export function getBestImageUrlForLayer(layer, { hiResMode = false } = {}) { + if (hiResMode && layer.svgUrl) { return { src: safeImageUrl(layer.svgUrl), crossOrigin: "anonymous" }; } else { return { src: safeImageUrl(layer.imageUrl), crossOrigin: "anonymous" };