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)],