From 787dc7da876ceb9bc6318b8681f235cc2665ee8a Mon Sep 17 00:00:00 2001 From: Matchu Date: Wed, 3 Feb 2021 14:27:02 -0800 Subject: [PATCH] Save user's preferred species for item previews --- src/app/ItemPage.js | 33 ++++++++++++++++++++++++++++----- src/server/types/Item.js | 17 ++++++++++++----- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/app/ItemPage.js b/src/app/ItemPage.js index 54b96c7..f96eed5 100644 --- a/src/app/ItemPage.js +++ b/src/app/ItemPage.js @@ -511,17 +511,40 @@ function ItemPageOutfitPreview({ itemId }) { // switch back to it though... we could maybe do something clever there!) appearanceId: null, }); + const [preferredSpeciesId, setPreferredSpeciesId] = useLocalStorage( + "DTIItemPreviewPreferredSpeciesId", + null + ); + + const setPetStateFromUserAction = (petState) => { + setPetState(petState); + + // When the user _intentionally_ chooses a species, save it in local + // storage for next time. (This won't update when e.g. their preferred + // species isn't available for this item, so we update to the canonical + // species automatically.) + if (petState.speciesId) { + // I have no reason to expect null to come in here, but, since this is + // touching client-persisted data, I want it to be even more guaranteed + // reliable than usual! + setPreferredSpeciesId(petState.speciesId); + } + }; // Start by loading the "canonical" pet and item appearance for the outfit // preview. We'll use this to initialize both the preview and the picker. // + // If the user has a preferred species saved from using the ItemPage in the + // past, we'll send that instead. This will return the appearance on that + // species if possible, or the default canonical species if not. + // // TODO: If this is a non-standard pet color, like Mutant, we'll do an extra // query after this loads, because our Apollo cache can't detect the // shared item appearance. (For standard colors though, our logic to // cover standard-color switches works for this preloading too.) const { loading: loadingGQL, error: errorGQL, data } = useQuery( gql` - query ItemPageOutfitPreview($itemId: ID!) { + query ItemPageOutfitPreview($itemId: ID!, $preferredSpeciesId: ID) { item(id: $itemId) { id name @@ -529,7 +552,7 @@ function ItemPageOutfitPreview({ itemId }) { id representsAllBodies } - canonicalAppearance { + canonicalAppearance(preferredSpeciesId: $preferredSpeciesId) { id ...ItemAppearanceForOutfitPreview body { @@ -556,7 +579,7 @@ function ItemPageOutfitPreview({ itemId }) { ${petAppearanceFragment} `, { - variables: { itemId }, + variables: { itemId, preferredSpeciesId }, onCompleted: (data) => { const canonicalBody = data?.item?.canonicalAppearance?.body; const canonicalPetAppearance = canonicalBody?.canonicalAppearance; @@ -692,7 +715,7 @@ function ItemPageOutfitPreview({ itemId }) { pose={petState.pose} idealPose={idealPose} onChange={(species, color, _, closestPose) => { - setPetState({ + setPetStateFromUserAction({ speciesId: species.id, colorId: color.id, pose: closestPose, @@ -732,7 +755,7 @@ function ItemPageOutfitPreview({ itemId }) { onChange={({ speciesId, colorId }) => { const validPoses = getValidPoses(valids, speciesId, colorId); const pose = getClosestPose(validPoses, idealPose); - setPetState({ + setPetStateFromUserAction({ speciesId, colorId, pose, diff --git a/src/server/types/Item.js b/src/server/types/Item.js index 637f70c..ab01359 100644 --- a/src/server/types/Item.js +++ b/src/server/types/Item.js @@ -60,7 +60,11 @@ const typeDefs = gql` # on the item page, to initialize the preview section. (You can find out # which species this is for by going through the body field on # ItemAppearance!) - canonicalAppearance: ItemAppearance @cacheControl(maxAge: 1, staleWhileRevalidate: ${oneWeek}) + # + # There's also an optional preferredSpeciesId field, which you can use to + # request a certain species if compatible. If not, we'll fall back to the + # default species, as described above. + canonicalAppearance(preferredSpeciesId: ID): ItemAppearance @cacheControl(maxAge: 1, staleWhileRevalidate: ${oneWeek}) # All zones that this item occupies, for at least one body. That is, it's # a union of zones for all of its appearances! We use this for overview @@ -313,20 +317,23 @@ const resolvers = { }, canonicalAppearance: async ( { id }, - _, + { preferredSpeciesId }, { itemBodiesWithAppearanceDataLoader } ) => { const rows = await itemBodiesWithAppearanceDataLoader.load(id); - const canonicalBodyId = rows[0].bodyId; + const preferredRow = preferredSpeciesId + ? rows.find((row) => row.speciesId === preferredSpeciesId) + : null; + const bestRow = preferredRow || rows[0]; return { item: { id }, - bodyId: canonicalBodyId, + bodyId: bestRow.bodyId, // An optimization: we know the species already, so fill it in here // without requiring an extra query if we want it. // TODO: Maybe this would be cleaner if we make the body -> species // loader, and prime it in the item bodies loader, rather than // setting it here? - body: { id: canonicalBodyId, species: { id: rows[0].speciesId } }, + body: { id: bestRow.bodyId, species: { id: bestRow.speciesId } }, }; }, allOccupiedZones: async ({ id }, _, { itemAllOccupiedZonesLoader }) => {