basic outfits page with pet-only thumbnails
This commit is contained in:
parent
b8d919b247
commit
30e0a149be
4 changed files with 89 additions and 21 deletions
|
@ -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");
|
||||
|
|
|
@ -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 (
|
||||
<Box>
|
||||
<Heading1>Your outfits</Heading1>
|
||||
<Heading1 marginBottom="4">Your outfits</Heading1>
|
||||
<UserOutfitsPageContent />
|
||||
</Box>
|
||||
);
|
||||
|
@ -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 {
|
||||
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 <ErrorMessage>Error loading outfits: {error.message}</ErrorMessage>;
|
||||
}
|
||||
|
||||
const outfits = data.currentUser.outfits;
|
||||
|
||||
return (
|
||||
<code>
|
||||
<pre>Data: {JSON.stringify(data, null, 4)}</pre>
|
||||
</code>
|
||||
<Wrap spacing="4">
|
||||
{outfits.map((outfit) => (
|
||||
<WrapItem key={outfit.id}>
|
||||
<OutfitCard outfit={outfit} />
|
||||
</WrapItem>
|
||||
))}
|
||||
</Wrap>
|
||||
);
|
||||
}
|
||||
|
||||
function OutfitCard({ outfit }) {
|
||||
const thumbnailUrl = buildOutfitThumbnailUrl(outfit.petAppearance, []);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
textAlign="center"
|
||||
boxShadow="md"
|
||||
borderRadius="md"
|
||||
padding="3"
|
||||
width="calc(150px + 2em)"
|
||||
>
|
||||
<Box as="img" src={thumbnailUrl} width={150} height={150} />
|
||||
<Box>{outfit.name}</Box>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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}
|
||||
`;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue