diff --git a/src/app/ItemPage.js b/src/app/ItemPage.js
index 5588282..fe811c3 100644
--- a/src/app/ItemPage.js
+++ b/src/app/ItemPage.js
@@ -14,6 +14,10 @@ import {
useColorModeValue,
useTheme,
useToast,
+ useToken,
+ Stack,
+ Wrap,
+ WrapItem,
} from "@chakra-ui/react";
import {
CheckIcon,
@@ -27,7 +31,7 @@ import { useQuery, useMutation } from "@apollo/client";
import { Link, useParams } from "react-router-dom";
import ItemPageLayout, { SubtleSkeleton } from "./ItemPageLayout";
-import { Delay, usePageTitle } from "./util";
+import { Delay, ErrorMessage, usePageTitle } from "./util";
import {
itemAppearanceFragment,
petAppearanceFragment,
@@ -601,90 +605,539 @@ function ItemPageOutfitPreview({ itemId }) {
const isIncompatible = Array.isArray(layers) && layers.length === 0;
return (
-
-
-
-
+
+
+
+
+ {hasAnimations && (
+ setIsPaused(!isPaused)}
+ />
+ )}
+
+
+
+
+ {
+ setPetState({
+ speciesId: species.id,
+ colorId: color.id,
+ pose: closestPose,
+ appearanceId: null,
+ });
+ }}
+ size="sm"
+ showPlaceholders
+ // This is just a UX affordance: while we could handle invalid states
+ // from a UI perspective, we figure that, if a pet preview is already
+ // visible and responsive to changes, it feels better to treat the
+ // changes as atomic and always-valid.
+ stateMustAlwaysBeValid
/>
- {hasAnimations && (
- : }
- aria-label={isPaused ? "Play" : "Pause"}
- onClick={() => setIsPaused(!isPaused)}
- borderRadius="full"
- boxShadow="md"
- color="gray.50"
- backgroundColor="blackAlpha.700"
- position="absolute"
- bottom="2"
- left="2"
- _hover={{ backgroundColor: "blackAlpha.900" }}
- _focus={{ backgroundColor: "blackAlpha.900" }}
- />
- )}
+
+ {isIncompatible && (
+
+
+
+ )}
+
-
-
-
- {
+
+
+
setPetState({
- speciesId: species.id,
- colorId: color.id,
- pose: closestPose,
+ speciesId,
+ colorId,
+ pose: idealPose,
appearanceId: null,
- });
- }}
- size="sm"
- showPlaceholders
- // This is just a UX affordance: while we could handle invalid states
- // from a UI perspective, we figure that, if a pet preview is already
- // visible and responsive to changes, it feels better to treat the
- // changes as atomic and always-valid.
- stateMustAlwaysBeValid
+ })
+ }
+ isLoading={loading}
/>
-
- {isIncompatible && (
-
-
-
- )}
-
-
+
);
}
+function PlayPauseButton({ isPaused, onClick }) {
+ return (
+ : }
+ aria-label={isPaused ? "Play" : "Pause"}
+ onClick={onClick}
+ borderRadius="full"
+ boxShadow="md"
+ color="gray.50"
+ backgroundColor="blackAlpha.700"
+ position="absolute"
+ bottom="2"
+ left="2"
+ _hover={{ backgroundColor: "blackAlpha.900" }}
+ _focus={{ backgroundColor: "blackAlpha.900" }}
+ />
+ );
+}
+
+function SpeciesFacesPicker({
+ itemId,
+ selectedSpeciesId,
+ onChange,
+ isLoading,
+}) {
+ const selectedBorderColor = useColorModeValue("green.600", "green.400");
+ const selectedBackgroundColor = useColorModeValue("green.200", "green.600");
+ const [
+ selectedBorderColorValue,
+ selectedBackgroundColorValue,
+ ] = useToken("colors", [selectedBorderColor, selectedBackgroundColor]);
+
+ const allSpeciesFaces = speciesFaces.sort((a, b) =>
+ a.speciesName.localeCompare(b.speciesName)
+ );
+
+ return (
+
+ {({ css }) => (
+
+ {allSpeciesFaces.map(({ speciesId, speciesName, colorId, src }) => (
+
+ onChange({ speciesId, colorId })}
+ />
+
+ & {
+ opacity: 1;
+ filter: saturate(110%);
+ }
+ `}
+ />
+
+
+ ))}
+
+ )}
+
+ );
+}
+
+// HACK: I'm just hardcoding all this, rather than connecting up to the
+// database and adding a loading state. Tbh I'm not sure it's a good idea
+// to load this dynamically until we have SSR to make it come in fast!
+// And it's not so bad if this gets out of sync with the database,
+// because the SpeciesColorPicker will still be usable!
+const colors = { BLUE: "8", RED: "61", GREEN: "34", YELLOW: "84" };
+const speciesFaces = [
+ {
+ speciesId: "1",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/obxdjm88/1/1.png",
+ colorId: colors.GREEN,
+ speciesName: "Acara",
+ },
+ {
+ speciesId: "2",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/n9ozx4z5/1/1.png",
+ colorId: colors.BLUE,
+ speciesName: "Aisha",
+ },
+ {
+ speciesId: "3",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/kfonqhdc/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Blumaroo",
+ },
+ {
+ speciesId: "4",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/sc2hhvhn/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Bori",
+ },
+ {
+ speciesId: "5",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/wqz8xn4t/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Bruce",
+ },
+ {
+ speciesId: "6",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/jc9klfxm/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Buzz",
+ },
+ {
+ speciesId: "7",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/4lrb4n3f/1/1.png",
+ colorId: colors.RED,
+ speciesName: "Chia",
+ },
+ {
+ speciesId: "8",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/bdml26md/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Chomby",
+ },
+ {
+ speciesId: "9",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/xl6msllv/1/1.png",
+ colorId: colors.GREEN,
+ speciesName: "Cybunny",
+ },
+ {
+ speciesId: "10",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/bob39shq/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Draik",
+ },
+ {
+ speciesId: "11",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/jhhhbrww/1/1.png",
+ colorId: colors.RED,
+ speciesName: "Elephante",
+ },
+ {
+ speciesId: "12",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/6kngmhvs/1/1.png",
+ colorId: colors.RED,
+ speciesName: "Eyrie",
+ },
+ {
+ speciesId: "13",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/47vt32x2/1/1.png",
+ colorId: colors.GREEN,
+ speciesName: "Flotsam",
+ },
+ {
+ speciesId: "14",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/5nrd2lvd/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Gelert",
+ },
+ {
+ speciesId: "15",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/6c275jcg/1/1.png",
+ colorId: colors.BLUE,
+ speciesName: "Gnorbu",
+ },
+ {
+ speciesId: "16",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/j7q65fv4/1/1.png",
+ colorId: colors.BLUE,
+ speciesName: "Grarrl",
+ },
+ {
+ speciesId: "17",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/5xn4kjf8/1/1.png",
+ colorId: colors.GREEN,
+ speciesName: "Grundo",
+ },
+ {
+ speciesId: "18",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/jsfvcqwt/1/1.png",
+ colorId: colors.RED,
+ speciesName: "Hissi",
+ },
+ {
+ speciesId: "19",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/w32r74vo/1/1.png",
+ colorId: colors.GREEN,
+ speciesName: "Ixi",
+ },
+ {
+ speciesId: "20",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/kz43rnld/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Jetsam",
+ },
+ {
+ speciesId: "21",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/m267j935/1/1.png",
+ colorId: colors.GREEN,
+ speciesName: "Jubjub",
+ },
+ {
+ speciesId: "22",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/4gsrb59g/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Kacheek",
+ },
+ {
+ speciesId: "23",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/ktlxmrtr/1/1.png",
+ colorId: colors.BLUE,
+ speciesName: "Kau",
+ },
+ {
+ speciesId: "24",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/42j5q3zx/1/1.png",
+ colorId: colors.GREEN,
+ speciesName: "Kiko",
+ },
+ {
+ speciesId: "25",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/ncfn87wk/1/1.png",
+ colorId: colors.GREEN,
+ speciesName: "Koi",
+ },
+ {
+ speciesId: "26",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/omx9c876/1/1.png",
+ colorId: colors.RED,
+ speciesName: "Korbat",
+ },
+ {
+ speciesId: "27",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/rfsbh59t/1/1.png",
+ colorId: colors.BLUE,
+ speciesName: "Kougra",
+ },
+ {
+ speciesId: "28",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/hxgsm5d4/1/1.png",
+ colorId: colors.BLUE,
+ speciesName: "Krawk",
+ },
+ {
+ speciesId: "29",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/blxmjgbk/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Kyrii",
+ },
+ {
+ speciesId: "30",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/8r94jhfq/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Lenny",
+ },
+ {
+ speciesId: "31",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/z42535zh/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Lupe",
+ },
+ {
+ speciesId: "32",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/qgg6z8s7/1/1.png",
+ colorId: colors.BLUE,
+ speciesName: "Lutari",
+ },
+ {
+ speciesId: "33",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/kk2nn2jr/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Meerca",
+ },
+ {
+ speciesId: "34",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/jgkoro5z/1/1.png",
+ colorId: colors.GREEN,
+ speciesName: "Moehog",
+ },
+ {
+ speciesId: "35",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/xwlo9657/1/1.png",
+ colorId: colors.BLUE,
+ speciesName: "Mynci",
+ },
+ {
+ speciesId: "36",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/bx7fho8x/1/1.png",
+ colorId: colors.BLUE,
+ speciesName: "Nimmo",
+ },
+ {
+ speciesId: "37",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/rjzmx24v/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Ogrin",
+ },
+ {
+ speciesId: "38",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/kokc52kh/1/1.png",
+ colorId: colors.RED,
+ speciesName: "Peophin",
+ },
+ {
+ speciesId: "39",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/fw6lvf3c/1/1.png",
+ colorId: colors.GREEN,
+ speciesName: "Poogle",
+ },
+ {
+ speciesId: "40",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/tjhwbro3/1/1.png",
+ colorId: colors.RED,
+ speciesName: "Pteri",
+ },
+ {
+ speciesId: "41",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/jdto7mj4/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Quiggle",
+ },
+ {
+ speciesId: "42",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/qsgbm5f6/1/1.png",
+ colorId: colors.BLUE,
+ speciesName: "Ruki",
+ },
+ {
+ speciesId: "43",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/hkjoncsx/1/1.png",
+ colorId: colors.RED,
+ speciesName: "Scorchio",
+ },
+ {
+ speciesId: "44",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/mmvn4tkg/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Shoyru",
+ },
+ {
+ speciesId: "45",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/fc4cxk3t/1/1.png",
+ colorId: colors.RED,
+ speciesName: "Skeith",
+ },
+ {
+ speciesId: "46",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/84gvowmj/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Techo",
+ },
+ {
+ speciesId: "47",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/jd433863/1/1.png",
+ colorId: colors.BLUE,
+ speciesName: "Tonu",
+ },
+ {
+ speciesId: "48",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/q39wn6vq/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Tuskaninny",
+ },
+ {
+ speciesId: "49",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/njzvoflw/1/1.png",
+ colorId: colors.GREEN,
+ speciesName: "Uni",
+ },
+ {
+ speciesId: "50",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/rox4mgh5/1/1.png",
+ colorId: colors.RED,
+ speciesName: "Usul",
+ },
+ {
+ speciesId: "51",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/dnr2kj4b/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Wocky",
+ },
+ {
+ speciesId: "52",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/tdkqr2b6/1/1.png",
+ colorId: colors.RED,
+ speciesName: "Xweetok",
+ },
+ {
+ speciesId: "53",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/h95cs547/1/1.png",
+ colorId: colors.RED,
+ speciesName: "Yurble",
+ },
+ {
+ speciesId: "54",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/x8c57g2l/1/1.png",
+ colorId: colors.BLUE,
+ speciesName: "Zafara",
+ },
+ {
+ speciesId: "55",
+ src: "https://pets.neopets-asset-proxy.openneo.net/cp/xkntzsww/1/1.png",
+ colorId: colors.YELLOW,
+ speciesName: "Vandagyre",
+ },
+];
+
export default ItemPage;