Compare commits

...

3 commits

Author SHA1 Message Date
f3e10dea7f Oops, fix missing field in item search results Apollo cache!
Oh right, `imageUrl` is the name of the field relative to what the app
expects, but under the hood `useOutfitAppearance` actually makes that
an alias for `imageUrlV2(idealSize: SIZE_600)`.

So we need to cache it as the same field with the same params, rather
than as just plain `imageUrl`!

This fixes the bug where wearing an item from search would require a
network round-trip and visually remove all items in the meantime.

(Also, none of this issue was visible to most users, because item
search is still feature-flagged onto the old GQL one for most people!)
2024-02-27 12:43:28 -08:00
66c1e14dd0 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!
2024-02-27 12:19:07 -08:00
752bee3c39 Use MajorErrorMessage for search result errors
It'd be nice to customize the message a bit, but this should be rare
and I'd prefer the simplicity of just going with the default text.

I ran into this when I made a mistake in how I process the return value
of search results, so React Query caught and raised the error via
React, as intended! And I was annoyed that it wasn't logged anywhere,
so that's my motivation for this change—but also, the old message is
pretty meh and has some layout problems anyway.
2024-02-27 12:03:23 -08:00
4 changed files with 90 additions and 15 deletions

View file

@ -4,6 +4,7 @@ import { Box, Text, useColorModeValue, VisuallyHidden } from "@chakra-ui/react";
import Item, { ItemListContainer, ItemListSkeleton } from "./Item";
import PaginationToolbar from "../components/PaginationToolbar";
import { useSearchResults } from "./useSearchResults";
import { MajorErrorMessage } from "../util";
export const SEARCH_PER_PAGE = 30;
@ -134,17 +135,8 @@ function SearchResults({
const searchPanelBackground = useColorModeValue("white", "gray.900");
// If the results aren't ready, we have some special case UI!
if (error) {
return (
<Text>
We hit an error trying to load your search results{" "}
<span role="img" aria-label="(sweat emoji)">
😓
</span>{" "}
Try again?
</Text>
);
return <MajorErrorMessage error={error} variant="network" />;
}
// Finally, render the item list, with checkboxes and Item components!

View file

@ -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 [];

View file

@ -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: imageUrlV2(idealSize: SIZE_600)
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),

View file

@ -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,