Use standard image URLs on Your Outfits page

The old URLs were glitchy because we weren't escaping the `layerUrls` param… and this will let us take better advantage of the same shared caching as other stuff!
This commit is contained in:
Emi Matchu 2021-09-03 15:37:38 -07:00
parent ae6b012f88
commit 9a68bd1355
3 changed files with 24 additions and 96 deletions

View file

@ -5,12 +5,9 @@ import gql from "graphql-tag";
import { useQuery } from "@apollo/client";
import { Link } from "react-router-dom";
import { ErrorMessage, Heading1, useCommonStyles } from "./util";
import { Heading1, MajorErrorMessage, useCommonStyles } from "./util";
import HangerSpinner from "./components/HangerSpinner";
import OutfitThumbnail, {
outfitThumbnailFragment,
getOutfitThumbnailRenderSize,
} from "./components/OutfitThumbnail";
import OutfitThumbnail from "./components/OutfitThumbnail";
import useRequireLogin from "./components/useRequireLogin";
import WIPCallout from "./components/WIPCallout";
@ -31,14 +28,13 @@ function UserOutfitsPageContent() {
const { loading: queryLoading, error, data } = useQuery(
gql`
query UserOutfitsPageContent($size: LayerImageSize) {
query UserOutfitsPageContent {
currentUser {
id
outfits {
id
name
...OutfitThumbnailFragment
updatedAt
# For alt text
petAppearance {
@ -58,13 +54,8 @@ function UserOutfitsPageContent() {
}
}
}
${outfitThumbnailFragment}
`,
{
variables: {
// NOTE: This parameter is used inside `OutfitThumbnailFragment`!
size: "SIZE_" + getOutfitThumbnailRenderSize(),
},
context: { sendAuth: true },
skip: userLoading,
}
@ -79,7 +70,7 @@ function UserOutfitsPageContent() {
}
if (error) {
return <ErrorMessage>Error loading outfits: {error.message}</ErrorMessage>;
return <MajorErrorMessage error={error} variant="network" />;
}
const outfits = data.currentUser.outfits;
@ -106,8 +97,8 @@ function OutfitCard({ outfit }) {
<ClassNames>
{({ css }) => (
<OutfitThumbnail
petAppearance={outfit.petAppearance}
itemAppearances={outfit.itemAppearances}
outfitId={outfit.id}
updatedAt={outfit.updatedAt}
alt={buildOutfitAltText(outfit)}
// Firefox shows alt text as a fallback for images it can't show yet.
// Show our alt text clearly if the image failed to load... but hide
@ -118,6 +109,7 @@ function OutfitCard({ outfit }) {
width={150}
height={150}
overflow="auto"
loading="lazy"
className={css`
&:-moz-loading {
visibility: hidden;

View file

@ -4,10 +4,7 @@ import gql from "graphql-tag";
import { useQuery } from "@apollo/client";
import * as Sentry from "@sentry/react";
import OutfitThumbnail, {
outfitThumbnailFragment,
getOutfitThumbnailRenderSize,
} from "../components/OutfitThumbnail";
import OutfitThumbnail from "../components/OutfitThumbnail";
import { useOutfitPreview } from "../components/OutfitPreview";
import { loadable, MajorErrorMessage, TestErrorSender } from "../util";
@ -65,21 +62,20 @@ function WardrobePreviewAndControls({
function OutfitThumbnailIfCached({ outfitId }) {
const { data } = useQuery(
gql`
query OutfitThumbnailIfCached($outfitId: ID!, $size: LayerImageSize!) {
query OutfitThumbnailIfCached($outfitId: ID!) {
outfit(id: $outfitId) {
id
...OutfitThumbnailFragment
updatedAt
}
}
${outfitThumbnailFragment}
`,
{
variables: {
outfitId,
// NOTE: This parameter is used inside `OutfitThumbnailFragment`!
size: "SIZE_" + getOutfitThumbnailRenderSize(),
skip: outfitId == null,
},
fetchPolicy: "cache-only",
onError: (e) => console.error(e),
}
);
@ -89,8 +85,8 @@ function OutfitThumbnailIfCached({ outfitId }) {
return (
<OutfitThumbnail
petAppearance={data.outfit.petAppearance}
itemAppearances={data.outfit.itemAppearances}
outfitId={data.outfit.id}
updatedAt={data.outfit.updatedAt}
alt=""
objectFit="contain"
width="100%"

View file

@ -1,81 +1,21 @@
import React from "react";
import { Box } from "@chakra-ui/react";
import gql from "graphql-tag";
import getVisibleLayers, {
petAppearanceFragmentForGetVisibleLayers,
itemAppearanceFragmentForGetVisibleLayers,
} from "../../shared/getVisibleLayers";
function OutfitThumbnail({ outfitId, updatedAt, ...props }) {
const versionTimestamp = new Date(updatedAt).getTime();
// NOTE: It'd be more reliable for testing to use a relative path, but
// generating these on dev is SO SLOW, that I'd rather just not.
const thumbnailUrl150 = `https://outfits.openneo-assets.net/outfits/${outfitId}/v/${versionTimestamp}/150.png`;
const thumbnailUrl300 = `https://outfits.openneo-assets.net/outfits/${outfitId}/v/${versionTimestamp}/300.png`;
function OutfitThumbnail({ petAppearance, itemAppearances, ...props }) {
return (
<Box
as="img"
src={buildOutfitThumbnailUrl(petAppearance, itemAppearances)}
src={thumbnailUrl150}
srcSet={`${thumbnailUrl150} 1x, ${thumbnailUrl300} 2x`}
{...props}
/>
);
}
function buildOutfitThumbnailUrl(petAppearance, itemAppearances) {
const size = getOutfitThumbnailRenderSize();
const visibleLayers = getVisibleLayers(petAppearance, itemAppearances);
const layerUrls = visibleLayers.map(
(layer) => layer.svgUrl || layer.imageUrl
);
return `/api/outfitImage?size=${size}&layerUrls=${layerUrls.join(",")}`;
}
/**
* getOutfitThumbnailRenderSize returns the right image size to render at
* 150x150, for the current device.
*
* On high-DPI devices, we'll download a 300x300 image to render at 150x150
* scale. On standard-DPI devices, we'll download a 150x150 image, to save
* bandwidth.
*/
export function getOutfitThumbnailRenderSize() {
if (window.devicePixelRatio > 1) {
return 300;
} else {
return 150;
}
}
// NOTE: The query must include a `$size: LayerImageSize` parameter, probably
// with the return value of `getOutfitThumbnailRenderSize`!
export const outfitThumbnailFragment = gql`
fragment OutfitThumbnailFragment on Outfit {
petAppearance {
id
layers {
id
svgUrl
imageUrl(size: $size)
}
species {
id
name
}
color {
id
name
}
...PetAppearanceForGetVisibleLayers
}
itemAppearances {
id
layers {
id
svgUrl
imageUrl(size: $size)
}
...ItemAppearanceForGetVisibleLayers
}
}
${petAppearanceFragmentForGetVisibleLayers}
${itemAppearanceFragmentForGetVisibleLayers}
`;
export default OutfitThumbnail;