added pet appearance queries, using in frontend

This commit is contained in:
Matt Dunn-Rankin 2020-04-23 14:23:46 -07:00
parent f5cb1d263c
commit dd5a7f9242
5 changed files with 283 additions and 88 deletions

View file

@ -2,6 +2,7 @@ GRANT SELECT ON openneo_impress.items TO impress2020;
GRANT SELECT ON openneo_impress.item_translations TO impress2020; GRANT SELECT ON openneo_impress.item_translations TO impress2020;
GRANT SELECT ON openneo_impress.parents_swf_assets TO impress2020; GRANT SELECT ON openneo_impress.parents_swf_assets TO impress2020;
GRANT SELECT ON openneo_impress.pet_types TO impress2020; GRANT SELECT ON openneo_impress.pet_types TO impress2020;
GRANT SELECT ON openneo_impress.pet_states TO impress2020;
GRANT SELECT ON openneo_impress.swf_assets TO impress2020; GRANT SELECT ON openneo_impress.swf_assets TO impress2020;
GRANT SELECT ON openneo_impress.zones TO impress2020; GRANT SELECT ON openneo_impress.zones TO impress2020;
GRANT SELECT ON openneo_impress.zone_translations TO impress2020; GRANT SELECT ON openneo_impress.zone_translations TO impress2020;

View file

@ -9,12 +9,25 @@ function OutfitPreview({ itemIds, speciesId, colorId }) {
const { loading, error, data } = useQuery( const { loading, error, data } = useQuery(
gql` gql`
query($itemIds: [ID!]!, $speciesId: ID!, $colorId: ID!) { query($itemIds: [ID!]!, $speciesId: ID!, $colorId: ID!) {
petAppearance(speciesId: $speciesId, colorId: $colorId) {
layers {
id
imageUrl(size: SIZE_600)
zone {
depth
}
}
}
items(ids: $itemIds) { items(ids: $itemIds) {
id id
appearanceOn(speciesId: $speciesId, colorId: $colorId) { appearanceOn(speciesId: $speciesId, colorId: $colorId) {
layers { layers {
id id
imageUrl(size: SIZE_600) imageUrl(size: SIZE_600)
zone {
depth
}
} }
} }
} }
@ -45,14 +58,22 @@ function OutfitPreview({ itemIds, speciesId, colorId }) {
); );
} }
const allLayers = [
...data.petAppearance.layers,
...data.items.map((i) => i.appearanceOn.layers).flat(),
];
allLayers.sort((a, b) => a.zone.depth - b.zone.depth);
return ( return (
<FullScreenCenter> <Box pos="relative" height="100%" width="100%">
<Image {allLayers.map((layer) => (
src="http://pets.neopets.com/cp/wgmdtdwz/1/7.png" <Box pos="absolute" top="0" right="0" bottom="0" left="0">
maxHeight="100%" <FullScreenCenter>
maxWidth="100%" <Image src={layer.imageUrl} maxWidth="100%" maxHeight="100%" />
/> </FullScreenCenter>
</FullScreenCenter> </Box>
))}
</Box>
); );
} }

View file

@ -1,7 +1,7 @@
const { gql } = require("apollo-server"); const { gql } = require("apollo-server");
const connectToDb = require("./db"); const connectToDb = require("./db");
const loaders = require("./loaders"); const buildLoaders = require("./loaders");
const typeDefs = gql` const typeDefs = gql`
enum LayerImageSize { enum LayerImageSize {
@ -14,14 +14,14 @@ const typeDefs = gql`
id: ID! id: ID!
name: String! name: String!
thumbnailUrl: String! thumbnailUrl: String!
appearanceOn(speciesId: ID!, colorId: ID!): ItemAppearance appearanceOn(speciesId: ID!, colorId: ID!): Appearance
} }
type ItemAppearance { type Appearance {
layers: [ItemAppearanceLayer!]! layers: [AppearanceLayer!]!
} }
type ItemAppearanceLayer { type AppearanceLayer {
id: ID! id: ID!
zone: Zone! zone: Zone!
imageUrl(size: LayerImageSize): String imageUrl(size: LayerImageSize): String
@ -35,6 +35,7 @@ const typeDefs = gql`
type Query { type Query {
items(ids: [ID!]!): [Item!]! items(ids: [ID!]!): [Item!]!
petAppearance(speciesId: ID!, colorId: ID!): Appearance
} }
`; `;
@ -44,26 +45,23 @@ const resolvers = {
const translation = await itemTranslationLoader.load(item.id); const translation = await itemTranslationLoader.load(item.id);
return translation.name; return translation.name;
}, },
appearanceOn: (item, { speciesId, colorId }) => ({ appearanceOn: async (
itemId: item.id, item,
speciesId, { speciesId, colorId },
colorId, { petTypeLoader, itemSwfAssetLoader }
}), ) => {
},
ItemAppearance: {
layers: async (ia, _, { petTypeLoader, swfAssetLoader }) => {
const petType = await petTypeLoader.load({ const petType = await petTypeLoader.load({
speciesId: ia.speciesId, speciesId: speciesId,
colorId: ia.colorId, colorId: colorId,
}); });
const swfAssets = await swfAssetLoader.load({ const swfAssets = await itemSwfAssetLoader.load({
itemId: ia.itemId, itemId: item.id,
bodyId: petType.bodyId, bodyId: petType.bodyId,
}); });
return swfAssets; return { layers: swfAssets };
}, },
}, },
ItemAppearanceLayer: { AppearanceLayer: {
zone: async (layer, _, { zoneLoader }) => { zone: async (layer, _, { zoneLoader }) => {
const zone = await zoneLoader.load(layer.zoneId); const zone = await zoneLoader.load(layer.zoneId);
return zone; return zone;
@ -82,7 +80,10 @@ const resolvers = {
const rid3 = paddedId.slice(6, 9); const rid3 = paddedId.slice(6, 9);
const time = Number(new Date(layer.convertedAt)); const time = Number(new Date(layer.convertedAt));
return `https://impress-asset-images.s3.amazonaws.com/object/${rid1}/${rid2}/${rid3}/${rid}/${sizeNum}x${sizeNum}.png?${time}`; return (
`https://impress-asset-images.s3.amazonaws.com/${layer.type}` +
`/${rid1}/${rid2}/${rid3}/${rid}/${sizeNum}x${sizeNum}.png?${time}`
);
}, },
}, },
Zone: { Zone: {
@ -92,10 +93,24 @@ const resolvers = {
}, },
}, },
Query: { Query: {
items: async (_, { ids }, { db }) => { items: async (_, { ids }, { itemLoader }) => {
const items = await loaders.loadItems(db, ids); const items = await itemLoader.loadMany(ids);
return items; return items;
}, },
petAppearance: async (
_,
{ speciesId, colorId },
{ petTypeLoader, petStateLoader, petSwfAssetLoader }
) => {
const petType = await petTypeLoader.load({
speciesId,
colorId,
});
const petStates = await petStateLoader.load(petType.id);
const petState = petStates[0]; // TODO
const swfAssets = await petSwfAssetLoader.load(petState.id);
return { layers: swfAssets };
},
}, },
}; };
@ -105,12 +120,7 @@ const config = {
context: async () => { context: async () => {
const db = await connectToDb(); const db = await connectToDb();
return { return {
db, ...buildLoaders(db),
itemTranslationLoader: loaders.buildItemTranslationLoader(db),
petTypeLoader: loaders.buildPetTypeLoader(db),
swfAssetLoader: loaders.buildSwfAssetLoader(db),
zoneLoader: loaders.buildZoneLoader(db),
zoneTranslationLoader: loaders.buildZoneTranslationLoader(db),
}; };
}, },

View file

@ -45,6 +45,11 @@ describe("Item", () => {
expect(res.data).toMatchInlineSnapshot(` expect(res.data).toMatchInlineSnapshot(`
Object { Object {
"items": Array [ "items": Array [
Object {
"id": "38913",
"name": "Zafara Agent Gloves",
"thumbnailUrl": "http://images.neopets.com/items/clo_zafara_agent_gloves.gif",
},
Object { Object {
"id": "38911", "id": "38911",
"name": "Zafara Agent Hood", "name": "Zafara Agent Hood",
@ -55,11 +60,6 @@ describe("Item", () => {
"name": "Zafara Agent Robe", "name": "Zafara Agent Robe",
"thumbnailUrl": "http://images.neopets.com/items/clo_zafara_agent_robe.gif", "thumbnailUrl": "http://images.neopets.com/items/clo_zafara_agent_robe.gif",
}, },
Object {
"id": "38913",
"name": "Zafara Agent Gloves",
"thumbnailUrl": "http://images.neopets.com/items/clo_zafara_agent_gloves.gif",
},
], ],
} }
`); `);
@ -76,9 +76,9 @@ describe("Item", () => {
Array [ Array [
"SELECT * FROM item_translations WHERE item_id IN (?,?,?) AND locale = \\"en\\"", "SELECT * FROM item_translations WHERE item_id IN (?,?,?) AND locale = \\"en\\"",
Array [ Array [
"38913",
"38911", "38911",
"38912", "38912",
"38913",
], ],
], ],
] ]
@ -117,18 +117,18 @@ describe("Item", () => {
"appearanceOn": Object { "appearanceOn": Object {
"layers": Array [ "layers": Array [
Object { Object {
"id": "30203", "id": "37128",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/006/6829/600x600.png?0", "imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/014/14856/600x600.png?1587653266000",
"zone": Object { "zone": Object {
"depth": 3, "depth": 30,
"id": "3", "id": "26",
"label": "Background", "label": "Jacket",
}, },
}, },
], ],
}, },
"id": "37375", "id": "38912",
"name": "Moon and Stars Background", "name": "Zafara Agent Robe",
}, },
Object { Object {
"appearanceOn": Object { "appearanceOn": Object {
@ -151,18 +151,18 @@ describe("Item", () => {
"appearanceOn": Object { "appearanceOn": Object {
"layers": Array [ "layers": Array [
Object { Object {
"id": "37128", "id": "30203",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/014/14856/600x600.png?1587653266000", "imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/006/6829/600x600.png?0",
"zone": Object { "zone": Object {
"depth": 30, "depth": 3,
"id": "26", "id": "3",
"label": "Jacket", "label": "Background",
}, },
}, },
], ],
}, },
"id": "38912", "id": "37375",
"name": "Zafara Agent Robe", "name": "Moon and Stars Background",
}, },
], ],
} }
@ -180,9 +180,9 @@ describe("Item", () => {
Array [ Array [
"SELECT * FROM item_translations WHERE item_id IN (?,?,?) AND locale = \\"en\\"", "SELECT * FROM item_translations WHERE item_id IN (?,?,?) AND locale = \\"en\\"",
Array [ Array [
"37375",
"38911",
"38912", "38912",
"38911",
"37375",
], ],
], ],
Array [ Array [
@ -203,28 +203,145 @@ describe("Item", () => {
rel.swf_asset_id = sa.id rel.swf_asset_id = sa.id
WHERE (rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0)) OR (rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0)) OR (rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0))", WHERE (rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0)) OR (rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0)) OR (rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0))",
Array [ Array [
"37375", "38912",
"180", "180",
"38911", "38911",
"180", "180",
"38912", "37375",
"180", "180",
], ],
], ],
Array [ Array [
"SELECT * FROM zones WHERE id IN (?,?,?)", "SELECT * FROM zones WHERE id IN (?,?,?)",
Array [ Array [
"3",
"40",
"26", "26",
"40",
"3",
], ],
], ],
Array [ Array [
"SELECT * FROM zone_translations WHERE zone_id IN (?,?,?) AND locale = \\"en\\"", "SELECT * FROM zone_translations WHERE zone_id IN (?,?,?) AND locale = \\"en\\"",
Array [ Array [
"3",
"40",
"26", "26",
"40",
"3",
],
],
]
`);
});
});
describe("PetAppearance", () => {
it("loads for species and color", async () => {
const res = await query({
query: gql`
query {
petAppearance(speciesId: "54", colorId: "75") {
layers {
id
imageUrl(size: SIZE_600)
zone {
depth
}
}
}
}
`,
});
expect(res).toHaveNoErrors();
expect(res.data).toMatchInlineSnapshot(`
Object {
"petAppearance": Object {
"layers": Array [
Object {
"id": "5995",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7941/600x600.png?0",
"zone": Object {
"depth": 18,
},
},
Object {
"id": "5996",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7942/600x600.png?0",
"zone": Object {
"depth": 7,
},
},
Object {
"id": "6000",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7946/600x600.png?0",
"zone": Object {
"depth": 40,
},
},
Object {
"id": "16467",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/024/24008/600x600.png?0",
"zone": Object {
"depth": 34,
},
},
Object {
"id": "19549",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/028/28548/600x600.png?1345719457000",
"zone": Object {
"depth": 37,
},
},
Object {
"id": "19550",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/028/28549/600x600.png?0",
"zone": Object {
"depth": 38,
},
},
Object {
"id": "163528",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/028/28549/600x600.png?1326455337000",
"zone": Object {
"depth": 38,
},
},
],
},
}
`);
expect(queryFn.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"SELECT * FROM pet_types WHERE (species_id = ? AND color_id = ?)",
Array [
"54",
"75",
],
],
Array [
"SELECT * FROM pet_states WHERE pet_type_id IN (?)",
Array [
"2",
],
],
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 [
"2",
],
],
Array [
"SELECT * FROM zones WHERE id IN (?,?,?,?,?,?)",
Array [
"15",
"5",
"37",
"30",
"33",
"34",
], ],
], ],
] ]

View file

@ -1,14 +1,21 @@
const DataLoader = require("dataloader"); const DataLoader = require("dataloader");
async function loadItems(db, ids) { const buildItemsLoader = (db) =>
const qs = ids.map((_) => "?").join(","); new DataLoader(async (ids) => {
const [rows, _] = await db.execute( const qs = ids.map((_) => "?").join(",");
`SELECT * FROM items WHERE id IN (${qs})`, const [rows, _] = await db.execute(
ids `SELECT * FROM items WHERE id IN (${qs})`,
); ids
const entities = rows.map(normalizeRow); );
return entities;
} const entities = rows.map(normalizeRow);
const entitiesById = new Map(entities.map((e) => [e.id, e]));
return ids.map(
(id) =>
entitiesById.get(id) || new Error(`could not find item with ID: ${id}`)
);
});
const buildItemTranslationLoader = (db) => const buildItemTranslationLoader = (db) =>
new DataLoader(async (itemIds) => { new DataLoader(async (itemIds) => {
@ -52,7 +59,7 @@ const buildPetTypeLoader = (db) =>
); );
}); });
const buildSwfAssetLoader = (db) => const buildItemSwfAssetLoader = (db) =>
new DataLoader(async (itemAndBodyPairs) => { new DataLoader(async (itemAndBodyPairs) => {
const conditions = []; const conditions = [];
const values = []; const values = [];
@ -82,21 +89,54 @@ const buildSwfAssetLoader = (db) =>
); );
}); });
const buildPetSwfAssetLoader = (db) =>
new DataLoader(async (petStateIds) => {
const qs = petStateIds.map((_) => "?").join(",");
const [rows, _] = await db.execute(
`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 (${qs})`,
petStateIds
);
const entities = rows.map(normalizeRow);
return petStateIds.map((petStateId) =>
entities.filter((e) => e.parentId === petStateId)
);
});
const buildPetStateLoader = (db) =>
new DataLoader(async (petTypeIds) => {
const qs = petTypeIds.map((_) => "?").join(",");
const [rows, _] = await db.execute(
`SELECT * FROM pet_states WHERE pet_type_id IN (${qs})`,
petTypeIds
);
const entities = rows.map(normalizeRow);
return petTypeIds.map((petTypeId) =>
entities.filter((e) => e.petTypeId === petTypeId)
);
});
const buildZoneLoader = (db) => const buildZoneLoader = (db) =>
new DataLoader(async (zoneIds) => { new DataLoader(async (ids) => {
const qs = zoneIds.map((_) => "?").join(","); const qs = ids.map((_) => "?").join(",");
const [rows, _] = await db.execute( const [rows, _] = await db.execute(
`SELECT * FROM zones WHERE id IN (${qs})`, `SELECT * FROM zones WHERE id IN (${qs})`,
zoneIds ids
); );
const entities = rows.map(normalizeRow); const entities = rows.map(normalizeRow);
const entitiesById = new Map(entities.map((e) => [e.id, e])); const entitiesById = new Map(entities.map((e) => [e.id, e]));
return zoneIds.map( return ids.map(
(zoneId) => (id) =>
entitiesById.get(zoneId) || entitiesById.get(id) || new Error(`could not find zone with ID: ${id}`)
new Error(`could not find zone with ID: ${zoneId}`)
); );
}); });
@ -130,11 +170,17 @@ function normalizeRow(row) {
return normalizedRow; return normalizedRow;
} }
module.exports = { function buildLoaders(db) {
loadItems, return {
buildItemTranslationLoader, itemLoader: buildItemsLoader(db),
buildPetTypeLoader, itemTranslationLoader: buildItemTranslationLoader(db),
buildSwfAssetLoader, petTypeLoader: buildPetTypeLoader(db),
buildZoneLoader, itemSwfAssetLoader: buildItemSwfAssetLoader(db),
buildZoneTranslationLoader, petSwfAssetLoader: buildPetSwfAssetLoader(db),
}; petStateLoader: buildPetStateLoader(db),
zoneLoader: buildZoneLoader(db),
zoneTranslationLoader: buildZoneTranslationLoader(db),
};
}
module.exports = buildLoaders;