diff --git a/app/javascript/wardrobe-2020/ItemPage/SpeciesFacesPicker.js b/app/javascript/wardrobe-2020/ItemPage/SpeciesFacesPicker.js
index 57496ee0..8838ed68 100644
--- a/app/javascript/wardrobe-2020/ItemPage/SpeciesFacesPicker.js
+++ b/app/javascript/wardrobe-2020/ItemPage/SpeciesFacesPicker.js
@@ -60,7 +60,7 @@ function SpeciesFacesPicker({
);
const allBodiesAreCompatible = compatibleBodies.some(
- (body) => body.representsAllBodies,
+ (body) => body.id === "0",
);
const compatibleBodyIds = compatibleBodies.map((body) => body.id);
diff --git a/app/javascript/wardrobe-2020/ItemPageOutfitPreview.js b/app/javascript/wardrobe-2020/ItemPageOutfitPreview.js
index 5e83ea20..e85b55ab 100644
--- a/app/javascript/wardrobe-2020/ItemPageOutfitPreview.js
+++ b/app/javascript/wardrobe-2020/ItemPageOutfitPreview.js
@@ -30,6 +30,7 @@ import {
} from "./components/useOutfitAppearance";
import { useOutfitPreview } from "./components/OutfitPreview";
import { logAndCapture, useLocalStorage } from "./util";
+import { useItemAppearances } from "./loaders/items";
function ItemPageOutfitPreview({ itemId }) {
const idealPose = React.useMemo(
@@ -99,6 +100,13 @@ function ItemPageOutfitPreview({ itemId }) {
const [initialPreferredSpeciesId] = React.useState(preferredSpeciesId);
const [initialPreferredColorId] = React.useState(preferredColorId);
+ const {
+ data: itemAppearancesData,
+ loading: loadingAppearances,
+ error: errorAppearances,
+ } = useItemAppearances(itemId);
+ const itemAppearances = itemAppearancesData || [];
+
// Start by loading the "canonical" pet and item appearance for the outfit
// preview. We'll use this to initialize both the preview and the picker.
//
@@ -128,20 +136,6 @@ function ItemPageOutfitPreview({ itemId }) {
id
label
}
- compatibleBodiesAndTheirZones {
- body {
- id
- representsAllBodies
- species {
- id
- name
- }
- }
- zones {
- id
- label
- }
- }
canonicalAppearance(
preferredSpeciesId: $preferredSpeciesId
preferredColorId: $preferredColorId
@@ -192,10 +186,7 @@ function ItemPageOutfitPreview({ itemId }) {
},
);
- const compatibleBodies =
- data?.item?.compatibleBodiesAndTheirZones?.map(({ body }) => body) || [];
- const compatibleBodiesAndTheirZones =
- data?.item?.compatibleBodiesAndTheirZones || [];
+ const compatibleBodies = itemAppearances?.map(({ body }) => body) || [];
// If there's only one compatible body, and the canonical species's name
// appears in the item name, then this is probably a species-specific item,
@@ -203,10 +194,12 @@ function ItemPageOutfitPreview({ itemId }) {
// model it.
const isProbablySpeciesSpecific =
compatibleBodies.length === 1 &&
- !compatibleBodies[0].representsAllBodies &&
- (data?.item?.name || "").includes(
- data?.item?.canonicalAppearance?.body?.canonicalAppearance?.species?.name,
- );
+ compatibleBodies[0] !== "all" &&
+ (data?.item?.name || "")
+ .toLowerCase()
+ .includes(
+ data?.item?.canonicalAppearance?.body?.canonicalAppearance?.species?.name.toLowerCase(),
+ );
const couldProbablyModelMoreData = !isProbablySpeciesSpecific;
// TODO: Does this double-trigger the HTTP request with SpeciesColorPicker?
@@ -257,7 +250,7 @@ function ItemPageOutfitPreview({ itemId }) {
const borderColor = useColorModeValue("green.700", "green.400");
const errorColor = useColorModeValue("red.600", "red.400");
- const error = errorGQL || errorValids;
+ const error = errorGQL || errorAppearances || errorValids;
if (error) {
return {error.message};
}
@@ -350,6 +343,7 @@ function ItemPageOutfitPreview({ itemId }) {
// Wait for us to start _requesting_ the appearance, and _then_
// for it to load, and _then_ check compatibility.
!loadingGQL &&
+ !loadingAppearances &&
!appearance.loading &&
petState.isValid &&
!isCompatible && (
@@ -386,13 +380,13 @@ function ItemPageOutfitPreview({ itemId }) {
compatibleBodies={compatibleBodies}
couldProbablyModelMoreData={couldProbablyModelMoreData}
onChange={onChange}
- isLoading={loadingGQL || loadingValids}
+ isLoading={loadingGQL || loadingAppearances || loadingValids}
/>
- {compatibleBodiesAndTheirZones.length > 0 && (
+ {itemAppearances.length > 0 && (
)}
@@ -526,13 +520,13 @@ function PlayPauseButton({ isPaused, onClick }) {
);
}
-function ItemZonesInfo({ compatibleBodiesAndTheirZones, restrictedZones }) {
+function ItemZonesInfo({ itemAppearances, restrictedZones }) {
// Reorganize the body-and-zones data, into zone-and-bodies data. Also, we're
// merging zones with the same label, because that's how user-facing zone UI
// generally works!
const zoneLabelsAndTheirBodiesMap = {};
- for (const { body, zones } of compatibleBodiesAndTheirZones) {
- for (const zone of zones) {
+ for (const { body, swfAssets } of itemAppearances) {
+ for (const { zone } of swfAssets) {
if (!zoneLabelsAndTheirBodiesMap[zone.label]) {
zoneLabelsAndTheirBodiesMap[zone.label] = {
zoneLabel: zone.label,
diff --git a/app/javascript/wardrobe-2020/loaders/items.js b/app/javascript/wardrobe-2020/loaders/items.js
new file mode 100644
index 00000000..fb12f70e
--- /dev/null
+++ b/app/javascript/wardrobe-2020/loaders/items.js
@@ -0,0 +1,57 @@
+import { useQuery } from "@tanstack/react-query";
+
+export function useItemAppearances(id, options = {}) {
+ return useQuery({
+ ...options,
+ queryKey: ["items", String(id)],
+ queryFn: () => loadItemAppearances(id),
+ });
+}
+
+async function loadItemAppearances(id) {
+ const res = await fetch(`/items/${encodeURIComponent(id)}/appearances.json`);
+
+ if (!res.ok) {
+ throw new Error(
+ `loading item appearances failed: ${res.status} ${res.statusText}`,
+ );
+ }
+
+ return res.json().then(normalizeItemAppearances);
+}
+
+function normalizeItemAppearances(appearances) {
+ return appearances.map((appearance) => ({
+ body: normalizeBody(appearance.body),
+ swfAssets: appearance.swf_assets.map((asset) => ({
+ id: String(asset.id),
+ knownGlitches: asset.known_glitches,
+ zone: normalizeZone(asset.zone),
+ restrictedZones: asset.restricted_zones.map((z) => normalizeZone(z)),
+ urls: {
+ swf: asset.urls.swf,
+ png: asset.urls.png,
+ manifest: asset.urls.manifest,
+ },
+ })),
+ }));
+}
+
+function normalizeBody(body) {
+ if (String(body.id) === "0") {
+ return { id: "0" };
+ }
+
+ return {
+ id: String(body.id),
+ species: {
+ id: String(body.species.id),
+ name: body.species.name,
+ humanName: body.species.humanName,
+ },
+ };
+}
+
+function normalizeZone(zone) {
+ return { id: String(zone.id), label: zone.label, depth: zone.depth };
+}
diff --git a/app/javascript/wardrobe-2020/loaders/outfits.js b/app/javascript/wardrobe-2020/loaders/outfits.js
index ac73ab6c..2063e7c3 100644
--- a/app/javascript/wardrobe-2020/loaders/outfits.js
+++ b/app/javascript/wardrobe-2020/loaders/outfits.js
@@ -1,6 +1,6 @@
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
-export function useSavedOutfit(id, options) {
+export function useSavedOutfit(id, options = {}) {
return useQuery({
...options,
queryKey: ["outfits", String(id)],