build form milestone

This commit is contained in:
Sendou
2020-02-03 01:47:27 +02:00
parent d9787ee471
commit 28c5681e0a
14 changed files with 318 additions and 76 deletions

View File

@@ -4,6 +4,7 @@ import {
HeadOnlyAbility,
ClothingOnlyAbility,
ShoesOnlyAbility,
Ability,
} from "../../types"
import { abilityIcons } from "../../assets/imageImports"
@@ -37,12 +38,7 @@ const subAbilityStyle = {
} as const
interface AbilityIconProps {
ability:
| StackableAbility
| HeadOnlyAbility
| ClothingOnlyAbility
| ShoesOnlyAbility
| "EMPTY"
ability: Ability | "EMPTY"
size: "MAIN" | "SUB"
}

View File

@@ -4,8 +4,6 @@ import { Flex, Box } from "@chakra-ui/core"
import { abilitiesGameOrder } from "../../utils/lists"
import AbilityIcon from "./AbilityIcon"
import { Ability } from "../../types"
import { useContext } from "react"
import MyThemeContext from "../../themeContext"
import Button from "../elements/Button"
interface AbilitySelectorProps {
@@ -18,7 +16,6 @@ const AbilitySelector: React.FC<AbilitySelectorProps> = ({
setAbilities,
}) => {
const [show, setShow] = useState(false)
const { themeColor } = useContext(MyThemeContext)
return show ? (
<>
<Button

View File

@@ -16,7 +16,7 @@ import { FaInfo } from "react-icons/fa"
import WeaponImage from "../common/WeaponImage"
import { top500 } from "../../assets/imageImports"
import ViewGear from "./ViewGear"
import ViewSlots from "./ViewSlots"
import ViewAP from "./ViewAP"
import MyThemeContext from "../../themeContext"
import { Build } from "../../types"
@@ -98,7 +98,7 @@ const BuildCard: React.FC<BuildCardProps & BoxProps> = ({
</Box>
)}
<Gears build={build} />
{apView ? <ViewAP build={build} /> : <ViewGear build={build} />}
{apView ? <ViewAP build={build} /> : <ViewSlots build={build} />}
<Flex justifyContent="space-between" mt="1em">
{build.description ? (
<Popover placement="top">

View File

@@ -33,7 +33,6 @@ const BuildsPage: React.FC<RouteComponentProps> = () => {
const [prefersAPView, setAPPreference] = useLocalStorage<boolean>(
"prefersAPView"
)
const isSmall = useBreakPoints(870)
const { data, error, loading } = useQuery<
SearchForBuildsData,
@@ -53,7 +52,7 @@ const BuildsPage: React.FC<RouteComponentProps> = () => {
...build.clothing,
...build.shoes,
])
return abilities.every(ability => abilitiesInBuild.has(ability))
return abilities.every(ability => abilitiesInBuild.has(ability as any))
})
return (
@@ -71,6 +70,7 @@ const BuildsPage: React.FC<RouteComponentProps> = () => {
/>
<Box mt="1em">
<WeaponSelector
label="Select a weapon to start viewing builds"
setValue={(weapon: string) => setWeapon(weapon as Weapon)}
autoFocus
/>

View File

@@ -1,22 +1,21 @@
import React from "react"
import { Grid } from "@chakra-ui/core"
import GearImage from "./GearImage"
import { Build } from "../../types"
import { Build, Ability } from "../../types"
import AbilityIcon from "./AbilityIcon"
import Box from "../elements/Box"
interface ViewGearProps {
build: Build
interface ViewSlotsProps {
build: Partial<Build>
}
const MARGINX = "3px"
const ViewGear: React.FC<ViewGearProps> = ({ build }) => {
const ViewSlots: React.FC<ViewSlotsProps> = ({ build }) => {
return (
<>
<Box asFlex alignItems="center" justifyContent="center">
{build.headgear.map((ability, index) => (
<Box mx={MARGINX} key={index}>
{(
build.headgear ??
(["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"] as Ability[])
).map((ability, index) => (
<Box mx="3px" key={index}>
<AbilityIcon
key={index}
ability={ability}
@@ -26,8 +25,11 @@ const ViewGear: React.FC<ViewGearProps> = ({ build }) => {
))}
</Box>
<Box asFlex alignItems="center" justifyContent="center" my="0.5em">
{build.clothing.map((ability, index) => (
<Box mx={MARGINX} key={index}>
{(
build.clothing ??
(["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"] as Ability[])
).map((ability, index) => (
<Box mx="3px" key={index}>
<AbilityIcon
key={index}
ability={ability}
@@ -37,8 +39,11 @@ const ViewGear: React.FC<ViewGearProps> = ({ build }) => {
))}
</Box>
<Box asFlex alignItems="center" justifyContent="center">
{build.shoes.map((ability, index) => (
<Box mx={MARGINX} key={index}>
{(
build.shoes ??
(["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"] as Ability[])
).map((ability, index) => (
<Box mx="3px" key={index}>
<AbilityIcon
key={index}
ability={ability}
@@ -51,4 +56,4 @@ const ViewGear: React.FC<ViewGearProps> = ({ build }) => {
)
}
export default ViewGear
export default ViewSlots

View File

@@ -7,7 +7,7 @@ import Select from "../elements/Select"
interface WeaponSelectorProps {
setValue: (value: any) => void
label?: string
label: string
required?: boolean
autoFocus?: boolean
clearable?: boolean
@@ -38,7 +38,6 @@ const WeaponSelector: React.FC<WeaponSelectorProps> = ({
required={required}
options={weaponSelectOptions}
setValue={setValue}
placeholder="Select weapon"
clearable={clearable}
components={{
IndicatorSeparator: () => null,

View File

@@ -1,26 +1,47 @@
import React, { useContext } from "react"
import { Input as ChakraInput } from "@chakra-ui/core"
import MyThemeContext from "../../themeContext"
import Label from "./Label"
import Box from "./Box"
interface InputProps {
value?: string
setValue: (value: string) => void
placeholder: string
label: string
limit?: number
required?: boolean
}
const Input: React.FC<InputProps> = ({ value, placeholder, setValue }) => {
const { themeColorHex } = useContext(MyThemeContext)
const Input: React.FC<InputProps> = ({
value,
setValue,
label,
required,
limit,
}) => {
const { themeColorHex, grayWithShade } = useContext(MyThemeContext)
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) =>
setValue(event.target.value)
return (
<ChakraInput
value={value ?? ""}
onChange={handleChange}
placeholder={placeholder}
focusBorderColor={themeColorHex}
/>
<>
{label && <Label required={required}>{label}</Label>}
<ChakraInput
value={value ?? ""}
onChange={handleChange}
focusBorderColor={themeColorHex}
/>
{limit && (
<Box
as="span"
color={(value ?? "").length > limit ? "red.500" : grayWithShade}
>
{(value ?? "").length}/{limit}
</Box>
)}
</>
)
}

View File

@@ -1,5 +1,4 @@
import React, { useContext } from "react"
import Button from "./Button"
import Box from "./Box"
import MyThemeContext from "../../themeContext"
import useBreakPoints from "../../hooks/useBreakPoints"
@@ -21,11 +20,11 @@ const Modal: React.FC<ModalProps> = ({ children, title }) => {
width="100%"
height="100%"
overflow="auto"
bg="rgba(0,0,0,0.4)"
bg="rgba(0,0,0,0.75)"
>
<Box
bg={darkerBgColor}
margin="15% auto"
margin="5% auto"
padding="20px"
border="1px solid #888"
borderRadius="5px"

View File

@@ -22,9 +22,8 @@ interface SelectProps {
label: string
value: string
}>
placeholder: string
width?: string
label?: string
label: string
required?: boolean
setValue: (value: any) => void
autoFocus?: boolean
@@ -40,7 +39,6 @@ interface SelectProps {
const Select: React.FC<SelectProps> = ({
options,
components,
placeholder,
setValue,
clearable,
autoFocus,
@@ -66,7 +64,7 @@ const Select: React.FC<SelectProps> = ({
className="basic-single"
classNamePrefix="select"
onChange={handleChange}
placeholder={placeholder}
placeholder={null}
isSearchable
isClearable={clearable}
options={options}

View File

@@ -0,0 +1,64 @@
import React from "react"
import { abilitiesGameOrder } from "../../utils/lists"
import AbilityIcon from "../builds/AbilityIcon"
import Box from "../elements/Box"
import Label from "../elements/Label"
import { Ability } from "../../types"
interface AbilityButtonsProps {
onClick: (ability: Ability) => void
}
const AbilityButtons: React.FC<AbilityButtonsProps> = ({ onClick }) => {
return (
<>
<Box my="1em" textAlign="center">
<Label>Main abilities (click to select)</Label>
</Box>
<Box
asFlex
flexWrap="wrap"
justifyContent="center"
maxW="340px"
mx="auto"
>
{abilitiesGameOrder
.slice(14, abilitiesGameOrder.length)
.map(ability => (
<Box
m="0.3em"
key={ability}
cursor="pointer"
onClick={() => onClick(ability)}
>
<AbilityIcon ability={ability} size="SUB" />
</Box>
))}
</Box>
<Box my="1em" textAlign="center">
<Label>Sub abilities</Label>
</Box>
<Box
asFlex
flexWrap="wrap"
justifyContent="center"
maxW="350px"
mx="auto"
>
{abilitiesGameOrder.slice(0, 14).map(ability => (
<Box
m="0.3em"
key={ability}
cursor="pointer"
onClick={() => onClick(ability)}
>
{ability === "OG" && <br />}
<AbilityIcon ability={ability} size="SUB" />
</Box>
))}
</Box>
</>
)
}
export default AbilityButtons

View File

@@ -1,25 +1,114 @@
import React, { useState } from "react"
import Modal from "../elements/Modal"
import WeaponSelector from "../common/WeaponSelector"
import { Weapon, Build, HeadGear, ClothingGear, ShoesGear } from "../../types"
import {
Weapon,
Build,
HeadGear,
ClothingGear,
ShoesGear,
Ability,
HeadOnlyAbility,
ClothingOnlyAbility,
ShoesOnlyAbility,
StackableAbility,
} from "../../types"
import WeaponImage from "../common/WeaponImage"
import Select from "../elements/Select"
import {
headSelectOptions,
clothingSelectOptions,
shoesSelectOptions,
headOnlyAbilities,
clothingOnlyAbilities,
shoesOnlyAbilities,
headGear,
} from "../../utils/lists"
import Box from "../elements/Box"
import GearImage from "../builds/GearImage"
import Input from "../elements/Input"
import ViewSlots from "../builds/ViewSlots"
import AbilityButtons from "./AbilityButtons"
interface BuildFormModalProps {}
interface BuildFormModalProps {
existingGear: ExistingGearObject
}
const BuildFormModal: React.FC<BuildFormModalProps> = ({}) => {
const [build, setBuild] = useState<Partial<Build>>({})
type ExistingGearObject = Record<
Partial<HeadGear | ClothingGear | ShoesGear>,
Ability[]
>
const BuildFormModal: React.FC<BuildFormModalProps> = ({ existingGear }) => {
const [build, setBuild] = useState<Partial<Build>>({
headgear: ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
clothing: ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
shoes: ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
})
const handleChange = (value: Object) => setBuild({ ...build, ...value })
const handleAbilityClick = (ability: Ability) => {
if (headOnlyAbilities.indexOf(ability as any) !== -1) {
if (build.headgear![0] === "UNKNOWN") {
handleChange({
headgear: [
ability,
build.headgear![1],
build.headgear![2],
build.headgear![3],
],
})
}
} else if (clothingOnlyAbilities.indexOf(ability as any) !== -1) {
if (build.clothing![0] === "UNKNOWN") {
handleChange({
clothing: [
ability,
build.clothing![1],
build.clothing![2],
build.clothing![3],
],
})
}
} else if (shoesOnlyAbilities.indexOf(ability as any) !== -1) {
if (build.shoes![0] === "UNKNOWN") {
handleChange({
shoes: [ability, build.shoes![1], build.shoes![2], build.shoes![3]],
})
}
} else {
const headI = build.headgear!.indexOf("UNKNOWN")
if (headI !== -1) {
const copy = build.headgear!.slice()
copy[headI] = ability as HeadOnlyAbility | StackableAbility
handleChange({
headgear: copy,
})
return
}
const clothingI = build.clothing!.indexOf("UNKNOWN")
if (clothingI !== -1) {
const copy = build.clothing!.slice()
copy[clothingI] = ability as ClothingOnlyAbility | StackableAbility
handleChange({
clothing: copy,
})
return
}
const shoesI = build.shoes!.indexOf("UNKNOWN")
if (shoesI !== -1) {
const copy = build.shoes!.slice()
copy[shoesI] = ability as ShoesOnlyAbility | StackableAbility
handleChange({
shoes: copy,
})
}
}
}
return (
<Modal title="Adding a new build">
<WeaponSelector
@@ -33,39 +122,81 @@ const BuildFormModal: React.FC<BuildFormModalProps> = ({}) => {
<Box asFlex mt="1em" justifyContent="space-between" flexWrap="wrap">
<Box asFlex flexDirection="column" alignItems="center">
<Select
placeholder="Select headgear"
label="Headgear"
options={headSelectOptions}
clearable
setValue={(headgearItem: HeadGear) =>
setBuild({ ...build, headgearItem })
}
setValue={(headgearItem: HeadGear) => {
if (
(!build.headgear ||
build.headgear.every(ability => ability === "UNKNOWN")) &&
existingGear.hasOwnProperty(headgearItem)
) {
handleChange({
headgearItem,
headgear: existingGear[headgearItem],
})
} else {
handleChange({ headgearItem })
}
}}
/>
{build.headgearItem && <GearImage englishName={build.headgearItem} />}
{build.headgearItem && (
<Box mt="0.5em">
<GearImage englishName={build.headgearItem} />
</Box>
)}
</Box>
<Box asFlex flexDirection="column" alignItems="center">
<Select
placeholder="Select headgear"
label="Clothing"
options={clothingSelectOptions}
clearable
setValue={(clothingItem: ClothingGear) =>
setBuild({ ...build, clothingItem })
}
setValue={(clothingItem: ClothingGear) => {
if (
(!build.clothing ||
build.clothing.every(ability => ability === "UNKNOWN")) &&
existingGear.hasOwnProperty(clothingItem)
) {
handleChange({
clothingItem,
clothing: existingGear[clothingItem],
})
} else {
handleChange({ clothingItem })
}
}}
/>
{build.clothingItem && <GearImage englishName={build.clothingItem} />}
{build.clothingItem && (
<Box mt="0.5em">
<GearImage englishName={build.clothingItem} />
</Box>
)}
</Box>
<Box asFlex flexDirection="column" alignItems="center">
<Select
placeholder="Select headgear"
label="Shoes"
options={shoesSelectOptions}
clearable
setValue={(shoesItem: ShoesGear) =>
setBuild({ ...build, shoesItem })
}
setValue={(shoesItem: ShoesGear) => {
if (
(!build.shoes ||
build.shoes.every(ability => ability === "UNKNOWN")) &&
existingGear.hasOwnProperty(shoesItem)
) {
handleChange({
shoesItem,
shoes: existingGear[shoesItem],
})
} else {
handleChange({ shoesItem })
}
}}
/>
{build.shoesItem && <GearImage englishName={build.shoesItem} />}
{build.shoesItem && (
<Box mt="0.5em">
<GearImage englishName={build.shoesItem} />
</Box>
)}
</Box>
</Box>
<Box mt="1em">
@@ -73,8 +204,16 @@ const BuildFormModal: React.FC<BuildFormModalProps> = ({}) => {
value={build.title}
setValue={(value: string) => handleChange({ title: value })}
placeholder="Title"
label="Title"
limit={100}
/>
</Box>
<Box mt="1em">
<ViewSlots build={build} />
</Box>
<Box mt="1em">
<AbilityButtons onClick={ability => handleAbilityClick(ability)} />
</Box>
</Modal>
)
}

View File

@@ -1,24 +1,48 @@
import React from "react"
import { Build } from "../../types"
import { Flex } from "@chakra-ui/core"
import BuildCard from "../builds/BuildCard"
import useLocalStorage from "@rehooks/local-storage"
import Button from "../elements/Button"
import { useState } from "react"
import Alert from "../elements/Alert"
import { HeadGear, ClothingGear, ShoesGear, Ability } from "../../types"
import BuildFormModal from "./BuildFormModal"
import Button from "../elements/Button"
import Alert from "../elements/Alert"
import Box from "../elements/Box"
import BuildCard from "../builds/BuildCard"
interface BuildTabProps {
builds: Build[]
canModifyBuilds: boolean
}
type ExistingGearObject = Record<
Partial<HeadGear | ClothingGear | ShoesGear>,
Ability[]
>
const buildsReducer = (acc: ExistingGearObject, cur: Build) => {
if (cur.headgearItem) {
acc[cur.headgearItem] = [...cur.headgear]
}
if (cur.clothingItem) {
acc[cur.clothingItem] = [...cur.clothing]
}
if (cur.shoesItem) {
acc[cur.shoesItem] = [...cur.shoes]
}
return acc
}
const BuildTab: React.FC<BuildTabProps> = ({ builds, canModifyBuilds }) => {
const [APView] = useLocalStorage<boolean>("prefersAPView")
const [formOpen, setFormOpen] = useState(true)
const existingGear = builds
? builds.reduce(buildsReducer, {} as ExistingGearObject)
: ({} as ExistingGearObject)
return (
<>
{formOpen && <BuildFormModal />}
{formOpen && <BuildFormModal existingGear={existingGear} />}
{canModifyBuilds && builds.length < 100 && (
<Button onClick={() => setFormOpen(true)}>Add build</Button>
)}
@@ -27,7 +51,7 @@ const BuildTab: React.FC<BuildTabProps> = ({ builds, canModifyBuilds }) => {
You already have 100 builds. Please delete one before adding a new one
</Alert>
)}
<Flex flexWrap="wrap" justifyContent="center" mt="1em">
<Box asFlex flexWrap="wrap" justifyContent="center" mt="1em">
{builds.map(build => (
<BuildCard
key={build.id}
@@ -36,7 +60,7 @@ const BuildTab: React.FC<BuildTabProps> = ({ builds, canModifyBuilds }) => {
m="0.5em"
/>
))}
</Flex>
</Box>
</>
)
}

View File

@@ -25,7 +25,9 @@ export type ThemeColor = ElementType<typeof themeColors>
export type HeadOnlyAbility = ElementType<typeof headOnlyAbilities>
export type ClothingOnlyAbility = ElementType<typeof clothingOnlyAbilities>
export type ShoesOnlyAbility = ElementType<typeof shoesOnlyAbilities>
export type StackableAbility = ElementType<typeof stackableAbilities>
export type StackableAbility =
| ElementType<typeof stackableAbilities>
| "UNKNOWN"
export type Ability =
| HeadOnlyAbility
| ClothingOnlyAbility
@@ -90,7 +92,7 @@ export interface Build {
HeadOnlyAbility | StackableAbility,
StackableAbility,
StackableAbility,
StackableAbility
StackableAbility | "UNKNOWN"
]
clothing: [
ClothingOnlyAbility | StackableAbility,
@@ -102,7 +104,7 @@ export interface Build {
ShoesOnlyAbility | StackableAbility,
StackableAbility,
StackableAbility,
StackableAbility
StackableAbility | "UNKNOWN"
]
headgearItem?: HeadGear
clothingItem?: ClothingGear

View File

@@ -1,6 +1,4 @@
import { Weapon } from "../types"
import { wpnSmall } from "../assets/imageImports"
import english_internal from "./english_internal.json"
export const headOnlyAbilities = ["CB", "LDE", "OG", "T"] as const
export const clothingOnlyAbilities = ["H", "NS", "TI", "RP", "AD"] as const