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 useCurrentUser from "../components/useCurrentUser";
|
||||||
import useOutfitAppearance from "../components/useOutfitAppearance";
|
import useOutfitAppearance from "../components/useOutfitAppearance";
|
||||||
import OutfitKnownGlitchesBadge from "./OutfitKnownGlitchesBadge";
|
import OutfitKnownGlitchesBadge from "./OutfitKnownGlitchesBadge";
|
||||||
|
import usePreferArchive from "../components/usePreferArchive";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OutfitControls is the set of controls layered over the outfit preview, to
|
* OutfitControls is the set of controls layered over the outfit preview, to
|
||||||
|
@ -500,28 +501,56 @@ function SettingsButton({ onLockFocus, onUnlockFocus }) {
|
||||||
|
|
||||||
function HiResModeSetting() {
|
function HiResModeSetting() {
|
||||||
const [hiResMode, setHiResMode] = useLocalStorage("DTIHiResMode", false);
|
const [hiResMode, setHiResMode] = useLocalStorage("DTIHiResMode", false);
|
||||||
|
const [preferArchive, setPreferArchive] = usePreferArchive();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl>
|
<Box>
|
||||||
<Flex>
|
<FormControl>
|
||||||
<Box>
|
<Flex>
|
||||||
<FormLabel htmlFor="hi-res-mode-setting" fontSize="sm" margin="0">
|
<Box>
|
||||||
Hi-res mode (SVG)
|
<FormLabel htmlFor="hi-res-mode-setting" fontSize="sm" margin="0">
|
||||||
</FormLabel>
|
Hi-res mode (SVG)
|
||||||
<FormHelperText marginTop="0" fontSize="xs">
|
</FormLabel>
|
||||||
Crisper at higher resolutions, but not always accurate
|
<FormHelperText marginTop="0" fontSize="xs">
|
||||||
</FormHelperText>
|
Crisper at higher resolutions, but not always accurate
|
||||||
</Box>
|
</FormHelperText>
|
||||||
<Box width="2" />
|
</Box>
|
||||||
<Switch
|
<Box width="2" />
|
||||||
id="hi-res-mode-setting"
|
<Switch
|
||||||
size="sm"
|
id="hi-res-mode-setting"
|
||||||
marginTop="0.1rem"
|
size="sm"
|
||||||
isChecked={hiResMode}
|
marginTop="0.1rem"
|
||||||
onChange={(e) => setHiResMode(e.target.checked)}
|
isChecked={hiResMode}
|
||||||
/>
|
onChange={(e) => setHiResMode(e.target.checked)}
|
||||||
</Flex>
|
/>
|
||||||
</FormControl>
|
</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) {
|
function useDownloadableImage(visibleLayers) {
|
||||||
const [hiResMode] = useLocalStorage("DTIHiResMode", false);
|
const [hiResMode] = useLocalStorage("DTIHiResMode", false);
|
||||||
|
const [preferArchive] = usePreferArchive();
|
||||||
|
|
||||||
const [downloadImageUrl, setDownloadImageUrl] = React.useState(null);
|
const [downloadImageUrl, setDownloadImageUrl] = React.useState(null);
|
||||||
const [preparedForLayerIds, setPreparedForLayerIds] = React.useState([]);
|
const [preparedForLayerIds, setPreparedForLayerIds] = React.useState([]);
|
||||||
|
@ -611,6 +641,7 @@ function useDownloadableImage(visibleLayers) {
|
||||||
const imagePromises = visibleLayers.map((layer) =>
|
const imagePromises = visibleLayers.map((layer) =>
|
||||||
loadImage(getBestImageUrlForLayer(layer, { hiResMode }), {
|
loadImage(getBestImageUrlForLayer(layer, { hiResMode }), {
|
||||||
crossOrigin: "anonymous",
|
crossOrigin: "anonymous",
|
||||||
|
preferArchive,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -644,7 +675,7 @@ function useDownloadableImage(visibleLayers) {
|
||||||
);
|
);
|
||||||
setDownloadImageUrl(canvas.toDataURL("image/png"));
|
setDownloadImageUrl(canvas.toDataURL("image/png"));
|
||||||
setPreparedForLayerIds(layerIds);
|
setPreparedForLayerIds(layerIds);
|
||||||
}, [preparedForLayerIds, visibleLayers, toast, hiResMode]);
|
}, [preparedForLayerIds, visibleLayers, toast, hiResMode, preferArchive]);
|
||||||
|
|
||||||
return [downloadImageUrl, prepareDownload];
|
return [downloadImageUrl, prepareDownload];
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import Link from "next/link";
|
||||||
|
|
||||||
import SquareItemCard from "./SquareItemCard";
|
import SquareItemCard from "./SquareItemCard";
|
||||||
import { safeImageUrl, useCommonStyles } from "../util";
|
import { safeImageUrl, useCommonStyles } from "../util";
|
||||||
|
import usePreferArchive from "./usePreferArchive";
|
||||||
|
|
||||||
function ItemCard({ item, badges, variant = "list", ...props }) {
|
function ItemCard({ item, badges, variant = "list", ...props }) {
|
||||||
const { brightBackground } = useCommonStyles();
|
const { brightBackground } = useCommonStyles();
|
||||||
|
@ -105,6 +106,7 @@ export function ItemThumbnail({
|
||||||
focusSelector,
|
focusSelector,
|
||||||
...props
|
...props
|
||||||
}) {
|
}) {
|
||||||
|
const [preferArchive] = usePreferArchive();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const borderColor = useColorModeValue(
|
const borderColor = useColorModeValue(
|
||||||
|
@ -170,7 +172,7 @@ export function ItemThumbnail({
|
||||||
as="img"
|
as="img"
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
src={safeImageUrl(item.thumbnailUrl)}
|
src={safeImageUrl(item.thumbnailUrl, { preferArchive })}
|
||||||
alt={`Thumbnail art for ${item.name}`}
|
alt={`Thumbnail art for ${item.name}`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import LRU from "lru-cache";
|
||||||
import { Box, Grid, useToast } from "@chakra-ui/react";
|
import { Box, Grid, useToast } from "@chakra-ui/react";
|
||||||
|
|
||||||
import { loadImage, logAndCapture, safeImageUrl } from "../util";
|
import { loadImage, logAndCapture, safeImageUrl } from "../util";
|
||||||
|
import usePreferArchive from "./usePreferArchive";
|
||||||
|
|
||||||
// Import EaselJS and TweenJS directly into the `window` object! The bundled
|
// Import EaselJS and TweenJS directly into the `window` object! The bundled
|
||||||
// scripts are built to attach themselves to `window.createjs`, and
|
// scripts are built to attach themselves to `window.createjs`, and
|
||||||
|
@ -24,6 +25,7 @@ function OutfitMovieLayer({
|
||||||
onLowFps = null,
|
onLowFps = null,
|
||||||
canvasProps = {},
|
canvasProps = {},
|
||||||
}) {
|
}) {
|
||||||
|
const [preferArchive] = usePreferArchive();
|
||||||
const [stage, setStage] = React.useState(null);
|
const [stage, setStage] = React.useState(null);
|
||||||
const [library, setLibrary] = React.useState(null);
|
const [library, setLibrary] = React.useState(null);
|
||||||
const [movieClip, setMovieClip] = React.useState(null);
|
const [movieClip, setMovieClip] = React.useState(null);
|
||||||
|
@ -129,7 +131,7 @@ function OutfitMovieLayer({
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let canceled = false;
|
let canceled = false;
|
||||||
|
|
||||||
const movieLibraryPromise = loadMovieLibrary(libraryUrl);
|
const movieLibraryPromise = loadMovieLibrary(libraryUrl, { preferArchive });
|
||||||
movieLibraryPromise
|
movieLibraryPromise
|
||||||
.then((library) => {
|
.then((library) => {
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
|
@ -154,7 +156,7 @@ function OutfitMovieLayer({
|
||||||
setLibrary(null);
|
setLibrary(null);
|
||||||
setMovieClip(null);
|
setMovieClip(null);
|
||||||
};
|
};
|
||||||
}, [libraryUrl, onError]);
|
}, [libraryUrl, preferArchive, onError]);
|
||||||
|
|
||||||
// This effect puts the `movieClip` on the `stage`, when both are ready.
|
// This effect puts the `movieClip` on the `stage`, when both are ready.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -305,7 +307,7 @@ function loadScriptTag(src) {
|
||||||
|
|
||||||
const MOVIE_LIBRARY_CACHE = new LRU(10);
|
const MOVIE_LIBRARY_CACHE = new LRU(10);
|
||||||
|
|
||||||
export function loadMovieLibrary(librarySrc) {
|
export function loadMovieLibrary(librarySrc, { preferArchive = false } = {}) {
|
||||||
const cancelableResourcePromises = [];
|
const cancelableResourcePromises = [];
|
||||||
const cancelAllResources = () =>
|
const cancelAllResources = () =>
|
||||||
cancelableResourcePromises.forEach((p) => p.cancel());
|
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!)
|
// 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);
|
cancelableResourcePromises.push(scriptPromise);
|
||||||
await scriptPromise;
|
await scriptPromise;
|
||||||
|
|
||||||
|
@ -372,6 +376,7 @@ export function loadMovieLibrary(librarySrc) {
|
||||||
id,
|
id,
|
||||||
loadImage(librarySrcDir + "/" + src, {
|
loadImage(librarySrcDir + "/" + src, {
|
||||||
crossOrigin: "anonymous",
|
crossOrigin: "anonymous",
|
||||||
|
preferArchive,
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
|
@ -20,6 +20,7 @@ import OutfitMovieLayer, {
|
||||||
import HangerSpinner from "./HangerSpinner";
|
import HangerSpinner from "./HangerSpinner";
|
||||||
import { loadImage, safeImageUrl, useLocalStorage } from "../util";
|
import { loadImage, safeImageUrl, useLocalStorage } from "../util";
|
||||||
import useOutfitAppearance from "./useOutfitAppearance";
|
import useOutfitAppearance from "./useOutfitAppearance";
|
||||||
|
import usePreferArchive from "./usePreferArchive";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OutfitPreview is for rendering a full outfit! It accepts outfit data,
|
* OutfitPreview is for rendering a full outfit! It accepts outfit data,
|
||||||
|
@ -169,6 +170,7 @@ export function OutfitLayers({
|
||||||
...props
|
...props
|
||||||
}) {
|
}) {
|
||||||
const [hiResMode] = useLocalStorage("DTIHiResMode", false);
|
const [hiResMode] = useLocalStorage("DTIHiResMode", false);
|
||||||
|
const [preferArchive] = usePreferArchive();
|
||||||
|
|
||||||
const containerRef = React.useRef(null);
|
const containerRef = React.useRef(null);
|
||||||
const [canvasSize, setCanvasSize] = React.useState(0);
|
const [canvasSize, setCanvasSize] = React.useState(0);
|
||||||
|
@ -281,7 +283,8 @@ export function OutfitLayers({
|
||||||
<Box
|
<Box
|
||||||
as="img"
|
as="img"
|
||||||
src={safeImageUrl(
|
src={safeImageUrl(
|
||||||
getBestImageUrlForLayer(layer, { hiResMode })
|
getBestImageUrlForLayer(layer, { hiResMode }),
|
||||||
|
{ preferArchive }
|
||||||
)}
|
)}
|
||||||
alt=""
|
alt=""
|
||||||
objectFit="contain"
|
objectFit="contain"
|
||||||
|
@ -367,6 +370,7 @@ export function getBestImageUrlForLayer(layer, { hiResMode = false } = {}) {
|
||||||
*/
|
*/
|
||||||
export function usePreloadLayers(layers) {
|
export function usePreloadLayers(layers) {
|
||||||
const [hiResMode] = useLocalStorage("DTIHiResMode", false);
|
const [hiResMode] = useLocalStorage("DTIHiResMode", false);
|
||||||
|
const [preferArchive] = usePreferArchive();
|
||||||
|
|
||||||
const [error, setError] = React.useState(null);
|
const [error, setError] = React.useState(null);
|
||||||
const [loadedLayers, setLoadedLayers] = React.useState([]);
|
const [loadedLayers, setLoadedLayers] = React.useState([]);
|
||||||
|
@ -393,7 +397,8 @@ export function usePreloadLayers(layers) {
|
||||||
const movieAssetPromises = [];
|
const movieAssetPromises = [];
|
||||||
for (const layer of layers) {
|
for (const layer of layers) {
|
||||||
const imageAssetPromise = loadImage(
|
const imageAssetPromise = loadImage(
|
||||||
getBestImageUrlForLayer(layer, { hiResMode })
|
getBestImageUrlForLayer(layer, { hiResMode }),
|
||||||
|
{ preferArchive }
|
||||||
);
|
);
|
||||||
imageAssetPromises.push(imageAssetPromise);
|
imageAssetPromises.push(imageAssetPromise);
|
||||||
|
|
||||||
|
@ -402,7 +407,8 @@ export function usePreloadLayers(layers) {
|
||||||
// request will still be the image, which we'll show as a
|
// request will still be the image, which we'll show as a
|
||||||
// placeholder, which should usually be noticeably faster!
|
// placeholder, which should usually be noticeably faster!
|
||||||
const movieLibraryPromise = loadMovieLibrary(
|
const movieLibraryPromise = loadMovieLibrary(
|
||||||
layer.canvasMovieLibraryUrl
|
layer.canvasMovieLibraryUrl,
|
||||||
|
{ preferArchive }
|
||||||
);
|
);
|
||||||
const movieAssetPromise = movieLibraryPromise.then((library) => ({
|
const movieAssetPromise = movieLibraryPromise.then((library) => ({
|
||||||
library,
|
library,
|
||||||
|
@ -465,7 +471,7 @@ export function usePreloadLayers(layers) {
|
||||||
return () => {
|
return () => {
|
||||||
canceled = true;
|
canceled = true;
|
||||||
};
|
};
|
||||||
}, [layers, hiResMode]);
|
}, [layers, hiResMode, preferArchive]);
|
||||||
|
|
||||||
return { loading, error, loadedLayers, layersHaveAnimations };
|
return { loading, error, loadedLayers, layersHaveAnimations };
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import Link from "next/link";
|
||||||
|
|
||||||
import { safeImageUrl, useCommonStyles } from "../util";
|
import { safeImageUrl, useCommonStyles } from "../util";
|
||||||
import { CheckIcon, CloseIcon, StarIcon } from "@chakra-ui/icons";
|
import { CheckIcon, CloseIcon, StarIcon } from "@chakra-ui/icons";
|
||||||
|
import usePreferArchive from "./usePreferArchive";
|
||||||
|
|
||||||
function SquareItemCard({
|
function SquareItemCard({
|
||||||
item,
|
item,
|
||||||
|
@ -183,6 +184,7 @@ function SquareItemCardLayout({
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemThumbnail({ item, tradeMatchingMode }) {
|
function ItemThumbnail({ item, tradeMatchingMode }) {
|
||||||
|
const [preferArchive] = usePreferArchive();
|
||||||
const kindColorScheme = item.isNc ? "purple" : item.isPb ? "orange" : "gray";
|
const kindColorScheme = item.isNc ? "purple" : item.isPb ? "orange" : "gray";
|
||||||
|
|
||||||
const thumbnailShadowColor = useColorModeValue(
|
const thumbnailShadowColor = useColorModeValue(
|
||||||
|
@ -229,7 +231,7 @@ function ItemThumbnail({ item, tradeMatchingMode }) {
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
<img
|
||||||
src={safeImageUrl(item.thumbnailUrl)}
|
src={safeImageUrl(item.thumbnailUrl, { preferArchive })}
|
||||||
alt={`Thumbnail art for ${item.name}`}
|
alt={`Thumbnail art for ${item.name}`}
|
||||||
width={80}
|
width={80}
|
||||||
height={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!
|
* 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) {
|
if (urlString == null) {
|
||||||
return urlString;
|
return urlString;
|
||||||
}
|
}
|
||||||
|
@ -148,7 +151,14 @@ export function safeImageUrl(urlString, { crossOrigin = null } = {}) {
|
||||||
url.origin === "https://images.neopets.com"
|
url.origin === "https://images.neopets.com"
|
||||||
) {
|
) {
|
||||||
url.protocol = "https:";
|
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";
|
url.host = "images.neopets-asset-proxy.openneo.net";
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -161,7 +171,7 @@ export function safeImageUrl(urlString, { crossOrigin = null } = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.protocol !== "https:") {
|
if (url.protocol !== "https:" && url.hostname !== "localhost") {
|
||||||
logAndCapture(
|
logAndCapture(
|
||||||
new Error(
|
new Error(
|
||||||
`safeImageUrl was provided an unsafe URL, but we don't know how to ` +
|
`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];
|
return [storedValue, setValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadImage(rawSrc, { crossOrigin = null } = {}) {
|
export function loadImage(
|
||||||
const src = safeImageUrl(rawSrc, { crossOrigin });
|
rawSrc,
|
||||||
|
{ crossOrigin = null, preferArchive = false } = {}
|
||||||
|
) {
|
||||||
|
const src = safeImageUrl(rawSrc, { crossOrigin, preferArchive });
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
let canceled = false;
|
let canceled = false;
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
|
|
Loading…
Reference in a new issue