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:
parent
ae6b012f88
commit
9a68bd1355
3 changed files with 24 additions and 96 deletions
|
@ -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;
|
||||
|
|
|
@ -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%"
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue