mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-07-16 08:27:05 -05:00
analyzer renders
This commit is contained in:
parent
52c3d98913
commit
0bc4f5b4e5
1
TODO.md
1
TODO.md
|
|
@ -5,6 +5,7 @@
|
|||
- [ ] Build Analyzer
|
||||
- [ ] Import /analyzer
|
||||
- [ ] Builds link to /analyzer
|
||||
- [ ] Builds mode icons
|
||||
- [ ] Script to parse data from Top 500 .json to the DB
|
||||
- [ ] Top 500 Leaderboards
|
||||
- [ ] Localization
|
||||
|
|
|
|||
233
components/analyzer/BuildStats.tsx
Normal file
233
components/analyzer/BuildStats.tsx
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
import {
|
||||
Box,
|
||||
Flex,
|
||||
IconButton,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Progress,
|
||||
useColorMode,
|
||||
} from "@chakra-ui/react";
|
||||
import { Ability } from "@prisma/client";
|
||||
import { ViewSlotsAbilities } from "components/builds/ViewSlots";
|
||||
import AbilityIcon from "components/common/AbilityIcon";
|
||||
import { isMainAbility } from "lib/lists/abilities";
|
||||
import { Explanation } from "lib/useAbilityEffects";
|
||||
import { useMyTheme } from "lib/useMyTheme";
|
||||
import { useState } from "react";
|
||||
import { FaChartLine, FaQuestion } from "react-icons/fa";
|
||||
import StatChart from "./StatChart";
|
||||
|
||||
interface BuildStatsProps {
|
||||
explanations: Explanation[];
|
||||
otherExplanations?: Explanation[];
|
||||
build: ViewSlotsAbilities;
|
||||
hideExtra?: boolean;
|
||||
showNotActualProgress?: boolean;
|
||||
startChartsAtZero?: boolean;
|
||||
}
|
||||
|
||||
const BuildStats: React.FC<BuildStatsProps> = ({
|
||||
explanations,
|
||||
otherExplanations,
|
||||
build,
|
||||
hideExtra = true,
|
||||
showNotActualProgress = false,
|
||||
startChartsAtZero = true,
|
||||
}) => {
|
||||
const [expandedCharts, setExpandedCharts] = useState<Set<string>>(new Set());
|
||||
|
||||
const abilityArrays: (Ability | "UNKNOWN")[][] = [
|
||||
build.headAbilities ?? [],
|
||||
build.clothingAbilities ?? [],
|
||||
build.shoesAbilities ?? [],
|
||||
];
|
||||
|
||||
const abilityToPoints: Partial<Record<Ability, number>> = {};
|
||||
abilityArrays.forEach((arr) =>
|
||||
arr.forEach((ability, index) => {
|
||||
if (ability !== "UNKNOWN") {
|
||||
let abilityPoints = index === 0 ? 10 : 3;
|
||||
if (isMainAbility(ability)) abilityPoints = 999;
|
||||
abilityToPoints[ability] = abilityToPoints.hasOwnProperty(ability)
|
||||
? (abilityToPoints[ability] as any) + abilityPoints
|
||||
: abilityPoints;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const BuildStat: React.FC<{
|
||||
title: string;
|
||||
effect: string;
|
||||
otherEffect?: string;
|
||||
ability: Ability;
|
||||
info?: string;
|
||||
progressBarValue: number;
|
||||
otherProgressBarValue?: number;
|
||||
getEffect?: (ap: number) => number;
|
||||
ap: number;
|
||||
otherAp?: number;
|
||||
chartExpanded: boolean;
|
||||
toggleChart: () => void;
|
||||
}> = ({
|
||||
title,
|
||||
effect,
|
||||
ability,
|
||||
otherEffect,
|
||||
otherProgressBarValue,
|
||||
getEffect,
|
||||
info,
|
||||
ap,
|
||||
otherAp,
|
||||
chartExpanded,
|
||||
toggleChart,
|
||||
progressBarValue = 0,
|
||||
}) => {
|
||||
const { themeColorShade, secondaryBgColor } = useMyTheme();
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex justifyContent="space-between">
|
||||
<Flex fontWeight="bold" mr="1em" mb="0.5em" alignItems="center">
|
||||
<Box minW="30px">
|
||||
<AbilityIcon ability={ability} size="TINY" />
|
||||
</Box>
|
||||
<IconButton
|
||||
aria-label="Show chart for the stat"
|
||||
mx="0.5rem"
|
||||
icon={<FaChartLine />}
|
||||
onClick={() => toggleChart()}
|
||||
isRound
|
||||
variant="ghost"
|
||||
/>
|
||||
{title}
|
||||
{info && (
|
||||
<Popover trigger="hover" placement="top-start">
|
||||
<PopoverTrigger>
|
||||
<Box>
|
||||
<Box
|
||||
color={themeColorShade}
|
||||
ml="0.2em"
|
||||
as={FaQuestion}
|
||||
mb="0.2em"
|
||||
/>
|
||||
</Box>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
zIndex={4}
|
||||
p="0.5em"
|
||||
bg={secondaryBgColor}
|
||||
border="0"
|
||||
>
|
||||
{info}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</Flex>
|
||||
<Box
|
||||
fontWeight="bold"
|
||||
color={`orange.${colorMode === "dark" ? "200" : "500"}`}
|
||||
alignSelf="flex-end"
|
||||
>
|
||||
{effect}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Progress
|
||||
colorScheme="orange"
|
||||
height={otherEffect ? "16px" : "32px"}
|
||||
value={progressBarValue}
|
||||
bg={colorMode === "dark" ? "#464b64" : `orange.100`}
|
||||
/>
|
||||
{otherEffect && (
|
||||
<>
|
||||
<Progress
|
||||
colorScheme="blue"
|
||||
height="16px"
|
||||
value={otherProgressBarValue}
|
||||
bg={colorMode === "dark" ? "#464b64" : `blue.100`}
|
||||
/>
|
||||
<Flex justifyContent="space-between">
|
||||
<Box />
|
||||
<Box
|
||||
fontWeight="bold"
|
||||
color={`blue.${colorMode === "dark" ? "200" : "500"}`}
|
||||
alignSelf="flex-end"
|
||||
>
|
||||
{otherEffect}
|
||||
</Box>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
{getEffect && chartExpanded && (
|
||||
<Box my="1em" ml="-26px">
|
||||
<StatChart
|
||||
title={title}
|
||||
ap={ap}
|
||||
otherAp={otherAp}
|
||||
getEffect={getEffect}
|
||||
startChartsAtZero={startChartsAtZero}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{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={
|
||||
showNotActualProgress
|
||||
? explanation.effectFromMax
|
||||
: explanation.effectFromMaxActual
|
||||
}
|
||||
otherEffect={otherExplanation?.effect}
|
||||
otherProgressBarValue={
|
||||
showNotActualProgress
|
||||
? otherExplanation?.effectFromMax
|
||||
: otherExplanation?.effectFromMaxActual
|
||||
}
|
||||
getEffect={explanation.getEffect}
|
||||
info={explanation.info}
|
||||
ap={explanation.ap}
|
||||
otherAp={otherExplanation?.ap}
|
||||
chartExpanded={expandedCharts.has(explanation.title)}
|
||||
toggleChart={() => {
|
||||
const newSet = new Set(expandedCharts);
|
||||
if (newSet.has(explanation.title)) {
|
||||
newSet.delete(explanation.title);
|
||||
} else {
|
||||
newSet.add(explanation.title);
|
||||
}
|
||||
|
||||
setExpandedCharts(newSet);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BuildStats;
|
||||
174
components/analyzer/EditableBuilds.tsx
Normal file
174
components/analyzer/EditableBuilds.tsx
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
import { Box, Button, Flex, IconButton } from "@chakra-ui/react";
|
||||
import { t } from "@lingui/macro";
|
||||
import ViewSlots, { ViewSlotsAbilities } from "components/builds/ViewSlots";
|
||||
import AbilitiesSelector from "components/u/AbilitiesSelector";
|
||||
import { AbilityOrUnknown } from "lib/types";
|
||||
import { FiCopy, FiEdit, FiSquare } from "react-icons/fi";
|
||||
import HeadOnlyToggle from "./HeadOnlyToggle";
|
||||
import LdeSlider from "./LdeSlider";
|
||||
|
||||
interface EditableBuildsProps {
|
||||
build: Omit<ViewSlotsAbilities, "weapon">;
|
||||
otherBuild: Omit<ViewSlotsAbilities, "weapon">;
|
||||
setBuild: React.Dispatch<
|
||||
React.SetStateAction<Omit<ViewSlotsAbilities, "weapon">>
|
||||
>;
|
||||
showOther: boolean;
|
||||
setShowOther: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
otherFocused: boolean;
|
||||
changeFocus: () => void;
|
||||
bonusAp: Partial<Record<AbilityOrUnknown, boolean>>;
|
||||
setBonusAp: React.Dispatch<
|
||||
React.SetStateAction<Partial<Record<AbilityOrUnknown, boolean>>>
|
||||
>;
|
||||
otherBonusAp: Partial<Record<AbilityOrUnknown, boolean>>;
|
||||
setOtherBonusAp: React.Dispatch<
|
||||
React.SetStateAction<Partial<Record<AbilityOrUnknown, boolean>>>
|
||||
>;
|
||||
lde: number;
|
||||
otherLde: number;
|
||||
setLde: React.Dispatch<React.SetStateAction<number>>;
|
||||
setOtherLde: React.Dispatch<React.SetStateAction<number>>;
|
||||
}
|
||||
|
||||
const EditableBuilds: React.FC<EditableBuildsProps> = ({
|
||||
build,
|
||||
otherBuild,
|
||||
setBuild,
|
||||
showOther,
|
||||
setShowOther,
|
||||
otherFocused,
|
||||
changeFocus,
|
||||
bonusAp,
|
||||
setBonusAp,
|
||||
otherBonusAp,
|
||||
setOtherBonusAp,
|
||||
lde,
|
||||
otherLde,
|
||||
setLde,
|
||||
setOtherLde,
|
||||
}) => {
|
||||
const buildToEdit = otherFocused ? otherBuild : build;
|
||||
const handleChange = (value: Object) =>
|
||||
setBuild({ ...buildToEdit, ...value });
|
||||
|
||||
const handleClickBuildAbility = (
|
||||
slot: "headAbilities" | "clothingAbilities" | "shoesAbilities",
|
||||
index: number
|
||||
) => {
|
||||
const copy = buildToEdit[slot].slice();
|
||||
copy[index] = "UNKNOWN";
|
||||
handleChange({
|
||||
[slot]: copy,
|
||||
});
|
||||
};
|
||||
|
||||
const headAbility = build.headAbilities ? build.headAbilities[0] : "SSU";
|
||||
const otherHeadAbility = otherBuild.headAbilities
|
||||
? otherBuild.headAbilities[0]
|
||||
: "SSU";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
leftIcon={showOther ? <FiSquare /> : <FiCopy />}
|
||||
onClick={() => {
|
||||
if (showOther && otherFocused) {
|
||||
changeFocus();
|
||||
}
|
||||
setShowOther(!showOther);
|
||||
}}
|
||||
mt="1em"
|
||||
mb="2em"
|
||||
>
|
||||
{showOther ? t`Stop comparing` : t`Compare`}
|
||||
</Button>
|
||||
<Flex justifyContent="space-evenly" flexWrap="wrap" mb="1em">
|
||||
<Flex flexDirection="column">
|
||||
{showOther && (
|
||||
<IconButton
|
||||
aria-label="Edit orange build"
|
||||
disabled={!otherFocused}
|
||||
colorScheme="orange"
|
||||
onClick={() => changeFocus()}
|
||||
icon={<FiEdit />}
|
||||
isRound
|
||||
mx="auto"
|
||||
/>
|
||||
)}
|
||||
<ViewSlots
|
||||
abilities={build}
|
||||
onAbilityClick={!otherFocused ? handleClickBuildAbility : undefined}
|
||||
m="1em"
|
||||
cursor={!otherFocused ? undefined : "not-allowed"}
|
||||
/>
|
||||
{["OG", "CB"].includes(headAbility) && (
|
||||
<HeadOnlyToggle
|
||||
ability={headAbility as any}
|
||||
active={bonusAp[headAbility] ?? false}
|
||||
setActive={() =>
|
||||
setBonusAp({
|
||||
...bonusAp,
|
||||
[headAbility]: !bonusAp[headAbility],
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{headAbility === "LDE" && (
|
||||
<LdeSlider
|
||||
value={lde}
|
||||
setValue={(value: number) => setLde(value)}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
{showOther && (
|
||||
<Flex flexDirection="column">
|
||||
<IconButton
|
||||
aria-label="Edit blue build"
|
||||
disabled={otherFocused}
|
||||
colorScheme="blue"
|
||||
onClick={() => changeFocus()}
|
||||
icon={<FiEdit />}
|
||||
isRound
|
||||
mx="auto"
|
||||
/>
|
||||
<ViewSlots
|
||||
abilities={otherBuild}
|
||||
onAbilityClick={
|
||||
otherFocused ? handleClickBuildAbility : undefined
|
||||
}
|
||||
m="1em"
|
||||
cursor={otherFocused ? undefined : "not-allowed"}
|
||||
/>
|
||||
{["OG", "CB"].includes(otherHeadAbility) && (
|
||||
<HeadOnlyToggle
|
||||
ability={otherHeadAbility as any}
|
||||
active={otherBonusAp[otherHeadAbility] ?? false}
|
||||
setActive={() =>
|
||||
setOtherBonusAp({
|
||||
...otherBonusAp,
|
||||
[otherHeadAbility]: !otherBonusAp[otherHeadAbility],
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{otherHeadAbility === "LDE" && (
|
||||
<LdeSlider
|
||||
value={otherLde}
|
||||
setValue={(value: number) => setOtherLde(value)}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<Box mt="1em">
|
||||
<AbilitiesSelector
|
||||
abilities={showOther ? otherBuild : build}
|
||||
setAbilities={(newAbilities) => setBuild(newAbilities)}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditableBuilds;
|
||||
62
components/analyzer/HeadOnlyToggle.tsx
Normal file
62
components/analyzer/HeadOnlyToggle.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { Box, Flex, FormLabel, Switch } from "@chakra-ui/react";
|
||||
import { t } from "@lingui/macro";
|
||||
import AbilityIcon from "components/common/AbilityIcon";
|
||||
import { useMyTheme } from "lib/useMyTheme";
|
||||
import React from "react";
|
||||
|
||||
interface HeadOnlyToggleProps {
|
||||
ability: "OG" | "CB";
|
||||
active: boolean;
|
||||
setActive: () => void;
|
||||
}
|
||||
|
||||
const HeadOnlyToggle: React.FC<HeadOnlyToggleProps> = ({
|
||||
ability,
|
||||
active,
|
||||
setActive,
|
||||
}) => {
|
||||
const { themeColorShade } = useMyTheme();
|
||||
return (
|
||||
<Flex
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
flexDirection="column"
|
||||
mb="1em"
|
||||
>
|
||||
<Box>
|
||||
<Switch
|
||||
id="show-all"
|
||||
color={themeColorShade}
|
||||
isChecked={active}
|
||||
onChange={() => setActive()}
|
||||
mr="0.5em"
|
||||
/>
|
||||
<FormLabel htmlFor="show-all">
|
||||
<AbilityIcon ability={ability} size="TINY" />
|
||||
</FormLabel>
|
||||
</Box>
|
||||
{active && ability === "OG" && (
|
||||
<Box color={themeColorShade} fontWeight="bold" mt="1em">
|
||||
+15{t`AP`}{" "}
|
||||
{["SSU", "RSU", "RES"].map((ability) => (
|
||||
<Box as="span" mx="0.2em" key={ability}>
|
||||
<AbilityIcon ability={ability as any} size="SUBTINY" />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{active && ability === "CB" && (
|
||||
<Box color={themeColorShade} fontWeight="bold" mt="1em">
|
||||
+10{t`AP`}{" "}
|
||||
{["ISM", "ISS", "REC", "RSU", "SSU", "SCU"].map((ability) => (
|
||||
<Box as="span" mx="0.2em" key={ability}>
|
||||
<AbilityIcon ability={ability as any} size="SUBTINY" />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeadOnlyToggle;
|
||||
89
components/analyzer/LdeSlider.tsx
Normal file
89
components/analyzer/LdeSlider.tsx
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import {
|
||||
Box,
|
||||
Flex,
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import AbilityIcon from "components/common/AbilityIcon";
|
||||
import { useMyTheme } from "lib/useMyTheme";
|
||||
|
||||
interface LdeSliderProps {
|
||||
value: number;
|
||||
setValue: (value: number) => void;
|
||||
}
|
||||
|
||||
const LdeSlider: React.FC<LdeSliderProps> = ({ value, setValue }) => {
|
||||
const { themeColorShade, gray } = useMyTheme();
|
||||
const bonusAp = Math.floor((24 / 21) * value);
|
||||
|
||||
const getLdeEffect = () => {
|
||||
if (value === 21)
|
||||
return t`Enemy has reached the 30 point mark OR there is 30 seconds or less on the clock OR it is overtime`;
|
||||
|
||||
const pointMark = 51 - value;
|
||||
if (value > 0)
|
||||
return <Trans>Enemy has reached the {pointMark} point mark</Trans>;
|
||||
return t`Enemy has not reached the 50 point mark or there is more than 30 seconds on the clock`;
|
||||
};
|
||||
return (
|
||||
<Flex
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
flexDirection="column"
|
||||
mb="1em"
|
||||
>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
color={gray}
|
||||
textTransform="uppercase"
|
||||
letterSpacing="wider"
|
||||
lineHeight="1rem"
|
||||
fontWeight="medium"
|
||||
mb={1}
|
||||
>
|
||||
{t`Intensity`}
|
||||
</Text>
|
||||
<NumberInput
|
||||
size="lg"
|
||||
defaultValue={0}
|
||||
min={0}
|
||||
max={21}
|
||||
value={value}
|
||||
onChange={(_, value) => setValue(value)}
|
||||
>
|
||||
<NumberInputField />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
{value > 0 && (
|
||||
<Box color={themeColorShade} fontWeight="bold" mt="1em">
|
||||
+{bonusAp}
|
||||
{t`AP`}{" "}
|
||||
{["ISM", "ISS", "REC"].map((ability) => (
|
||||
<Box as="span" mx="0.2em" key={ability}>
|
||||
<AbilityIcon ability={ability as any} size="SUBTINY" />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
color={gray}
|
||||
fontSize="0.75em"
|
||||
maxW="200px"
|
||||
mt="1em"
|
||||
textAlign="center"
|
||||
>
|
||||
{getLdeEffect()}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default LdeSlider;
|
||||
114
components/analyzer/StatChart.tsx
Normal file
114
components/analyzer/StatChart.tsx
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import { t } from "@lingui/macro";
|
||||
import { abilityPoints } from "lib/lists/abilityPoints";
|
||||
import { useMyTheme } from "lib/useMyTheme";
|
||||
import React from "react";
|
||||
import {
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
Line,
|
||||
LineChart,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
|
||||
interface StatChartProps {
|
||||
title: string;
|
||||
getEffect: (ap: number) => number;
|
||||
ap: number;
|
||||
otherAp?: number;
|
||||
startChartsAtZero: boolean;
|
||||
}
|
||||
|
||||
const StatChart: React.FC<StatChartProps> = ({
|
||||
title,
|
||||
ap,
|
||||
otherAp,
|
||||
getEffect,
|
||||
startChartsAtZero,
|
||||
}) => {
|
||||
const { themeColorHex, secondaryBgColor } = useMyTheme();
|
||||
|
||||
const getData = () =>
|
||||
abilityPoints.map((ap) => ({ name: `${ap}AP`, [title]: getEffect(ap) }));
|
||||
|
||||
const CustomizedDot = (props: any) => {
|
||||
const { cx, cy, payload } = props;
|
||||
|
||||
if (payload.name === `${ap}${t`AP`}`) {
|
||||
return (
|
||||
<svg
|
||||
x={cx - 5}
|
||||
y={cy - 5}
|
||||
width={100}
|
||||
height={100}
|
||||
fill="red"
|
||||
viewBox="0 0 1024 1024"
|
||||
>
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
stroke="white"
|
||||
strokeWidth="18"
|
||||
fill="#DD6B20"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
if (payload.name === `${otherAp}${t`AP`}`) {
|
||||
return (
|
||||
<svg
|
||||
x={cx - 5}
|
||||
y={cy - 5}
|
||||
width={100}
|
||||
height={100}
|
||||
fill="red"
|
||||
viewBox="0 0 1024 1024"
|
||||
>
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
stroke="white"
|
||||
strokeWidth="18"
|
||||
fill="#3182CE"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart data={getData()}>
|
||||
<CartesianGrid strokeDasharray="3 3" color="#000" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis
|
||||
domain={startChartsAtZero ? undefined : ["dataMin", "dataMax"]}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
background: secondaryBgColor,
|
||||
borderRadius: "5px",
|
||||
border: 0,
|
||||
}}
|
||||
/>
|
||||
<Legend />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey={title}
|
||||
stroke={themeColorHex}
|
||||
dot={<CustomizedDot />}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatChart;
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { Box, BoxProps, Flex } from "@chakra-ui/react";
|
||||
import { Ability as DBAbility } from "@prisma/client";
|
||||
import AbilityIcon from "components/common/AbilityIcon";
|
||||
import { AbilityOrUnknown } from "lib/types";
|
||||
|
||||
export type ViewSlotsAbilities = {
|
||||
headAbilities: (DBAbility | "UNKNOWN")[];
|
||||
clothingAbilities: (DBAbility | "UNKNOWN")[];
|
||||
shoesAbilities: (DBAbility | "UNKNOWN")[];
|
||||
headAbilities: AbilityOrUnknown[];
|
||||
clothingAbilities: AbilityOrUnknown[];
|
||||
shoesAbilities: AbilityOrUnknown[];
|
||||
};
|
||||
|
||||
interface ViewSlotsProps {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Box, Divider, Flex } from "@chakra-ui/react";
|
||||
import ViewSlots, { ViewSlotsAbilities } from "components/builds/ViewSlots";
|
||||
import { ViewSlotsAbilities } from "components/builds/ViewSlots";
|
||||
import AbilityIcon from "components/common/AbilityIcon";
|
||||
import { abilities as allAbilities } from "lib/lists/abilities";
|
||||
|
||||
|
|
@ -88,12 +88,6 @@ const AbilitiesSelector: React.FC<Props> = ({ abilities, setAbilities }) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<ViewSlots
|
||||
abilities={abilities}
|
||||
onAbilityClick={(gear, index) =>
|
||||
setAbilities(getModifiedAbilities("UNKNOWN", gear, index))
|
||||
}
|
||||
/>
|
||||
<Flex
|
||||
flexWrap="wrap"
|
||||
justifyContent="center"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import { Ability } from "@prisma/client";
|
||||
import ViewSlots from "components/builds/ViewSlots";
|
||||
import MySelect from "components/common/MySelect";
|
||||
import WeaponSelector from "components/common/WeaponSelector";
|
||||
import { getToastOptions } from "lib/getToastOptions";
|
||||
|
|
@ -176,6 +177,33 @@ const BuildModal: React.FC<Props> = ({ onClose, build }) => {
|
|||
value: shoesAbilities,
|
||||
}) => (
|
||||
<Box mt={4}>
|
||||
<ViewSlots
|
||||
abilities={{
|
||||
headAbilities,
|
||||
clothingAbilities,
|
||||
shoesAbilities,
|
||||
}}
|
||||
onAbilityClick={(gear, index) => {
|
||||
const abilityArrays = {
|
||||
headAbilities,
|
||||
clothingAbilities,
|
||||
shoesAbilities,
|
||||
};
|
||||
|
||||
const onChange = {
|
||||
headAbilities: onHeadChange,
|
||||
clothingAbilities: onClothingChange,
|
||||
shoesAbilities: onShoesChange,
|
||||
};
|
||||
|
||||
const newAbilityArray = {
|
||||
...abilityArrays[gear],
|
||||
};
|
||||
|
||||
newAbilityArray[index] = "UNKNOWN";
|
||||
onChange[gear](newAbilityArray);
|
||||
}}
|
||||
/>
|
||||
<AbilitiesSelector
|
||||
abilities={{
|
||||
headAbilities,
|
||||
|
|
|
|||
|
|
@ -52,3 +52,12 @@ export const isMainAbility = (ability: any) =>
|
|||
|
||||
export const isAbility = (ability: any) =>
|
||||
abilities.some((abilityObject) => abilityObject.code === ability);
|
||||
|
||||
export const isSlotOnlyAbility = (
|
||||
ability: any,
|
||||
slot: "HEAD" | "CLOTHING" | "SHOES"
|
||||
) =>
|
||||
abilities.some(
|
||||
(abilityObject) =>
|
||||
abilityObject.code === ability && abilityObject.type === slot
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,2 +1,6 @@
|
|||
import { Ability } from "@prisma/client";
|
||||
|
||||
export type Unpacked<T> = T extends (infer U)[] ? U : T;
|
||||
export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
||||
|
||||
export type AbilityOrUnknown = Ability | "UNKNOWN";
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ export interface Explanation {
|
|||
}
|
||||
|
||||
interface useAbilityEffectsArgs {
|
||||
weapon: Weapon;
|
||||
buildsAbilities: ViewSlotsAbilities;
|
||||
weapon: string;
|
||||
abilities: ViewSlotsAbilities;
|
||||
bonusAp?: Partial<Record<Ability, boolean>>;
|
||||
lde?: number;
|
||||
}
|
||||
|
|
@ -127,8 +127,8 @@ function buildToAP(
|
|||
}
|
||||
|
||||
export default function useAbilityEffects({
|
||||
weapon,
|
||||
buildsAbilities,
|
||||
weapon: untypedWeapon,
|
||||
abilities: buildsAbilities,
|
||||
bonusAp = {},
|
||||
lde = 0,
|
||||
}: useAbilityEffectsArgs) {
|
||||
|
|
@ -139,6 +139,8 @@ export default function useAbilityEffects({
|
|||
any
|
||||
> = weaponJson;
|
||||
|
||||
const weapon = untypedWeapon as Weapon;
|
||||
|
||||
function calculateISM(amount: number) {
|
||||
const ISM = abilityJson["Ink Saver (Main)"];
|
||||
const buildWeaponData = weaponData[weapon];
|
||||
|
|
@ -1232,7 +1234,7 @@ export default function useAbilityEffects({
|
|||
|
||||
toReturn.push({
|
||||
title: `${subWeaponTranslated} ${t`Quick Super Jump`} ${t`boost`}`,
|
||||
effect: `${Math.floor(effect[0])}${t`abilityPointShort`}`,
|
||||
effect: `${Math.floor(effect[0])}${t`AP`}`,
|
||||
effectFromMax: effect[1],
|
||||
ability: "BRU" as Ability,
|
||||
ap: amount,
|
||||
230
package-lock.json
generated
230
package-lock.json
generated
|
|
@ -1755,6 +1755,21 @@
|
|||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"@types/d3-path": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz",
|
||||
"integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/d3-shape": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.5.tgz",
|
||||
"integrity": "sha512-aPEax03owTAKynoK8ZkmkZEDZvvT4Y5pWgii4Jp4oQt0gH45j6siDl9gNDVC5kl64XHN2goN9jbYoHK88tFAcA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/d3-path": "^1"
|
||||
}
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
|
||||
|
|
@ -1858,6 +1873,16 @@
|
|||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/recharts": {
|
||||
"version": "1.8.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/recharts/-/recharts-1.8.16.tgz",
|
||||
"integrity": "sha512-xBXjOsSJJVP2xGtq/kML4rHGyKxA4x8ZqIU7iwbmWrperpSXzoQ1PWjqf4cBdmcLAWaidDHPJxUVHkZr3R/PEA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/d3-shape": "^1",
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/sinonjs__fake-timers": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz",
|
||||
|
|
@ -3345,6 +3370,11 @@
|
|||
"toggle-selection": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "2.6.12",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
|
||||
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
|
|
@ -3851,6 +3881,73 @@
|
|||
"type": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"d3-array": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
|
||||
"integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
|
||||
},
|
||||
"d3-collection": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz",
|
||||
"integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
|
||||
},
|
||||
"d3-color": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz",
|
||||
"integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q=="
|
||||
},
|
||||
"d3-format": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz",
|
||||
"integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ=="
|
||||
},
|
||||
"d3-interpolate": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz",
|
||||
"integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==",
|
||||
"requires": {
|
||||
"d3-color": "1"
|
||||
}
|
||||
},
|
||||
"d3-path": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
|
||||
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
|
||||
},
|
||||
"d3-scale": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz",
|
||||
"integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==",
|
||||
"requires": {
|
||||
"d3-array": "^1.2.0",
|
||||
"d3-collection": "1",
|
||||
"d3-format": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-time": "1",
|
||||
"d3-time-format": "2"
|
||||
}
|
||||
},
|
||||
"d3-shape": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
|
||||
"integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
|
||||
"requires": {
|
||||
"d3-path": "1"
|
||||
}
|
||||
},
|
||||
"d3-time": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
|
||||
"integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
|
||||
},
|
||||
"d3-time-format": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz",
|
||||
"integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==",
|
||||
"requires": {
|
||||
"d3-time": "1"
|
||||
}
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
|
|
@ -3887,6 +3984,11 @@
|
|||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||
},
|
||||
"decimal.js-light": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
|
||||
},
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
|
|
@ -4023,6 +4125,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"dom-helpers": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
|
||||
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2"
|
||||
}
|
||||
},
|
||||
"dom-serializer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.0.1.tgz",
|
||||
|
|
@ -6204,6 +6314,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
|
||||
},
|
||||
"lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
|
||||
},
|
||||
"lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
|
|
@ -6249,6 +6364,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
||||
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg="
|
||||
},
|
||||
"lodash.throttle": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
|
||||
},
|
||||
"log-symbols": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
|
||||
|
|
@ -6407,6 +6527,11 @@
|
|||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"math-expression-evaluator": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.3.3.tgz",
|
||||
"integrity": "sha512-geKTlqoxnjqHoWqB71h0kchWIC23a3yfwwbZu4E2amjvGLF+fTjCCwBQOHkE0/oHc6KdnSVmMt3QB82KaPmKEA=="
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
|
@ -7360,8 +7485,7 @@
|
|||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
|
||||
"dev": true
|
||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.2.2",
|
||||
|
|
@ -8032,6 +8156,14 @@
|
|||
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
|
||||
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
|
||||
},
|
||||
"raf": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
|
||||
"requires": {
|
||||
"performance-now": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"ramda": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz",
|
||||
|
|
@ -8172,6 +8304,11 @@
|
|||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"react-markdown": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-5.0.3.tgz",
|
||||
|
|
@ -8215,6 +8352,28 @@
|
|||
"tslib": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"react-resize-detector": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-2.3.0.tgz",
|
||||
"integrity": "sha512-oCAddEWWeFWYH5FAcHdBYcZjAw9fMzRUK9sWSx6WvSSOPVRxcHd5zTIGy/mOus+AhN/u6T4TMiWxvq79PywnJQ==",
|
||||
"requires": {
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"resize-observer-polyfill": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"react-smooth": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-1.0.5.tgz",
|
||||
"integrity": "sha512-eW057HT0lFgCKh8ilr0y2JaH2YbNcuEdFpxyg7Gf/qDKk9hqGMyXryZJ8iMGJEuKH0+wxS0ccSsBBB3W8yCn8w==",
|
||||
"requires": {
|
||||
"lodash": "~4.17.4",
|
||||
"prop-types": "^15.6.0",
|
||||
"raf": "^3.4.0",
|
||||
"react-transition-group": "^2.5.0"
|
||||
}
|
||||
},
|
||||
"react-string-replace": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/react-string-replace/-/react-string-replace-0.4.4.tgz",
|
||||
|
|
@ -8233,6 +8392,17 @@
|
|||
"tslib": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"react-transition-group": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
|
||||
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
|
||||
"requires": {
|
||||
"dom-helpers": "^3.4.0",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
|
|
@ -8251,6 +8421,57 @@
|
|||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"recharts": {
|
||||
"version": "1.8.5",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-1.8.5.tgz",
|
||||
"integrity": "sha512-tM9mprJbXVEBxjM7zHsIy6Cc41oO/pVYqyAsOHLxlJrbNBuLs0PHB3iys2M+RqCF0//k8nJtZF6X6swSkWY3tg==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.5",
|
||||
"core-js": "^2.6.10",
|
||||
"d3-interpolate": "^1.3.0",
|
||||
"d3-scale": "^2.1.0",
|
||||
"d3-shape": "^1.2.0",
|
||||
"lodash": "^4.17.5",
|
||||
"prop-types": "^15.6.0",
|
||||
"react-resize-detector": "^2.3.0",
|
||||
"react-smooth": "^1.0.5",
|
||||
"recharts-scale": "^0.4.2",
|
||||
"reduce-css-calc": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"recharts-scale": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.3.tgz",
|
||||
"integrity": "sha512-t8p5sccG9Blm7c1JQK/ak9O8o95WGhNXD7TXg/BW5bYbVlr6eCeRBNpgyigD4p6pSSMehC5nSvBUPj6F68rbFA==",
|
||||
"requires": {
|
||||
"decimal.js-light": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"reduce-css-calc": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz",
|
||||
"integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=",
|
||||
"requires": {
|
||||
"balanced-match": "^0.4.2",
|
||||
"math-expression-evaluator": "^1.2.14",
|
||||
"reduce-function-call": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"balanced-match": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
|
||||
"integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
|
||||
}
|
||||
}
|
||||
},
|
||||
"reduce-function-call": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz",
|
||||
"integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"reflect-metadata": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
||||
|
|
@ -8339,6 +8560,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"resize-observer-polyfill": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
"react-infinite-scroller": "^1.2.4",
|
||||
"react-markdown": "^5.0.3",
|
||||
"react-string-replace": "^0.4.4",
|
||||
"recharts": "^1.8.5",
|
||||
"swr": "^0.3.9",
|
||||
"zod": "^1.11.10"
|
||||
},
|
||||
|
|
@ -53,6 +54,7 @@
|
|||
"@types/node": "^14.14.10",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-infinite-scroller": "^1.2.1",
|
||||
"@types/recharts": "^1.8.16",
|
||||
"cypress": "^6.0.0",
|
||||
"prettier": "^2.2.0",
|
||||
"ts-node": "^9.0.0",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,11 @@ const extendedTheme = extendTheme({
|
|||
colorScheme: "theme",
|
||||
},
|
||||
},
|
||||
Badge: {
|
||||
defaultProps: {
|
||||
colorScheme: "theme",
|
||||
},
|
||||
},
|
||||
Modal: {
|
||||
baseStyle: (props) => ({
|
||||
dialog: {
|
||||
|
|
|
|||
203
pages/analyzer.tsx
Normal file
203
pages/analyzer.tsx
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
FormLabel,
|
||||
Switch,
|
||||
Wrap,
|
||||
} from "@chakra-ui/react";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import BuildStats from "components/analyzer/BuildStats";
|
||||
import EditableBuilds from "components/analyzer/EditableBuilds";
|
||||
import { ViewSlotsAbilities } from "components/builds/ViewSlots";
|
||||
import WeaponSelector from "components/common/WeaponSelector";
|
||||
import { AbilityOrUnknown } from "lib/types";
|
||||
import useAbilityEffects from "lib/useAbilityEffects";
|
||||
import { useMyTheme } from "lib/useMyTheme";
|
||||
import { useState } from "react";
|
||||
import { FiSettings } from "react-icons/fi";
|
||||
|
||||
const CURRENT_PATCH = "5.3.";
|
||||
|
||||
const defaultBuild: ViewSlotsAbilities = {
|
||||
headAbilities: ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
|
||||
clothingAbilities: ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
|
||||
shoesAbilities: ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
|
||||
};
|
||||
|
||||
const BuildAnalyzerPage = () => {
|
||||
const { themeColorShade, gray } = useMyTheme();
|
||||
const [build, setBuild] = useState<ViewSlotsAbilities>({
|
||||
...defaultBuild,
|
||||
});
|
||||
const [otherBuild, setOtherBuild] = useState<ViewSlotsAbilities>({
|
||||
...defaultBuild,
|
||||
});
|
||||
const [weapon, setWeapon] = useState("Splattershot Jr.");
|
||||
const [showOther, setShowOther] = useState(false);
|
||||
const [showNotActualProgress, setShowNotActualProgress] = useState(false);
|
||||
const [startChartsAtZero, setStartChartsAtZero] = useState(true);
|
||||
const [otherFocused, setOtherFocused] = useState(false);
|
||||
const [hideExtra, setHideExtra] = useState(true);
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
|
||||
const [bonusAp, setBonusAp] = useState<
|
||||
Partial<Record<AbilityOrUnknown, boolean>>
|
||||
>({});
|
||||
const [otherBonusAp, setOtherBonusAp] = useState<
|
||||
Partial<Record<AbilityOrUnknown, boolean>>
|
||||
>({});
|
||||
const [lde, setLde] = useState(0);
|
||||
const [otherLde, setOtherLde] = useState(0);
|
||||
|
||||
const explanations = useAbilityEffects({
|
||||
abilities: build,
|
||||
weapon,
|
||||
bonusAp,
|
||||
lde,
|
||||
});
|
||||
const otherExplanations = useAbilityEffects({
|
||||
abilities: otherBuild,
|
||||
weapon,
|
||||
bonusAp: otherBonusAp,
|
||||
lde: otherLde,
|
||||
});
|
||||
|
||||
// function getBuildFromUrl() {
|
||||
// const buildToReturn: AnalyzerBuild = {
|
||||
// ...defaultBuild,
|
||||
// weapon: "Splattershot Jr.",
|
||||
// };
|
||||
// const decoded = new URLSearchParams(location.search);
|
||||
|
||||
// for (const [key, value] of decoded) {
|
||||
// switch (key) {
|
||||
// case "weapon":
|
||||
// buildToReturn.weapon = value;
|
||||
// break;
|
||||
// case "headgear":
|
||||
// case "clothing":
|
||||
// case "shoes":
|
||||
// const abilityKey = key as "headgear" | "clothing" | "shoes";
|
||||
// buildToReturn[abilityKey] = value.split(",") as any;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return buildToReturn;
|
||||
// }
|
||||
|
||||
// useEffect(() => {
|
||||
// const { weapon, ...buildFromUrl } = getBuildFromUrl();
|
||||
|
||||
// setWeapon(weapon);
|
||||
// setBuild(buildFromUrl);
|
||||
// // eslint-disable-next-line
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex justifyContent="space-between">
|
||||
<Badge>
|
||||
<Trans>Patch {CURRENT_PATCH}</Trans>
|
||||
</Badge>
|
||||
<Box color={gray} fontSize="0.75em">
|
||||
<Trans>AP = Ability Point = Mains * 10 + Subs * 3</Trans>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Box my="1em">
|
||||
<WeaponSelector value={weapon} onChange={setWeapon} />
|
||||
</Box>
|
||||
<Wrap justify="space-between">
|
||||
<Box>
|
||||
<Box position="sticky" top={0}>
|
||||
{weapon && (
|
||||
<EditableBuilds
|
||||
build={build}
|
||||
otherBuild={otherBuild}
|
||||
setBuild={otherFocused ? setOtherBuild : setBuild}
|
||||
showOther={showOther}
|
||||
setShowOther={setShowOther}
|
||||
changeFocus={() => {
|
||||
setOtherFocused(!otherFocused);
|
||||
}}
|
||||
otherFocused={otherFocused}
|
||||
bonusAp={bonusAp}
|
||||
setBonusAp={setBonusAp}
|
||||
otherBonusAp={otherBonusAp}
|
||||
setOtherBonusAp={setOtherBonusAp}
|
||||
lde={lde}
|
||||
setLde={setLde}
|
||||
otherLde={otherLde}
|
||||
setOtherLde={setOtherLde}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box>
|
||||
<Button
|
||||
leftIcon={<FiSettings />}
|
||||
onClick={() => setShowSettings(!showSettings)}
|
||||
my="1rem"
|
||||
>
|
||||
{showSettings ? t`Hide settings` : t`Show settings`}
|
||||
</Button>
|
||||
{showSettings && (
|
||||
<Box my="1em">
|
||||
<Switch
|
||||
id="show-all"
|
||||
color="blue"
|
||||
isChecked={hideExtra}
|
||||
onChange={() => setHideExtra(!hideExtra)}
|
||||
mr="0.5em"
|
||||
/>
|
||||
<FormLabel htmlFor="show-all">
|
||||
{t`Hide stats at base value`}
|
||||
</FormLabel>
|
||||
|
||||
<Box>
|
||||
<Switch
|
||||
id="show-not-actual"
|
||||
color="blue"
|
||||
isChecked={showNotActualProgress}
|
||||
onChange={() =>
|
||||
setShowNotActualProgress(!showNotActualProgress)
|
||||
}
|
||||
mr="0.5em"
|
||||
/>
|
||||
<FormLabel htmlFor="show-not-actual">
|
||||
{t`Progress bars show progress to max value`}
|
||||
</FormLabel>
|
||||
</Box>
|
||||
<Box>
|
||||
<Switch
|
||||
id="charts-zero"
|
||||
color="blue"
|
||||
isChecked={startChartsAtZero}
|
||||
onChange={() => setStartChartsAtZero(!startChartsAtZero)}
|
||||
mr="0.5em"
|
||||
/>
|
||||
<FormLabel htmlFor="charts-zero">
|
||||
{t`Start charts Y axis from zero`}
|
||||
</FormLabel>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<Box m="1rem" w={["95%", null, "30rem"]}>
|
||||
<BuildStats
|
||||
build={build}
|
||||
explanations={explanations}
|
||||
otherExplanations={showOther ? otherExplanations : undefined}
|
||||
hideExtra={hideExtra}
|
||||
showNotActualProgress={showNotActualProgress}
|
||||
startChartsAtZero={startChartsAtZero}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Wrap>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BuildAnalyzerPage;
|
||||
Loading…
Reference in New Issue
Block a user