impress-2020/src/app/components/useOutfitAppearance.js

222 lines
5.9 KiB
JavaScript
Raw Normal View History

import React from "react";
import gql from "graphql-tag";
import { useQuery } from "@apollo/client";
/**
* useOutfitAppearance downloads the outfit's appearance data, and returns
* visibleLayers for rendering.
*/
export default function useOutfitAppearance(outfitState) {
const { wornItemIds, speciesId, colorId, pose, appearanceId } = outfitState;
// We split this query out from the other one, so that we can HTTP cache it.
//
// While Apollo gives us fine-grained caching during the page session, we can
// only HTTP a full query at a time.
//
// This is a minor optimization with respect to keeping the user's cache
// populated with their favorite species/color combinations. Once we start
// caching the items by body instead of species/color, this could make color
// changes really snappy!
//
// The larger optimization is that this enables the CDN to edge-cache the
// most popular species/color combinations, for very fast previews on the
// HomePage. At time of writing, Vercel isn't actually edge-caching these, I
// assume because our traffic isn't enough - so let's keep an eye on this!
const { loading: loading1, error: error1, data: data1 } = useQuery(
appearanceId == null
? gql`
query OutfitPetAppearance(
$speciesId: ID!
$colorId: ID!
$pose: Pose!
) {
petAppearance(
speciesId: $speciesId
colorId: $colorId
pose: $pose
) {
...PetAppearanceForOutfitPreview
}
}
${petAppearanceFragment}
`
: gql`
query OutfitPetAppearanceById($appearanceId: ID!) {
petAppearance: petAppearanceById(id: $appearanceId) {
...PetAppearanceForOutfitPreview
}
}
${petAppearanceFragment}
`,
{
variables: {
speciesId,
colorId,
pose,
appearanceId,
},
skip: speciesId == null || colorId == null || pose == null,
}
);
const { loading: loading2, error: error2, data: data2 } = useQuery(
gql`
query OutfitItemsAppearance(
$speciesId: ID!
$colorId: ID!
$wornItemIds: [ID!]!
) {
items(ids: $wornItemIds) {
id
2021-02-10 13:47:02 -08:00
name # HACK: This is for HTML5 detection UI in OutfitControls!
appearance: appearanceOn(speciesId: $speciesId, colorId: $colorId) {
...ItemAppearanceForOutfitPreview
}
}
}
${itemAppearanceFragment}
`,
{
variables: {
speciesId,
colorId,
wornItemIds,
},
skip: speciesId == null || colorId == null || wornItemIds.length === 0,
}
);
const petAppearance = data1?.petAppearance;
const items = data2?.items;
const itemAppearances = React.useMemo(
() => (items || []).map((i) => i.appearance),
[items]
);
const visibleLayers = React.useMemo(
() => getVisibleLayers(petAppearance, itemAppearances),
[petAppearance, itemAppearances]
);
const bodyId = petAppearance?.bodyId;
return {
loading: loading1 || loading2,
error: error1 || error2,
petAppearance,
items: items || [],
itemAppearances,
visibleLayers,
bodyId,
};
}
2020-05-02 21:04:54 -07:00
export function getVisibleLayers(petAppearance, itemAppearances) {
if (!petAppearance) {
return [];
}
const validItemAppearances = itemAppearances.filter((a) => a);
const itemLayers = validItemAppearances
.map((a) => a.layers)
.flat()
2021-03-13 01:08:48 -08:00
.map((l) => ({ ...l, source: "item" }));
const itemOccupiedZoneIds = new Set(itemLayers.map((l) => l.zone.id));
const petLayers = petAppearance.layers
.map((l) => ({ ...l, source: "pet" }))
// Copying weird Neopets.com behavior: if an item occupies a zone that the
// pet also occupies, the pet's corresponding zone is hidden.
.filter((l) => !itemOccupiedZoneIds.has(l.zone.id));
let allLayers = [...petLayers, ...itemLayers];
2020-08-31 19:23:56 -07:00
const itemRestrictedZoneIds = validItemAppearances
.map((a) => a.restrictedZones)
.flat()
.map((z) => z.id);
const petRestrictedZoneIds = petAppearance.restrictedZones.map((z) => z.id);
2020-08-31 19:23:56 -07:00
const allRestrictedZoneIds = new Set([
...itemRestrictedZoneIds,
...petRestrictedZoneIds,
]);
const visibleLayers = allLayers.filter(
2020-08-31 19:23:56 -07:00
(l) => !allRestrictedZoneIds.has(l.zone.id)
);
visibleLayers.sort((a, b) => a.zone.depth - b.zone.depth);
return visibleLayers;
}
export const itemAppearanceFragmentForGetVisibleLayers = gql`
fragment ItemAppearanceForGetVisibleLayers on ItemAppearance {
id
layers {
id
zone {
id
depth @client
}
}
restrictedZones {
id
}
}
`;
export const itemAppearanceFragment = gql`
fragment ItemAppearanceForOutfitPreview on ItemAppearance {
id
layers {
id
2020-08-14 22:09:52 -07:00
remoteId # HACK: This is for Support tools, but other views don't need it
2020-05-11 21:19:34 -07:00
svgUrl
canvasMovieLibraryUrl
imageUrl(size: SIZE_600)
swfUrl # HACK: This is for Support tools, but other views don't need it
knownGlitches # HACK: This is for Support tools, but other views don't need it
bodyId
zone {
label @client # HACK: This is for Support tools, but other views don't need it
}
}
...ItemAppearanceForGetVisibleLayers
}
${itemAppearanceFragmentForGetVisibleLayers}
`;
export const petAppearanceFragmentForGetVisibleLayers = gql`
fragment PetAppearanceForGetVisibleLayers on PetAppearance {
id
layers {
id
zone {
id
depth @client
}
}
restrictedZones {
id
}
}
`;
export const petAppearanceFragment = gql`
fragment PetAppearanceForOutfitPreview on PetAppearance {
id
bodyId
layers {
id
svgUrl
canvasMovieLibraryUrl
imageUrl(size: SIZE_600)
}
...PetAppearanceForGetVisibleLayers
}
${petAppearanceFragmentForGetVisibleLayers}
`;