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,
|
||||
} from "./components/ItemCard";
|
||||
import { Delay, Heading1, usePageTitle } from "./util";
|
||||
import {
|
||||
itemAppearanceFragment,
|
||||
petAppearanceFragment,
|
||||
} from "./components/useOutfitAppearance";
|
||||
import OutfitPreview from "./components/OutfitPreview";
|
||||
import SpeciesColorPicker from "./components/SpeciesColorPicker";
|
||||
|
||||
|
@ -439,16 +443,41 @@ function ItemPageOutfitPreview({ itemId }) {
|
|||
[]
|
||||
);
|
||||
const [petState, setPetState] = React.useState({
|
||||
// Start by looking up Acara appearance data.
|
||||
speciesId: "1",
|
||||
colorId: "8",
|
||||
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
|
||||
// appearance, but only against the cache. That way, we don't send a
|
||||
// redundant network request just for this (the OutfitPreview component will
|
||||
// handle it!), but we'll get an update once it arrives in the cache.
|
||||
const { data } = useQuery(
|
||||
const { cachedData } = useQuery(
|
||||
gql`
|
||||
query ItemPageOutfitPreview_CacheOnly(
|
||||
$itemId: ID!
|
||||
|
@ -477,7 +506,7 @@ function ItemPageOutfitPreview({ itemId }) {
|
|||
// 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
|
||||
// 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 borderColor = useColorModeValue("green.700", "green.400");
|
||||
|
|
|
@ -294,11 +294,13 @@ const buildItemsThatNeedModelsLoader = (db) =>
|
|||
return [lastResult];
|
||||
});
|
||||
|
||||
const buildItemSpeciesWithAppearanceDataLoader = (db) =>
|
||||
const buildItemBodiesWithAppearanceDataLoader = (db) =>
|
||||
new DataLoader(async (itemIds) => {
|
||||
const qs = itemIds.map((_) => "?").join(",");
|
||||
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
|
||||
INNER JOIN parents_swf_assets ON
|
||||
items.id = parents_swf_assets.parent_id AND
|
||||
|
@ -307,8 +309,13 @@ const buildItemSpeciesWithAppearanceDataLoader = (db) =>
|
|||
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 = ${qs}
|
||||
ORDER BY id`,
|
||||
INNER JOIN colors ON
|
||||
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
|
||||
);
|
||||
|
||||
|
@ -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) =>
|
||||
new DataLoader(async (ids) => {
|
||||
const qs = ids.map((_) => "?").join(",");
|
||||
|
@ -617,7 +668,7 @@ function buildLoaders(db) {
|
|||
loaders.itemSearchLoader = buildItemSearchLoader(db, loaders);
|
||||
loaders.itemSearchToFitLoader = buildItemSearchToFitLoader(db, loaders);
|
||||
loaders.itemsThatNeedModelsLoader = buildItemsThatNeedModelsLoader(db);
|
||||
loaders.itemSpeciesWithAppearanceDataLoader = buildItemSpeciesWithAppearanceDataLoader(
|
||||
loaders.itemBodiesWithAppearanceDataLoader = buildItemBodiesWithAppearanceDataLoader(
|
||||
db
|
||||
);
|
||||
loaders.petTypeLoader = buildPetTypeLoader(db);
|
||||
|
@ -637,6 +688,10 @@ function buildLoaders(db) {
|
|||
db,
|
||||
loaders
|
||||
);
|
||||
loaders.canonicalPetStateForBodyLoader = buildCanonicalPetStateForBodyLoader(
|
||||
db,
|
||||
loaders
|
||||
);
|
||||
loaders.speciesLoader = buildSpeciesLoader(db);
|
||||
loaders.speciesTranslationLoader = buildSpeciesTranslationLoader(db);
|
||||
loaders.userLoader = buildUserLoader(db);
|
||||
|
|
|
@ -431,66 +431,158 @@ describe("Item", () => {
|
|||
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({
|
||||
query: gql`
|
||||
query {
|
||||
item(
|
||||
id: "38911" # Zafara Agent Hood
|
||||
) {
|
||||
speciesWithAppearanceDataForThisItem {
|
||||
canonicalAppearance {
|
||||
id
|
||||
layers {
|
||||
id
|
||||
}
|
||||
body {
|
||||
species {
|
||||
name
|
||||
}
|
||||
canonicalAppearance {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.item.speciesWithAppearanceDataForThisItem).toHaveLength(1);
|
||||
expect(res.data.item.speciesWithAppearanceDataForThisItem[0].name).toEqual(
|
||||
"Zafara"
|
||||
const body = res.data.item.canonicalAppearance.body;
|
||||
expect(body.species.name).toEqual("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({
|
||||
query: gql`
|
||||
query {
|
||||
item(
|
||||
id: "74967" # 17th Birthday Party Hat
|
||||
) {
|
||||
speciesWithAppearanceDataForThisItem {
|
||||
canonicalAppearance {
|
||||
id
|
||||
layers {
|
||||
id
|
||||
}
|
||||
body {
|
||||
species {
|
||||
name
|
||||
}
|
||||
canonicalAppearance {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.item.speciesWithAppearanceDataForThisItem).toHaveLength(55);
|
||||
expect(getDbCalls()).toMatchSnapshot();
|
||||
const body = res.data.item.canonicalAppearance.body;
|
||||
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({
|
||||
query: gql`
|
||||
query {
|
||||
item(
|
||||
id: "37375" # Moon and Stars Background
|
||||
) {
|
||||
speciesWithAppearanceDataForThisItem {
|
||||
canonicalAppearance {
|
||||
id
|
||||
layers {
|
||||
id
|
||||
}
|
||||
body {
|
||||
species {
|
||||
name
|
||||
}
|
||||
canonicalAppearance {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.item.speciesWithAppearanceDataForThisItem).toHaveLength(55);
|
||||
expect(getDbCalls()).toMatchSnapshot();
|
||||
const body = res.data.item.canonicalAppearance.body;
|
||||
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`] = `
|
||||
Object {
|
||||
"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`] = `
|
||||
Object {
|
||||
"items": Array [
|
||||
|
|
|
@ -42,17 +42,19 @@ const typeDefs = gql`
|
|||
# bodies like Blue, Green, Red, etc.
|
||||
speciesThatNeedModels(colorId: ID): [Species!]!
|
||||
|
||||
# Species that we know how they look wearing this item. Used to initialize
|
||||
# the preview on the item page with a compatible species.
|
||||
# TODO: This would probably make more sense as like, compatible bodies, so
|
||||
# we could also encode special-color stuff in here too.
|
||||
speciesWithAppearanceDataForThisItem: [Species!]!
|
||||
# Return a single ItemAppearance for this item. It'll be for the species
|
||||
# with the smallest ID for which we have item appearance data. We use this
|
||||
# on the item page, to initialize the preview section. (You can find out
|
||||
# which species this is for by going through the body field on
|
||||
# ItemAppearance!)
|
||||
canonicalAppearance: ItemAppearance
|
||||
}
|
||||
|
||||
type ItemAppearance {
|
||||
id: ID!
|
||||
item: Item!
|
||||
bodyId: ID!
|
||||
bodyId: ID! # Deprecated, use body->id.
|
||||
body: Body!
|
||||
layers: [AppearanceLayer!]
|
||||
restrictedZones: [Zone!]!
|
||||
}
|
||||
|
@ -182,18 +184,29 @@ const resolvers = {
|
|||
);
|
||||
return unmodeledSpeciesIds.map((id) => ({ id }));
|
||||
},
|
||||
speciesWithAppearanceDataForThisItem: async (
|
||||
canonicalAppearance: async (
|
||||
{ id },
|
||||
_,
|
||||
{ itemSpeciesWithAppearanceDataLoader }
|
||||
{ itemBodiesWithAppearanceDataLoader }
|
||||
) => {
|
||||
const rows = await itemSpeciesWithAppearanceDataLoader.load(id);
|
||||
return rows.map((row) => ({ id: row.id }));
|
||||
const rows = await itemBodiesWithAppearanceDataLoader.load(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: {
|
||||
id: ({ item, bodyId }) => `item-${item.id}-body-${bodyId}`,
|
||||
body: ({ body, bodyId }) => body || { id: bodyId },
|
||||
layers: async ({ item, bodyId }, _, { itemSwfAssetLoader }) => {
|
||||
const allSwfAssets = await itemSwfAssetLoader.load({
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
type PetAppearance @cacheControl(maxAge: 604800) {
|
||||
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: {
|
||||
color: async ({ id }, _, { petStateLoader, petTypeLoader }) => {
|
||||
const petState = await petStateLoader.load(id);
|
||||
|
|
Loading…
Reference in a new issue