add zones to user items page
idk the labels section was feeling empty, and I didn't see a way to streamline it more, so I figured, add info that might be useful! lol
This commit is contained in:
parent
5111ebcbfe
commit
274a4f716f
7 changed files with 177 additions and 79 deletions
|
@ -13,6 +13,7 @@ import ItemCard, {
|
|||
NpBadge,
|
||||
YouOwnThisBadge,
|
||||
YouWantThisBadge,
|
||||
ZoneBadgeList,
|
||||
} from "./components/ItemCard";
|
||||
import useCurrentUser from "./components/useCurrentUser";
|
||||
import WIPCallout from "./components/WIPCallout";
|
||||
|
@ -35,6 +36,10 @@ function UserItemsPage() {
|
|||
isNc
|
||||
name
|
||||
thumbnailUrl
|
||||
allOccupiedZones {
|
||||
id
|
||||
label @client
|
||||
}
|
||||
}
|
||||
|
||||
itemsTheyWant {
|
||||
|
@ -42,6 +47,10 @@ function UserItemsPage() {
|
|||
isNc
|
||||
name
|
||||
thumbnailUrl
|
||||
allOccupiedZones {
|
||||
id
|
||||
label @client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,18 +123,24 @@ function UserItemsPage() {
|
|||
{isCurrentUser ? "Items you own" : `Items ${data.user.username} owns`}
|
||||
</Heading2>
|
||||
<ItemCardList>
|
||||
{sortedItemsTheyOwn.map((item) => (
|
||||
<ItemCard
|
||||
key={item.id}
|
||||
item={item}
|
||||
badges={
|
||||
<ItemBadgeList>
|
||||
{item.isNc ? <NcBadge /> : <NpBadge />}
|
||||
{showYouWantThisBadge(item) && <YouWantThisBadge />}
|
||||
</ItemBadgeList>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{sortedItemsTheyOwn.map((item) => {
|
||||
return (
|
||||
<ItemCard
|
||||
key={item.id}
|
||||
item={item}
|
||||
badges={
|
||||
<ItemBadgeList>
|
||||
{item.isNc ? <NcBadge /> : <NpBadge />}
|
||||
{showYouWantThisBadge(item) && <YouWantThisBadge />}
|
||||
<ZoneBadgeList
|
||||
zones={item.allOccupiedZones}
|
||||
variant="occupies"
|
||||
/>
|
||||
</ItemBadgeList>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ItemCardList>
|
||||
|
||||
<Heading2 marginBottom="6" marginTop="8">
|
||||
|
@ -140,6 +155,10 @@ function UserItemsPage() {
|
|||
<ItemBadgeList>
|
||||
{item.isNc ? <NcBadge /> : <NpBadge />}
|
||||
{showYouOwnThisBadge(item) && <YouOwnThisBadge />}
|
||||
<ZoneBadgeList
|
||||
zones={item.allOccupiedZones}
|
||||
variant="occupies"
|
||||
/>
|
||||
</ItemBadgeList>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from "react";
|
||||
import { css, cx } from "emotion";
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Flex,
|
||||
IconButton,
|
||||
|
@ -10,24 +9,19 @@ import {
|
|||
useColorModeValue,
|
||||
useTheme,
|
||||
} from "@chakra-ui/core";
|
||||
import {
|
||||
EditIcon,
|
||||
DeleteIcon,
|
||||
InfoIcon,
|
||||
NotAllowedIcon,
|
||||
} from "@chakra-ui/icons";
|
||||
import { EditIcon, DeleteIcon, InfoIcon } from "@chakra-ui/icons";
|
||||
import { Link } from "react-router-dom";
|
||||
import loadable from "@loadable/component";
|
||||
|
||||
import {
|
||||
ItemCardContent,
|
||||
ItemBadgeList,
|
||||
ItemBadgeTooltip,
|
||||
MaybeAnimatedBadge,
|
||||
NcBadge,
|
||||
NpBadge,
|
||||
YouOwnThisBadge,
|
||||
YouWantThisBadge,
|
||||
ZoneBadgeList,
|
||||
} from "../components/ItemCard";
|
||||
import SupportOnly from "./support/SupportOnly";
|
||||
import useSupport from "./support/useSupport";
|
||||
|
@ -207,11 +201,9 @@ function ItemContainer({ children, isDisabled = false }) {
|
|||
|
||||
function ItemBadges({ item }) {
|
||||
const { isSupportUser } = useSupport();
|
||||
const occupiedZoneLabels = getZoneLabels(
|
||||
item.appearanceOn.layers.map((l) => l.zone)
|
||||
);
|
||||
const restrictedZoneLabels = getZoneLabels(
|
||||
item.appearanceOn.restrictedZones.filter((z) => z.isCommonlyUsedByItems)
|
||||
const occupiedZones = item.appearanceOn.layers.map((l) => l.zone);
|
||||
const restrictedZones = item.appearanceOn.restrictedZones.filter(
|
||||
(z) => z.isCommonlyUsedByItems
|
||||
);
|
||||
const isMaybeAnimated = item.appearanceOn.layers.some(
|
||||
(l) => l.canvasMovieLibraryUrl
|
||||
|
@ -239,12 +231,8 @@ function ItemBadges({ item }) {
|
|||
}
|
||||
{item.currentUserOwnsThis && <YouOwnThisBadge variant="short" />}
|
||||
{item.currentUserWantsThis && <YouWantThisBadge variant="short" />}
|
||||
{occupiedZoneLabels.map((zoneLabel) => (
|
||||
<ZoneBadge key={zoneLabel} variant="occupies" zoneLabel={zoneLabel} />
|
||||
))}
|
||||
{restrictedZoneLabels.map((zoneLabel) => (
|
||||
<ZoneBadge key={zoneLabel} variant="restricts" zoneLabel={zoneLabel} />
|
||||
))}
|
||||
<ZoneBadgeList zones={occupiedZones} variant="occupied" />
|
||||
<ZoneBadgeList zones={restrictedZones} variant="restricts" />
|
||||
</ItemBadgeList>
|
||||
);
|
||||
}
|
||||
|
@ -323,53 +311,6 @@ export function ItemListSkeleton({ count }) {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* getZoneLabels returns the set of labels for the given zones. Sometimes an
|
||||
* item occupies multiple zones of the same name, so it's especially important
|
||||
* to de-duplicate them here!
|
||||
*/
|
||||
function getZoneLabels(zones) {
|
||||
let labels = zones.map((z) => z.label);
|
||||
labels = new Set(labels);
|
||||
labels = [...labels].sort();
|
||||
return labels;
|
||||
}
|
||||
|
||||
function ZoneBadge({ variant, zoneLabel }) {
|
||||
// Shorten the label when necessary, to make the badges less bulky
|
||||
const shorthand = zoneLabel
|
||||
.replace("Background Item", "BG Item")
|
||||
.replace("Foreground Item", "FG Item")
|
||||
.replace("Lower-body", "Lower")
|
||||
.replace("Upper-body", "Upper")
|
||||
.replace("Transient", "Trans")
|
||||
.replace("Biology", "Bio");
|
||||
|
||||
if (variant === "restricts") {
|
||||
return (
|
||||
<ItemBadgeTooltip
|
||||
label={`Restricted: This item can't be worn with ${zoneLabel} items`}
|
||||
>
|
||||
<Badge>
|
||||
<Box display="flex" alignItems="center">
|
||||
{shorthand} <NotAllowedIcon marginLeft="1" />
|
||||
</Box>
|
||||
</Badge>
|
||||
</ItemBadgeTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (shorthand !== zoneLabel) {
|
||||
return (
|
||||
<ItemBadgeTooltip label={zoneLabel}>
|
||||
<Badge>{shorthand}</Badge>
|
||||
</ItemBadgeTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return <Badge>{shorthand}</Badge>;
|
||||
}
|
||||
|
||||
/**
|
||||
* containerHasFocus is a common CSS selector, for the case where our parent
|
||||
* .item-container is hovered or the adjacent hidden radio/checkbox is
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
useColorModeValue,
|
||||
useTheme,
|
||||
} from "@chakra-ui/core";
|
||||
import { CheckIcon, StarIcon } from "@chakra-ui/icons";
|
||||
import { CheckIcon, NotAllowedIcon, StarIcon } from "@chakra-ui/icons";
|
||||
import { HiSparkles } from "react-icons/hi";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
|
@ -279,6 +279,53 @@ export function YouWantThisBadge({ variant = "long" }) {
|
|||
return badge;
|
||||
}
|
||||
|
||||
function ZoneBadge({ variant, zoneLabel }) {
|
||||
// Shorten the label when necessary, to make the badges less bulky
|
||||
const shorthand = zoneLabel
|
||||
.replace("Background Item", "BG Item")
|
||||
.replace("Foreground Item", "FG Item")
|
||||
.replace("Lower-body", "Lower")
|
||||
.replace("Upper-body", "Upper")
|
||||
.replace("Transient", "Trans")
|
||||
.replace("Biology", "Bio");
|
||||
|
||||
if (variant === "restricts") {
|
||||
return (
|
||||
<ItemBadgeTooltip
|
||||
label={`Restricted: This item can't be worn with ${zoneLabel} items`}
|
||||
>
|
||||
<Badge>
|
||||
<Box display="flex" alignItems="center">
|
||||
{shorthand} <NotAllowedIcon marginLeft="1" />
|
||||
</Box>
|
||||
</Badge>
|
||||
</ItemBadgeTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (shorthand !== zoneLabel) {
|
||||
return (
|
||||
<ItemBadgeTooltip label={zoneLabel}>
|
||||
<Badge>{shorthand}</Badge>
|
||||
</ItemBadgeTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return <Badge>{shorthand}</Badge>;
|
||||
}
|
||||
|
||||
export function ZoneBadgeList({ zones, variant }) {
|
||||
// Get the sorted zone labels. Sometimes an item occupies multiple zones of
|
||||
// the same name, so it's important to de-duplicate them!
|
||||
let labels = zones.map((z) => z.label);
|
||||
labels = new Set(labels);
|
||||
labels = [...labels].sort();
|
||||
|
||||
return labels.map((label) => (
|
||||
<ZoneBadge key={label} zoneLabel={label} variant={variant} />
|
||||
));
|
||||
}
|
||||
|
||||
export function MaybeAnimatedBadge() {
|
||||
return (
|
||||
<ItemBadgeTooltip label="Maybe animated? (Support only)">
|
||||
|
|
|
@ -324,6 +324,26 @@ const buildItemBodiesWithAppearanceDataLoader = (db) =>
|
|||
return itemIds.map((itemId) => entities.filter((e) => e.itemId === itemId));
|
||||
});
|
||||
|
||||
const buildItemAllOccupiedZonesLoader = (db) =>
|
||||
new DataLoader(async (itemIds) => {
|
||||
const qs = itemIds.map((_) => "?").join(", ");
|
||||
const [rows, _] = await db.execute(
|
||||
`SELECT items.id, GROUP_CONCAT(DISTINCT sa.zone_id) AS zone_ids FROM items
|
||||
INNER JOIN parents_swf_assets psa
|
||||
ON psa.parent_type = "Item" AND psa.parent_id = items.id
|
||||
INNER JOIN swf_assets sa ON sa.id = psa.swf_asset_id
|
||||
WHERE items.id IN (${qs})
|
||||
GROUP BY items.id;`,
|
||||
itemIds
|
||||
);
|
||||
|
||||
const entities = rows.map(normalizeRow);
|
||||
|
||||
return itemIds.map((itemId) =>
|
||||
entities.find((e) => e.id === itemId).zoneIds.split(",")
|
||||
);
|
||||
});
|
||||
|
||||
const buildPetTypeLoader = (db, loaders) =>
|
||||
new DataLoader(async (petTypeIds) => {
|
||||
const qs = petTypeIds.map((_) => "?").join(",");
|
||||
|
@ -745,6 +765,7 @@ function buildLoaders(db) {
|
|||
loaders.itemBodiesWithAppearanceDataLoader = buildItemBodiesWithAppearanceDataLoader(
|
||||
db
|
||||
);
|
||||
loaders.itemAllOccupiedZonesLoader = buildItemAllOccupiedZonesLoader(db);
|
||||
loaders.petTypeLoader = buildPetTypeLoader(db, loaders);
|
||||
loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader(
|
||||
db,
|
||||
|
|
|
@ -26,6 +26,9 @@ describe("Item", () => {
|
|||
name
|
||||
}
|
||||
explicitlyBodySpecific
|
||||
allOccupiedZones {
|
||||
label
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
@ -57,6 +60,22 @@ describe("Item", () => {
|
|||
"78104",
|
||||
],
|
||||
],
|
||||
Array [
|
||||
"SELECT items.id, GROUP_CONCAT(DISTINCT sa.zone_id) AS zone_ids FROM items
|
||||
INNER JOIN parents_swf_assets psa
|
||||
ON psa.parent_type = \\"Item\\" AND psa.parent_id = items.id
|
||||
INNER JOIN swf_assets sa ON sa.id = psa.swf_asset_id
|
||||
WHERE items.id IN (?, ?, ?, ?, ?, ?)
|
||||
GROUP BY items.id;",
|
||||
Array [
|
||||
"38913",
|
||||
"38911",
|
||||
"38912",
|
||||
"55788",
|
||||
"77530",
|
||||
"78104",
|
||||
],
|
||||
],
|
||||
Array [
|
||||
"SELECT * FROM color_translations
|
||||
WHERE color_id IN (?) AND locale = \\"en\\"",
|
||||
|
@ -64,6 +83,17 @@ describe("Item", () => {
|
|||
"44",
|
||||
],
|
||||
],
|
||||
Array [
|
||||
"SELECT * FROM zone_translations WHERE zone_id IN (?,?,?,?,?,?) AND locale = \\"en\\"",
|
||||
Array [
|
||||
"25",
|
||||
"40",
|
||||
"26",
|
||||
"46",
|
||||
"23",
|
||||
"3",
|
||||
],
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -7983,6 +7983,11 @@ exports[`Item loads metadata 1`] = `
|
|||
Object {
|
||||
"items": Array [
|
||||
Object {
|
||||
"allOccupiedZones": Array [
|
||||
Object {
|
||||
"label": "Gloves",
|
||||
},
|
||||
],
|
||||
"createdAt": null,
|
||||
"description": "Dont leave any trace that you were there with these gloves.",
|
||||
"explicitlyBodySpecific": false,
|
||||
|
@ -7994,6 +7999,11 @@ Object {
|
|||
"thumbnailUrl": "http://images.neopets.com/items/clo_zafara_agent_gloves.gif",
|
||||
},
|
||||
Object {
|
||||
"allOccupiedZones": Array [
|
||||
Object {
|
||||
"label": "Hat",
|
||||
},
|
||||
],
|
||||
"createdAt": null,
|
||||
"description": "Hide your face and hair so no one can recognise you.",
|
||||
"explicitlyBodySpecific": false,
|
||||
|
@ -8005,6 +8015,11 @@ Object {
|
|||
"thumbnailUrl": "http://images.neopets.com/items/clo_zafara_agent_hood.gif",
|
||||
},
|
||||
Object {
|
||||
"allOccupiedZones": Array [
|
||||
Object {
|
||||
"label": "Jacket",
|
||||
},
|
||||
],
|
||||
"createdAt": null,
|
||||
"description": "This robe is great for being stealthy.",
|
||||
"explicitlyBodySpecific": false,
|
||||
|
@ -8016,6 +8031,11 @@ Object {
|
|||
"thumbnailUrl": "http://images.neopets.com/items/clo_zafara_agent_robe.gif",
|
||||
},
|
||||
Object {
|
||||
"allOccupiedZones": Array [
|
||||
Object {
|
||||
"label": "Static",
|
||||
},
|
||||
],
|
||||
"createdAt": "2020-01-01T00:00:00.000Z",
|
||||
"description": "Maybe youll be discovered by some Neopets from the future and thawed out!",
|
||||
"explicitlyBodySpecific": true,
|
||||
|
@ -8027,6 +8047,11 @@ Object {
|
|||
"thumbnailUrl": "http://images.neopets.com/items/mall_petinice.gif",
|
||||
},
|
||||
Object {
|
||||
"allOccupiedZones": Array [
|
||||
Object {
|
||||
"label": "Shirt/Dress",
|
||||
},
|
||||
],
|
||||
"createdAt": "2020-01-01T00:00:00.000Z",
|
||||
"description": "Made with the finest jewels of the sea!",
|
||||
"explicitlyBodySpecific": false,
|
||||
|
@ -8041,6 +8066,11 @@ Object {
|
|||
"thumbnailUrl": "http://images.neopets.com/items/mall_clo_marabluegown.gif",
|
||||
},
|
||||
Object {
|
||||
"allOccupiedZones": Array [
|
||||
Object {
|
||||
"label": "Background",
|
||||
},
|
||||
],
|
||||
"createdAt": "2020-01-01T00:00:00.000Z",
|
||||
"description": "You truly are the number one fan of Altador Cup, and your room reflects this!",
|
||||
"explicitlyBodySpecific": false,
|
||||
|
|
|
@ -48,6 +48,11 @@ const typeDefs = gql`
|
|||
# which species this is for by going through the body field on
|
||||
# ItemAppearance!)
|
||||
canonicalAppearance: ItemAppearance
|
||||
|
||||
# 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
|
||||
# info about the item.
|
||||
allOccupiedZones: [Zone!]!
|
||||
}
|
||||
|
||||
type ItemAppearance {
|
||||
|
@ -210,6 +215,11 @@ const resolvers = {
|
|||
body: { id: canonicalBodyId, species: { id: rows[0].speciesId } },
|
||||
};
|
||||
},
|
||||
allOccupiedZones: async ({ id }, _, { itemAllOccupiedZonesLoader }) => {
|
||||
const zoneIds = await itemAllOccupiedZonesLoader.load(id);
|
||||
const zones = zoneIds.map((id) => ({ id }));
|
||||
return zones;
|
||||
},
|
||||
},
|
||||
|
||||
ItemAppearance: {
|
||||
|
|
Loading…
Reference in a new issue