forked from OpenNeo/impress
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:
parent
fe6264b20a
commit
5e25e0bda6
4 changed files with 82 additions and 31 deletions
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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,10 +194,12 @@ 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;
|
||||||
|
|
||||||
// TODO: Does this double-trigger the HTTP request with SpeciesColorPicker?
|
// 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 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,
|
||||||
|
|
57
app/javascript/wardrobe-2020/loaders/items.js
Normal file
57
app/javascript/wardrobe-2020/loaders/items.js
Normal 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 };
|
||||||
|
}
|
|
@ -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)],
|
||||||
|
|
Loading…
Reference in a new issue