add real offering/seeking counts to item page
This commit is contained in:
parent
90233c0a03
commit
68e6e9e243
3 changed files with 112 additions and 10 deletions
|
@ -552,22 +552,54 @@ function ItemPageWantButton({ itemId, isChecked }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemPageTradeLinks({ itemId }) {
|
function ItemPageTradeLinks({ itemId }) {
|
||||||
|
const { data, loading, error } = useQuery(
|
||||||
|
gql`
|
||||||
|
query ItemPageTradeLinks($itemId: ID!) {
|
||||||
|
item(id: $itemId) {
|
||||||
|
id
|
||||||
|
numUsersOfferingThis
|
||||||
|
numUsersSeekingThis
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{ variables: { itemId } }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <Box color="red.400">{error.message}</Box>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip label="These are placeholders—real trade lists coming soon!">
|
|
||||||
<HStack spacing="2">
|
<HStack spacing="2">
|
||||||
<Box as="header" fontSize="sm" fontWeight="bold">
|
<Box as="header" fontSize="sm" fontWeight="bold">
|
||||||
Trading:
|
Trading:
|
||||||
</Box>
|
</Box>
|
||||||
<ItemPageTradeLink count={8} label="offering" colorScheme="green" />
|
<SubtleSkeleton isLoaded={!loading}>
|
||||||
<ItemPageTradeLink count={12} label="seeking" colorScheme="blue" />
|
<ItemPageTradeLink
|
||||||
|
itemId={itemId}
|
||||||
|
count={data?.item?.numUsersOfferingThis || 0}
|
||||||
|
label="offering"
|
||||||
|
colorScheme="green"
|
||||||
|
/>
|
||||||
|
</SubtleSkeleton>
|
||||||
|
<SubtleSkeleton isLoaded={!loading}>
|
||||||
|
<ItemPageTradeLink
|
||||||
|
itemId={itemId}
|
||||||
|
count={data?.item?.numUsersSeekingThis || 0}
|
||||||
|
label="seeking"
|
||||||
|
colorScheme="blue"
|
||||||
|
/>
|
||||||
|
</SubtleSkeleton>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Tooltip>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemPageTradeLink({ count, label, colorScheme }) {
|
function ItemPageTradeLink({ itemId, count, label, colorScheme }) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
as="a"
|
||||||
|
// TODO: Link to a new Impress 2020 page instead!
|
||||||
|
href={`https://impress.openneo.net/items/${itemId}`}
|
||||||
size="xs"
|
size="xs"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
colorScheme={colorScheme}
|
colorScheme={colorScheme}
|
||||||
|
|
|
@ -388,6 +388,56 @@ const buildItemAllOccupiedZonesLoader = (db) =>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const buildItemTradeCountsLoader = (db) =>
|
||||||
|
new DataLoader(
|
||||||
|
async (itemIdOwnedPairs) => {
|
||||||
|
const qs = itemIdOwnedPairs
|
||||||
|
.map((_) => "(closet_hangers.item_id = ? AND closet_hangers.owned = ?)")
|
||||||
|
.join(" OR ");
|
||||||
|
const values = itemIdOwnedPairs
|
||||||
|
.map(({ itemId, isOwned }) => [itemId, isOwned])
|
||||||
|
.flat();
|
||||||
|
const [rows, _] = await db.execute(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
items.id AS item_id, closet_hangers.owned AS is_owned,
|
||||||
|
count(DISTINCT closet_hangers.user_id) AS users_count
|
||||||
|
FROM items
|
||||||
|
INNER JOIN closet_hangers ON closet_hangers.item_id = items.id
|
||||||
|
INNER JOIN users ON users.id = closet_hangers.user_id
|
||||||
|
LEFT JOIN closet_lists ON closet_lists.id = closet_hangers.list_id
|
||||||
|
WHERE (
|
||||||
|
(${qs})
|
||||||
|
AND (
|
||||||
|
(closet_hangers.list_id IS NOT NULL AND closet_lists.visibility >= 2)
|
||||||
|
OR (
|
||||||
|
closet_hangers.list_id IS NULL AND closet_hangers.owned = 1
|
||||||
|
AND users.owned_closet_hangers_visibility >= 2
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
closet_hangers.list_id IS NULL AND closet_hangers.owned = 0
|
||||||
|
AND users.wanted_closet_hangers_visibility >= 2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
GROUP BY items.id, closet_hangers.owned;
|
||||||
|
`,
|
||||||
|
values
|
||||||
|
);
|
||||||
|
|
||||||
|
const entities = rows.map(normalizeRow);
|
||||||
|
|
||||||
|
return itemIdOwnedPairs.map(({ itemId, isOwned }) => {
|
||||||
|
// NOTE: There may be no matching row, if there are 0 such trades.
|
||||||
|
const entity = entities.find(
|
||||||
|
(e) => e.itemId === itemId && Boolean(e.isOwned) === isOwned
|
||||||
|
);
|
||||||
|
return entity ? entity.usersCount : 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ cacheKeyFn: ({ itemId, isOwned }) => `${itemId}-${isOwned}` }
|
||||||
|
);
|
||||||
|
|
||||||
const buildPetTypeLoader = (db, loaders) =>
|
const buildPetTypeLoader = (db, loaders) =>
|
||||||
new DataLoader(async (petTypeIds) => {
|
new DataLoader(async (petTypeIds) => {
|
||||||
const qs = petTypeIds.map((_) => "?").join(",");
|
const qs = petTypeIds.map((_) => "?").join(",");
|
||||||
|
@ -811,6 +861,7 @@ function buildLoaders(db) {
|
||||||
db
|
db
|
||||||
);
|
);
|
||||||
loaders.itemAllOccupiedZonesLoader = buildItemAllOccupiedZonesLoader(db);
|
loaders.itemAllOccupiedZonesLoader = buildItemAllOccupiedZonesLoader(db);
|
||||||
|
loaders.itemTradeCountsLoader = buildItemTradeCountsLoader(db);
|
||||||
loaders.petTypeLoader = buildPetTypeLoader(db, loaders);
|
loaders.petTypeLoader = buildPetTypeLoader(db, loaders);
|
||||||
loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader(
|
loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader(
|
||||||
db,
|
db,
|
||||||
|
|
|
@ -22,6 +22,10 @@ const typeDefs = gql`
|
||||||
currentUserOwnsThis: Boolean!
|
currentUserOwnsThis: Boolean!
|
||||||
currentUserWantsThis: Boolean!
|
currentUserWantsThis: Boolean!
|
||||||
|
|
||||||
|
# How many users are offering/seeking this in their public trade lists.
|
||||||
|
numUsersOfferingThis: Int!
|
||||||
|
numUsersSeekingThis: Int!
|
||||||
|
|
||||||
# How this item appears on the given species/color combo. If it does not
|
# How this item appears on the given species/color combo. If it does not
|
||||||
# fit the pet, we'll return an empty ItemAppearance with no layers.
|
# fit the pet, we'll return an empty ItemAppearance with no layers.
|
||||||
appearanceOn(speciesId: ID!, colorId: ID!): ItemAppearance!
|
appearanceOn(speciesId: ID!, colorId: ID!): ItemAppearance!
|
||||||
|
@ -185,6 +189,21 @@ const resolvers = {
|
||||||
return closetHangers.some((h) => h.itemId === id && !h.owned);
|
return closetHangers.some((h) => h.itemId === id && !h.owned);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
numUsersOfferingThis: async ({ id }, _, { itemTradeCountsLoader }) => {
|
||||||
|
const count = await itemTradeCountsLoader.load({
|
||||||
|
itemId: id,
|
||||||
|
isOwned: true,
|
||||||
|
});
|
||||||
|
return count;
|
||||||
|
},
|
||||||
|
numUsersSeekingThis: async ({ id }, _, { itemTradeCountsLoader }) => {
|
||||||
|
const count = await itemTradeCountsLoader.load({
|
||||||
|
itemId: id,
|
||||||
|
isOwned: false,
|
||||||
|
});
|
||||||
|
return count;
|
||||||
|
},
|
||||||
|
|
||||||
appearanceOn: async (
|
appearanceOn: async (
|
||||||
{ id },
|
{ id },
|
||||||
{ speciesId, colorId },
|
{ speciesId, colorId },
|
||||||
|
|
Loading…
Reference in a new issue