Save user's preferred species for item previews
This commit is contained in:
parent
13a5a0a7aa
commit
787dc7da87
2 changed files with 40 additions and 10 deletions
|
@ -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,
|
||||||
|
|
|
@ -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 }) => {
|
||||||
|
|
Loading…
Reference in a new issue