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.parents_swf_assets 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.zones 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(
gql`
query($itemIds: [ID!]!, $speciesId: ID!, $colorId: ID!) {
petAppearance(speciesId: $speciesId, colorId: $colorId) {
layers {
id
imageUrl(size: SIZE_600)
zone {
depth
}
}
}
items(ids: $itemIds) {
id
appearanceOn(speciesId: $speciesId, colorId: $colorId) {
layers {
id
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 (
<FullScreenCenter>
<Image
src="http://pets.neopets.com/cp/wgmdtdwz/1/7.png"
maxHeight="100%"
maxWidth="100%"
/>
</FullScreenCenter>
<Box pos="relative" height="100%" width="100%">
{allLayers.map((layer) => (
<Box pos="absolute" top="0" right="0" bottom="0" left="0">
<FullScreenCenter>
<Image src={layer.imageUrl} maxWidth="100%" maxHeight="100%" />
</FullScreenCenter>
</Box>
))}
</Box>
);
}

View file

@ -1,7 +1,7 @@
const { gql } = require("apollo-server");
const connectToDb = require("./db");
const loaders = require("./loaders");
const buildLoaders = require("./loaders");
const typeDefs = gql`
enum LayerImageSize {
@ -14,14 +14,14 @@ const typeDefs = gql`
id: ID!
name: String!
thumbnailUrl: String!
appearanceOn(speciesId: ID!, colorId: ID!): ItemAppearance
appearanceOn(speciesId: ID!, colorId: ID!): Appearance
}
type ItemAppearance {
layers: [ItemAppearanceLayer!]!
type Appearance {
layers: [AppearanceLayer!]!
}
type ItemAppearanceLayer {
type AppearanceLayer {
id: ID!
zone: Zone!
imageUrl(size: LayerImageSize): String
@ -35,6 +35,7 @@ const typeDefs = gql`
type Query {
items(ids: [ID!]!): [Item!]!
petAppearance(speciesId: ID!, colorId: ID!): Appearance
}
`;
@ -44,26 +45,23 @@ const resolvers = {
const translation = await itemTranslationLoader.load(item.id);
return translation.name;
},
appearanceOn: (item, { speciesId, colorId }) => ({
itemId: item.id,
speciesId,
colorId,
}),
},
ItemAppearance: {
layers: async (ia, _, { petTypeLoader, swfAssetLoader }) => {
appearanceOn: async (
item,
{ speciesId, colorId },
{ petTypeLoader, itemSwfAssetLoader }
) => {
const petType = await petTypeLoader.load({
speciesId: ia.speciesId,
colorId: ia.colorId,
speciesId: speciesId,
colorId: colorId,
});
const swfAssets = await swfAssetLoader.load({
itemId: ia.itemId,
const swfAssets = await itemSwfAssetLoader.load({
itemId: item.id,
bodyId: petType.bodyId,
});
return swfAssets;
return { layers: swfAssets };
},
},
ItemAppearanceLayer: {
AppearanceLayer: {
zone: async (layer, _, { zoneLoader }) => {
const zone = await zoneLoader.load(layer.zoneId);
return zone;
@ -82,7 +80,10 @@ const resolvers = {
const rid3 = paddedId.slice(6, 9);
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: {
@ -92,10 +93,24 @@ const resolvers = {
},
},
Query: {
items: async (_, { ids }, { db }) => {
const items = await loaders.loadItems(db, ids);
items: async (_, { ids }, { itemLoader }) => {
const items = await itemLoader.loadMany(ids);
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 () => {
const db = await connectToDb();
return {
db,
itemTranslationLoader: loaders.buildItemTranslationLoader(db),
petTypeLoader: loaders.buildPetTypeLoader(db),
swfAssetLoader: loaders.buildSwfAssetLoader(db),
zoneLoader: loaders.buildZoneLoader(db),
zoneTranslationLoader: loaders.buildZoneTranslationLoader(db),
...buildLoaders(db),
};
},

View file

@ -45,6 +45,11 @@ describe("Item", () => {
expect(res.data).toMatchInlineSnapshot(`
Object {
"items": Array [
Object {
"id": "38913",
"name": "Zafara Agent Gloves",
"thumbnailUrl": "http://images.neopets.com/items/clo_zafara_agent_gloves.gif",
},
Object {
"id": "38911",
"name": "Zafara Agent Hood",
@ -55,11 +60,6 @@ describe("Item", () => {
"name": "Zafara Agent Robe",
"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 [
"SELECT * FROM item_translations WHERE item_id IN (?,?,?) AND locale = \\"en\\"",
Array [
"38913",
"38911",
"38912",
"38913",
],
],
]
@ -117,18 +117,18 @@ describe("Item", () => {
"appearanceOn": Object {
"layers": Array [
Object {
"id": "30203",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/006/6829/600x600.png?0",
"id": "37128",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/014/14856/600x600.png?1587653266000",
"zone": Object {
"depth": 3,
"id": "3",
"label": "Background",
"depth": 30,
"id": "26",
"label": "Jacket",
},
},
],
},
"id": "37375",
"name": "Moon and Stars Background",
"id": "38912",
"name": "Zafara Agent Robe",
},
Object {
"appearanceOn": Object {
@ -151,18 +151,18 @@ describe("Item", () => {
"appearanceOn": Object {
"layers": Array [
Object {
"id": "37128",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/014/14856/600x600.png?1587653266000",
"id": "30203",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/006/6829/600x600.png?0",
"zone": Object {
"depth": 30,
"id": "26",
"label": "Jacket",
"depth": 3,
"id": "3",
"label": "Background",
},
},
],
},
"id": "38912",
"name": "Zafara Agent Robe",
"id": "37375",
"name": "Moon and Stars Background",
},
],
}
@ -180,9 +180,9 @@ describe("Item", () => {
Array [
"SELECT * FROM item_translations WHERE item_id IN (?,?,?) AND locale = \\"en\\"",
Array [
"37375",
"38911",
"38912",
"38911",
"37375",
],
],
Array [
@ -203,28 +203,145 @@ describe("Item", () => {
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))",
Array [
"37375",
"38912",
"180",
"38911",
"180",
"38912",
"37375",
"180",
],
],
Array [
"SELECT * FROM zones WHERE id IN (?,?,?)",
Array [
"3",
"40",
"26",
"40",
"3",
],
],
Array [
"SELECT * FROM zone_translations WHERE zone_id IN (?,?,?) AND locale = \\"en\\"",
Array [
"3",
"40",
"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");
async function loadItems(db, ids) {
const qs = ids.map((_) => "?").join(",");
const [rows, _] = await db.execute(
`SELECT * FROM items WHERE id IN (${qs})`,
ids
);
const entities = rows.map(normalizeRow);
return entities;
}
const buildItemsLoader = (db) =>
new DataLoader(async (ids) => {
const qs = ids.map((_) => "?").join(",");
const [rows, _] = await db.execute(
`SELECT * FROM items WHERE id IN (${qs})`,
ids
);
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) =>
new DataLoader(async (itemIds) => {
@ -52,7 +59,7 @@ const buildPetTypeLoader = (db) =>
);
});
const buildSwfAssetLoader = (db) =>
const buildItemSwfAssetLoader = (db) =>
new DataLoader(async (itemAndBodyPairs) => {
const conditions = [];
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) =>
new DataLoader(async (zoneIds) => {
const qs = zoneIds.map((_) => "?").join(",");
new DataLoader(async (ids) => {
const qs = ids.map((_) => "?").join(",");
const [rows, _] = await db.execute(
`SELECT * FROM zones WHERE id IN (${qs})`,
zoneIds
ids
);
const entities = rows.map(normalizeRow);
const entitiesById = new Map(entities.map((e) => [e.id, e]));
return zoneIds.map(
(zoneId) =>
entitiesById.get(zoneId) ||
new Error(`could not find zone with ID: ${zoneId}`)
return ids.map(
(id) =>
entitiesById.get(id) || new Error(`could not find zone with ID: ${id}`)
);
});
@ -130,11 +170,17 @@ function normalizeRow(row) {
return normalizedRow;
}
module.exports = {
loadItems,
buildItemTranslationLoader,
buildPetTypeLoader,
buildSwfAssetLoader,
buildZoneLoader,
buildZoneTranslationLoader,
};
function buildLoaders(db) {
return {
itemLoader: buildItemsLoader(db),
itemTranslationLoader: buildItemTranslationLoader(db),
petTypeLoader: buildPetTypeLoader(db),
itemSwfAssetLoader: buildItemSwfAssetLoader(db),
petSwfAssetLoader: buildPetSwfAssetLoader(db),
petStateLoader: buildPetStateLoader(db),
zoneLoader: buildZoneLoader(db),
zoneTranslationLoader: buildZoneTranslationLoader(db),
};
}
module.exports = buildLoaders;