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!)
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,

View file

@ -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 }) => {