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 // 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, error } = useQuery( const { loading, error, data } = useQuery(
gql` gql`
query ItemPageOutfitPreview($itemId: ID!) { query ItemPageOutfitPreview($itemId: ID!) {
item(id: $itemId) { item(id: $itemId) {
id id
compatibleBodies {
id
representsAllBodies
}
canonicalAppearance { canonicalAppearance {
id id
...ItemAppearanceForOutfitPreview ...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 // 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 // appearance, but only against the cache. That way, we don't send a
// redundant network request just for this (the OutfitPreview component will // redundant network request just for this (the OutfitPreview component will
@ -688,7 +694,7 @@ function ItemPageOutfitPreview({ itemId }) {
</VStack> </VStack>
<SpeciesFacesPicker <SpeciesFacesPicker
selectedSpeciesId={petState.speciesId} selectedSpeciesId={petState.speciesId}
compatibleBodyIds={["180"]} compatibleBodies={compatibleBodies}
onChange={({ speciesId, colorId }) => onChange={({ speciesId, colorId }) =>
setPetState({ setPetState({
speciesId, speciesId,
@ -754,10 +760,15 @@ function PlayPauseButton({ isPaused, onClick }) {
function SpeciesFacesPicker({ function SpeciesFacesPicker({
selectedSpeciesId, selectedSpeciesId,
compatibleBodyIds, compatibleBodies,
onChange, onChange,
isLoading, isLoading,
}) { }) {
const allBodiesAreCompatible = compatibleBodies.some(
(body) => body.representsAllBodies
);
const compatibleBodyIds = compatibleBodies.map((body) => body.id);
const allSpeciesFaces = speciesFaces.sort((a, b) => const allSpeciesFaces = speciesFaces.sort((a, b) =>
a.speciesName.localeCompare(b.speciesName) a.speciesName.localeCompare(b.speciesName)
); );
@ -791,7 +802,10 @@ function SpeciesFacesPicker({
speciesName={speciesFace.speciesName} speciesName={speciesFace.speciesName}
colorId={speciesFace.colorId} colorId={speciesFace.colorId}
neopetsImageHash={speciesFace.neopetsImageHash} neopetsImageHash={speciesFace.neopetsImageHash}
isCompatible={compatibleBodyIds.includes(speciesFace.bodyId)} isCompatible={
allBodiesAreCompatible ||
compatibleBodyIds.includes(speciesFace.bodyId)
}
isSelected={speciesFace.speciesId === selectedSpeciesId} isSelected={speciesFace.speciesId === selectedSpeciesId}
onChange={onChange} onChange={onChange}
isLoading={isLoading} isLoading={isLoading}
@ -833,23 +847,32 @@ function SpeciesFaceOption({
const isHappy = isLoading || isCompatible; const isHappy = isLoading || isCompatible;
const emotionId = isHappy ? "1" : "2"; const emotionId = isHappy ? "1" : "2";
const tooltipLabel = isCompatible ? ( const tooltipLabel =
speciesName isCompatible || isLoading ? (
) : ( speciesName
<div style={{ textAlign: "center" }}> ) : (
{speciesName} <div style={{ textAlign: "center" }}>
<div style={{ fontStyle: "italic", fontSize: "0.75em" }}> {speciesName}
(Not compatible yet) <div style={{ fontStyle: "italic", fontSize: "0.75em" }}>
(Not compatible yet)
</div>
</div> </div>
</div> );
);
const cursor = isLoading ? "wait" : !isCompatible ? "not-allowed" : "pointer"; const cursor = isLoading ? "wait" : !isCompatible ? "not-allowed" : "pointer";
return ( return (
<ClassNames> <ClassNames>
{({ css }) => ( {({ 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}> <Box as="label" cursor={cursor}>
<VisuallyHidden <VisuallyHidden
as="input" 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 # a union of zones for all of its appearances! We use this for overview
# info about the item. # info about the item.
allOccupiedZones: [Zone!]! 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 { type ItemAppearance {
@ -330,6 +334,24 @@ const resolvers = {
const zones = zoneIds.map((id) => ({ id })); const zones = zoneIds.map((id) => ({ id }));
return zones; 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: { ItemAppearance: {

View file

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