mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-07-31 08:06:51 -05:00
analyzer graph view
This commit is contained in:
parent
33d0fb5766
commit
79aba396f8
6
frontend-react/package-lock.json
generated
6
frontend-react/package-lock.json
generated
|
|
@ -2168,9 +2168,9 @@
|
|||
}
|
||||
},
|
||||
"@testing-library/user-event": {
|
||||
"version": "10.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-10.3.4.tgz",
|
||||
"integrity": "sha512-ZuqR2cBAOArvBdPiIjmAKaZdFAtZSH6Oz2I5+uHonMXRyR0uTtz2fTWyPuiu4JATdK7gLIq1s02BMJGWgmh1lA==",
|
||||
"version": "10.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-10.3.5.tgz",
|
||||
"integrity": "sha512-9Jg5pg12099EoOb4Z/+UNtCFpFIblD6INkgeWuV/AjUiYP+s/u6aYGPaLrUYioJ8duQ3V421B29GVE/3JELjCQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.9.6"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"@sendou/react-sketch": "^0.5.2",
|
||||
"@testing-library/jest-dom": "^5.8.0",
|
||||
"@testing-library/react": "^10.0.4",
|
||||
"@testing-library/user-event": "^10.3.4",
|
||||
"@testing-library/user-event": "^10.3.5",
|
||||
"@types/jest": "^25.2.3",
|
||||
"@types/reach__router": "^1.3.5",
|
||||
"@types/react": "^16.9.35",
|
||||
|
|
|
|||
|
|
@ -7,19 +7,26 @@ import { Build } from "../../types"
|
|||
import PageHeader from "../common/PageHeader"
|
||||
import WeaponSelector from "../common/WeaponSelector"
|
||||
import BuildStats from "./BuildStats"
|
||||
import EditableBuild from "./EditableBuild"
|
||||
import EditableBuilds from "./EditableBuilds"
|
||||
import MyThemeContext from "../../themeContext"
|
||||
|
||||
const defaultBuild: Partial<Build> = {
|
||||
weapon: "Splattershot Jr.",
|
||||
headgear: ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
|
||||
clothing: ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
|
||||
shoes: ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
|
||||
}
|
||||
|
||||
const BuildAnalyzerPage: React.FC<RouteComponentProps> = () => {
|
||||
const { themeColor } = useContext(MyThemeContext)
|
||||
const [build, setBuild] = useState<Partial<Build>>({
|
||||
weapon: "Splattershot Jr.",
|
||||
headgear: ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
|
||||
clothing: ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
|
||||
shoes: ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
|
||||
})
|
||||
const [hideExtra, setHideExtra] = useState(true)
|
||||
const [build, setBuild] = useState<Partial<Build>>(defaultBuild)
|
||||
const [otherBuild, setOtherBuild] = useState<Partial<Build>>(defaultBuild)
|
||||
const [showOther, setShowOther] = useState(false)
|
||||
const [otherFocused, setOtherFocused] = useState(false)
|
||||
const [hideExtra, setHideExtra] = useState(false)
|
||||
|
||||
const explanations = useAbilityEffects(build)
|
||||
const otherExplanations = useAbilityEffects(otherBuild)
|
||||
|
||||
console.log("explanations", explanations)
|
||||
|
||||
|
|
@ -34,11 +41,24 @@ const BuildAnalyzerPage: React.FC<RouteComponentProps> = () => {
|
|||
<WeaponSelector
|
||||
value={build.weapon}
|
||||
label=""
|
||||
setValue={(weapon) => setBuild({ ...build, weapon })}
|
||||
setValue={(weapon) => {
|
||||
setBuild({ ...build, weapon })
|
||||
setOtherBuild({ ...otherBuild, weapon })
|
||||
}}
|
||||
menuIsOpen={!build.weapon}
|
||||
/>
|
||||
</Box>
|
||||
<EditableBuild build={build} setBuild={setBuild} />
|
||||
<EditableBuilds
|
||||
build={build}
|
||||
otherBuild={otherBuild}
|
||||
setBuild={otherFocused ? setOtherBuild : setBuild}
|
||||
showOther={showOther}
|
||||
setShowOther={setShowOther}
|
||||
changeFocus={() => {
|
||||
setOtherFocused(!otherFocused)
|
||||
}}
|
||||
otherFocused={otherFocused}
|
||||
/>
|
||||
<Box my="1em">
|
||||
<FormLabel htmlFor="show-all">Hide stats at base value</FormLabel>
|
||||
<Switch
|
||||
|
|
@ -52,6 +72,7 @@ const BuildAnalyzerPage: React.FC<RouteComponentProps> = () => {
|
|||
<BuildStats
|
||||
build={build}
|
||||
explanations={explanations}
|
||||
otherExplanations={showOther ? otherExplanations : undefined}
|
||||
hideExtra={hideExtra}
|
||||
/>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,44 @@
|
|||
import React, { useContext } from "react"
|
||||
import React, { useContext, useState } from "react"
|
||||
import { Explanation } from "../../hooks/useAbilityEffects"
|
||||
import MyThemeContext from "../../themeContext"
|
||||
import { Ability, Build } from "../../types"
|
||||
import { mainOnlyAbilities } from "../../utils/lists"
|
||||
import { Progress, Box, Flex } from "@chakra-ui/core"
|
||||
import {
|
||||
Progress,
|
||||
Box,
|
||||
Flex,
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
} from "@chakra-ui/core"
|
||||
import AbilityIcon from "../builds/AbilityIcon"
|
||||
import {
|
||||
LineChart,
|
||||
CartesianGrid,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Legend,
|
||||
Tooltip,
|
||||
Line,
|
||||
ResponsiveContainer,
|
||||
} from "recharts"
|
||||
import IconButton from "../elements/IconButton"
|
||||
import { FaChartLine, FaInfo, FaQuestion } from "react-icons/fa"
|
||||
|
||||
interface BuildStatsProps {
|
||||
explanations: Explanation[]
|
||||
otherExplanations?: Explanation[]
|
||||
build: Partial<Build>
|
||||
hideExtra: boolean
|
||||
}
|
||||
|
||||
const BuildStats: React.FC<BuildStatsProps> = ({
|
||||
explanations,
|
||||
otherExplanations,
|
||||
build,
|
||||
hideExtra,
|
||||
}) => {
|
||||
const { themeColor, themeColorWithShade } = useContext(MyThemeContext)
|
||||
const { colorMode, themeColorWithShade } = useContext(MyThemeContext)
|
||||
const abilityArrays: Ability[][] = [
|
||||
build.headgear ?? [],
|
||||
build.clothing ?? [],
|
||||
|
|
@ -39,11 +60,37 @@ const BuildStats: React.FC<BuildStatsProps> = ({
|
|||
)
|
||||
|
||||
const BuildStat: React.FC<{
|
||||
title: String
|
||||
title: string
|
||||
effect: string
|
||||
otherEffect?: string
|
||||
ability: Ability
|
||||
info?: string
|
||||
progressBarValue: number
|
||||
}> = ({ title, effect, ability, progressBarValue = 0 }) => {
|
||||
otherProgressBarValue?: number
|
||||
getEffect?: (ap: number) => number
|
||||
}> = ({
|
||||
title,
|
||||
effect,
|
||||
ability,
|
||||
otherEffect,
|
||||
otherProgressBarValue,
|
||||
getEffect,
|
||||
info,
|
||||
progressBarValue = 0,
|
||||
}) => {
|
||||
const [showChart, setShowChart] = useState(false)
|
||||
const { themeColorHex, darkerBgColor, themeColorWithShade } = useContext(
|
||||
MyThemeContext
|
||||
)
|
||||
|
||||
const getData = () => {
|
||||
const toReturn = []
|
||||
for (let i = 0; i < 58; i++) {
|
||||
toReturn.push({ name: `${i}AP`, [title]: getEffect!(i) })
|
||||
}
|
||||
|
||||
return toReturn
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Flex justifyContent="space-between">
|
||||
|
|
@ -51,43 +98,136 @@ const BuildStats: React.FC<BuildStatsProps> = ({
|
|||
<Box mr="0.5em">
|
||||
<AbilityIcon ability={ability} size="TINY" />
|
||||
</Box>
|
||||
<IconButton
|
||||
icon={FaChartLine}
|
||||
onClick={() => setShowChart(!showChart)}
|
||||
/>
|
||||
{title}
|
||||
{info && (
|
||||
<Popover trigger="hover" placement="top-start">
|
||||
<PopoverTrigger>
|
||||
<Box>
|
||||
<Box
|
||||
color={themeColorWithShade}
|
||||
ml="0.2em"
|
||||
as={FaQuestion}
|
||||
mb="0.2em"
|
||||
/>
|
||||
</Box>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
zIndex={4}
|
||||
p="0.5em"
|
||||
bg={darkerBgColor}
|
||||
border="0"
|
||||
>
|
||||
{info}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</Flex>
|
||||
<Box
|
||||
fontWeight="bold"
|
||||
color={themeColorWithShade}
|
||||
color={`orange.${colorMode === "dark" ? "200" : "500"}`}
|
||||
alignSelf="flex-end"
|
||||
>
|
||||
{effect}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Progress
|
||||
color={themeColor}
|
||||
height="32px"
|
||||
color="orange"
|
||||
height={otherEffect ? "16px" : "32px"}
|
||||
value={progressBarValue}
|
||||
hasStripe
|
||||
isAnimated
|
||||
/>
|
||||
{otherEffect && (
|
||||
<>
|
||||
<Progress
|
||||
color="blue"
|
||||
height="16px"
|
||||
value={otherProgressBarValue}
|
||||
hasStripe
|
||||
isAnimated
|
||||
/>
|
||||
<Flex justifyContent="space-between">
|
||||
<Box />
|
||||
<Box
|
||||
fontWeight="bold"
|
||||
color={`blue.${colorMode === "dark" ? "200" : "500"}`}
|
||||
alignSelf="flex-end"
|
||||
>
|
||||
{otherEffect}
|
||||
</Box>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
{getEffect && showChart && (
|
||||
<Box my="1em" ml="-26px">
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart data={getData()}>
|
||||
<CartesianGrid strokeDasharray="3 3" color="#000" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
background: darkerBgColor,
|
||||
borderRadius: "5px",
|
||||
border: 0,
|
||||
}}
|
||||
/>
|
||||
<Legend />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey={title}
|
||||
stroke={themeColorHex}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const explanationsToUse = !hideExtra
|
||||
/*const explanationsToUse = !hideExtra
|
||||
? explanations
|
||||
: explanations.filter((e) => e.effectFromMax !== 0)
|
||||
: explanations.filter((e) => e.effectFromMax !== 0)*/
|
||||
|
||||
return (
|
||||
<>
|
||||
{explanationsToUse.map((explanation) => (
|
||||
<Box my="1em" key={explanation.title}>
|
||||
<BuildStat
|
||||
title={explanation.title}
|
||||
effect={explanation.effect}
|
||||
ability={explanation.ability}
|
||||
progressBarValue={explanation.effectFromMax}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
{explanations.map((_explanation, index) => {
|
||||
const explanation = explanations[index]
|
||||
const otherExplanation = otherExplanations
|
||||
? otherExplanations[index]
|
||||
: undefined
|
||||
|
||||
if (
|
||||
explanation.effectFromMax === 0 &&
|
||||
(!otherExplanation || otherExplanation.effectFromMax === 0) &&
|
||||
hideExtra
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Box my="1em" key={explanation.title}>
|
||||
<BuildStat
|
||||
title={explanation.title}
|
||||
ability={explanation.ability}
|
||||
effect={explanation.effect}
|
||||
progressBarValue={
|
||||
explanation.effectFromMaxActual ?? explanation.effectFromMax
|
||||
}
|
||||
otherEffect={otherExplanation?.effect}
|
||||
otherProgressBarValue={otherExplanation?.effectFromMax}
|
||||
getEffect={explanation.getEffect}
|
||||
info={explanation.info}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,125 +0,0 @@
|
|||
import React from "react"
|
||||
import AbilityButtons from "../user/AbilityButtons"
|
||||
import ViewSlots from "../builds/ViewSlots"
|
||||
import { Box, Flex } from "@chakra-ui/core"
|
||||
import {
|
||||
Build,
|
||||
Ability,
|
||||
HeadOnlyAbility,
|
||||
ClothingOnlyAbility,
|
||||
ShoesOnlyAbility,
|
||||
StackableAbility,
|
||||
} from "../../types"
|
||||
import {
|
||||
headOnlyAbilities,
|
||||
clothingOnlyAbilities,
|
||||
shoesOnlyAbilities,
|
||||
} from "../../utils/lists"
|
||||
import { Explanation } from "../../hooks/useAbilityEffects"
|
||||
|
||||
interface EditableBuildProps {
|
||||
build: Partial<Build>
|
||||
setBuild: React.Dispatch<React.SetStateAction<Partial<Build>>>
|
||||
}
|
||||
|
||||
const EditableBuild: React.FC<EditableBuildProps> = ({ build, setBuild }) => {
|
||||
const handleChange = (value: Object) => setBuild({ ...build, ...value })
|
||||
|
||||
const handleAbilityButtonClick = (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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleClickBuildAbility = (
|
||||
slot: "HEAD" | "CLOTHING" | "SHOES",
|
||||
index: number
|
||||
) => {
|
||||
if (slot === "HEAD") {
|
||||
const copy = build.headgear!.slice()
|
||||
copy[index] = "UNKNOWN"
|
||||
handleChange({
|
||||
headgear: copy,
|
||||
})
|
||||
} else if (slot === "CLOTHING") {
|
||||
const copy = build.clothing!.slice()
|
||||
copy[index] = "UNKNOWN"
|
||||
handleChange({
|
||||
clothing: copy,
|
||||
})
|
||||
} else {
|
||||
const copy = build.shoes!.slice()
|
||||
copy[index] = "UNKNOWN"
|
||||
handleChange({
|
||||
shoes: copy,
|
||||
})
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<ViewSlots build={build} onAbilityClick={handleClickBuildAbility} />
|
||||
<Box mt="1em">
|
||||
<AbilityButtons
|
||||
onClick={(ability) => handleAbilityButtonClick(ability)}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditableBuild
|
||||
189
frontend-react/src/components/analyzer/EditableBuilds.tsx
Normal file
189
frontend-react/src/components/analyzer/EditableBuilds.tsx
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
import React from "react"
|
||||
import AbilityButtons from "../user/AbilityButtons"
|
||||
import ViewSlots from "../builds/ViewSlots"
|
||||
import { Box, Flex } from "@chakra-ui/core"
|
||||
import {
|
||||
Build,
|
||||
Ability,
|
||||
HeadOnlyAbility,
|
||||
ClothingOnlyAbility,
|
||||
ShoesOnlyAbility,
|
||||
StackableAbility,
|
||||
} from "../../types"
|
||||
import {
|
||||
headOnlyAbilities,
|
||||
clothingOnlyAbilities,
|
||||
shoesOnlyAbilities,
|
||||
} from "../../utils/lists"
|
||||
import Button from "../elements/Button"
|
||||
import { FaPlus, FaMinus } from "react-icons/fa"
|
||||
|
||||
interface EditableBuildsProps {
|
||||
build: Partial<Build>
|
||||
otherBuild: Partial<Build>
|
||||
setBuild: React.Dispatch<React.SetStateAction<Partial<Build>>>
|
||||
showOther: boolean
|
||||
setShowOther: React.Dispatch<React.SetStateAction<boolean>>
|
||||
otherFocused: boolean
|
||||
changeFocus: () => void
|
||||
}
|
||||
|
||||
const EditableBuilds: React.FC<EditableBuildsProps> = ({
|
||||
build,
|
||||
otherBuild,
|
||||
setBuild,
|
||||
showOther,
|
||||
setShowOther,
|
||||
otherFocused,
|
||||
changeFocus,
|
||||
}) => {
|
||||
const buildToEdit = otherFocused ? otherBuild : build
|
||||
const handleChange = (value: Object) => setBuild({ ...buildToEdit, ...value })
|
||||
|
||||
const handleAbilityButtonClick = (ability: Ability) => {
|
||||
if (headOnlyAbilities.indexOf(ability as any) !== -1) {
|
||||
if (buildToEdit.headgear![0] === "UNKNOWN") {
|
||||
handleChange({
|
||||
headgear: [
|
||||
ability,
|
||||
buildToEdit.headgear![1],
|
||||
buildToEdit.headgear![2],
|
||||
buildToEdit.headgear![3],
|
||||
],
|
||||
})
|
||||
}
|
||||
} else if (clothingOnlyAbilities.indexOf(ability as any) !== -1) {
|
||||
if (buildToEdit.clothing![0] === "UNKNOWN") {
|
||||
handleChange({
|
||||
clothing: [
|
||||
ability,
|
||||
buildToEdit.clothing![1],
|
||||
buildToEdit.clothing![2],
|
||||
buildToEdit.clothing![3],
|
||||
],
|
||||
})
|
||||
}
|
||||
} else if (shoesOnlyAbilities.indexOf(ability as any) !== -1) {
|
||||
if (buildToEdit.shoes![0] === "UNKNOWN") {
|
||||
handleChange({
|
||||
shoes: [
|
||||
ability,
|
||||
buildToEdit.shoes![1],
|
||||
buildToEdit.shoes![2],
|
||||
buildToEdit.shoes![3],
|
||||
],
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const headI = buildToEdit.headgear!.indexOf("UNKNOWN")
|
||||
if (headI !== -1) {
|
||||
const copy = buildToEdit.headgear!.slice()
|
||||
copy[headI] = ability as HeadOnlyAbility | StackableAbility
|
||||
handleChange({
|
||||
headgear: copy,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const clothingI = buildToEdit.clothing!.indexOf("UNKNOWN")
|
||||
if (clothingI !== -1) {
|
||||
const copy = buildToEdit.clothing!.slice()
|
||||
copy[clothingI] = ability as ClothingOnlyAbility | StackableAbility
|
||||
handleChange({
|
||||
clothing: copy,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const shoesI = buildToEdit.shoes!.indexOf("UNKNOWN")
|
||||
if (shoesI !== -1) {
|
||||
const copy = buildToEdit.shoes!.slice()
|
||||
copy[shoesI] = ability as ShoesOnlyAbility | StackableAbility
|
||||
handleChange({
|
||||
shoes: copy,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleClickBuildAbility = (
|
||||
slot: "HEAD" | "CLOTHING" | "SHOES",
|
||||
index: number
|
||||
) => {
|
||||
if (slot === "HEAD") {
|
||||
const copy = buildToEdit.headgear!.slice()
|
||||
copy[index] = "UNKNOWN"
|
||||
handleChange({
|
||||
headgear: copy,
|
||||
})
|
||||
} else if (slot === "CLOTHING") {
|
||||
const copy = buildToEdit.clothing!.slice()
|
||||
copy[index] = "UNKNOWN"
|
||||
handleChange({
|
||||
clothing: copy,
|
||||
})
|
||||
} else {
|
||||
const copy = buildToEdit.shoes!.slice()
|
||||
copy[index] = "UNKNOWN"
|
||||
handleChange({
|
||||
shoes: copy,
|
||||
})
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Flex justifyContent="space-evenly" flexWrap="wrap" mb="1em">
|
||||
<Flex flexDirection="column">
|
||||
<ViewSlots
|
||||
build={build}
|
||||
onAbilityClick={!otherFocused ? handleClickBuildAbility : undefined}
|
||||
m="1em"
|
||||
cursor={!otherFocused ? undefined : "not-allowed"}
|
||||
/>
|
||||
{showOther && (
|
||||
<Button
|
||||
disabled={!otherFocused}
|
||||
color="orange"
|
||||
onClick={() => changeFocus()}
|
||||
>
|
||||
{!otherFocused ? "Editing" : "Edit"}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
{showOther && (
|
||||
<Flex flexDirection="column">
|
||||
<ViewSlots
|
||||
build={otherBuild}
|
||||
onAbilityClick={
|
||||
otherFocused ? handleClickBuildAbility : undefined
|
||||
}
|
||||
m="1em"
|
||||
cursor={otherFocused ? undefined : "not-allowed"}
|
||||
/>
|
||||
<Button
|
||||
disabled={otherFocused}
|
||||
color="blue"
|
||||
onClick={() => changeFocus()}
|
||||
>
|
||||
{otherFocused ? "Editing" : "Edit"}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<Button
|
||||
icon={showOther ? FaMinus : FaPlus}
|
||||
onClick={() => setShowOther(!showOther)}
|
||||
my="1em"
|
||||
>
|
||||
{showOther ? "Stop comparing" : "Compare"}
|
||||
</Button>
|
||||
<Box mt="1em">
|
||||
<AbilityButtons
|
||||
onClick={(ability) => handleAbilityButtonClick(ability)}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditableBuilds
|
||||
|
|
@ -1,16 +1,20 @@
|
|||
import React from "react"
|
||||
import { Build, Ability } from "../../types"
|
||||
import AbilityIcon from "./AbilityIcon"
|
||||
import { Flex, Box } from "@chakra-ui/core"
|
||||
import { Flex, Box, BoxProps } from "@chakra-ui/core"
|
||||
|
||||
interface ViewSlotsProps {
|
||||
build: Partial<Build>
|
||||
onAbilityClick?: (gear: "HEAD" | "CLOTHING" | "SHOES", index: number) => void
|
||||
}
|
||||
|
||||
const ViewSlots: React.FC<ViewSlotsProps> = ({ build, onAbilityClick }) => {
|
||||
const ViewSlots: React.FC<ViewSlotsProps & BoxProps> = ({
|
||||
build,
|
||||
onAbilityClick,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<Box {...props}>
|
||||
<Flex alignItems="center" justifyContent="center">
|
||||
{(
|
||||
build.headgear ??
|
||||
|
|
@ -76,7 +80,7 @@ const ViewSlots: React.FC<ViewSlotsProps> = ({ build, onAbilityClick }) => {
|
|||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ interface ButtonProps {
|
|||
size?: "xs" | "sm" | "lg" | "md"
|
||||
icon?: IconType
|
||||
width?: string
|
||||
color?: string
|
||||
outlined?: boolean
|
||||
disabled?: boolean
|
||||
loading?: boolean
|
||||
|
|
@ -26,6 +27,7 @@ const Button: React.FC<ButtonProps & ChakraButtonProps> = ({
|
|||
disabled,
|
||||
loading,
|
||||
width,
|
||||
color,
|
||||
outlined = false,
|
||||
...props
|
||||
}) => {
|
||||
|
|
@ -33,7 +35,7 @@ const Button: React.FC<ButtonProps & ChakraButtonProps> = ({
|
|||
return (
|
||||
<ChakraButton
|
||||
variant={outlined ? "outline" : "solid"}
|
||||
variantColor={themeColor}
|
||||
variantColor={color ?? themeColor}
|
||||
leftIcon={icon}
|
||||
onClick={onClick}
|
||||
size={size}
|
||||
|
|
|
|||
|
|
@ -8,33 +8,10 @@ export interface Explanation {
|
|||
title: string
|
||||
effect: string
|
||||
effectFromMax: number
|
||||
effectFromMaxActual?: number
|
||||
ability: Ability
|
||||
info?: string
|
||||
}
|
||||
|
||||
interface WeaponDataFromJson {
|
||||
InkSaverLv?: "Middle" | "High" | string
|
||||
InkSaverType?: "A" | "B" | "C" | "D" | string
|
||||
Sub?: string
|
||||
Special?: string
|
||||
mInkConsume?: number
|
||||
mInkConsumeRepeat?: number
|
||||
mFullChargeInkConsume?: number
|
||||
mMinChargeInkConsume?: number
|
||||
mInkConsumeSplashJump?: number
|
||||
mInkConsumeSplashStand?: number
|
||||
mSideStepInkConsume?: number
|
||||
mInkConsumeUmbrella?: number
|
||||
ShotMoveVelType?: "A" | "B" | "C" | "D" | "E" | string
|
||||
MoveVelLv?: "Low" | "Middle" | "High" | string
|
||||
SpecialCost?: number
|
||||
//mBurst_PaintR?: number
|
||||
//mBurst_PaintRMid?: number
|
||||
//mBurst_PaintRHigh?: number
|
||||
//mTargetInCircleRadius?: number
|
||||
//mTargetInCircleRadiusMid?: number
|
||||
//mTargetInCircleRadiusHigh?: number
|
||||
//mPaintGauge_SpecialFrm?: number
|
||||
getEffect?: (ap: number) => number
|
||||
}
|
||||
|
||||
function buildToAP(build: Partial<Build>) {
|
||||
|
|
@ -119,7 +96,12 @@ export default function useAbilityEffects(build: Partial<Build>) {
|
|||
title,
|
||||
effect: `${parseFloat((1 / (mInkConsume * effect[0])).toFixed(2))}`,
|
||||
effectFromMax: effect[1],
|
||||
effectFromMaxActual: (getEffect(highMidLow, 57)[0] / effect[0]) * 100,
|
||||
ability: "ISM" as Ability,
|
||||
getEffect: (ap: number) =>
|
||||
parseFloat(
|
||||
(1 / (mInkConsume * getEffect(highMidLow, ap)[0])).toFixed(2)
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +113,12 @@ export default function useAbilityEffects(build: Partial<Build>) {
|
|||
(1 / (mInkConsumeRepeat * effect[0])).toFixed(2)
|
||||
)}`,
|
||||
effectFromMax: effect[1],
|
||||
effectFromMaxActual: (getEffect(highMidLow, 57)[0] / effect[0]) * 100,
|
||||
ability: "ISM" as Ability,
|
||||
getEffect: (ap: number) =>
|
||||
parseFloat(
|
||||
(1 / (mInkConsumeRepeat * getEffect(highMidLow, ap)[0])).toFixed(2)
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +130,15 @@ export default function useAbilityEffects(build: Partial<Build>) {
|
|||
(1 / (mFullChargeInkConsume * effect[0])).toFixed(2)
|
||||
)}`,
|
||||
effectFromMax: effect[1],
|
||||
effectFromMaxActual: (getEffect(highMidLow, 57)[0] / effect[0]) * 100,
|
||||
ability: "ISM" as Ability,
|
||||
getEffect: (ap: number) =>
|
||||
parseFloat(
|
||||
(
|
||||
1 /
|
||||
(mFullChargeInkConsume * getEffect(highMidLow, ap)[0])
|
||||
).toFixed(2)
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +150,14 @@ export default function useAbilityEffects(build: Partial<Build>) {
|
|||
(1 / (mMinChargeInkConsume * effect[0])).toFixed(2)
|
||||
)}`,
|
||||
effectFromMax: effect[1],
|
||||
effectFromMaxActual: (getEffect(highMidLow, 57)[0] / effect[0]) * 100,
|
||||
ability: "ISM" as Ability,
|
||||
getEffect: (ap: number) =>
|
||||
parseFloat(
|
||||
(1 / (mMinChargeInkConsume * getEffect(highMidLow, ap)[0])).toFixed(
|
||||
2
|
||||
)
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -172,7 +174,15 @@ export default function useAbilityEffects(build: Partial<Build>) {
|
|||
(1 / (mInkConsumeSplashJump * effect[0])).toFixed(2)
|
||||
)}`,
|
||||
effectFromMax: effect[1],
|
||||
effectFromMaxActual: (getEffect(highMidLow, 57)[0] / effect[0]) * 100,
|
||||
ability: "ISM" as Ability,
|
||||
getEffect: (ap: number) =>
|
||||
parseFloat(
|
||||
(
|
||||
1 /
|
||||
(mInkConsumeSplashJump * getEffect(highMidLow, ap)[0])
|
||||
).toFixed(2)
|
||||
),
|
||||
})
|
||||
} else if (mInkConsumeSplashJump && mInkConsumeSplashStand) {
|
||||
toReturn.push({
|
||||
|
|
@ -181,7 +191,15 @@ export default function useAbilityEffects(build: Partial<Build>) {
|
|||
(1 / (mInkConsumeSplashStand * effect[0])).toFixed(2)
|
||||
)}`,
|
||||
effectFromMax: effect[1],
|
||||
effectFromMaxActual: (getEffect(highMidLow, 57)[0] / effect[0]) * 100,
|
||||
ability: "ISM" as Ability,
|
||||
getEffect: (ap: number) =>
|
||||
parseFloat(
|
||||
(
|
||||
1 /
|
||||
(mInkConsumeSplashStand * getEffect(highMidLow, ap)[0])
|
||||
).toFixed(2)
|
||||
),
|
||||
})
|
||||
|
||||
toReturn.push({
|
||||
|
|
@ -190,31 +208,57 @@ export default function useAbilityEffects(build: Partial<Build>) {
|
|||
(1 / (mInkConsumeSplashJump * effect[0])).toFixed(2)
|
||||
)}`,
|
||||
effectFromMax: effect[1],
|
||||
effectFromMaxActual: (getEffect(highMidLow, 57)[0] / effect[0]) * 100,
|
||||
ability: "ISM" as Ability,
|
||||
getEffect: (ap: number) =>
|
||||
parseFloat(
|
||||
(
|
||||
1 /
|
||||
(mInkConsumeSplashJump * getEffect(highMidLow, ap)[0])
|
||||
).toFixed(2)
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
const mSideStepInkConsume = buildWeaponData.mSideStepInkConsume
|
||||
if (mSideStepInkConsume) {
|
||||
toReturn.push({
|
||||
title: "Dodge rolls per ink tank",
|
||||
title: "Dodge roll ink consumption",
|
||||
effect: `${parseFloat(
|
||||
(mSideStepInkConsume * effect[0] * 100).toFixed(2)
|
||||
)}% of ink tank`,
|
||||
effectFromMax: effect[1],
|
||||
effectFromMaxActual: parseFloat(
|
||||
(mSideStepInkConsume * effect[0] * 100).toFixed(2)
|
||||
),
|
||||
ability: "ISM" as Ability,
|
||||
getEffect: (ap: number) =>
|
||||
parseFloat(
|
||||
(mSideStepInkConsume * getEffect(highMidLow, ap)[0] * 100).toFixed(
|
||||
2
|
||||
)
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
const mInkConsumeUmbrella = buildWeaponData.mInkConsumeUmbrella
|
||||
if (mInkConsumeUmbrella) {
|
||||
toReturn.push({
|
||||
title: "Brella launch ink consumption",
|
||||
title: "Brella shield launch ink consumption",
|
||||
effect: `${parseFloat(
|
||||
(mInkConsumeUmbrella * effect[0] * 100).toFixed(2)
|
||||
)}% of ink tank`,
|
||||
effectFromMax: effect[1],
|
||||
effectFromMaxActual: parseFloat(
|
||||
(mInkConsumeUmbrella * effect[0] * 100).toFixed(2)
|
||||
),
|
||||
ability: "ISM" as Ability,
|
||||
getEffect: (ap: number) =>
|
||||
parseFloat(
|
||||
(mInkConsumeUmbrella * getEffect(highMidLow, ap)[0] * 100).toFixed(
|
||||
2
|
||||
)
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -246,6 +290,9 @@ export default function useAbilityEffects(build: Partial<Build>) {
|
|||
(effect[0] * inkConsumption * 100).toFixed(2)
|
||||
)}% of ink tank`,
|
||||
effectFromMax: effect[1],
|
||||
effectFromMaxActual: parseFloat(
|
||||
(effect[0] * inkConsumption * 100).toFixed(2)
|
||||
),
|
||||
ability: "ISS" as Ability,
|
||||
},
|
||||
]
|
||||
|
|
@ -279,6 +326,8 @@ export default function useAbilityEffects(build: Partial<Build>) {
|
|||
(Math.ceil(effectSquid[0]) / 60).toFixed(2)
|
||||
)} seconds)`,
|
||||
effectFromMax: effectSquid[1],
|
||||
effectFromMaxActual:
|
||||
(effectSquid[0] / getEffect(highMidLowSquid, 0)[0]) * 100,
|
||||
ability: "REC" as Ability,
|
||||
},
|
||||
/*{
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user