basic modeling page cute cards!

This commit is contained in:
Emi Matchu 2020-09-06 23:49:04 -07:00
parent 715f466df4
commit 1cc4d718a5
3 changed files with 211 additions and 156 deletions

View file

@ -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>
); );
} }

View file

@ -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>
); );
} }

View 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;