GQL for canonical appearance for body
gonna use this for item page! I walked back my supported species idea 😅
This commit is contained in:
parent
b53d95cda8
commit
1b59b9631b
6 changed files with 613 additions and 226 deletions
|
@ -33,6 +33,10 @@ import {
|
||||||
NpBadge,
|
NpBadge,
|
||||||
} from "./components/ItemCard";
|
} from "./components/ItemCard";
|
||||||
import { Delay, Heading1, usePageTitle } from "./util";
|
import { Delay, Heading1, usePageTitle } from "./util";
|
||||||
|
import {
|
||||||
|
itemAppearanceFragment,
|
||||||
|
petAppearanceFragment,
|
||||||
|
} from "./components/useOutfitAppearance";
|
||||||
import OutfitPreview from "./components/OutfitPreview";
|
import OutfitPreview from "./components/OutfitPreview";
|
||||||
import SpeciesColorPicker from "./components/SpeciesColorPicker";
|
import SpeciesColorPicker from "./components/SpeciesColorPicker";
|
||||||
|
|
||||||
|
@ -439,16 +443,41 @@ function ItemPageOutfitPreview({ itemId }) {
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
const [petState, setPetState] = React.useState({
|
const [petState, setPetState] = React.useState({
|
||||||
|
// Start by looking up Acara appearance data.
|
||||||
speciesId: "1",
|
speciesId: "1",
|
||||||
colorId: "8",
|
colorId: "8",
|
||||||
pose: idealPose,
|
pose: idealPose,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
const { loading, error, data } = useQuery(gql`
|
||||||
|
query ItemPageOutfitPreview($itemId: ID!) {
|
||||||
|
item(id: $itemId) {
|
||||||
|
id
|
||||||
|
canonicalAppearance {
|
||||||
|
id
|
||||||
|
...ItemAppearanceFragment
|
||||||
|
body {
|
||||||
|
id
|
||||||
|
canonicalAppearance {
|
||||||
|
id
|
||||||
|
...PetAppearanceFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${itemAppearanceFragment}
|
||||||
|
${petAppearanceFragment}
|
||||||
|
`);
|
||||||
|
|
||||||
// To check whether the item is compatible with this pet, query for the
|
// To check whether the item is compatible with this pet, query for the
|
||||||
// appearance, but only against the cache. That way, we don't send a
|
// appearance, but only against the cache. That way, we don't send a
|
||||||
// redundant network request just for this (the OutfitPreview component will
|
// redundant network request just for this (the OutfitPreview component will
|
||||||
// handle it!), but we'll get an update once it arrives in the cache.
|
// handle it!), but we'll get an update once it arrives in the cache.
|
||||||
const { data } = useQuery(
|
const { cachedData } = useQuery(
|
||||||
gql`
|
gql`
|
||||||
query ItemPageOutfitPreview_CacheOnly(
|
query ItemPageOutfitPreview_CacheOnly(
|
||||||
$itemId: ID!
|
$itemId: ID!
|
||||||
|
@ -477,7 +506,7 @@ function ItemPageOutfitPreview({ itemId }) {
|
||||||
// If the layers are null-y, then we're still loading. Otherwise, if the
|
// If the layers are null-y, then we're still loading. Otherwise, if the
|
||||||
// layers are an empty array, then we're incomaptible. Or, if they're a
|
// layers are an empty array, then we're incomaptible. Or, if they're a
|
||||||
// non-empty array, then we're compatible!
|
// non-empty array, then we're compatible!
|
||||||
const layers = data?.item?.appearanceOn?.layers;
|
const layers = cachedData?.item?.appearanceOn?.layers;
|
||||||
const isIncompatible = Array.isArray(layers) && layers.length === 0;
|
const isIncompatible = Array.isArray(layers) && layers.length === 0;
|
||||||
|
|
||||||
const borderColor = useColorModeValue("green.700", "green.400");
|
const borderColor = useColorModeValue("green.700", "green.400");
|
||||||
|
|
|
@ -294,11 +294,13 @@ const buildItemsThatNeedModelsLoader = (db) =>
|
||||||
return [lastResult];
|
return [lastResult];
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildItemSpeciesWithAppearanceDataLoader = (db) =>
|
const buildItemBodiesWithAppearanceDataLoader = (db) =>
|
||||||
new DataLoader(async (itemIds) => {
|
new DataLoader(async (itemIds) => {
|
||||||
const qs = itemIds.map((_) => "?").join(",");
|
const qs = itemIds.map((_) => "?").join(",");
|
||||||
const [rows, _] = await db.execute(
|
const [rows, _] = await db.execute(
|
||||||
`SELECT DISTINCT pet_types.species_id AS id, items.id AS item_id
|
// TODO: I'm not sure this ORDER BY clause will reliably get standard
|
||||||
|
// bodies to the top, it seems like it depends how DISTINCT works?
|
||||||
|
`SELECT pet_types.body_id, pet_types.species_id, items.id AS item_id
|
||||||
FROM items
|
FROM items
|
||||||
INNER JOIN parents_swf_assets ON
|
INNER JOIN parents_swf_assets ON
|
||||||
items.id = parents_swf_assets.parent_id AND
|
items.id = parents_swf_assets.parent_id AND
|
||||||
|
@ -307,8 +309,13 @@ const buildItemSpeciesWithAppearanceDataLoader = (db) =>
|
||||||
parents_swf_assets.swf_asset_id = swf_assets.id
|
parents_swf_assets.swf_asset_id = swf_assets.id
|
||||||
INNER JOIN pet_types ON
|
INNER JOIN pet_types ON
|
||||||
pet_types.body_id = swf_assets.body_id OR swf_assets.body_id = 0
|
pet_types.body_id = swf_assets.body_id OR swf_assets.body_id = 0
|
||||||
WHERE items.id = ${qs}
|
INNER JOIN colors ON
|
||||||
ORDER BY id`,
|
pet_types.color_id = colors.id
|
||||||
|
WHERE items.id IN (${qs})
|
||||||
|
GROUP BY pet_types.body_id
|
||||||
|
ORDER BY
|
||||||
|
pet_types.species_id,
|
||||||
|
colors.standard DESC`,
|
||||||
itemIds
|
itemIds
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -503,6 +510,50 @@ const buildPetStatesForPetTypeLoader = (db, loaders) =>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** Given a bodyId, loads the canonical PetState to show as an example. */
|
||||||
|
const buildCanonicalPetStateForBodyLoader = (db, loaders) =>
|
||||||
|
new DataLoader(async (bodyIds) => {
|
||||||
|
// I don't know how to do this query in bulk, so we'll just do it in
|
||||||
|
// parallel!
|
||||||
|
return await Promise.all(
|
||||||
|
bodyIds.map(async (bodyId) => {
|
||||||
|
// Randomly-ish choose which gender presentation to prefer, based on
|
||||||
|
// body ID. This makes the outcome stable, which is nice for caching
|
||||||
|
// and testing and just generally not being surprised, but sitll
|
||||||
|
// creates an even distribution.
|
||||||
|
const gender = bodyId % 2 === 0 ? "masc" : "fem";
|
||||||
|
|
||||||
|
const [rows, _] = await db.execute(
|
||||||
|
{
|
||||||
|
sql: `
|
||||||
|
SELECT pet_states.*, pet_types.* FROM pet_states
|
||||||
|
INNER JOIN pet_types ON pet_types.id = pet_states.pet_type_id
|
||||||
|
WHERE pet_types.body_id = ?
|
||||||
|
ORDER BY
|
||||||
|
pet_types.color_id = 8 DESC, -- Prefer Blue
|
||||||
|
pet_states.mood_id = 1 DESC, -- Prefer Happy
|
||||||
|
pet_states.female = ? DESC, -- Prefer given gender
|
||||||
|
pet_states.id DESC, -- Prefer recent models (like in the app)
|
||||||
|
pet_states.glitched ASC -- Prefer not glitched (like in the app)
|
||||||
|
LIMIT 1`,
|
||||||
|
nestTables: true,
|
||||||
|
},
|
||||||
|
[bodyId, gender === "fem"]
|
||||||
|
);
|
||||||
|
const petState = normalizeRow(rows[0].pet_states);
|
||||||
|
const petType = normalizeRow(rows[0].pet_types);
|
||||||
|
if (!petState || !petType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
loaders.petStateLoader.prime(petState.id, petState);
|
||||||
|
loaders.petTypeLoader.prime(petType.id, petType);
|
||||||
|
|
||||||
|
return petState;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const buildUserLoader = (db) =>
|
const buildUserLoader = (db) =>
|
||||||
new DataLoader(async (ids) => {
|
new DataLoader(async (ids) => {
|
||||||
const qs = ids.map((_) => "?").join(",");
|
const qs = ids.map((_) => "?").join(",");
|
||||||
|
@ -617,7 +668,7 @@ function buildLoaders(db) {
|
||||||
loaders.itemSearchLoader = buildItemSearchLoader(db, loaders);
|
loaders.itemSearchLoader = buildItemSearchLoader(db, loaders);
|
||||||
loaders.itemSearchToFitLoader = buildItemSearchToFitLoader(db, loaders);
|
loaders.itemSearchToFitLoader = buildItemSearchToFitLoader(db, loaders);
|
||||||
loaders.itemsThatNeedModelsLoader = buildItemsThatNeedModelsLoader(db);
|
loaders.itemsThatNeedModelsLoader = buildItemsThatNeedModelsLoader(db);
|
||||||
loaders.itemSpeciesWithAppearanceDataLoader = buildItemSpeciesWithAppearanceDataLoader(
|
loaders.itemBodiesWithAppearanceDataLoader = buildItemBodiesWithAppearanceDataLoader(
|
||||||
db
|
db
|
||||||
);
|
);
|
||||||
loaders.petTypeLoader = buildPetTypeLoader(db);
|
loaders.petTypeLoader = buildPetTypeLoader(db);
|
||||||
|
@ -637,6 +688,10 @@ function buildLoaders(db) {
|
||||||
db,
|
db,
|
||||||
loaders
|
loaders
|
||||||
);
|
);
|
||||||
|
loaders.canonicalPetStateForBodyLoader = buildCanonicalPetStateForBodyLoader(
|
||||||
|
db,
|
||||||
|
loaders
|
||||||
|
);
|
||||||
loaders.speciesLoader = buildSpeciesLoader(db);
|
loaders.speciesLoader = buildSpeciesLoader(db);
|
||||||
loaders.speciesTranslationLoader = buildSpeciesTranslationLoader(db);
|
loaders.speciesTranslationLoader = buildSpeciesTranslationLoader(db);
|
||||||
loaders.userLoader = buildUserLoader(db);
|
loaders.userLoader = buildUserLoader(db);
|
||||||
|
|
|
@ -431,66 +431,158 @@ describe("Item", () => {
|
||||||
expect(getDbCalls()).toMatchSnapshot();
|
expect(getDbCalls()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("loads species with appearance data for single-species item", async () => {
|
it("loads canonical appearance for single-species item", async () => {
|
||||||
const res = await query({
|
const res = await query({
|
||||||
query: gql`
|
query: gql`
|
||||||
query {
|
query {
|
||||||
item(
|
item(
|
||||||
id: "38911" # Zafara Agent Hood
|
id: "38911" # Zafara Agent Hood
|
||||||
) {
|
) {
|
||||||
speciesWithAppearanceDataForThisItem {
|
canonicalAppearance {
|
||||||
|
id
|
||||||
|
layers {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
species {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
canonicalAppearance {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res).toHaveNoErrors();
|
expect(res).toHaveNoErrors();
|
||||||
expect(res.data.item.speciesWithAppearanceDataForThisItem).toHaveLength(1);
|
const body = res.data.item.canonicalAppearance.body;
|
||||||
expect(res.data.item.speciesWithAppearanceDataForThisItem[0].name).toEqual(
|
expect(body.species.name).toEqual("Zafara");
|
||||||
"Zafara"
|
expect(res.data.item.canonicalAppearance.layers).toMatchSnapshot(
|
||||||
|
"item layers"
|
||||||
);
|
);
|
||||||
expect(getDbCalls()).toMatchSnapshot();
|
expect(body.canonicalAppearance).toBeTruthy();
|
||||||
|
expect(body.canonicalAppearance).toMatchSnapshot("pet layers");
|
||||||
|
expect(getDbCalls()).toMatchSnapshot("db");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("loads species with appearance data for all-species item", async () => {
|
it("loads canonical appearance for all-species item", async () => {
|
||||||
const res = await query({
|
const res = await query({
|
||||||
query: gql`
|
query: gql`
|
||||||
query {
|
query {
|
||||||
item(
|
item(
|
||||||
id: "74967" # 17th Birthday Party Hat
|
id: "74967" # 17th Birthday Party Hat
|
||||||
) {
|
) {
|
||||||
speciesWithAppearanceDataForThisItem {
|
canonicalAppearance {
|
||||||
|
id
|
||||||
|
layers {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
species {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
canonicalAppearance {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res).toHaveNoErrors();
|
expect(res).toHaveNoErrors();
|
||||||
expect(res.data.item.speciesWithAppearanceDataForThisItem).toHaveLength(55);
|
const body = res.data.item.canonicalAppearance.body;
|
||||||
expect(getDbCalls()).toMatchSnapshot();
|
expect(body.species.name).toEqual("Acara");
|
||||||
|
expect(res.data.item.canonicalAppearance.layers).toMatchSnapshot(
|
||||||
|
"item layers"
|
||||||
|
);
|
||||||
|
expect(body.canonicalAppearance).toBeTruthy();
|
||||||
|
expect(body.canonicalAppearance).toMatchSnapshot("pet layers");
|
||||||
|
expect(getDbCalls()).toMatchSnapshot("db");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("loads species with appearance data for bodyId=0 item", async () => {
|
it("loads canonical appearance for all-species Maraquan item", async () => {
|
||||||
|
const res = await query({
|
||||||
|
query: gql`
|
||||||
|
query {
|
||||||
|
item(
|
||||||
|
id: "77530" # Maraquan Sea Blue Gown
|
||||||
|
) {
|
||||||
|
canonicalAppearance {
|
||||||
|
id
|
||||||
|
layers {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
canonicalAppearance {
|
||||||
|
color {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
species {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
layers {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).toHaveNoErrors();
|
||||||
|
const body = res.data.item.canonicalAppearance.body;
|
||||||
|
expect(res.data.item.canonicalAppearance).toBeTruthy();
|
||||||
|
expect(res.data.item.canonicalAppearance.layers).toMatchSnapshot(
|
||||||
|
"item layers"
|
||||||
|
);
|
||||||
|
expect(body.canonicalAppearance).toBeTruthy();
|
||||||
|
expect(body.canonicalAppearance.species.name).toEqual("Acara");
|
||||||
|
expect(body.canonicalAppearance.color.name).toEqual("Maraquan");
|
||||||
|
expect(body.canonicalAppearance.layers).toMatchSnapshot("pet layers");
|
||||||
|
expect(getDbCalls()).toMatchSnapshot("db");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("loads canonical appearance for bodyId=0 item", async () => {
|
||||||
const res = await query({
|
const res = await query({
|
||||||
query: gql`
|
query: gql`
|
||||||
query {
|
query {
|
||||||
item(
|
item(
|
||||||
id: "37375" # Moon and Stars Background
|
id: "37375" # Moon and Stars Background
|
||||||
) {
|
) {
|
||||||
speciesWithAppearanceDataForThisItem {
|
canonicalAppearance {
|
||||||
|
id
|
||||||
|
layers {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
species {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
canonicalAppearance {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res).toHaveNoErrors();
|
expect(res).toHaveNoErrors();
|
||||||
expect(res.data.item.speciesWithAppearanceDataForThisItem).toHaveLength(55);
|
const body = res.data.item.canonicalAppearance.body;
|
||||||
expect(getDbCalls()).toMatchSnapshot();
|
expect(body.species.name).toEqual("Acara");
|
||||||
|
expect(res.data.item.canonicalAppearance.layers).toMatchSnapshot(
|
||||||
|
"item layers"
|
||||||
|
);
|
||||||
|
expect(body.canonicalAppearance).toBeTruthy();
|
||||||
|
expect(body.canonicalAppearance).toMatchSnapshot("pet layers");
|
||||||
|
expect(getDbCalls()).toMatchSnapshot("db");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -81,6 +81,365 @@ Object {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads canonical appearance for all-species Maraquan item: db 1`] = `
|
||||||
|
Array [
|
||||||
|
Array [
|
||||||
|
"SELECT pet_types.body_id, pet_types.species_id, items.id AS item_id
|
||||||
|
FROM items
|
||||||
|
INNER JOIN parents_swf_assets ON
|
||||||
|
items.id = parents_swf_assets.parent_id AND
|
||||||
|
parents_swf_assets.parent_type = \\"Item\\"
|
||||||
|
INNER JOIN swf_assets ON
|
||||||
|
parents_swf_assets.swf_asset_id = swf_assets.id
|
||||||
|
INNER JOIN pet_types ON
|
||||||
|
pet_types.body_id = swf_assets.body_id OR swf_assets.body_id = 0
|
||||||
|
INNER JOIN colors ON
|
||||||
|
pet_types.color_id = colors.id
|
||||||
|
WHERE items.id IN (?)
|
||||||
|
GROUP BY pet_types.body_id
|
||||||
|
ORDER BY
|
||||||
|
pet_types.species_id,
|
||||||
|
colors.standard DESC",
|
||||||
|
Array [
|
||||||
|
"77530",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"SELECT sa.*, rel.parent_id FROM swf_assets sa
|
||||||
|
INNER JOIN parents_swf_assets rel ON
|
||||||
|
rel.parent_type = \\"Item\\" AND
|
||||||
|
rel.swf_asset_id = sa.id
|
||||||
|
WHERE (rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0))",
|
||||||
|
Array [
|
||||||
|
"77530",
|
||||||
|
"112",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"nestTables": true,
|
||||||
|
"sql": "
|
||||||
|
SELECT pet_states.*, pet_types.* FROM pet_states
|
||||||
|
INNER JOIN pet_types ON pet_types.id = pet_states.pet_type_id
|
||||||
|
WHERE pet_types.body_id = ?
|
||||||
|
ORDER BY
|
||||||
|
pet_types.color_id = 8 DESC, -- Prefer Blue
|
||||||
|
pet_states.mood_id = 1 DESC, -- Prefer Happy
|
||||||
|
pet_states.female = ? DESC, -- Prefer given gender
|
||||||
|
pet_states.id DESC, -- Prefer recent models (like in the app)
|
||||||
|
pet_states.glitched ASC -- Prefer not glitched (like in the app)
|
||||||
|
LIMIT 1",
|
||||||
|
"values": Array [
|
||||||
|
"112",
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Array [
|
||||||
|
"112",
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"SELECT sa.*, rel.parent_id FROM swf_assets sa
|
||||||
|
INNER JOIN parents_swf_assets rel ON
|
||||||
|
rel.parent_type = \\"PetState\\" AND
|
||||||
|
rel.swf_asset_id = sa.id
|
||||||
|
WHERE rel.parent_id IN (?)",
|
||||||
|
Array [
|
||||||
|
"5233",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"SELECT * FROM color_translations
|
||||||
|
WHERE color_id IN (?) AND locale = \\"en\\"",
|
||||||
|
Array [
|
||||||
|
"44",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"SELECT * FROM species_translations
|
||||||
|
WHERE species_id IN (?) AND locale = \\"en\\"",
|
||||||
|
Array [
|
||||||
|
"1",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads canonical appearance for all-species Maraquan item: item layers 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"id": "442864",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads canonical appearance for all-species Maraquan item: pet layers 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"id": "2652",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "2653",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "2654",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "2656",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "2663",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads canonical appearance for all-species item: db 1`] = `
|
||||||
|
Array [
|
||||||
|
Array [
|
||||||
|
"SELECT pet_types.body_id, pet_types.species_id, items.id AS item_id
|
||||||
|
FROM items
|
||||||
|
INNER JOIN parents_swf_assets ON
|
||||||
|
items.id = parents_swf_assets.parent_id AND
|
||||||
|
parents_swf_assets.parent_type = \\"Item\\"
|
||||||
|
INNER JOIN swf_assets ON
|
||||||
|
parents_swf_assets.swf_asset_id = swf_assets.id
|
||||||
|
INNER JOIN pet_types ON
|
||||||
|
pet_types.body_id = swf_assets.body_id OR swf_assets.body_id = 0
|
||||||
|
INNER JOIN colors ON
|
||||||
|
pet_types.color_id = colors.id
|
||||||
|
WHERE items.id IN (?)
|
||||||
|
GROUP BY pet_types.body_id
|
||||||
|
ORDER BY
|
||||||
|
pet_types.species_id,
|
||||||
|
colors.standard DESC",
|
||||||
|
Array [
|
||||||
|
"74967",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"SELECT sa.*, rel.parent_id FROM swf_assets sa
|
||||||
|
INNER JOIN parents_swf_assets rel ON
|
||||||
|
rel.parent_type = \\"Item\\" AND
|
||||||
|
rel.swf_asset_id = sa.id
|
||||||
|
WHERE (rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0))",
|
||||||
|
Array [
|
||||||
|
"74967",
|
||||||
|
"93",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"SELECT * FROM species_translations
|
||||||
|
WHERE species_id IN (?) AND locale = \\"en\\"",
|
||||||
|
Array [
|
||||||
|
"1",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"nestTables": true,
|
||||||
|
"sql": "
|
||||||
|
SELECT pet_states.*, pet_types.* FROM pet_states
|
||||||
|
INNER JOIN pet_types ON pet_types.id = pet_states.pet_type_id
|
||||||
|
WHERE pet_types.body_id = ?
|
||||||
|
ORDER BY
|
||||||
|
pet_types.color_id = 8 DESC, -- Prefer Blue
|
||||||
|
pet_states.mood_id = 1 DESC, -- Prefer Happy
|
||||||
|
pet_states.female = ? DESC, -- Prefer given gender
|
||||||
|
pet_states.id DESC, -- Prefer recent models (like in the app)
|
||||||
|
pet_states.glitched ASC -- Prefer not glitched (like in the app)
|
||||||
|
LIMIT 1",
|
||||||
|
"values": Array [
|
||||||
|
"93",
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Array [
|
||||||
|
"93",
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads canonical appearance for all-species item: item layers 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"id": "395679",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads canonical appearance for all-species item: pet layers 1`] = `
|
||||||
|
Object {
|
||||||
|
"id": "5161",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads canonical appearance for bodyId=0 item: db 1`] = `
|
||||||
|
Array [
|
||||||
|
Array [
|
||||||
|
"SELECT pet_types.body_id, pet_types.species_id, items.id AS item_id
|
||||||
|
FROM items
|
||||||
|
INNER JOIN parents_swf_assets ON
|
||||||
|
items.id = parents_swf_assets.parent_id AND
|
||||||
|
parents_swf_assets.parent_type = \\"Item\\"
|
||||||
|
INNER JOIN swf_assets ON
|
||||||
|
parents_swf_assets.swf_asset_id = swf_assets.id
|
||||||
|
INNER JOIN pet_types ON
|
||||||
|
pet_types.body_id = swf_assets.body_id OR swf_assets.body_id = 0
|
||||||
|
INNER JOIN colors ON
|
||||||
|
pet_types.color_id = colors.id
|
||||||
|
WHERE items.id IN (?)
|
||||||
|
GROUP BY pet_types.body_id
|
||||||
|
ORDER BY
|
||||||
|
pet_types.species_id,
|
||||||
|
colors.standard DESC",
|
||||||
|
Array [
|
||||||
|
"37375",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"SELECT sa.*, rel.parent_id FROM swf_assets sa
|
||||||
|
INNER JOIN parents_swf_assets rel ON
|
||||||
|
rel.parent_type = \\"Item\\" AND
|
||||||
|
rel.swf_asset_id = sa.id
|
||||||
|
WHERE (rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0))",
|
||||||
|
Array [
|
||||||
|
"37375",
|
||||||
|
"93",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"SELECT * FROM species_translations
|
||||||
|
WHERE species_id IN (?) AND locale = \\"en\\"",
|
||||||
|
Array [
|
||||||
|
"1",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"nestTables": true,
|
||||||
|
"sql": "
|
||||||
|
SELECT pet_states.*, pet_types.* FROM pet_states
|
||||||
|
INNER JOIN pet_types ON pet_types.id = pet_states.pet_type_id
|
||||||
|
WHERE pet_types.body_id = ?
|
||||||
|
ORDER BY
|
||||||
|
pet_types.color_id = 8 DESC, -- Prefer Blue
|
||||||
|
pet_states.mood_id = 1 DESC, -- Prefer Happy
|
||||||
|
pet_states.female = ? DESC, -- Prefer given gender
|
||||||
|
pet_states.id DESC, -- Prefer recent models (like in the app)
|
||||||
|
pet_states.glitched ASC -- Prefer not glitched (like in the app)
|
||||||
|
LIMIT 1",
|
||||||
|
"values": Array [
|
||||||
|
"93",
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Array [
|
||||||
|
"93",
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads canonical appearance for bodyId=0 item: item layers 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"id": "30203",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads canonical appearance for bodyId=0 item: pet layers 1`] = `
|
||||||
|
Object {
|
||||||
|
"id": "5161",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads canonical appearance for single-species item: db 1`] = `
|
||||||
|
Array [
|
||||||
|
Array [
|
||||||
|
"SELECT pet_types.body_id, pet_types.species_id, items.id AS item_id
|
||||||
|
FROM items
|
||||||
|
INNER JOIN parents_swf_assets ON
|
||||||
|
items.id = parents_swf_assets.parent_id AND
|
||||||
|
parents_swf_assets.parent_type = \\"Item\\"
|
||||||
|
INNER JOIN swf_assets ON
|
||||||
|
parents_swf_assets.swf_asset_id = swf_assets.id
|
||||||
|
INNER JOIN pet_types ON
|
||||||
|
pet_types.body_id = swf_assets.body_id OR swf_assets.body_id = 0
|
||||||
|
INNER JOIN colors ON
|
||||||
|
pet_types.color_id = colors.id
|
||||||
|
WHERE items.id IN (?)
|
||||||
|
GROUP BY pet_types.body_id
|
||||||
|
ORDER BY
|
||||||
|
pet_types.species_id,
|
||||||
|
colors.standard DESC",
|
||||||
|
Array [
|
||||||
|
"38911",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"SELECT sa.*, rel.parent_id FROM swf_assets sa
|
||||||
|
INNER JOIN parents_swf_assets rel ON
|
||||||
|
rel.parent_type = \\"Item\\" AND
|
||||||
|
rel.swf_asset_id = sa.id
|
||||||
|
WHERE (rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0))",
|
||||||
|
Array [
|
||||||
|
"38911",
|
||||||
|
"180",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"SELECT * FROM species_translations
|
||||||
|
WHERE species_id IN (?) AND locale = \\"en\\"",
|
||||||
|
Array [
|
||||||
|
"54",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"nestTables": true,
|
||||||
|
"sql": "
|
||||||
|
SELECT pet_states.*, pet_types.* FROM pet_states
|
||||||
|
INNER JOIN pet_types ON pet_types.id = pet_states.pet_type_id
|
||||||
|
WHERE pet_types.body_id = ?
|
||||||
|
ORDER BY
|
||||||
|
pet_types.color_id = 8 DESC, -- Prefer Blue
|
||||||
|
pet_states.mood_id = 1 DESC, -- Prefer Happy
|
||||||
|
pet_states.female = ? DESC, -- Prefer given gender
|
||||||
|
pet_states.id DESC, -- Prefer recent models (like in the app)
|
||||||
|
pet_states.glitched ASC -- Prefer not glitched (like in the app)
|
||||||
|
LIMIT 1",
|
||||||
|
"values": Array [
|
||||||
|
"180",
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Array [
|
||||||
|
"180",
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads canonical appearance for single-species item: item layers 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"id": "37129",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads canonical appearance for single-species item: pet layers 1`] = `
|
||||||
|
Object {
|
||||||
|
"id": "17861",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Item loads items that need models 1`] = `
|
exports[`Item loads items that need models 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"babyItems": Array [
|
"babyItems": Array [
|
||||||
|
@ -11998,198 +12357,6 @@ Object {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Item loads species with appearance data for all-species item 1`] = `
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
"SELECT DISTINCT pet_types.species_id AS id, items.id AS item_id
|
|
||||||
FROM items
|
|
||||||
INNER JOIN parents_swf_assets ON
|
|
||||||
items.id = parents_swf_assets.parent_id AND
|
|
||||||
parents_swf_assets.parent_type = \\"Item\\"
|
|
||||||
INNER JOIN swf_assets ON
|
|
||||||
parents_swf_assets.swf_asset_id = swf_assets.id
|
|
||||||
INNER JOIN pet_types ON
|
|
||||||
pet_types.body_id = swf_assets.body_id OR swf_assets.body_id = 0
|
|
||||||
WHERE items.id = ?
|
|
||||||
ORDER BY id",
|
|
||||||
Array [
|
|
||||||
"74967",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
"SELECT * FROM species_translations
|
|
||||||
WHERE species_id IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) AND locale = \\"en\\"",
|
|
||||||
Array [
|
|
||||||
"1",
|
|
||||||
"2",
|
|
||||||
"3",
|
|
||||||
"4",
|
|
||||||
"5",
|
|
||||||
"6",
|
|
||||||
"7",
|
|
||||||
"8",
|
|
||||||
"9",
|
|
||||||
"10",
|
|
||||||
"11",
|
|
||||||
"12",
|
|
||||||
"13",
|
|
||||||
"14",
|
|
||||||
"15",
|
|
||||||
"16",
|
|
||||||
"17",
|
|
||||||
"18",
|
|
||||||
"19",
|
|
||||||
"20",
|
|
||||||
"21",
|
|
||||||
"22",
|
|
||||||
"23",
|
|
||||||
"24",
|
|
||||||
"25",
|
|
||||||
"26",
|
|
||||||
"27",
|
|
||||||
"28",
|
|
||||||
"29",
|
|
||||||
"30",
|
|
||||||
"31",
|
|
||||||
"32",
|
|
||||||
"33",
|
|
||||||
"34",
|
|
||||||
"35",
|
|
||||||
"36",
|
|
||||||
"37",
|
|
||||||
"38",
|
|
||||||
"39",
|
|
||||||
"40",
|
|
||||||
"41",
|
|
||||||
"42",
|
|
||||||
"43",
|
|
||||||
"44",
|
|
||||||
"45",
|
|
||||||
"46",
|
|
||||||
"47",
|
|
||||||
"48",
|
|
||||||
"49",
|
|
||||||
"50",
|
|
||||||
"51",
|
|
||||||
"52",
|
|
||||||
"53",
|
|
||||||
"54",
|
|
||||||
"55",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Item loads species with appearance data for bodyId=0 item 1`] = `
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
"SELECT DISTINCT pet_types.species_id AS id, items.id AS item_id
|
|
||||||
FROM items
|
|
||||||
INNER JOIN parents_swf_assets ON
|
|
||||||
items.id = parents_swf_assets.parent_id AND
|
|
||||||
parents_swf_assets.parent_type = \\"Item\\"
|
|
||||||
INNER JOIN swf_assets ON
|
|
||||||
parents_swf_assets.swf_asset_id = swf_assets.id
|
|
||||||
INNER JOIN pet_types ON
|
|
||||||
pet_types.body_id = swf_assets.body_id OR swf_assets.body_id = 0
|
|
||||||
WHERE items.id = ?
|
|
||||||
ORDER BY id",
|
|
||||||
Array [
|
|
||||||
"37375",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
"SELECT * FROM species_translations
|
|
||||||
WHERE species_id IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) AND locale = \\"en\\"",
|
|
||||||
Array [
|
|
||||||
"1",
|
|
||||||
"2",
|
|
||||||
"3",
|
|
||||||
"4",
|
|
||||||
"5",
|
|
||||||
"6",
|
|
||||||
"7",
|
|
||||||
"8",
|
|
||||||
"9",
|
|
||||||
"10",
|
|
||||||
"11",
|
|
||||||
"12",
|
|
||||||
"13",
|
|
||||||
"14",
|
|
||||||
"15",
|
|
||||||
"16",
|
|
||||||
"17",
|
|
||||||
"18",
|
|
||||||
"19",
|
|
||||||
"20",
|
|
||||||
"21",
|
|
||||||
"22",
|
|
||||||
"23",
|
|
||||||
"24",
|
|
||||||
"25",
|
|
||||||
"26",
|
|
||||||
"27",
|
|
||||||
"28",
|
|
||||||
"29",
|
|
||||||
"30",
|
|
||||||
"31",
|
|
||||||
"32",
|
|
||||||
"33",
|
|
||||||
"34",
|
|
||||||
"35",
|
|
||||||
"36",
|
|
||||||
"37",
|
|
||||||
"38",
|
|
||||||
"39",
|
|
||||||
"40",
|
|
||||||
"41",
|
|
||||||
"42",
|
|
||||||
"43",
|
|
||||||
"44",
|
|
||||||
"45",
|
|
||||||
"46",
|
|
||||||
"47",
|
|
||||||
"48",
|
|
||||||
"49",
|
|
||||||
"50",
|
|
||||||
"51",
|
|
||||||
"52",
|
|
||||||
"53",
|
|
||||||
"54",
|
|
||||||
"55",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Item loads species with appearance data for single-species item 1`] = `
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
"SELECT DISTINCT pet_types.species_id AS id, items.id AS item_id
|
|
||||||
FROM items
|
|
||||||
INNER JOIN parents_swf_assets ON
|
|
||||||
items.id = parents_swf_assets.parent_id AND
|
|
||||||
parents_swf_assets.parent_type = \\"Item\\"
|
|
||||||
INNER JOIN swf_assets ON
|
|
||||||
parents_swf_assets.swf_asset_id = swf_assets.id
|
|
||||||
INNER JOIN pet_types ON
|
|
||||||
pet_types.body_id = swf_assets.body_id OR swf_assets.body_id = 0
|
|
||||||
WHERE items.id = ?
|
|
||||||
ORDER BY id",
|
|
||||||
Array [
|
|
||||||
"38911",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
"SELECT * FROM species_translations
|
|
||||||
WHERE species_id IN (?) AND locale = \\"en\\"",
|
|
||||||
Array [
|
|
||||||
"54",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Item returns empty appearance for incompatible items 1`] = `
|
exports[`Item returns empty appearance for incompatible items 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"items": Array [
|
"items": Array [
|
||||||
|
|
|
@ -42,17 +42,19 @@ const typeDefs = gql`
|
||||||
# bodies like Blue, Green, Red, etc.
|
# bodies like Blue, Green, Red, etc.
|
||||||
speciesThatNeedModels(colorId: ID): [Species!]!
|
speciesThatNeedModels(colorId: ID): [Species!]!
|
||||||
|
|
||||||
# Species that we know how they look wearing this item. Used to initialize
|
# Return a single ItemAppearance for this item. It'll be for the species
|
||||||
# the preview on the item page with a compatible species.
|
# with the smallest ID for which we have item appearance data. We use this
|
||||||
# TODO: This would probably make more sense as like, compatible bodies, so
|
# on the item page, to initialize the preview section. (You can find out
|
||||||
# we could also encode special-color stuff in here too.
|
# which species this is for by going through the body field on
|
||||||
speciesWithAppearanceDataForThisItem: [Species!]!
|
# ItemAppearance!)
|
||||||
|
canonicalAppearance: ItemAppearance
|
||||||
}
|
}
|
||||||
|
|
||||||
type ItemAppearance {
|
type ItemAppearance {
|
||||||
id: ID!
|
id: ID!
|
||||||
item: Item!
|
item: Item!
|
||||||
bodyId: ID!
|
bodyId: ID! # Deprecated, use body->id.
|
||||||
|
body: Body!
|
||||||
layers: [AppearanceLayer!]
|
layers: [AppearanceLayer!]
|
||||||
restrictedZones: [Zone!]!
|
restrictedZones: [Zone!]!
|
||||||
}
|
}
|
||||||
|
@ -182,18 +184,29 @@ const resolvers = {
|
||||||
);
|
);
|
||||||
return unmodeledSpeciesIds.map((id) => ({ id }));
|
return unmodeledSpeciesIds.map((id) => ({ id }));
|
||||||
},
|
},
|
||||||
speciesWithAppearanceDataForThisItem: async (
|
canonicalAppearance: async (
|
||||||
{ id },
|
{ id },
|
||||||
_,
|
_,
|
||||||
{ itemSpeciesWithAppearanceDataLoader }
|
{ itemBodiesWithAppearanceDataLoader }
|
||||||
) => {
|
) => {
|
||||||
const rows = await itemSpeciesWithAppearanceDataLoader.load(id);
|
const rows = await itemBodiesWithAppearanceDataLoader.load(id);
|
||||||
return rows.map((row) => ({ id: row.id }));
|
const canonicalBodyId = rows[0].bodyId;
|
||||||
|
return {
|
||||||
|
item: { id },
|
||||||
|
bodyId: canonicalBodyId,
|
||||||
|
// An optimization: we know the species already, so fill it in here
|
||||||
|
// without requiring an extra query if we want it.
|
||||||
|
// TODO: Maybe this would be cleaner if we make the body -> species
|
||||||
|
// loader, and prime it in the item bodies loader, rather than
|
||||||
|
// setting it here?
|
||||||
|
body: { id: canonicalBodyId, species: { id: rows[0].speciesId } },
|
||||||
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
ItemAppearance: {
|
ItemAppearance: {
|
||||||
id: ({ item, bodyId }) => `item-${item.id}-body-${bodyId}`,
|
id: ({ item, bodyId }) => `item-${item.id}-body-${bodyId}`,
|
||||||
|
body: ({ body, bodyId }) => body || { id: bodyId },
|
||||||
layers: async ({ item, bodyId }, _, { itemSwfAssetLoader }) => {
|
layers: async ({ item, bodyId }, _, { itemSwfAssetLoader }) => {
|
||||||
const allSwfAssets = await itemSwfAssetLoader.load({
|
const allSwfAssets = await itemSwfAssetLoader.load({
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
|
|
|
@ -38,6 +38,14 @@ const typeDefs = gql`
|
||||||
UNKNOWN # for when we have the data, but we don't know what it is
|
UNKNOWN # for when we have the data, but we don't know what it is
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Body {
|
||||||
|
id: ID!
|
||||||
|
species: Species!
|
||||||
|
|
||||||
|
# A PetAppearance that has this body. Prefers Blue and happy poses.
|
||||||
|
canonicalAppearance: PetAppearance
|
||||||
|
}
|
||||||
|
|
||||||
# Cache for 1 week (unlikely to change)
|
# Cache for 1 week (unlikely to change)
|
||||||
type PetAppearance @cacheControl(maxAge: 604800) {
|
type PetAppearance @cacheControl(maxAge: 604800) {
|
||||||
id: ID!
|
id: ID!
|
||||||
|
@ -102,6 +110,29 @@ const resolvers = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Body: {
|
||||||
|
species: ({ species }) => {
|
||||||
|
if (species) {
|
||||||
|
return species;
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
"HACK: We populate this when you look up a canonicalAppearance, but " +
|
||||||
|
"don't have a direct query for it yet, oops!"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
canonicalAppearance: async (
|
||||||
|
{ id },
|
||||||
|
_,
|
||||||
|
{ canonicalPetStateForBodyLoader }
|
||||||
|
) => {
|
||||||
|
const petState = await canonicalPetStateForBodyLoader.load(id);
|
||||||
|
if (!petState) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return { id: petState.id };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
PetAppearance: {
|
PetAppearance: {
|
||||||
color: async ({ id }, _, { petStateLoader, petTypeLoader }) => {
|
color: async ({ id }, _, { petStateLoader, petTypeLoader }) => {
|
||||||
const petState = await petStateLoader.load(id);
|
const petState = await petStateLoader.load(id);
|
||||||
|
|
Loading…
Reference in a new issue