sketched out the pet compatibility chooser

can't actually set things properly yet, but you can scroll through options and see how it fits!
This commit is contained in:
Emi Matchu 2020-08-01 14:12:57 -07:00
parent 488299353a
commit 213f30b2f2
6 changed files with 243 additions and 89 deletions

View file

@ -2,6 +2,10 @@ import * as React from "react";
import {
Button,
Box,
FormControl,
FormErrorMessage,
FormHelperText,
FormLabel,
HStack,
Modal,
ModalBody,
@ -9,74 +13,80 @@ import {
ModalContent,
ModalHeader,
ModalOverlay,
Radio,
RadioGroup,
} from "@chakra-ui/core";
import { ExternalLinkIcon } from "@chakra-ui/icons";
function ItemSupportAppearanceLayerModal({ item, itemLayer, isOpen, onClose }) {
import { OutfitLayers } from "../../components/OutfitPreview";
import SpeciesColorPicker from "../../components/SpeciesColorPicker";
import useOutfitAppearance from "../../components/useOutfitAppearance";
function ItemSupportAppearanceLayerModal({
item,
itemLayer,
outfitState,
isOpen,
onClose,
}) {
return (
<Modal size="xl" isOpen={isOpen} onClose={onClose}>
<ModalOverlay>
<ModalContent>
<ModalContent color="green.800">
<ModalHeader>
Layer {itemLayer.id}: {item.name}
</ModalHeader>
<ModalCloseButton />
<ModalBody mb="4" pb="4">
<Box
as="dl"
display="grid"
gridTemplateColumns="max-content auto"
gridRowGap="1"
gridColumnGap="2"
>
<Box as="dt" gridColumn="1" fontWeight="bold">
ID:
</Box>
<Box as="dd" gridColumn="2">
{itemLayer.id}
</Box>
<Box as="dt" gridColumn="1" fontWeight="bold">
Zone:
</Box>
<Box as="dd" gridColumn="2">
<Metadata>
<MetadataLabel>ID:</MetadataLabel>
<MetadataValue>{itemLayer.id}</MetadataValue>
<MetadataLabel>Zone:</MetadataLabel>
<MetadataValue>
{itemLayer.zone.label} ({itemLayer.zone.id})
</Box>
<Box as="dt" gridColumn="1" fontWeight="bold">
Assets:
</Box>
<HStack as="dd" gridColumn="2" spacing="2">
{itemLayer.svgUrl ? (
<Button
as="a"
size="xs"
target="_blank"
href={itemLayer.svgUrl}
colorScheme="teal"
>
SVG <ExternalLinkIcon ml="1" />
</Button>
) : (
<Button size="xs" isDisabled>
No SVG
</Button>
)}
{itemLayer.imageUrl ? (
<Button
as="a"
size="xs"
target="_blank"
href={itemLayer.imageUrl}
colorScheme="teal"
>
PNG <ExternalLinkIcon ml="1" />
</Button>
) : (
<Button size="xs" isDisabled>
No PNG
</Button>
)}
</HStack>
</Box>
</MetadataValue>
<MetadataLabel>Assets:</MetadataLabel>
<MetadataValue>
<HStack spacing="2">
{itemLayer.svgUrl ? (
<Button
as="a"
size="xs"
target="_blank"
href={itemLayer.svgUrl}
colorScheme="teal"
>
SVG <ExternalLinkIcon ml="1" />
</Button>
) : (
<Button size="xs" isDisabled>
No SVG
</Button>
)}
{itemLayer.imageUrl ? (
<Button
as="a"
size="xs"
target="_blank"
href={itemLayer.imageUrl}
colorScheme="teal"
>
PNG <ExternalLinkIcon ml="1" />
</Button>
) : (
<Button size="xs" isDisabled>
No PNG
</Button>
)}
</HStack>
</MetadataValue>
</Metadata>
<Box height="8" />
<ItemSupportAppearanceLayerPetCompatibility
item={item}
itemLayer={itemLayer}
outfitState={outfitState}
/>
</ModalBody>
</ModalContent>
</ModalOverlay>
@ -84,4 +94,122 @@ function ItemSupportAppearanceLayerModal({ item, itemLayer, isOpen, onClose }) {
);
}
function ItemSupportAppearanceLayerPetCompatibility({
item,
itemLayer,
outfitState,
}) {
const [selectedBiology, setSelectedBiology] = React.useState({
speciesId: outfitState.speciesId,
colorId: outfitState.colorId,
pose: outfitState.pose,
isValid: true,
});
const [visibleBiology, setVisibleBiology] = React.useState(selectedBiology);
console.log(selectedBiology, visibleBiology);
const { loading, error, visibleLayers } = useOutfitAppearance({
speciesId: visibleBiology.speciesId,
colorId: visibleBiology.colorId,
pose: visibleBiology.pose,
wornItemIds: [item.id],
});
const biologyLayers = visibleLayers.filter((l) => l.source === "pet");
return (
<FormControl isInvalid={error || !selectedBiology.isValid ? true : false}>
<FormLabel>Pet compatibility</FormLabel>
<RadioGroup
colorScheme="green"
value={itemLayer.bodyId}
onChange={(e) => console.log(e)}
marginBottom="4"
>
<Radio value="0">
Fits all pets{" "}
<Box display="inline" color="gray.400" fontSize="sm">
(Body ID: 0)
</Box>
</Radio>
<Radio as="div" value="100" marginTop="2">
Fits all pets with the same body as:{" "}
<Box display="inline" color="gray.400" fontSize="sm">
(Body ID: 100)
</Box>
</Radio>
</RadioGroup>
<Box display="flex" flexDirection="column" alignItems="center">
<Box
width="150px"
height="150px"
marginTop="2"
marginBottom="2"
boxShadow="md"
borderRadius="md"
>
<OutfitLayers
loading={loading}
visibleLayers={[...biologyLayers, itemLayer]}
/>
</Box>
<SpeciesColorPicker
speciesId={selectedBiology.speciesId}
colorId={selectedBiology.colorId}
idealPose={outfitState.pose}
isDisabled={itemLayer.bodyId === "0"}
size="sm"
showPlaceholders
onChange={(species, color, isValid, pose) => {
const speciesId = species.id;
const colorId = color.id;
setSelectedBiology({ speciesId, colorId, isValid, pose });
if (isValid) {
setVisibleBiology({ speciesId, colorId, isValid, pose });
}
}}
/>
<Box height="1" />
{!error && (
<FormHelperText>
If it doesn't look right, try some other options until it does!
</FormHelperText>
)}
{error && <FormErrorMessage>{error.message}</FormErrorMessage>}
</Box>
</FormControl>
);
}
function Metadata({ children }) {
return (
<Box
as="dl"
display="grid"
gridTemplateColumns="max-content auto"
gridRowGap="1"
gridColumnGap="2"
>
{children}
</Box>
);
}
function MetadataLabel({ children }) {
return (
<Box as="dt" gridColumn="1" fontWeight="bold">
{children}
</Box>
);
}
function MetadataValue({ children }) {
return (
<Box as="dd" gridColumn="2">
{children}
</Box>
);
}
export default ItemSupportAppearanceLayerModal;

View file

@ -250,9 +250,10 @@ function ItemSupportAppearanceFields({ item, outfitState }) {
<HStack spacing="4" overflow="auto" paddingX="1">
{itemLayers.map((itemLayer) => (
<ItemSupportAppearanceLayer
biologyLayers={biologyLayers}
itemLayer={itemLayer}
item={item}
itemLayer={itemLayer}
biologyLayers={biologyLayers}
outfitState={outfitState}
/>
))}
</HStack>
@ -261,7 +262,12 @@ function ItemSupportAppearanceFields({ item, outfitState }) {
);
}
function ItemSupportAppearanceLayer({ biologyLayers, itemLayer, item }) {
function ItemSupportAppearanceLayer({
item,
itemLayer,
biologyLayers,
outfitState,
}) {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
@ -311,6 +317,7 @@ function ItemSupportAppearanceLayer({ biologyLayers, itemLayer, item }) {
<ItemSupportAppearanceLayerModal
item={item}
itemLayer={itemLayer}
outfitState={outfitState}
isOpen={isOpen}
onClose={onClose}
/>

View file

@ -167,7 +167,7 @@ export function OutfitLayers({
opacity={isMounted && loading ? 1 : 0}
transition={`opacity 0.2s ${loading ? loadingDelay : "0s"}`}
>
<FullScreenCenter>
<FullScreenCenter zIndex="9000">
<Box
width="100%"
height="100%"
@ -175,7 +175,7 @@ export function OutfitLayers({
opacity="0.8"
/>
</FullScreenCenter>
<FullScreenCenter>
<FullScreenCenter zIndex="9001">
<HangerSpinner color="green.300" boxSize="48px" />
</FullScreenCenter>
</Box>

View file

@ -15,7 +15,9 @@ function SpeciesColorPicker({
speciesId,
colorId,
idealPose,
showPlaceholders,
showPlaceholders = false,
isDisabled = false,
size = "md",
dark = false,
onChange,
}) {
@ -51,30 +53,38 @@ function SpeciesColorPicker({
const backgroundColor = dark ? "gray.600" : "white";
const borderColor = dark ? "transparent" : "green.600";
const textColor = dark ? "gray.50" : "inherit";
const SpeciesColorSelect = ({ ...props }) => (
<Select
backgroundColor={backgroundColor}
color={textColor}
border="1px"
borderColor={borderColor}
boxShadow="md"
width="auto"
_hover={{
borderColor: "green.400",
}}
_disabled={{
// Visually the disabled state is the same as the normal state, but
// with a wait cursor. We don't expect this to take long, and the flash
// of content is rough! (The caret still flashes, but that's small and
// harder to style in Chakra.)
opacity: 1,
cursor: "wait",
}}
isInvalid={valids && !pairIsValid(valids, speciesId, colorId)}
errorBorderColor="red.300"
{...props}
/>
);
const SpeciesColorSelect = ({ isDisabled, isLoading, ...props }) => {
const loadingProps = isLoading
? {
// Visually the disabled state is the same as the normal state, but
// with a wait cursor. We don't expect this to take long, and the flash
// of content is rough! (The caret still flashes, but that's small and
// harder to style in Chakra.)
opacity: 1,
cursor: "wait",
}
: {};
return (
<Select
backgroundColor={backgroundColor}
color={textColor}
size={size}
border="1px"
borderColor={borderColor}
boxShadow="md"
width="auto"
_hover={{
borderColor: "green.400",
}}
isInvalid={valids && !pairIsValid(valids, speciesId, colorId)}
isDisabled={isDisabled || isLoading}
errorBorderColor="red.300"
{...props}
{...loadingProps}
/>
);
};
if ((loadingMeta || loadingValids) && !showPlaceholders) {
return (
@ -125,7 +135,8 @@ function SpeciesColorPicker({
<SpeciesColorSelect
aria-label="Pet color"
value={colorId}
isDisabled={allColors.length === 0}
isLoading={allColors.length === 0}
isDisabled={isDisabled}
onChange={onChangeColor}
>
{allColors.length === 0 && (
@ -141,11 +152,12 @@ function SpeciesColorPicker({
</option>
))}
</SpeciesColorSelect>
<Box width="4" />
<Box width={size === "sm" ? 2 : 4} />
<SpeciesColorSelect
aria-label="Pet species"
value={speciesId}
isDisabled={allSpecies.length === 0}
isLoading={allSpecies.length === 0}
isDisabled={isDisabled}
onChange={onChangeSpecies}
>
{allSpecies.length === 0 && (

View file

@ -117,6 +117,7 @@ export const itemAppearanceFragment = gql`
id
svgUrl
imageUrl(size: SIZE_600)
bodyId
zone {
id
depth

View file

@ -104,6 +104,12 @@ const typeDefs = gql`
or if it's not as simple as a single SVG (e.g. animated).
"""
svgUrl: String
"""
This layer can fit on PetAppearances with the same bodyId. "0" is a
special body ID that indicates it fits all PetAppearances.
"""
bodyId: ID!
}
# Cache for 1 week (unlikely to change)