Hook up compatibility data for SpeciesFacesPicker

It looks really nice!! :3
This commit is contained in:
Emi Matchu 2021-02-02 15:22:08 -08:00
parent 93c00c5e79
commit cdff51dfef
3 changed files with 65 additions and 14 deletions

View file

@ -513,11 +513,15 @@ function ItemPageOutfitPreview({ itemId }) {
// 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, error } = useQuery(
const { loading, error, data } = useQuery(
gql`
query ItemPageOutfitPreview($itemId: ID!) {
item(id: $itemId) {
id
compatibleBodies {
id
representsAllBodies
}
canonicalAppearance {
id
...ItemAppearanceForOutfitPreview
@ -559,6 +563,8 @@ function ItemPageOutfitPreview({ itemId }) {
}
);
const compatibleBodies = data?.item?.compatibleBodies || [];
// To check whether the item is compatible with this pet, query for the
// appearance, but only against the cache. That way, we don't send a
// redundant network request just for this (the OutfitPreview component will
@ -688,7 +694,7 @@ function ItemPageOutfitPreview({ itemId }) {
</VStack>
<SpeciesFacesPicker
selectedSpeciesId={petState.speciesId}
compatibleBodyIds={["180"]}
compatibleBodies={compatibleBodies}
onChange={({ speciesId, colorId }) =>
setPetState({
speciesId,
@ -754,10 +760,15 @@ function PlayPauseButton({ isPaused, onClick }) {
function SpeciesFacesPicker({
selectedSpeciesId,
compatibleBodyIds,
compatibleBodies,
onChange,
isLoading,
}) {
const allBodiesAreCompatible = compatibleBodies.some(
(body) => body.representsAllBodies
);
const compatibleBodyIds = compatibleBodies.map((body) => body.id);
const allSpeciesFaces = speciesFaces.sort((a, b) =>
a.speciesName.localeCompare(b.speciesName)
);
@ -791,7 +802,10 @@ function SpeciesFacesPicker({
speciesName={speciesFace.speciesName}
colorId={speciesFace.colorId}
neopetsImageHash={speciesFace.neopetsImageHash}
isCompatible={compatibleBodyIds.includes(speciesFace.bodyId)}
isCompatible={
allBodiesAreCompatible ||
compatibleBodyIds.includes(speciesFace.bodyId)
}
isSelected={speciesFace.speciesId === selectedSpeciesId}
onChange={onChange}
isLoading={isLoading}
@ -833,23 +847,32 @@ function SpeciesFaceOption({
const isHappy = isLoading || isCompatible;
const emotionId = isHappy ? "1" : "2";
const tooltipLabel = isCompatible ? (
speciesName
) : (
<div style={{ textAlign: "center" }}>
{speciesName}
<div style={{ fontStyle: "italic", fontSize: "0.75em" }}>
(Not compatible yet)
const tooltipLabel =
isCompatible || isLoading ? (
speciesName
) : (
<div style={{ textAlign: "center" }}>
{speciesName}
<div style={{ fontStyle: "italic", fontSize: "0.75em" }}>
(Not compatible yet)
</div>
</div>
</div>
);
);
const cursor = isLoading ? "wait" : !isCompatible ? "not-allowed" : "pointer";
return (
<ClassNames>
{({ css }) => (
<Tooltip label={tooltipLabel} placement="top" gutter={-12}>
<Tooltip
label={tooltipLabel}
placement="top"
// TODO: This looks great visually, but disrupts the hover state and
// causes flicker. I couldn't figure out how to apply
// `pointer-events: none` to the portal container, which I
// think is intercepting the hover even if the label doesn't.
gutter={-12}
>
<Box as="label" cursor={cursor}>
<VisuallyHidden
as="input"

View file

@ -66,6 +66,10 @@ const typeDefs = gql`
# a union of zones for all of its appearances! We use this for overview
# info about the item.
allOccupiedZones: [Zone!]!
# All bodies that this item is compatible with. Note that this might return
# the special representsAllPets body, e.g. if this is just a Background!
compatibleBodies: [Body!]!
}
type ItemAppearance {
@ -330,6 +334,24 @@ const resolvers = {
const zones = zoneIds.map((id) => ({ id }));
return zones;
},
compatibleBodies: async ({ id }, _, { db }) => {
const [rows, __] = await db.query(
`
SELECT DISTINCT swf_assets.body_id
FROM items
INNER JOIN parents_swf_assets ON
items.id = parents_swf_assets.parent_id AND
parents_swf_assets.parent_type = "Item"
INNER JOIN swf_assets ON
parents_swf_assets.swf_asset_id = swf_assets.id
WHERE items.id = ?
`,
[id]
);
const bodyIds = rows.map((row) => row.body_id);
const bodies = bodyIds.map((id) => ({ id }));
return bodies;
},
},
ItemAppearance: {

View file

@ -44,6 +44,9 @@ const typeDefs = gql`
# A PetAppearance that has this body. Prefers Blue and happy poses.
canonicalAppearance: PetAppearance
# Whether this is the special body type that represents fitting _all_ pets.
representsAllBodies: Boolean!
}
# Cache for 1 week (unlikely to change)
@ -131,6 +134,9 @@ const resolvers = {
"don't have a direct query for it yet, oops!"
);
},
representsAllBodies: ({ id }) => {
return id == "0";
},
canonicalAppearance: async (
{ id },
_,