analyzer renders

This commit is contained in:
Kalle (Sendou) 2020-11-27 10:28:26 +02:00
parent 52c3d98913
commit 0bc4f5b4e5
16 changed files with 1164 additions and 18 deletions

View File

@ -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

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

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

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

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

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

View File

@ -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 {

View File

@ -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"

View File

@ -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,

View File

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

View File

@ -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";

View File

@ -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
View File

@ -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",

View File

@ -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",

View File

@ -46,6 +46,11 @@ const extendedTheme = extendTheme({
colorScheme: "theme",
},
},
Badge: {
defaultProps: {
colorScheme: "theme",
},
},
Modal: {
baseStyle: (props) => ({
dialog: {

203
pages/analyzer.tsx Normal file
View 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;