forked from OpenNeo/impress
In a previous change, I moved the margin for item badges onto an ItemBadge element… but I didn't think through how that would break the spacing for the loading state of ItemPage. Now, the loading skeleton items _contained_ the badge margin, and so the spacing between badges was shiny skeleton-y. Here, I replace ZoneBadgesList with a function that just returns the elements, and go back to using Chakra's Wrap component. That will apply the margin to direct children, and the zone badges are direct children now. One option I'm thinking of in hindsight is an idea I had earlier: Chakra hacks the margin onto _React_ children, but could we use CSS direct child selector instead? A bit trickier to resolve the margin size to the theme's value, but plenty doable… something to consider!
208 lines
4.9 KiB
JavaScript
208 lines
4.9 KiB
JavaScript
import React from "react";
|
|
import { Badge, Box, Tooltip } from "@chakra-ui/core";
|
|
import gql from "graphql-tag";
|
|
import { useQuery } from "@apollo/client";
|
|
|
|
import { Delay } from "./util";
|
|
import HangerSpinner from "./components/HangerSpinner";
|
|
import { Heading1, Heading2, usePageTitle } from "./util";
|
|
import ItemCard, {
|
|
ItemBadgeList,
|
|
ItemCardList,
|
|
NcBadge,
|
|
YouOwnThisBadge,
|
|
} from "./components/ItemCard";
|
|
|
|
function ModelingPage() {
|
|
usePageTitle("Modeling Hub");
|
|
|
|
return (
|
|
<Box>
|
|
<Heading1 marginBottom="2">Modeling Hub</Heading1>
|
|
<ItemModelsSection />
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
function ItemModelsSection() {
|
|
const { loading, error, data } = useQuery(gql`
|
|
query ModelingPage {
|
|
standardItems: itemsThatNeedModels {
|
|
...ItemFields
|
|
speciesThatNeedModels {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
|
|
babyItems: itemsThatNeedModels(colorId: "6") {
|
|
...ItemFields
|
|
speciesThatNeedModels(colorId: "6") {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
|
|
maraquanItems: itemsThatNeedModels(colorId: "44") {
|
|
...ItemFields
|
|
speciesThatNeedModels(colorId: "44") {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
|
|
mutantItems: itemsThatNeedModels(colorId: "46") {
|
|
...ItemFields
|
|
speciesThatNeedModels(colorId: "46") {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
|
|
currentUser {
|
|
itemsTheyOwn {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
|
|
fragment ItemFields on Item {
|
|
id
|
|
name
|
|
thumbnailUrl
|
|
isNc
|
|
createdAt
|
|
}
|
|
`);
|
|
|
|
if (loading) {
|
|
return (
|
|
<>
|
|
<Heading2 marginBottom="2">Items we need modeled</Heading2>
|
|
<Box
|
|
display="flex"
|
|
flexDirection="column"
|
|
alignItems="center"
|
|
marginTop="8"
|
|
>
|
|
<HangerSpinner />
|
|
<Box fontSize="xs" marginTop="1">
|
|
<Delay ms={2500}>Checking all the items…</Delay>
|
|
</Box>
|
|
</Box>
|
|
</>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return <Box color="red.400">{error.message}</Box>;
|
|
}
|
|
|
|
const ownedItemIds = new Set(
|
|
data.currentUser?.itemsTheyOwn?.map((item) => item.id)
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<Heading2 marginBottom="2">Items we need modeled</Heading2>
|
|
<ItemModelsColorSection
|
|
items={data.standardItems}
|
|
ownedItemIds={ownedItemIds}
|
|
/>
|
|
<Heading2 marginTop="6" marginBottom="2">
|
|
Items we need modeled on Baby pets
|
|
</Heading2>
|
|
<ItemModelsColorSection
|
|
items={data.babyItems}
|
|
ownedItemIds={ownedItemIds}
|
|
/>
|
|
<Heading2 marginTop="6" marginBottom="2">
|
|
Items we need modeled on Maraquan pets
|
|
</Heading2>
|
|
<ItemModelsColorSection
|
|
items={data.maraquanItems}
|
|
ownedItemIds={ownedItemIds}
|
|
/>
|
|
<Heading2 marginTop="6">Items we need modeled on Mutant pets</Heading2>
|
|
<ItemModelsColorSection
|
|
items={data.mutantItems}
|
|
ownedItemIds={ownedItemIds}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function ItemModelsColorSection({ items, ownedItemIds }) {
|
|
items = items
|
|
// enough MMEs are broken that I just don't want to deal right now!
|
|
// TODO: solve this with our new database omission feature instead?
|
|
.filter((item) => !item.name.includes("MME"))
|
|
.sort((a, b) => {
|
|
// This is a cute sort hack. We sort first by, bringing "New!" to the
|
|
// top, and then sorting by name _within_ those two groups.
|
|
const aName = `${itemIsNew(a) ? "000" : "999"} ${a.name}`;
|
|
const bName = `${itemIsNew(b) ? "000" : "999"} ${b.name}`;
|
|
return aName.localeCompare(bName);
|
|
});
|
|
|
|
return (
|
|
<ItemCardList>
|
|
{items.map((item) => (
|
|
<ItemModelCard
|
|
key={item.id}
|
|
item={item}
|
|
currentUserOwnsItem={ownedItemIds.has(item.id)}
|
|
/>
|
|
))}
|
|
</ItemCardList>
|
|
);
|
|
}
|
|
|
|
function ItemModelCard({ item, currentUserOwnsItem, ...props }) {
|
|
const badges = (
|
|
<ItemModelBadges item={item} currentUserOwnsItem={currentUserOwnsItem} />
|
|
);
|
|
|
|
return <ItemCard item={item} badges={badges} {...props} />;
|
|
}
|
|
|
|
function ItemModelBadges({ item, currentUserOwnsItem }) {
|
|
return (
|
|
<ItemBadgeList>
|
|
{itemIsNew(item) && <NewItemBadge createdAt={item.createdAt} />}
|
|
{item.isNc && <NcBadge />}
|
|
{currentUserOwnsItem && <YouOwnThisBadge />}
|
|
{item.speciesThatNeedModels.map((species) => (
|
|
<Badge>{species.name}</Badge>
|
|
))}
|
|
</ItemBadgeList>
|
|
);
|
|
}
|
|
|
|
const fullDateFormatter = new Intl.DateTimeFormat("en-US", {
|
|
dateStyle: "long",
|
|
});
|
|
function NewItemBadge({ createdAt }) {
|
|
const date = new Date(createdAt);
|
|
|
|
return (
|
|
<Tooltip
|
|
label={`Added on ${fullDateFormatter.format(date)}`}
|
|
placement="top"
|
|
openDelay={400}
|
|
>
|
|
<Badge colorScheme="yellow">New!</Badge>
|
|
</Tooltip>
|
|
);
|
|
}
|
|
|
|
function itemIsNew(item) {
|
|
const date = new Date(item.createdAt);
|
|
|
|
const oneMonthAgo = new Date();
|
|
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
|
|
|
|
return date > oneMonthAgo;
|
|
}
|
|
|
|
export default ModelingPage;
|