basic modeling page cute cards!
This commit is contained in:
parent
715f466df4
commit
1cc4d718a5
3 changed files with 211 additions and 156 deletions
|
@ -1,15 +1,17 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Box } from "@chakra-ui/core";
|
import { Badge, Box, SimpleGrid } from "@chakra-ui/core";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
|
|
||||||
import HangerSpinner from "./components/HangerSpinner";
|
import HangerSpinner from "./components/HangerSpinner";
|
||||||
import { Heading1 } from "./util";
|
import { Heading1, Heading2 } from "./util";
|
||||||
|
import ItemSummary, { ItemSummaryBadgeList } from "./components/ItemSummary";
|
||||||
|
|
||||||
function ModelingPage() {
|
function ModelingPage() {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Heading1 marginBottom="2">Modeling Hub</Heading1>
|
<Heading1 marginBottom="2">Modeling Hub</Heading1>
|
||||||
|
<Heading2 marginBottom="2">Item models we need</Heading2>
|
||||||
<ItemModelsList />
|
<ItemModelsList />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -42,24 +44,35 @@ function ItemModelsList() {
|
||||||
return <Box color="red.400">{error.message}</Box>;
|
return <Box color="red.400">{error.message}</Box>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = [...data.itemsThatNeedModels].sort((a, b) =>
|
const items = data.itemsThatNeedModels
|
||||||
a.name.localeCompare(b.name)
|
// enough MMEs are broken that I just don't want to deal right now!
|
||||||
);
|
.filter((item) => !item.name.includes("MME"))
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box as="ul">
|
<SimpleGrid columns={{ sm: 1, lg: 2 }} spacing="6">
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<ItemModelCard item={item} />
|
<ItemModelCard key={item.id} item={item} />
|
||||||
))}
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ItemModelCard({ item, ...props }) {
|
||||||
|
return (
|
||||||
|
<Box p="2" boxShadow="lg" borderRadius="lg" width="400px" {...props}>
|
||||||
|
<ItemSummary item={item} badges={<ItemModelBadges item={item} />} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemModelCard({ item }) {
|
function ItemModelBadges({ item }) {
|
||||||
return (
|
return (
|
||||||
<Box as="li" listStyleType="none" boxShadow="lg">
|
<ItemSummaryBadgeList>
|
||||||
{item.name}
|
{item.speciesThatNeedModels.map((species) => (
|
||||||
</Box>
|
<Badge>{species.name}</Badge>
|
||||||
|
))}
|
||||||
|
</ItemSummaryBadgeList>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,8 @@ import {
|
||||||
Box,
|
Box,
|
||||||
Flex,
|
Flex,
|
||||||
IconButton,
|
IconButton,
|
||||||
Image,
|
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Wrap,
|
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@chakra-ui/core";
|
} from "@chakra-ui/core";
|
||||||
|
@ -20,7 +18,7 @@ import {
|
||||||
} from "@chakra-ui/icons";
|
} from "@chakra-ui/icons";
|
||||||
import loadable from "@loadable/component";
|
import loadable from "@loadable/component";
|
||||||
|
|
||||||
import { safeImageUrl } from "../util";
|
import ItemSummary, { ItemSummaryBadgeList } from "../components/ItemSummary";
|
||||||
import SupportOnly from "./support/SupportOnly";
|
import SupportOnly from "./support/SupportOnly";
|
||||||
|
|
||||||
const LoadableItemSupportDrawer = loadable(() =>
|
const LoadableItemSupportDrawer = loadable(() =>
|
||||||
|
@ -56,59 +54,19 @@ function Item({
|
||||||
}) {
|
}) {
|
||||||
const [supportDrawerIsOpen, setSupportDrawerIsOpen] = React.useState(false);
|
const [supportDrawerIsOpen, setSupportDrawerIsOpen] = React.useState(false);
|
||||||
|
|
||||||
const occupiedZoneLabels = getZoneLabels(
|
|
||||||
item.appearanceOn.layers.map((l) => l.zone)
|
|
||||||
);
|
|
||||||
const restrictedZoneLabels = getZoneLabels(
|
|
||||||
item.appearanceOn.restrictedZones.filter((z) => z.isCommonlyUsedByItems)
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ItemContainer isDisabled={isDisabled}>
|
<ItemContainer isDisabled={isDisabled}>
|
||||||
<Box flex="0 0 auto" marginRight="3">
|
<Box flex="1 1 0" minWidth="0">
|
||||||
<ItemThumbnail
|
<ItemSummary
|
||||||
src={safeImageUrl(item.thumbnailUrl)}
|
item={item}
|
||||||
|
badges={<ItemBadges item={item} />}
|
||||||
|
itemNameId={itemNameId}
|
||||||
isWorn={isWorn}
|
isWorn={isWorn}
|
||||||
isDisabled={isDisabled}
|
isDiabled={isDisabled}
|
||||||
|
focusSelector={containerHasFocus}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box flex="1 1 0" minWidth="0" marginTop="1px">
|
|
||||||
<ItemName id={itemNameId} isWorn={isWorn} isDisabled={isDisabled}>
|
|
||||||
{item.name}
|
|
||||||
</ItemName>
|
|
||||||
<Wrap spacing="2" marginTop="1" opacity="0.7">
|
|
||||||
{item.isNc ? (
|
|
||||||
<ItemBadgeTooltip label="Neocash">
|
|
||||||
<Badge colorScheme="purple">NC</Badge>
|
|
||||||
</ItemBadgeTooltip>
|
|
||||||
) : (
|
|
||||||
// The main purpose of the NP badge is alignment: if there are
|
|
||||||
// zone badges, we want them to start at the same rough position,
|
|
||||||
// even if there's an NC badge at the start. But if this view
|
|
||||||
// generally avoids zone badges, we'd rather have the NC badge be
|
|
||||||
// a little extra that might pop up and hide the NP case, rather
|
|
||||||
// than try to line things up like a table.
|
|
||||||
<ItemBadgeTooltip label="Neopoints">
|
|
||||||
<Badge>NP</Badge>
|
|
||||||
</ItemBadgeTooltip>
|
|
||||||
)}
|
|
||||||
{occupiedZoneLabels.map((zoneLabel) => (
|
|
||||||
<ZoneBadge
|
|
||||||
key={zoneLabel}
|
|
||||||
variant="occupies"
|
|
||||||
zoneLabel={zoneLabel}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{restrictedZoneLabels.map((zoneLabel) => (
|
|
||||||
<ZoneBadge
|
|
||||||
key={zoneLabel}
|
|
||||||
variant="restricts"
|
|
||||||
zoneLabel={zoneLabel}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Wrap>
|
|
||||||
</Box>
|
|
||||||
<Box flex="0 0 auto" marginTop="5px">
|
<Box flex="0 0 auto" marginTop="5px">
|
||||||
{isInOutfit && (
|
{isInOutfit && (
|
||||||
<ItemActionButton
|
<ItemActionButton
|
||||||
|
@ -222,105 +180,38 @@ function ItemContainer({ children, isDisabled = false }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function ItemBadges({ item }) {
|
||||||
* ItemThumbnail shows a small preview image for the item, including some
|
const occupiedZoneLabels = getZoneLabels(
|
||||||
* hover/focus and worn/unworn states.
|
item.appearanceOn.layers.map((l) => l.zone)
|
||||||
*/
|
|
||||||
function ItemThumbnail({ src, isWorn, isDisabled }) {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const borderColor = useColorModeValue(
|
|
||||||
theme.colors.green["700"],
|
|
||||||
"transparent"
|
|
||||||
);
|
);
|
||||||
|
const restrictedZoneLabels = getZoneLabels(
|
||||||
const focusBorderColor = useColorModeValue(
|
item.appearanceOn.restrictedZones.filter((z) => z.isCommonlyUsedByItems)
|
||||||
theme.colors.green["600"],
|
|
||||||
"transparent"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<ItemSummaryBadgeList>
|
||||||
width="50px"
|
{item.isNc ? (
|
||||||
height="50px"
|
<ItemBadgeTooltip label="Neocash">
|
||||||
transition="all 0.15s"
|
<Badge colorScheme="purple">NC</Badge>
|
||||||
transformOrigin="center"
|
</ItemBadgeTooltip>
|
||||||
position="relative"
|
) : (
|
||||||
className={css([
|
// The main purpose of the NP badge is alignment: if there are
|
||||||
{
|
// zone badges, we want them to start at the same rough position,
|
||||||
transform: "scale(0.8)",
|
// even if there's an NC badge at the start. But if this view
|
||||||
},
|
// generally avoids zone badges, we'd rather have the NC badge be
|
||||||
!isDisabled &&
|
// a little extra that might pop up and hide the NP case, rather
|
||||||
!isWorn && {
|
// than try to line things up like a table.
|
||||||
[containerHasFocus]: {
|
<ItemBadgeTooltip label="Neopoints">
|
||||||
opacity: "0.9",
|
<Badge>NP</Badge>
|
||||||
transform: "scale(0.9)",
|
</ItemBadgeTooltip>
|
||||||
},
|
)}
|
||||||
},
|
{occupiedZoneLabels.map((zoneLabel) => (
|
||||||
!isDisabled &&
|
<ZoneBadge key={zoneLabel} variant="occupies" zoneLabel={zoneLabel} />
|
||||||
isWorn && {
|
))}
|
||||||
opacity: 1,
|
{restrictedZoneLabels.map((zoneLabel) => (
|
||||||
transform: "none",
|
<ZoneBadge key={zoneLabel} variant="restricts" zoneLabel={zoneLabel} />
|
||||||
},
|
))}
|
||||||
])}
|
</ItemSummaryBadgeList>
|
||||||
>
|
|
||||||
<Box
|
|
||||||
borderRadius="lg"
|
|
||||||
boxShadow="md"
|
|
||||||
border="1px"
|
|
||||||
overflow="hidden"
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
className={css([
|
|
||||||
{
|
|
||||||
borderColor: `${borderColor} !important`,
|
|
||||||
},
|
|
||||||
!isDisabled &&
|
|
||||||
!isWorn && {
|
|
||||||
[containerHasFocus]: {
|
|
||||||
borderColor: `${focusBorderColor} !important`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])}
|
|
||||||
>
|
|
||||||
<Image width="100%" height="100%" src={src} alt="" />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ItemName shows the item's name, including some hover/focus and worn/unworn
|
|
||||||
* states.
|
|
||||||
*/
|
|
||||||
function ItemName({ children, isDisabled, ...props }) {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
fontSize="md"
|
|
||||||
transition="all 0.15s"
|
|
||||||
overflow="hidden"
|
|
||||||
whiteSpace="nowrap"
|
|
||||||
textOverflow="ellipsis"
|
|
||||||
className={
|
|
||||||
!isDisabled &&
|
|
||||||
css`
|
|
||||||
${containerHasFocus} {
|
|
||||||
opacity: 0.9;
|
|
||||||
font-weight: ${theme.fontWeights.medium};
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + .item-container & {
|
|
||||||
opacity: 1;
|
|
||||||
font-weight: ${theme.fontWeights.bold};
|
|
||||||
}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
151
src/app/components/ItemSummary.js
Normal file
151
src/app/components/ItemSummary.js
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
import React from "react";
|
||||||
|
import { css } from "emotion";
|
||||||
|
import { Box, Image, Wrap, useColorModeValue, useTheme } from "@chakra-ui/core";
|
||||||
|
|
||||||
|
import { safeImageUrl } from "../util";
|
||||||
|
|
||||||
|
function ItemSummary({
|
||||||
|
item,
|
||||||
|
badges,
|
||||||
|
isWorn,
|
||||||
|
isDisabled,
|
||||||
|
itemNameId,
|
||||||
|
focusSelector,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box display="flex">
|
||||||
|
<Box flex="0 0 auto" marginRight="3">
|
||||||
|
<ItemThumbnail
|
||||||
|
src={safeImageUrl(item.thumbnailUrl)}
|
||||||
|
isWorn={isWorn}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
focusSelector={focusSelector}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box flex="1 1 0" minWidth="0" marginTop="1px">
|
||||||
|
<ItemName
|
||||||
|
id={itemNameId}
|
||||||
|
isWorn={isWorn}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
focusSelector={focusSelector}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</ItemName>
|
||||||
|
|
||||||
|
{badges}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ItemThumbnail shows a small preview image for the item, including some
|
||||||
|
* hover/focus and worn/unworn states.
|
||||||
|
*/
|
||||||
|
function ItemThumbnail({ src, isWorn, isDisabled, focusSelector }) {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const borderColor = useColorModeValue(
|
||||||
|
theme.colors.green["700"],
|
||||||
|
"transparent"
|
||||||
|
);
|
||||||
|
|
||||||
|
const focusBorderColor = useColorModeValue(
|
||||||
|
theme.colors.green["600"],
|
||||||
|
"transparent"
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
width="50px"
|
||||||
|
height="50px"
|
||||||
|
transition="all 0.15s"
|
||||||
|
transformOrigin="center"
|
||||||
|
position="relative"
|
||||||
|
className={css([
|
||||||
|
{
|
||||||
|
transform: "scale(0.8)",
|
||||||
|
},
|
||||||
|
!isDisabled &&
|
||||||
|
!isWorn && {
|
||||||
|
[focusSelector]: {
|
||||||
|
opacity: "0.9",
|
||||||
|
transform: "scale(0.9)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
!isDisabled &&
|
||||||
|
isWorn && {
|
||||||
|
opacity: 1,
|
||||||
|
transform: "none",
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
borderRadius="lg"
|
||||||
|
boxShadow="md"
|
||||||
|
border="1px"
|
||||||
|
overflow="hidden"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
className={css([
|
||||||
|
{
|
||||||
|
borderColor: `${borderColor} !important`,
|
||||||
|
},
|
||||||
|
!isDisabled &&
|
||||||
|
!isWorn && {
|
||||||
|
[focusSelector]: {
|
||||||
|
borderColor: `${focusBorderColor} !important`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
>
|
||||||
|
<Image width="100%" height="100%" src={src} alt="" />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ItemName shows the item's name, including some hover/focus and worn/unworn
|
||||||
|
* states.
|
||||||
|
*/
|
||||||
|
function ItemName({ children, isDisabled, focusSelector, ...props }) {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
fontSize="md"
|
||||||
|
transition="all 0.15s"
|
||||||
|
overflow="hidden"
|
||||||
|
whiteSpace="nowrap"
|
||||||
|
textOverflow="ellipsis"
|
||||||
|
className={
|
||||||
|
!isDisabled &&
|
||||||
|
css`
|
||||||
|
${focusSelector} {
|
||||||
|
opacity: 0.9;
|
||||||
|
font-weight: ${theme.fontWeights.medium};
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .item-container & {
|
||||||
|
opacity: 1;
|
||||||
|
font-weight: ${theme.fontWeights.bold};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ItemSummaryBadgeList({ children }) {
|
||||||
|
return (
|
||||||
|
<Wrap spacing="2" marginTop="1" opacity="0.7">
|
||||||
|
{children}
|
||||||
|
</Wrap>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ItemSummary;
|
Loading…
Reference in a new issue