From 30e0a149be17cf8629f6eaccbd1f8b1747b009fd Mon Sep 17 00:00:00 2001 From: Matchu Date: Mon, 4 Jan 2021 08:10:35 +0000 Subject: [PATCH] basic outfits page with pet-only thumbnails --- api/outfitImage.js | 13 +++-- src/app/UserOutfitsPage.js | 71 +++++++++++++++++++---- src/app/components/useOutfitAppearance.js | 24 ++++++-- src/server/types/AppearanceLayer.js | 2 +- 4 files changed, 89 insertions(+), 21 deletions(-) diff --git a/api/outfitImage.js b/api/outfitImage.js index aa26777..60dd33b 100644 --- a/api/outfitImage.js +++ b/api/outfitImage.js @@ -10,8 +10,8 @@ const beeline = require("honeycomb-beeline")({ const { renderOutfitImage } = require("../src/server/outfit-images"); const VALID_LAYER_URLS = [ - /^https:\/\/impress-asset-images\.s3\.amazonaws\.com\/(biology|object)\/[0-9]{3}\/[0-9]{3}\/[0-9]{3}\/[0-9]+\/150x150\.png$/, - /^http:\/\/images\.neopets\.com\/cp\/(biology|object)\/data\/[0-9]{3}\/[0-9]{3}\/[0-9]{3}\/[a-zA-Z0-9_]+\/[a-zA-Z0-9_]+\.svg$/, + /^https:\/\/impress-asset-images\.s3\.amazonaws\.com\/(biology|object)\/[0-9]{3}\/[0-9]{3}\/[0-9]{3}\/[0-9]+\/(150|300)x(150|300)\.png(\?[a-zA-Z0-9_-]+)?$/, + /^http:\/\/images\.neopets\.com\/cp\/(bio|object)\/data\/[0-9]{3}\/[0-9]{3}\/[0-9]{3}\/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+\.svg$/, ]; async function handle(req, res) { @@ -19,9 +19,14 @@ async function handle(req, res) { res.setHeader("Content-Type", "text/plain"); return res.status(400).send(`Missing required parameter: layerUrls`); } - const layerUrls = req.query.layerUrls.split(","); + const size = parseInt(req.query.size); + if (size !== 150 && size !== 300) { + res.setHeader("Content-Type", "text/plain"); + return res.status(400).send(`Size must be 150 or 300`); + } + for (const layerUrl of layerUrls) { if (!VALID_LAYER_URLS.some((pattern) => layerUrl.match(pattern))) { return res.status(400).send(`Unexpected layer URL format: ${layerUrl}`); @@ -30,7 +35,7 @@ async function handle(req, res) { let imageResult; try { - imageResult = await renderOutfitImage(layerUrls, 150); + imageResult = await renderOutfitImage(layerUrls, size); } catch (e) { console.error(e); res.setHeader("Content-Type", "text/plain"); diff --git a/src/app/UserOutfitsPage.js b/src/app/UserOutfitsPage.js index fe76f62..8bdbc68 100644 --- a/src/app/UserOutfitsPage.js +++ b/src/app/UserOutfitsPage.js @@ -1,16 +1,20 @@ import React from "react"; -import { Box, Center } from "@chakra-ui/react"; +import { Box, Center, Flex, Wrap, WrapItem } from "@chakra-ui/react"; import gql from "graphql-tag"; import { useQuery } from "@apollo/client"; import { ErrorMessage, Heading1 } from "./util"; +import { + getVisibleLayers, + petAppearanceFragmentForGetVisibleLayers, +} from "./components/useOutfitAppearance"; import HangerSpinner from "./components/HangerSpinner"; import useRequireLogin from "./components/useRequireLogin"; function UserOutfitsPage() { return ( - Your outfits + Your outfits ); @@ -21,22 +25,26 @@ function UserOutfitsPageContent() { const { loading: queryLoading, error, data } = useQuery( gql` - query UserOutfitsPageContent { + query UserOutfitsPageContent($size: LayerImageSize) { currentUser { outfits { id name petAppearance { id - } - wornItems { - id + layers { + id + svgUrl + imageUrl(size: $size) + } + ...PetAppearanceForGetVisibleLayers } } } } + ${petAppearanceFragmentForGetVisibleLayers} `, - { skip: userLoading } + { variables: { size: "SIZE_" + getBestImageSize() }, skip: userLoading } ); if (userLoading || queryLoading) { @@ -51,11 +59,54 @@ function UserOutfitsPageContent() { return Error loading outfits: {error.message}; } + const outfits = data.currentUser.outfits; + return ( - -
Data: {JSON.stringify(data, null, 4)}
-
+ + {outfits.map((outfit) => ( + + + + ))} + ); } +function OutfitCard({ outfit }) { + const thumbnailUrl = buildOutfitThumbnailUrl(outfit.petAppearance, []); + + return ( + + + {outfit.name} + + ); +} + +function buildOutfitThumbnailUrl(petAppearance, itemAppearances) { + const size = getBestImageSize(); + const visibleLayers = getVisibleLayers(petAppearance, itemAppearances); + const layerUrls = visibleLayers.map( + (layer) => layer.svgUrl || layer.imageUrl + ); + + return `/api/outfitImage?size=${size}&layerUrls=${layerUrls.join(",")}`; +} + +function getBestImageSize() { + if (window.devicePixelRatio > 1) { + return 300; + } else { + return 150; + } +} + export default UserOutfitsPage; diff --git a/src/app/components/useOutfitAppearance.js b/src/app/components/useOutfitAppearance.js index 9493e15..124149c 100644 --- a/src/app/components/useOutfitAppearance.js +++ b/src/app/components/useOutfitAppearance.js @@ -160,15 +160,11 @@ export const itemAppearanceFragment = gql` } `; -export const petAppearanceFragment = gql` - fragment PetAppearanceForOutfitPreview on PetAppearance { +export const petAppearanceFragmentForGetVisibleLayers = gql` + fragment PetAppearanceForGetVisibleLayers on PetAppearance { id - bodyId layers { id - svgUrl - canvasMovieLibraryUrl - imageUrl(size: SIZE_600) zone { id depth @client @@ -179,3 +175,19 @@ export const petAppearanceFragment = gql` } } `; + +export const petAppearanceFragment = gql` + fragment PetAppearanceForOutfitPreview on PetAppearance { + id + bodyId + layers { + id + svgUrl + canvasMovieLibraryUrl + imageUrl(size: SIZE_600) + } + ...PetAppearanceForGetVisibleLayers + } + + ${petAppearanceFragmentForGetVisibleLayers} +`; diff --git a/src/server/types/AppearanceLayer.js b/src/server/types/AppearanceLayer.js index 197e25c..f79de58 100644 --- a/src/server/types/AppearanceLayer.js +++ b/src/server/types/AppearanceLayer.js @@ -91,7 +91,7 @@ const resolvers = { const layer = await swfAssetLoader.load(id); return layer.url; }, - imageUrl: async ({ id }, { size }, { swfAssetLoader }) => { + imageUrl: async ({ id }, { size = "SIZE_150" }, { swfAssetLoader }) => { const layer = await swfAssetLoader.load(id); // If there's no image, return null. (In the development db, which isn't