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 (
- : }
- size="sm"
- color="gray.100"
- variant="outline"
- borderColor="gray.200"
- borderRadius="full"
- backgroundColor="blackAlpha.600"
- boxShadow="md"
- position="absolute"
- _hover={{
- backgroundColor: "gray.600",
- borderColor: "gray.50",
- color: "gray.50",
- }}
- _focus={{
- backgroundColor: "gray.600",
- borderColor: "gray.50",
- color: "gray.50",
- }}
onClick={() => setIsPaused(!isPaused)}
{...props}
>
{isPaused ? <>Paused> : <>Playing>}
-
+
);
}
);
+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" };