Save user's preferred species for item previews

This commit is contained in:
Emi Matchu 2021-02-03 14:27:02 -08:00
parent 13a5a0a7aa
commit 787dc7da87
2 changed files with 40 additions and 10 deletions

View file

@ -511,17 +511,40 @@ function ItemPageOutfitPreview({ itemId }) {
// switch back to it though... we could maybe do something clever there!) // switch back to it though... we could maybe do something clever there!)
appearanceId: null, 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 // 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. // 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 // 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 // query after this loads, because our Apollo cache can't detect the
// shared item appearance. (For standard colors though, our logic to // shared item appearance. (For standard colors though, our logic to
// cover standard-color switches works for this preloading too.) // cover standard-color switches works for this preloading too.)
const { loading: loadingGQL, error: errorGQL, data } = useQuery( const { loading: loadingGQL, error: errorGQL, data } = useQuery(
gql` gql`
query ItemPageOutfitPreview($itemId: ID!) { query ItemPageOutfitPreview($itemId: ID!, $preferredSpeciesId: ID) {
item(id: $itemId) { item(id: $itemId) {
id id
name name
@ -529,7 +552,7 @@ function ItemPageOutfitPreview({ itemId }) {
id id
representsAllBodies representsAllBodies
} }
canonicalAppearance { canonicalAppearance(preferredSpeciesId: $preferredSpeciesId) {
id id
...ItemAppearanceForOutfitPreview ...ItemAppearanceForOutfitPreview
body { body {
@ -556,7 +579,7 @@ function ItemPageOutfitPreview({ itemId }) {
${petAppearanceFragment} ${petAppearanceFragment}
`, `,
{ {
variables: { itemId }, variables: { itemId, preferredSpeciesId },
onCompleted: (data) => { onCompleted: (data) => {
const canonicalBody = data?.item?.canonicalAppearance?.body; const canonicalBody = data?.item?.canonicalAppearance?.body;
const canonicalPetAppearance = canonicalBody?.canonicalAppearance; const canonicalPetAppearance = canonicalBody?.canonicalAppearance;
@ -692,7 +715,7 @@ function ItemPageOutfitPreview({ itemId }) {
pose={petState.pose} pose={petState.pose}
idealPose={idealPose} idealPose={idealPose}
onChange={(species, color, _, closestPose) => { onChange={(species, color, _, closestPose) => {
setPetState({ setPetStateFromUserAction({
speciesId: species.id, speciesId: species.id,
colorId: color.id, colorId: color.id,
pose: closestPose, pose: closestPose,
@ -732,7 +755,7 @@ function ItemPageOutfitPreview({ itemId }) {
onChange={({ speciesId, colorId }) => { onChange={({ speciesId, colorId }) => {
const validPoses = getValidPoses(valids, speciesId, colorId); const validPoses = getValidPoses(valids, speciesId, colorId);
const pose = getClosestPose(validPoses, idealPose); const pose = getClosestPose(validPoses, idealPose);
setPetState({ setPetStateFromUserAction({
speciesId, speciesId,
colorId, colorId,
pose, pose,

View file

@ -60,7 +60,11 @@ const typeDefs = gql`
# on the item page, to initialize the preview section. (You can find out # 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 # which species this is for by going through the body field on
# ItemAppearance!) # 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 # 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 # a union of zones for all of its appearances! We use this for overview
@ -313,20 +317,23 @@ const resolvers = {
}, },
canonicalAppearance: async ( canonicalAppearance: async (
{ id }, { id },
_, { preferredSpeciesId },
{ itemBodiesWithAppearanceDataLoader } { itemBodiesWithAppearanceDataLoader }
) => { ) => {
const rows = await itemBodiesWithAppearanceDataLoader.load(id); 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 { return {
item: { id }, item: { id },
bodyId: canonicalBodyId, bodyId: bestRow.bodyId,
// An optimization: we know the species already, so fill it in here // An optimization: we know the species already, so fill it in here
// without requiring an extra query if we want it. // without requiring an extra query if we want it.
// TODO: Maybe this would be cleaner if we make the body -> species // TODO: Maybe this would be cleaner if we make the body -> species
// loader, and prime it in the item bodies loader, rather than // loader, and prime it in the item bodies loader, rather than
// setting it here? // setting it here?
body: { id: canonicalBodyId, species: { id: rows[0].speciesId } }, body: { id: bestRow.bodyId, species: { id: bestRow.speciesId } },
}; };
}, },
allOccupiedZones: async ({ id }, _, { itemAllOccupiedZonesLoader }) => { allOccupiedZones: async ({ id }, _, { itemAllOccupiedZonesLoader }) => {