Share appearance data via useOutfitPreview
Here, we offer a second syntax for `<OutfitPreview />`: a hook that offers the same UI as `preview`, but _also_ shares the `appearance` data. This makes it easier to have UI that depends on the outfit appearance, without having to commit to all the `useOutfitAppearance` stuff in the parent. Same easy syntax! :3 I've refactored the item page to use this for compatibility testing, instead of using the Apollo cache (which was also cute and same perf impact, but more overhead!)
This commit is contained in:
parent
8694729b38
commit
193273f00f
3 changed files with 50 additions and 58 deletions
|
@ -40,7 +40,7 @@ import {
|
|||
itemAppearanceFragment,
|
||||
petAppearanceFragment,
|
||||
} from "./components/useOutfitAppearance";
|
||||
import OutfitPreview from "./components/OutfitPreview";
|
||||
import { useOutfitPreview } from "./components/OutfitPreview";
|
||||
import SpeciesColorPicker, {
|
||||
useAllValidPetPoses,
|
||||
getValidPoses,
|
||||
|
@ -646,39 +646,28 @@ function ItemPageOutfitPreview({ itemId }) {
|
|||
valids,
|
||||
} = useAllValidPetPoses();
|
||||
|
||||
// To check whether the item is compatible with this pet, query for the
|
||||
// appearance, but only against the cache. That way, we don't send a
|
||||
// redundant network request just for this (the OutfitPreview component will
|
||||
// handle it!), but we'll get an update once it arrives in the cache.
|
||||
const { data: cachedData } = useQuery(
|
||||
gql`
|
||||
query ItemPageOutfitPreview_CacheOnly(
|
||||
$itemId: ID!
|
||||
$speciesId: ID!
|
||||
$colorId: ID!
|
||||
) {
|
||||
item(id: $itemId) {
|
||||
appearanceOn(speciesId: $speciesId, colorId: $colorId) {
|
||||
layers {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
variables: {
|
||||
itemId,
|
||||
speciesId: petState.speciesId,
|
||||
colorId: petState.colorId,
|
||||
},
|
||||
fetchPolicy: "cache-only",
|
||||
}
|
||||
);
|
||||
|
||||
const [hasAnimations, setHasAnimations] = React.useState(false);
|
||||
const [isPaused, setIsPaused] = useLocalStorage("DTIOutfitIsPaused", true);
|
||||
|
||||
// This is like <OutfitPreview />, but we can use the appearance data, too!
|
||||
const { appearance, preview } = useOutfitPreview({
|
||||
speciesId: petState.speciesId,
|
||||
colorId: petState.colorId,
|
||||
pose: petState.pose,
|
||||
appearanceId: petState.appearanceId,
|
||||
wornItemIds: [itemId],
|
||||
isLoading: loadingGQL || loadingValids,
|
||||
spinnerVariant: "corner",
|
||||
loadingDelayMs: 200,
|
||||
engine: "canvas",
|
||||
onChangeHasAnimations: setHasAnimations,
|
||||
});
|
||||
|
||||
// If there's an appearance loaded for this item, but it's empty, then the
|
||||
// item is incompatible. (There should only be one item appearance: this one!)
|
||||
const itemAppearance = appearance?.itemAppearances?.[0];
|
||||
const isIncompatible = itemAppearance && itemAppearance.layers.length === 0;
|
||||
|
||||
const borderColor = useColorModeValue("green.700", "green.400");
|
||||
const errorColor = useColorModeValue("red.600", "red.400");
|
||||
|
||||
|
@ -687,12 +676,6 @@ function ItemPageOutfitPreview({ itemId }) {
|
|||
return <Box color="red.400">{error.message}</Box>;
|
||||
}
|
||||
|
||||
// If the layers are null-y, then we're still loading. Otherwise, if the
|
||||
// layers are an empty array, then we're incomaptible. Or, if they're a
|
||||
// non-empty array, then we're compatible!
|
||||
const layers = cachedData?.item?.appearanceOn?.layers;
|
||||
const isIncompatible = Array.isArray(layers) && layers.length === 0;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction={{ base: "column", md: "row" }}
|
||||
|
@ -714,18 +697,7 @@ function ItemPageOutfitPreview({ itemId }) {
|
|||
overflow="hidden"
|
||||
>
|
||||
<Box>
|
||||
<OutfitPreview
|
||||
speciesId={petState.speciesId}
|
||||
colorId={petState.colorId}
|
||||
pose={petState.pose}
|
||||
appearanceId={petState.appearanceId}
|
||||
wornItemIds={[itemId]}
|
||||
isLoading={loadingGQL || loadingValids}
|
||||
spinnerVariant="corner"
|
||||
loadingDelayMs={2000}
|
||||
engine="canvas"
|
||||
onChangeHasAnimations={setHasAnimations}
|
||||
/>
|
||||
{preview}
|
||||
<CustomizeMoreButton
|
||||
speciesId={petState.speciesId}
|
||||
colorId={petState.colorId}
|
||||
|
|
|
@ -28,7 +28,19 @@ import useOutfitAppearance from "./useOutfitAppearance";
|
|||
* TODO: There's some duplicate work happening in useOutfitAppearance and
|
||||
* useOutfitState both getting appearance data on first load...
|
||||
*/
|
||||
function OutfitPreview({
|
||||
function OutfitPreview(props) {
|
||||
const { preview } = useOutfitPreview(props);
|
||||
return preview;
|
||||
}
|
||||
|
||||
/**
|
||||
* useOutfitPreview is like `<OutfitPreview />`, but a bit more power!
|
||||
*
|
||||
* It takes the same props and returns a `preview` field, which is just like
|
||||
* `<OutfitPreview />` - but it also returns `appearance` data too, in case you
|
||||
* want to show some additional UI that uses the appearance data we loaded!
|
||||
*/
|
||||
export function useOutfitPreview({
|
||||
speciesId,
|
||||
colorId,
|
||||
pose,
|
||||
|
@ -41,13 +53,14 @@ function OutfitPreview({
|
|||
spinnerVariant,
|
||||
onChangeHasAnimations = null,
|
||||
}) {
|
||||
const { loading, error, visibleLayers } = useOutfitAppearance({
|
||||
const appearance = useOutfitAppearance({
|
||||
speciesId,
|
||||
colorId,
|
||||
pose,
|
||||
appearanceId,
|
||||
wornItemIds,
|
||||
});
|
||||
const { loading, error, visibleLayers } = appearance;
|
||||
|
||||
const {
|
||||
loading: loading2,
|
||||
|
@ -76,7 +89,7 @@ function OutfitPreview({
|
|||
);
|
||||
}
|
||||
|
||||
return (
|
||||
const preview = (
|
||||
<OutfitLayers
|
||||
loading={isLoading || loading || loading2}
|
||||
visibleLayers={loadedLayers}
|
||||
|
@ -89,6 +102,8 @@ function OutfitPreview({
|
|||
isPaused={isPaused}
|
||||
/>
|
||||
);
|
||||
|
||||
return { appearance, preview };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -69,7 +69,7 @@ export default function useOutfitAppearance(outfitState) {
|
|||
) {
|
||||
items(ids: $wornItemIds) {
|
||||
id
|
||||
appearanceOn(speciesId: $speciesId, colorId: $colorId) {
|
||||
appearance: appearanceOn(speciesId: $speciesId, colorId: $colorId) {
|
||||
...ItemAppearanceForOutfitPreview
|
||||
}
|
||||
}
|
||||
|
@ -86,20 +86,25 @@ export default function useOutfitAppearance(outfitState) {
|
|||
}
|
||||
);
|
||||
|
||||
const petAppearance = data1?.petAppearance;
|
||||
const items = data2?.items;
|
||||
const itemAppearances = React.useMemo(
|
||||
() => (data2?.items || []).map((i) => i.appearanceOn),
|
||||
[data2]
|
||||
() => (items || []).map((i) => i.appearance),
|
||||
[items]
|
||||
);
|
||||
const visibleLayers = React.useMemo(
|
||||
() => getVisibleLayers(data1?.petAppearance, itemAppearances),
|
||||
[data1, itemAppearances]
|
||||
() => getVisibleLayers(petAppearance, itemAppearances),
|
||||
[petAppearance, itemAppearances]
|
||||
);
|
||||
|
||||
const bodyId = data1?.petAppearance?.bodyId;
|
||||
const bodyId = petAppearance?.bodyId;
|
||||
|
||||
return {
|
||||
loading: loading1 || loading2,
|
||||
error: error1 || error2,
|
||||
petAppearance,
|
||||
items,
|
||||
itemAppearances,
|
||||
visibleLayers,
|
||||
bodyId,
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue