From 390c21b53e42c6169d91eede1109f46dc4783251 Mon Sep 17 00:00:00 2001 From: Matchu Date: Tue, 25 May 2021 03:35:32 -0700 Subject: [PATCH] Add image converter to /outfit-urls Adding the tool that will help people convert one image at a time! --- src/app/OutfitUrlsPage.js | 220 ++++++++++++++++++++++++++++++++++++- src/server/types/Outfit.js | 23 ++++ 2 files changed, 237 insertions(+), 6 deletions(-) diff --git a/src/app/OutfitUrlsPage.js b/src/app/OutfitUrlsPage.js index 6231e0f..2487899 100644 --- a/src/app/OutfitUrlsPage.js +++ b/src/app/OutfitUrlsPage.js @@ -1,8 +1,31 @@ import React from "react"; import { css } from "@emotion/react"; -import { VStack } from "@chakra-ui/react"; +import { + AspectRatio, + Box, + Button, + Center, + FormControl, + FormErrorMessage, + FormLabel, + Grid, + Input, + InputGroup, + InputRightElement, + Tab, + TabList, + TabPanel, + TabPanels, + Tabs, + useBreakpointValue, + useClipboard, + useColorModeValue, + VStack, +} from "@chakra-ui/react"; -import { Heading1, Heading2 } from "./util"; +import { Delay, Heading1, Heading2 } from "./util"; +import HangerSpinner from "./components/HangerSpinner"; +import { gql, useQuery } from "@apollo/client"; function OutfitUrlsPage() { return ( @@ -27,13 +50,14 @@ function OutfitUrlsPage() { `} >
-

Hi, friends! Sorry for the trouble 😓

- In short: Old outfit image URLs are expiring, but you can get the - updated URL right here! + Hi, friends! Sorry for the trouble 😓 In short, by switching to the + new outfit URLs below, we'll decrease our hosting costs by + $20/month! 🙏

-

TODO: Outfit image URL converter goes here

+
+
The history

@@ -71,4 +95,188 @@ function OutfitUrlsPage() { ); } +function OutfitUrlConverter() { + return ( + + + Convert an image + Convert a lookup/petpage + + + + + + + + + + + ); +} + +function SingleImageConverter() { + const [inputUrl, setInputUrl] = React.useState(""); + + let parsedUrl; + let parseError; + try { + parsedUrl = parseS3OutfitUrl(inputUrl); + } catch (e) { + parseError = e; + } + + const outfitId = parsedUrl?.outfitId; + const size = parsedUrl?.size; + + const { loading, error: gqlError, data } = useQuery( + gql` + query OutfitUrlsSingleImageConverter( + $outfitId: ID! + $size: OutfitImageSize + ) { + outfit(id: $outfitId) { + id + imageUrl(size: $size) + } + } + `, + { + variables: { outfitId, size: `SIZE_${size}` }, + skip: outfitId == null || size == null, + onError: (e) => console.error(e), + } + ); + + const imageUrl = data?.outfit?.imageUrl; + + const previewBackground = useColorModeValue("gray.200", "whiteAlpha.300"); + const spinnerSize = useBreakpointValue({ base: "md", md: "sm" }); + + const { onCopy, hasCopied } = useClipboard(imageUrl); + + return ( + + + Enter an outfit image URL + setInputUrl(e.target.value)} + /> + + {parseError?.message || + (gqlError && `Error loading outfit data. Try again?`) || + null} + + + + + Then, use this new URL in your layouts instead: + + + + {imageUrl && ( + + + + )} + + + +

+ {loading ? ( + + + + ) : imageUrl ? ( + + ) : null} +
+ + + ); +} + +function BulkImageConverter() { + return TODO: Bulk image converter; +} + +const S3_OUTFIT_URL_PATTERN = /^https?:\/\/openneo-uploads\.s3\.amazonaws\.com\/outfits\/([0-9]{3})\/([0-9]{3})\/([0-9]{3})\/(preview|medium_preview|small_preview)\.png$/; +const S3_FILENAMES_TO_SIZES = { + preview: 600, + medium_preview: 300, + small_preview: 150, +}; + +function parseS3OutfitUrl(url) { + if (!url) { + return null; + } + + const match = S3_OUTFIT_URL_PATTERN.exec(url); + if (!match) { + throw new Error( + `This URL didn't match the expected pattern. Make sure it's formatted like this: https://openneo-uploads.s3.amazonaws.com/outfits/123/456/789/preview.png` + ); + } + + // Convert ID to number to remove leading 0s, then convert back to string for + // consistency with how we handle outfit IDs in this app. + const outfitId = String(Number(`${match[1]}${match[2]}${match[3]}`)); + const size = S3_FILENAMES_TO_SIZES[match[4]]; + + return { outfitId, size }; +} + export default OutfitUrlsPage; diff --git a/src/server/types/Outfit.js b/src/server/types/Outfit.js index 42287c9..31a4acd 100644 --- a/src/server/types/Outfit.js +++ b/src/server/types/Outfit.js @@ -2,6 +2,12 @@ import { gql } from "apollo-server"; import { getPoseFromPetState } from "../util"; const typeDefs = gql` + enum OutfitImageSize { + SIZE_600 + SIZE_300 + SIZE_150 + } + type Outfit { id: ID! name: String @@ -19,6 +25,8 @@ const typeDefs = gql` # This is a convenience field: you could query this from the combination of # petAppearance and wornItems, but this gets you it in one shot! itemAppearances: [ItemAppearance!]! + + imageUrl(size: OutfitImageSize): String! } extend type Query { @@ -103,6 +111,21 @@ const resolvers = { const outfit = await outfitLoader.load(id); return outfit.updatedAt.toISOString(); }, + imageUrl: async ({ id }, { size = "SIZE_600" }, { outfitLoader }) => { + const outfit = await outfitLoader.load(id); + + const updatedAtTimestamp = Math.floor( + new Date(outfit.updatedAt).getTime() / 1000 + ); + const sizeNum = size.split("_")[1]; + + return ( + `https://impress-outfit-images.openneo.net/outfits` + + `/${encodeURIComponent(outfit.id)}` + + `/v/${encodeURIComponent(updatedAtTimestamp)}` + + `/${sizeNum}.png` + ); + }, }, Query: {