Add "Use DTI's image archive" option
just a lil thing for people to turn on if it gets truly miserable again!!
This commit is contained in:
parent
8dee9ddbed
commit
4619e86ae0
7 changed files with 117 additions and 36 deletions
|
@ -43,6 +43,7 @@ import { loadImage, useLocalStorage } from "../util";
|
|||
import useCurrentUser from "../components/useCurrentUser";
|
||||
import useOutfitAppearance from "../components/useOutfitAppearance";
|
||||
import OutfitKnownGlitchesBadge from "./OutfitKnownGlitchesBadge";
|
||||
import usePreferArchive from "../components/usePreferArchive";
|
||||
|
||||
/**
|
||||
* OutfitControls is the set of controls layered over the outfit preview, to
|
||||
|
@ -500,28 +501,56 @@ function SettingsButton({ onLockFocus, onUnlockFocus }) {
|
|||
|
||||
function HiResModeSetting() {
|
||||
const [hiResMode, setHiResMode] = useLocalStorage("DTIHiResMode", false);
|
||||
const [preferArchive, setPreferArchive] = usePreferArchive();
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<Flex>
|
||||
<Box>
|
||||
<FormLabel htmlFor="hi-res-mode-setting" fontSize="sm" margin="0">
|
||||
Hi-res mode (SVG)
|
||||
</FormLabel>
|
||||
<FormHelperText marginTop="0" fontSize="xs">
|
||||
Crisper at higher resolutions, but not always accurate
|
||||
</FormHelperText>
|
||||
</Box>
|
||||
<Box width="2" />
|
||||
<Switch
|
||||
id="hi-res-mode-setting"
|
||||
size="sm"
|
||||
marginTop="0.1rem"
|
||||
isChecked={hiResMode}
|
||||
onChange={(e) => setHiResMode(e.target.checked)}
|
||||
/>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
<Box>
|
||||
<FormControl>
|
||||
<Flex>
|
||||
<Box>
|
||||
<FormLabel htmlFor="hi-res-mode-setting" fontSize="sm" margin="0">
|
||||
Hi-res mode (SVG)
|
||||
</FormLabel>
|
||||
<FormHelperText marginTop="0" fontSize="xs">
|
||||
Crisper at higher resolutions, but not always accurate
|
||||
</FormHelperText>
|
||||
</Box>
|
||||
<Box width="2" />
|
||||
<Switch
|
||||
id="hi-res-mode-setting"
|
||||
size="sm"
|
||||
marginTop="0.1rem"
|
||||
isChecked={hiResMode}
|
||||
onChange={(e) => setHiResMode(e.target.checked)}
|
||||
/>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
<Box height="2" />
|
||||
<FormControl>
|
||||
<Flex>
|
||||
<Box>
|
||||
<FormLabel
|
||||
htmlFor="prefer-archive-setting"
|
||||
fontSize="sm"
|
||||
margin="0"
|
||||
>
|
||||
Use DTI's image archive
|
||||
</FormLabel>
|
||||
<FormHelperText marginTop="0" fontSize="xs">
|
||||
Turn this on when images.neopets.com is slow!
|
||||
</FormHelperText>
|
||||
</Box>
|
||||
<Box width="2" />
|
||||
<Switch
|
||||
id="prefer-archive-setting"
|
||||
size="sm"
|
||||
marginTop="0.1rem"
|
||||
isChecked={preferArchive ?? false}
|
||||
onChange={(e) => setPreferArchive(e.target.checked)}
|
||||
/>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -585,6 +614,7 @@ function ControlButton({ icon, "aria-label": ariaLabel, ...props }) {
|
|||
*/
|
||||
function useDownloadableImage(visibleLayers) {
|
||||
const [hiResMode] = useLocalStorage("DTIHiResMode", false);
|
||||
const [preferArchive] = usePreferArchive();
|
||||
|
||||
const [downloadImageUrl, setDownloadImageUrl] = React.useState(null);
|
||||
const [preparedForLayerIds, setPreparedForLayerIds] = React.useState([]);
|
||||
|
@ -611,6 +641,7 @@ function useDownloadableImage(visibleLayers) {
|
|||
const imagePromises = visibleLayers.map((layer) =>
|
||||
loadImage(getBestImageUrlForLayer(layer, { hiResMode }), {
|
||||
crossOrigin: "anonymous",
|
||||
preferArchive,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -644,7 +675,7 @@ function useDownloadableImage(visibleLayers) {
|
|||
);
|
||||
setDownloadImageUrl(canvas.toDataURL("image/png"));
|
||||
setPreparedForLayerIds(layerIds);
|
||||
}, [preparedForLayerIds, visibleLayers, toast, hiResMode]);
|
||||
}, [preparedForLayerIds, visibleLayers, toast, hiResMode, preferArchive]);
|
||||
|
||||
return [downloadImageUrl, prepareDownload];
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import Link from "next/link";
|
|||
|
||||
import SquareItemCard from "./SquareItemCard";
|
||||
import { safeImageUrl, useCommonStyles } from "../util";
|
||||
import usePreferArchive from "./usePreferArchive";
|
||||
|
||||
function ItemCard({ item, badges, variant = "list", ...props }) {
|
||||
const { brightBackground } = useCommonStyles();
|
||||
|
@ -105,6 +106,7 @@ export function ItemThumbnail({
|
|||
focusSelector,
|
||||
...props
|
||||
}) {
|
||||
const [preferArchive] = usePreferArchive();
|
||||
const theme = useTheme();
|
||||
|
||||
const borderColor = useColorModeValue(
|
||||
|
@ -170,7 +172,7 @@ export function ItemThumbnail({
|
|||
as="img"
|
||||
width="100%"
|
||||
height="100%"
|
||||
src={safeImageUrl(item.thumbnailUrl)}
|
||||
src={safeImageUrl(item.thumbnailUrl, { preferArchive })}
|
||||
alt={`Thumbnail art for ${item.name}`}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -3,6 +3,7 @@ import LRU from "lru-cache";
|
|||
import { Box, Grid, useToast } from "@chakra-ui/react";
|
||||
|
||||
import { loadImage, logAndCapture, safeImageUrl } from "../util";
|
||||
import usePreferArchive from "./usePreferArchive";
|
||||
|
||||
// Import EaselJS and TweenJS directly into the `window` object! The bundled
|
||||
// scripts are built to attach themselves to `window.createjs`, and
|
||||
|
@ -24,6 +25,7 @@ function OutfitMovieLayer({
|
|||
onLowFps = null,
|
||||
canvasProps = {},
|
||||
}) {
|
||||
const [preferArchive] = usePreferArchive();
|
||||
const [stage, setStage] = React.useState(null);
|
||||
const [library, setLibrary] = React.useState(null);
|
||||
const [movieClip, setMovieClip] = React.useState(null);
|
||||
|
@ -129,7 +131,7 @@ function OutfitMovieLayer({
|
|||
React.useEffect(() => {
|
||||
let canceled = false;
|
||||
|
||||
const movieLibraryPromise = loadMovieLibrary(libraryUrl);
|
||||
const movieLibraryPromise = loadMovieLibrary(libraryUrl, { preferArchive });
|
||||
movieLibraryPromise
|
||||
.then((library) => {
|
||||
if (canceled) {
|
||||
|
@ -154,7 +156,7 @@ function OutfitMovieLayer({
|
|||
setLibrary(null);
|
||||
setMovieClip(null);
|
||||
};
|
||||
}, [libraryUrl, onError]);
|
||||
}, [libraryUrl, preferArchive, onError]);
|
||||
|
||||
// This effect puts the `movieClip` on the `stage`, when both are ready.
|
||||
React.useEffect(() => {
|
||||
|
@ -305,7 +307,7 @@ function loadScriptTag(src) {
|
|||
|
||||
const MOVIE_LIBRARY_CACHE = new LRU(10);
|
||||
|
||||
export function loadMovieLibrary(librarySrc) {
|
||||
export function loadMovieLibrary(librarySrc, { preferArchive = false } = {}) {
|
||||
const cancelableResourcePromises = [];
|
||||
const cancelAllResources = () =>
|
||||
cancelableResourcePromises.forEach((p) => p.cancel());
|
||||
|
@ -323,7 +325,9 @@ export function loadMovieLibrary(librarySrc) {
|
|||
}
|
||||
|
||||
// Then, load the script tag. (Make sure we set it up to be cancelable!)
|
||||
const scriptPromise = loadScriptTag(safeImageUrl(librarySrc));
|
||||
const scriptPromise = loadScriptTag(
|
||||
safeImageUrl(librarySrc, { preferArchive })
|
||||
);
|
||||
cancelableResourcePromises.push(scriptPromise);
|
||||
await scriptPromise;
|
||||
|
||||
|
@ -372,6 +376,7 @@ export function loadMovieLibrary(librarySrc) {
|
|||
id,
|
||||
loadImage(librarySrcDir + "/" + src, {
|
||||
crossOrigin: "anonymous",
|
||||
preferArchive,
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
|
|
@ -20,6 +20,7 @@ import OutfitMovieLayer, {
|
|||
import HangerSpinner from "./HangerSpinner";
|
||||
import { loadImage, safeImageUrl, useLocalStorage } from "../util";
|
||||
import useOutfitAppearance from "./useOutfitAppearance";
|
||||
import usePreferArchive from "./usePreferArchive";
|
||||
|
||||
/**
|
||||
* OutfitPreview is for rendering a full outfit! It accepts outfit data,
|
||||
|
@ -169,6 +170,7 @@ export function OutfitLayers({
|
|||
...props
|
||||
}) {
|
||||
const [hiResMode] = useLocalStorage("DTIHiResMode", false);
|
||||
const [preferArchive] = usePreferArchive();
|
||||
|
||||
const containerRef = React.useRef(null);
|
||||
const [canvasSize, setCanvasSize] = React.useState(0);
|
||||
|
@ -281,7 +283,8 @@ export function OutfitLayers({
|
|||
<Box
|
||||
as="img"
|
||||
src={safeImageUrl(
|
||||
getBestImageUrlForLayer(layer, { hiResMode })
|
||||
getBestImageUrlForLayer(layer, { hiResMode }),
|
||||
{ preferArchive }
|
||||
)}
|
||||
alt=""
|
||||
objectFit="contain"
|
||||
|
@ -367,6 +370,7 @@ export function getBestImageUrlForLayer(layer, { hiResMode = false } = {}) {
|
|||
*/
|
||||
export function usePreloadLayers(layers) {
|
||||
const [hiResMode] = useLocalStorage("DTIHiResMode", false);
|
||||
const [preferArchive] = usePreferArchive();
|
||||
|
||||
const [error, setError] = React.useState(null);
|
||||
const [loadedLayers, setLoadedLayers] = React.useState([]);
|
||||
|
@ -393,7 +397,8 @@ export function usePreloadLayers(layers) {
|
|||
const movieAssetPromises = [];
|
||||
for (const layer of layers) {
|
||||
const imageAssetPromise = loadImage(
|
||||
getBestImageUrlForLayer(layer, { hiResMode })
|
||||
getBestImageUrlForLayer(layer, { hiResMode }),
|
||||
{ preferArchive }
|
||||
);
|
||||
imageAssetPromises.push(imageAssetPromise);
|
||||
|
||||
|
@ -402,7 +407,8 @@ export function usePreloadLayers(layers) {
|
|||
// request will still be the image, which we'll show as a
|
||||
// placeholder, which should usually be noticeably faster!
|
||||
const movieLibraryPromise = loadMovieLibrary(
|
||||
layer.canvasMovieLibraryUrl
|
||||
layer.canvasMovieLibraryUrl,
|
||||
{ preferArchive }
|
||||
);
|
||||
const movieAssetPromise = movieLibraryPromise.then((library) => ({
|
||||
library,
|
||||
|
@ -465,7 +471,7 @@ export function usePreloadLayers(layers) {
|
|||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [layers, hiResMode]);
|
||||
}, [layers, hiResMode, preferArchive]);
|
||||
|
||||
return { loading, error, loadedLayers, layersHaveAnimations };
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import Link from "next/link";
|
|||
|
||||
import { safeImageUrl, useCommonStyles } from "../util";
|
||||
import { CheckIcon, CloseIcon, StarIcon } from "@chakra-ui/icons";
|
||||
import usePreferArchive from "./usePreferArchive";
|
||||
|
||||
function SquareItemCard({
|
||||
item,
|
||||
|
@ -183,6 +184,7 @@ function SquareItemCardLayout({
|
|||
}
|
||||
|
||||
function ItemThumbnail({ item, tradeMatchingMode }) {
|
||||
const [preferArchive] = usePreferArchive();
|
||||
const kindColorScheme = item.isNc ? "purple" : item.isPb ? "orange" : "gray";
|
||||
|
||||
const thumbnailShadowColor = useColorModeValue(
|
||||
|
@ -229,7 +231,7 @@ function ItemThumbnail({ item, tradeMatchingMode }) {
|
|||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={safeImageUrl(item.thumbnailUrl)}
|
||||
src={safeImageUrl(item.thumbnailUrl, { preferArchive })}
|
||||
alt={`Thumbnail art for ${item.name}`}
|
||||
width={80}
|
||||
height={80}
|
||||
|
|
22
src/app/components/usePreferArchive.js
Normal file
22
src/app/components/usePreferArchive.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { useLocalStorage } from "../util";
|
||||
|
||||
/**
|
||||
* usePreferArchive helps the user choose to try using our archive before
|
||||
* using images.neopets.com, when images.neopets.com is being slow and bleh!
|
||||
*/
|
||||
function usePreferArchive() {
|
||||
const [preferArchiveSavedValue, setPreferArchive] = useLocalStorage(
|
||||
"DTIPreferArchive",
|
||||
null
|
||||
);
|
||||
|
||||
// Oct 13 2022: I might default this back to on again if the lag gets
|
||||
// miserable again, but it's okaaay right now? ish? Bad enough that I want to
|
||||
// offer this option, but decent enough that I don't want to turn it on by
|
||||
// default and break new items yet!
|
||||
const preferArchive = preferArchiveSavedValue ?? false;
|
||||
|
||||
return [preferArchive, setPreferArchive];
|
||||
}
|
||||
|
||||
export default usePreferArchive;
|
|
@ -115,7 +115,10 @@ export function useCommonStyles() {
|
|||
/**
|
||||
* safeImageUrl returns an HTTPS-safe image URL for Neopets assets!
|
||||
*/
|
||||
export function safeImageUrl(urlString, { crossOrigin = null } = {}) {
|
||||
export function safeImageUrl(
|
||||
urlString,
|
||||
{ crossOrigin = null, preferArchive = false } = {}
|
||||
) {
|
||||
if (urlString == null) {
|
||||
return urlString;
|
||||
}
|
||||
|
@ -148,7 +151,14 @@ export function safeImageUrl(urlString, { crossOrigin = null } = {}) {
|
|||
url.origin === "https://images.neopets.com"
|
||||
) {
|
||||
url.protocol = "https:";
|
||||
if (crossOrigin) {
|
||||
if (preferArchive) {
|
||||
const archiveUrl = new URL(
|
||||
`/api/readFromArchive`,
|
||||
window.location.origin
|
||||
);
|
||||
archiveUrl.search = new URLSearchParams({ url: url.toString() });
|
||||
url = archiveUrl;
|
||||
} else if (crossOrigin) {
|
||||
url.host = "images.neopets-asset-proxy.openneo.net";
|
||||
}
|
||||
} else if (
|
||||
|
@ -161,7 +171,7 @@ export function safeImageUrl(urlString, { crossOrigin = null } = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
if (url.protocol !== "https:") {
|
||||
if (url.protocol !== "https:" && url.hostname !== "localhost") {
|
||||
logAndCapture(
|
||||
new Error(
|
||||
`safeImageUrl was provided an unsafe URL, but we don't know how to ` +
|
||||
|
@ -337,8 +347,11 @@ export function useLocalStorage(key, initialValue) {
|
|||
return [storedValue, setValue];
|
||||
}
|
||||
|
||||
export function loadImage(rawSrc, { crossOrigin = null } = {}) {
|
||||
const src = safeImageUrl(rawSrc, { crossOrigin });
|
||||
export function loadImage(
|
||||
rawSrc,
|
||||
{ crossOrigin = null, preferArchive = false } = {}
|
||||
) {
|
||||
const src = safeImageUrl(rawSrc, { crossOrigin, preferArchive });
|
||||
const image = new Image();
|
||||
let canceled = false;
|
||||
let resolved = false;
|
||||
|
|
Loading…
Reference in a new issue