Load item compatibility data from the Rails app, not impress-2020

Ok progress! We've moved the info about what bodies this fits, and
what zones it occupies, into a Rails API endpoint that we now load at
the same time as the other data!

We'll keep moving more over, too!
This commit is contained in:
Emi Matchu 2023-11-11 08:15:10 -08:00
parent fe6264b20a
commit 5e25e0bda6
4 changed files with 82 additions and 31 deletions

View file

@ -60,7 +60,7 @@ function SpeciesFacesPicker({
); );
const allBodiesAreCompatible = compatibleBodies.some( const allBodiesAreCompatible = compatibleBodies.some(
(body) => body.representsAllBodies, (body) => body.id === "0",
); );
const compatibleBodyIds = compatibleBodies.map((body) => body.id); const compatibleBodyIds = compatibleBodies.map((body) => body.id);

View file

@ -30,6 +30,7 @@ import {
} from "./components/useOutfitAppearance"; } from "./components/useOutfitAppearance";
import { useOutfitPreview } from "./components/OutfitPreview"; import { useOutfitPreview } from "./components/OutfitPreview";
import { logAndCapture, useLocalStorage } from "./util"; import { logAndCapture, useLocalStorage } from "./util";
import { useItemAppearances } from "./loaders/items";
function ItemPageOutfitPreview({ itemId }) { function ItemPageOutfitPreview({ itemId }) {
const idealPose = React.useMemo( const idealPose = React.useMemo(
@ -99,6 +100,13 @@ function ItemPageOutfitPreview({ itemId }) {
const [initialPreferredSpeciesId] = React.useState(preferredSpeciesId); const [initialPreferredSpeciesId] = React.useState(preferredSpeciesId);
const [initialPreferredColorId] = React.useState(preferredColorId); 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 // 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. // preview. We'll use this to initialize both the preview and the picker.
// //
@ -128,20 +136,6 @@ function ItemPageOutfitPreview({ itemId }) {
id id
label label
} }
compatibleBodiesAndTheirZones {
body {
id
representsAllBodies
species {
id
name
}
}
zones {
id
label
}
}
canonicalAppearance( canonicalAppearance(
preferredSpeciesId: $preferredSpeciesId preferredSpeciesId: $preferredSpeciesId
preferredColorId: $preferredColorId preferredColorId: $preferredColorId
@ -192,10 +186,7 @@ function ItemPageOutfitPreview({ itemId }) {
}, },
); );
const compatibleBodies = const compatibleBodies = itemAppearances?.map(({ body }) => body) || [];
data?.item?.compatibleBodiesAndTheirZones?.map(({ body }) => body) || [];
const compatibleBodiesAndTheirZones =
data?.item?.compatibleBodiesAndTheirZones || [];
// If there's only one compatible body, and the canonical species's name // 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, // appears in the item name, then this is probably a species-specific item,
@ -203,9 +194,11 @@ function ItemPageOutfitPreview({ itemId }) {
// model it. // model it.
const isProbablySpeciesSpecific = const isProbablySpeciesSpecific =
compatibleBodies.length === 1 && compatibleBodies.length === 1 &&
!compatibleBodies[0].representsAllBodies && compatibleBodies[0] !== "all" &&
(data?.item?.name || "").includes( (data?.item?.name || "")
data?.item?.canonicalAppearance?.body?.canonicalAppearance?.species?.name, .toLowerCase()
.includes(
data?.item?.canonicalAppearance?.body?.canonicalAppearance?.species?.name.toLowerCase(),
); );
const couldProbablyModelMoreData = !isProbablySpeciesSpecific; const couldProbablyModelMoreData = !isProbablySpeciesSpecific;
@ -257,7 +250,7 @@ function ItemPageOutfitPreview({ itemId }) {
const borderColor = useColorModeValue("green.700", "green.400"); const borderColor = useColorModeValue("green.700", "green.400");
const errorColor = useColorModeValue("red.600", "red.400"); const errorColor = useColorModeValue("red.600", "red.400");
const error = errorGQL || errorValids; const error = errorGQL || errorAppearances || errorValids;
if (error) { if (error) {
return <Box color="red.400">{error.message}</Box>; return <Box color="red.400">{error.message}</Box>;
} }
@ -350,6 +343,7 @@ function ItemPageOutfitPreview({ itemId }) {
// Wait for us to start _requesting_ the appearance, and _then_ // Wait for us to start _requesting_ the appearance, and _then_
// for it to load, and _then_ check compatibility. // for it to load, and _then_ check compatibility.
!loadingGQL && !loadingGQL &&
!loadingAppearances &&
!appearance.loading && !appearance.loading &&
petState.isValid && petState.isValid &&
!isCompatible && ( !isCompatible && (
@ -386,13 +380,13 @@ function ItemPageOutfitPreview({ itemId }) {
compatibleBodies={compatibleBodies} compatibleBodies={compatibleBodies}
couldProbablyModelMoreData={couldProbablyModelMoreData} couldProbablyModelMoreData={couldProbablyModelMoreData}
onChange={onChange} onChange={onChange}
isLoading={loadingGQL || loadingValids} isLoading={loadingGQL || loadingAppearances || loadingValids}
/> />
</Box> </Box>
<Flex gridArea="zones" justifySelf="center" align="center"> <Flex gridArea="zones" justifySelf="center" align="center">
{compatibleBodiesAndTheirZones.length > 0 && ( {itemAppearances.length > 0 && (
<ItemZonesInfo <ItemZonesInfo
compatibleBodiesAndTheirZones={compatibleBodiesAndTheirZones} itemAppearances={itemAppearances}
restrictedZones={data?.item?.restrictedZones || []} restrictedZones={data?.item?.restrictedZones || []}
/> />
)} )}
@ -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 // 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 // merging zones with the same label, because that's how user-facing zone UI
// generally works! // generally works!
const zoneLabelsAndTheirBodiesMap = {}; const zoneLabelsAndTheirBodiesMap = {};
for (const { body, zones } of compatibleBodiesAndTheirZones) { for (const { body, swfAssets } of itemAppearances) {
for (const zone of zones) { for (const { zone } of swfAssets) {
if (!zoneLabelsAndTheirBodiesMap[zone.label]) { if (!zoneLabelsAndTheirBodiesMap[zone.label]) {
zoneLabelsAndTheirBodiesMap[zone.label] = { zoneLabelsAndTheirBodiesMap[zone.label] = {
zoneLabel: zone.label, zoneLabel: zone.label,

View file

@ -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 };
}

View file

@ -1,6 +1,6 @@
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
export function useSavedOutfit(id, options) { export function useSavedOutfit(id, options = {}) {
return useQuery({ return useQuery({
...options, ...options,
queryKey: ["outfits", String(id)], queryKey: ["outfits", String(id)],