From d27395bda27e66de7b29ba4b1cdfbf091f309391 Mon Sep 17 00:00:00 2001 From: Matchu Date: Tue, 22 Sep 2020 03:53:48 -0700 Subject: [PATCH] hooray, animated items seem to be working?! ^w^ --- src/app/components/OutfitPreview.js | 23 +- src/app/components/useOutfitAppearance.js | 2 + src/server/query-tests/Item.test.js | 26 +- .../__snapshots__/Item.test.js.snap | 385 ++---------------- src/server/types/AppearanceLayer.js | 95 +++-- 5 files changed, 134 insertions(+), 397 deletions(-) diff --git a/src/app/components/OutfitPreview.js b/src/app/components/OutfitPreview.js index e15b34e..656bb29 100644 --- a/src/app/components/OutfitPreview.js +++ b/src/app/components/OutfitPreview.js @@ -6,6 +6,7 @@ import { CSSTransition, TransitionGroup } from "react-transition-group"; import OutfitCanvas, { OutfitCanvasImage, + OutfitCanvasMovie, loadImage, useEaselDependenciesLoader, } from "./OutfitCanvas"; @@ -152,13 +153,21 @@ export function OutfitLayers({ !loadingEasel && ( - {visibleLayers.map((layer) => ( - - ))} + {visibleLayers.map((layer) => + layer.canvasMovieLibraryUrl ? ( + + ) : ( + + ) + )} ) diff --git a/src/app/components/useOutfitAppearance.js b/src/app/components/useOutfitAppearance.js index c4d8fb6..9493e15 100644 --- a/src/app/components/useOutfitAppearance.js +++ b/src/app/components/useOutfitAppearance.js @@ -144,6 +144,7 @@ export const itemAppearanceFragment = gql` id remoteId # HACK: This is for Support tools, but other views don't need it svgUrl + canvasMovieLibraryUrl imageUrl(size: SIZE_600) swfUrl # HACK: This is for Support tools, but other views don't need it bodyId @@ -166,6 +167,7 @@ export const petAppearanceFragment = gql` layers { id svgUrl + canvasMovieLibraryUrl imageUrl(size: SIZE_600) zone { id diff --git a/src/server/query-tests/Item.test.js b/src/server/query-tests/Item.test.js index 8ef513b..7514ec2 100644 --- a/src/server/query-tests/Item.test.js +++ b/src/server/query-tests/Item.test.js @@ -65,7 +65,14 @@ describe("Item", () => { const res = await query({ query: gql` query { - items(ids: ["38912", "38911", "37375"]) { + items( + ids: [ + "38912" # Zafara Agent Robe + "38911" # Zafara Agent Hood + "37375" # Moon and Stars Background + "78244" # Bubbles on Water Foreground + ] + ) { id name @@ -75,6 +82,7 @@ describe("Item", () => { remoteId imageUrl(size: SIZE_600) svgUrl + canvasMovieLibraryUrl zone { id depth @@ -96,11 +104,12 @@ describe("Item", () => { expect(getDbCalls()).toMatchInlineSnapshot(` 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 [ "38912", "38911", "37375", + "78244", ], ], Array [ @@ -115,7 +124,7 @@ describe("Item", () => { 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)) 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)) OR (rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0))", Array [ "38912", "180", @@ -123,30 +132,35 @@ describe("Item", () => { "180", "37375", "180", + "78244", + "180", ], ], Array [ - "SELECT * FROM items WHERE id IN (?,?,?)", + "SELECT * FROM items WHERE id IN (?,?,?,?)", Array [ "38912", "38911", "37375", + "78244", ], ], Array [ - "SELECT * FROM zones WHERE id IN (?,?,?)", + "SELECT * FROM zones WHERE id IN (?,?,?,?)", Array [ "26", "40", "3", + "45", ], ], Array [ - "SELECT * FROM zone_translations WHERE zone_id IN (?,?,?) AND locale = \\"en\\"", + "SELECT * FROM zone_translations WHERE zone_id IN (?,?,?,?) AND locale = \\"en\\"", Array [ "26", "40", "3", + "45", ], ], ] diff --git a/src/server/query-tests/__snapshots__/Item.test.js.snap b/src/server/query-tests/__snapshots__/Item.test.js.snap index 5b343f1..269e713 100644 --- a/src/server/query-tests/__snapshots__/Item.test.js.snap +++ b/src/server/query-tests/__snapshots__/Item.test.js.snap @@ -7,8 +7,9 @@ Object { "appearanceOn": Object { "layers": Array [ Object { + "canvasMovieLibraryUrl": null, "id": "37128", - "imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/014/14856/600x600.png?v2-1587653266000", + "imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/014/14856/600x600.png?v2-1577836800000", "remoteId": "14856", "svgUrl": null, "zone": Object { @@ -34,8 +35,9 @@ Object { "appearanceOn": Object { "layers": Array [ Object { + "canvasMovieLibraryUrl": null, "id": "37129", - "imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/014/14857/600x600.png?v2-0", + "imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/014/14857/600x600.png?v2-1577836800000", "remoteId": "14857", "svgUrl": null, "zone": Object { @@ -61,8 +63,9 @@ Object { "appearanceOn": Object { "layers": Array [ Object { + "canvasMovieLibraryUrl": null, "id": "30203", - "imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/006/6829/600x600.png?v2-1598519675000", + "imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/006/6829/600x600.png?v2-1577836800000", "remoteId": "6829", "svgUrl": null, "zone": Object { @@ -77,366 +80,28 @@ Object { "id": "37375", "name": "Moon and Stars Background", }, - ], -} -`; - -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, - ], + "appearanceOn": Object { + "layers": Array [ + Object { + "canvasMovieLibraryUrl": "http://images.neopets.com/cp/items/data/000/000/564/564507_fc3216b9b8/all-item_foreground_lower.js", + "id": "468155", + "imageUrl": "https://impress-asset-images.s3.amazonaws.com/object/000/000/564/564507/600x600.png?v2-1577836800000", + "remoteId": "564507", + "svgUrl": null, + "zone": Object { + "depth": 50, + "id": "45", + "label": "Lower Foreground Item", + }, + }, + ], + "restrictedZones": Array [], + }, + "id": "78244", + "name": "Bubbles on Water Foreground", }, - 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", } `; diff --git a/src/server/types/AppearanceLayer.js b/src/server/types/AppearanceLayer.js index ad48bee..59b40ac 100644 --- a/src/server/types/AppearanceLayer.js +++ b/src/server/types/AppearanceLayer.js @@ -38,6 +38,14 @@ const typeDefs = gql` """ swfUrl: String + """ + This layer as an HTML canvas library JS file, if available. + + This will be empty for layers that don't animate, and might also be empty + for animated layers not yet converted by Neopets. + """ + canvasMovieLibraryUrl: String + """ This layer can fit on PetAppearances with the same bodyId. "0" is a special body ID that indicates it fits all PetAppearances. @@ -111,30 +119,7 @@ const resolvers = { // When the manifest is specifically null, that means we don't know if // it exists yet. Load it to find out! if (manifest === null) { - manifest = await loadAssetManifest(layer.url); - - // Then, write the new manifest. We make sure to write an empty string - // if there was no manifest, to signify that it doesn't exist, so we - // don't need to bother looking it up again. - // - // TODO: Someday the manifests will all exist, right? So we'll want to - // reload all the missing ones at that time. - manifest = manifest || ""; - const [ - result, - ] = await db.execute( - `UPDATE swf_assets SET manifest = ? WHERE id = ? LIMIT 1;`, - [manifest, layer.id] - ); - if (result.affectedRows !== 1) { - throw new Error( - `Expected to affect 1 asset, but affected ${result.affectedRows}` - ); - } - console.log( - `Loaded and saved manifest for ${layer.type} ${layer.remoteId}. ` + - `DTI ID: ${layer.id}. Exists?: ${Boolean(manifest)}` - ); + manifest = await loadAndCacheAssetManifest(db, layer); } if (!manifest) { @@ -163,6 +148,39 @@ const resolvers = { const url = new URL(assetDatum.path, "http://images.neopets.com"); return url.toString(); }, + canvasMovieLibraryUrl: async ({ id }, _, { db, swfAssetLoader }) => { + const layer = await swfAssetLoader.load(id); + let manifest = layer.manifest && JSON.parse(layer.manifest); + + // When the manifest is specifically null, that means we don't know if + // it exists yet. Load it to find out! + if (manifest === null) { + manifest = await loadAndCacheAssetManifest(db, layer); + } + + if (!manifest) { + return null; + } + + if (manifest.assets.length !== 1) { + return null; + } + + const asset = manifest.assets[0]; + if (asset.format !== "lod") { + return null; + } + + const jsAssetDatum = asset.assetData.find((ad) => + ad.path.endsWith(".js") + ); + if (!jsAssetDatum) { + return null; + } + + const url = new URL(jsAssetDatum.path, "http://images.neopets.com"); + return url.toString(); + }, item: async ({ id }, _, { db }) => { // TODO: If this becomes a popular request, we'll definitely need to // loaderize this! I'm cheating for now because it's just Support, one at @@ -207,6 +225,35 @@ async function loadAssetManifest(swfUrl) { }; } +async function loadAndCacheAssetManifest(db, layer) { + let manifest = await loadAssetManifest(layer.url); + + // Then, write the new manifest. We make sure to write an empty string + // if there was no manifest, to signify that it doesn't exist, so we + // don't need to bother looking it up again. + // + // TODO: Someday the manifests will all exist, right? So we'll want to + // reload all the missing ones at that time. + manifest = manifest || ""; + const [ + result, + ] = await db.execute( + `UPDATE swf_assets SET manifest = ? WHERE id = ? LIMIT 1;`, + [manifest, layer.id] + ); + if (result.affectedRows !== 1) { + throw new Error( + `Expected to affect 1 asset, but affected ${result.affectedRows}` + ); + } + console.log( + `Loaded and saved manifest for ${layer.type} ${layer.remoteId}. ` + + `DTI ID: ${layer.id}. Exists?: ${Boolean(manifest)}` + ); + + return manifest; +} + const SWF_URL_PATTERN = /^http:\/\/images\.neopets\.com\/cp\/(.+?)\/swf\/(.+?)\.swf$/; function convertSwfUrlToManifestUrl(swfUrl) {