From 66c1e14dd0e2b6666c13d7fc70f5a4f867578570 Mon Sep 17 00:00:00 2001 From: Emi Matchu Date: Tue, 27 Feb 2024 12:19:07 -0800 Subject: [PATCH] Add item search results to Apollo cache, use in finding item conflicts This makes clicking on search results in the new mode actually work! It correctly adds it to the outfit, and removes other items. The thing that's behaving strangely is that, when you add the item, we visually remove all items until we can finish a fresh network request for what they should all look like. This probably means that the cache lookup for `useOutfitAppearance` is not as satisfied with what we cache here as `findItemConflicts` is? Something to investigate! --- .../WardrobePage/useOutfitState.js | 14 +++- app/javascript/wardrobe-2020/loaders/items.js | 77 ++++++++++++++++++- .../wardrobe-2020/loaders/shared-types.js | 2 + 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/app/javascript/wardrobe-2020/WardrobePage/useOutfitState.js b/app/javascript/wardrobe-2020/WardrobePage/useOutfitState.js index 3a22fc3a..4d956ede 100644 --- a/app/javascript/wardrobe-2020/WardrobePage/useOutfitState.js +++ b/app/javascript/wardrobe-2020/WardrobePage/useOutfitState.js @@ -495,7 +495,8 @@ function getOutfitStateFromOutfitData(outfit) { function findItemConflicts(itemIdToAdd, state, apolloClient) { const { wornItemIds, speciesId, colorId, altStyleId } = state; - const { items } = apolloClient.readQuery({ + const itemIds = [itemIdToAdd, ...wornItemIds]; + const data = apolloClient.readQuery({ query: gql` query OutfitStateItemConflicts( $itemIds: [ID!]! @@ -524,12 +525,21 @@ function findItemConflicts(itemIdToAdd, state, apolloClient) { } `, variables: { - itemIds: [itemIdToAdd, ...wornItemIds], + itemIds, speciesId, colorId, altStyleId, }, }); + if (data == null) { + throw new Error( + `[findItemConflicts] Cache lookup failed for: ` + + `items=${itemIds.join(",")}, speciesId=${speciesId}, ` + + `colorId=${colorId}, altStyleId=${altStyleId}`, + ); + } + + const { items } = data; const itemToAdd = items.find((i) => i.id === itemIdToAdd); if (!itemToAdd.appearanceOn) { return []; diff --git a/app/javascript/wardrobe-2020/loaders/items.js b/app/javascript/wardrobe-2020/loaders/items.js index 36ac315f..8c23b4ef 100644 --- a/app/javascript/wardrobe-2020/loaders/items.js +++ b/app/javascript/wardrobe-2020/loaders/items.js @@ -1,5 +1,7 @@ +import gql from "graphql-tag"; import { useQuery } from "@tanstack/react-query"; +import apolloClient from "../apolloClient"; import { normalizeSwfAssetToLayer, normalizeZone } from "./shared-types"; export function useItemAppearances(id, options = {}) { @@ -87,12 +89,77 @@ async function loadItemSearch(searchOptions) { ); } - return res - .json() - .then((data) => normalizeItemSearchData(data, searchOptions)); + const data = await res.json(); + const result = normalizeItemSearchData(data, searchOptions); + + for (const item of result.items) { + writeItemToApolloCache(item, searchOptions.withAppearancesFor); + } + + return result; } window.loadItemSearch = loadItemSearch; +/** + * writeItemToApolloCache is one last important bridge between our loaders and + * GQL! In `useOutfitState`, we consult the GraphQL cache to look up basic item + * info like zones, to decide when wearing an item would trigger a conflict + * with another. + */ +function writeItemToApolloCache(item, { speciesId, colorId, altStyleId }) { + apolloClient.writeQuery({ + query: gql` + query WriteItemFromLoader( + $itemId: ID! + $speciesId: ID! + $colorId: ID! + $altStyleId: ID + ) { + item(id: $itemId) { + id + name + thumbnailUrl + isNc + isPb + currentUserOwnsThis + currentUserWantsThis + appearanceOn( + speciesId: $speciesId + colorId: $colorId + altStyleId: $altStyleId + ) { + id + layers { + id + remoteId + bodyId + knownGlitches + svgUrl + canvasMovieLibraryUrl + imageUrl + swfUrl + zone { + id + } + } + + restrictedZones { + id + } + } + } + } + `, + variables: { + itemId: item.id, + speciesId, + colorId, + altStyleId, + }, + data: { item }, + }); +} + function normalizeItemAppearancesData(data) { return { name: data.name, @@ -106,9 +173,11 @@ function normalizeItemAppearancesData(data) { function normalizeItemSearchData(data, searchOptions) { return { + __typename: "ItemSearchResultV2", id: buildItemSearchParams(searchOptions), numTotalPages: data.total_pages, items: data.items.map((item) => ({ + __typename: "Item", id: String(item.id), name: item.name, thumbnailUrl: item.thumbnail_url, @@ -130,6 +199,7 @@ function normalizeItemSearchAppearance(data, item) { } return { + __typename: "ItemAppearance", id: `item-${item.id}-body-${data.body.id}`, layers: data.swf_assets.map(normalizeSwfAssetToLayer), restrictedZones: data.swf_assets @@ -145,6 +215,7 @@ function normalizeBody(body) { } return { + __typename: "Body", id: String(body.id), species: { id: String(body.species.id), diff --git a/app/javascript/wardrobe-2020/loaders/shared-types.js b/app/javascript/wardrobe-2020/loaders/shared-types.js index 9558e2f3..3736dff4 100644 --- a/app/javascript/wardrobe-2020/loaders/shared-types.js +++ b/app/javascript/wardrobe-2020/loaders/shared-types.js @@ -1,5 +1,6 @@ export function normalizeSwfAssetToLayer(data) { return { + __typename: "AppearanceLayer", id: String(data.id), remoteId: String(data.remote_id), zone: normalizeZone(data.zone), @@ -15,6 +16,7 @@ export function normalizeSwfAssetToLayer(data) { export function normalizeZone(data) { return { + __typename: "Zone", id: String(data.id), depth: data.depth, label: data.label,