From 0bc4f5b4e533e1508ea0fe0692b6f8d204af10c4 Mon Sep 17 00:00:00 2001 From: "Kalle (Sendou)" <38327916+Sendouc@users.noreply.github.com> Date: Fri, 27 Nov 2020 10:28:26 +0200 Subject: [PATCH] analyzer renders --- TODO.md | 1 + components/analyzer/BuildStats.tsx | 233 ++++++++++++++++++ components/analyzer/EditableBuilds.tsx | 174 +++++++++++++ components/analyzer/HeadOnlyToggle.tsx | 62 +++++ components/analyzer/LdeSlider.tsx | 89 +++++++ components/analyzer/StatChart.tsx | 114 +++++++++ components/builds/ViewSlots.tsx | 8 +- components/u/AbilitiesSelector.tsx | 8 +- components/u/BuildModal.tsx | 28 +++ lib/lists/abilities.ts | 9 + lib/types.ts | 4 + .../{useAbilityEffects.ts => index.ts} | 12 +- package-lock.json | 230 ++++++++++++++++- package.json | 2 + pages/_app.tsx | 5 + pages/analyzer.tsx | 203 +++++++++++++++ 16 files changed, 1164 insertions(+), 18 deletions(-) create mode 100644 components/analyzer/BuildStats.tsx create mode 100644 components/analyzer/EditableBuilds.tsx create mode 100644 components/analyzer/HeadOnlyToggle.tsx create mode 100644 components/analyzer/LdeSlider.tsx create mode 100644 components/analyzer/StatChart.tsx rename lib/useAbilityEffects/{useAbilityEffects.ts => index.ts} (99%) create mode 100644 pages/analyzer.tsx diff --git a/TODO.md b/TODO.md index a0343dda4..b22aedb3c 100644 --- a/TODO.md +++ b/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 diff --git a/components/analyzer/BuildStats.tsx b/components/analyzer/BuildStats.tsx new file mode 100644 index 000000000..85a0b5892 --- /dev/null +++ b/components/analyzer/BuildStats.tsx @@ -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 = ({ + explanations, + otherExplanations, + build, + hideExtra = true, + showNotActualProgress = false, + startChartsAtZero = true, +}) => { + const [expandedCharts, setExpandedCharts] = useState>(new Set()); + + const abilityArrays: (Ability | "UNKNOWN")[][] = [ + build.headAbilities ?? [], + build.clothingAbilities ?? [], + build.shoesAbilities ?? [], + ]; + + const abilityToPoints: Partial> = {}; + 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 ( + <> + + + + + + } + onClick={() => toggleChart()} + isRound + variant="ghost" + /> + {title} + {info && ( + + + + + + + + {info} + + + )} + + + {effect} + + + + {otherEffect && ( + <> + + + + + {otherEffect} + + + + )} + {getEffect && chartExpanded && ( + + + + )} + + ); + }; + + 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 ( + + { + const newSet = new Set(expandedCharts); + if (newSet.has(explanation.title)) { + newSet.delete(explanation.title); + } else { + newSet.add(explanation.title); + } + + setExpandedCharts(newSet); + }} + /> + + ); + })} + + ); +}; + +export default BuildStats; diff --git a/components/analyzer/EditableBuilds.tsx b/components/analyzer/EditableBuilds.tsx new file mode 100644 index 000000000..b9cfe9235 --- /dev/null +++ b/components/analyzer/EditableBuilds.tsx @@ -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; + otherBuild: Omit; + setBuild: React.Dispatch< + React.SetStateAction> + >; + showOther: boolean; + setShowOther: React.Dispatch>; + otherFocused: boolean; + changeFocus: () => void; + bonusAp: Partial>; + setBonusAp: React.Dispatch< + React.SetStateAction>> + >; + otherBonusAp: Partial>; + setOtherBonusAp: React.Dispatch< + React.SetStateAction>> + >; + lde: number; + otherLde: number; + setLde: React.Dispatch>; + setOtherLde: React.Dispatch>; +} + +const EditableBuilds: React.FC = ({ + 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 ( + <> + + + + {showOther && ( + changeFocus()} + icon={} + isRound + mx="auto" + /> + )} + + {["OG", "CB"].includes(headAbility) && ( + + setBonusAp({ + ...bonusAp, + [headAbility]: !bonusAp[headAbility], + }) + } + /> + )} + {headAbility === "LDE" && ( + setLde(value)} + /> + )} + + {showOther && ( + + changeFocus()} + icon={} + isRound + mx="auto" + /> + + {["OG", "CB"].includes(otherHeadAbility) && ( + + setOtherBonusAp({ + ...otherBonusAp, + [otherHeadAbility]: !otherBonusAp[otherHeadAbility], + }) + } + /> + )} + {otherHeadAbility === "LDE" && ( + setOtherLde(value)} + /> + )} + + )} + + + setBuild(newAbilities)} + /> + + + ); +}; + +export default EditableBuilds; diff --git a/components/analyzer/HeadOnlyToggle.tsx b/components/analyzer/HeadOnlyToggle.tsx new file mode 100644 index 000000000..f3767a064 --- /dev/null +++ b/components/analyzer/HeadOnlyToggle.tsx @@ -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 = ({ + ability, + active, + setActive, +}) => { + const { themeColorShade } = useMyTheme(); + return ( + + + setActive()} + mr="0.5em" + /> + + + + + {active && ability === "OG" && ( + + +15{t`AP`}{" "} + {["SSU", "RSU", "RES"].map((ability) => ( + + + + ))} + + )} + {active && ability === "CB" && ( + + +10{t`AP`}{" "} + {["ISM", "ISS", "REC", "RSU", "SSU", "SCU"].map((ability) => ( + + + + ))} + + )} + + ); +}; + +export default HeadOnlyToggle; diff --git a/components/analyzer/LdeSlider.tsx b/components/analyzer/LdeSlider.tsx new file mode 100644 index 000000000..6f14e9d13 --- /dev/null +++ b/components/analyzer/LdeSlider.tsx @@ -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 = ({ 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 Enemy has reached the {pointMark} point mark; + return t`Enemy has not reached the 50 point mark or there is more than 30 seconds on the clock`; + }; + return ( + + + {t`Intensity`} + + setValue(value)} + > + + + + + + + {value > 0 && ( + + +{bonusAp} + {t`AP`}{" "} + {["ISM", "ISS", "REC"].map((ability) => ( + + + + ))} + + )} + + {getLdeEffect()} + + + ); +}; + +export default LdeSlider; diff --git a/components/analyzer/StatChart.tsx b/components/analyzer/StatChart.tsx new file mode 100644 index 000000000..0d5b00d9d --- /dev/null +++ b/components/analyzer/StatChart.tsx @@ -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 = ({ + 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 ( + + + + ); + } + + if (payload.name === `${otherAp}${t`AP`}`) { + return ( + + + + ); + } + + return null; + }; + + return ( + + + + + + + + } + isAnimationActive={false} + /> + + + ); +}; + +export default StatChart; diff --git a/components/builds/ViewSlots.tsx b/components/builds/ViewSlots.tsx index 0ee241287..2095bb1d9 100644 --- a/components/builds/ViewSlots.tsx +++ b/components/builds/ViewSlots.tsx @@ -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 { diff --git a/components/u/AbilitiesSelector.tsx b/components/u/AbilitiesSelector.tsx index 7c11bf6f2..81eb39b1a 100644 --- a/components/u/AbilitiesSelector.tsx +++ b/components/u/AbilitiesSelector.tsx @@ -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 = ({ abilities, setAbilities }) => { return ( <> - - setAbilities(getModifiedAbilities("UNKNOWN", gear, index)) - } - /> = ({ onClose, build }) => { value: shoesAbilities, }) => ( + { + const abilityArrays = { + headAbilities, + clothingAbilities, + shoesAbilities, + }; + + const onChange = { + headAbilities: onHeadChange, + clothingAbilities: onClothingChange, + shoesAbilities: onShoesChange, + }; + + const newAbilityArray = { + ...abilityArrays[gear], + }; + + newAbilityArray[index] = "UNKNOWN"; + onChange[gear](newAbilityArray); + }} + /> 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 + ); diff --git a/lib/types.ts b/lib/types.ts index 7379a6ea5..7eaa08c6c 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,2 +1,6 @@ +import { Ability } from "@prisma/client"; + export type Unpacked = T extends (infer U)[] ? U : T; export type PartialBy = Omit & Partial>; + +export type AbilityOrUnknown = Ability | "UNKNOWN"; diff --git a/lib/useAbilityEffects/useAbilityEffects.ts b/lib/useAbilityEffects/index.ts similarity index 99% rename from lib/useAbilityEffects/useAbilityEffects.ts rename to lib/useAbilityEffects/index.ts index ce0219758..872cb614d 100644 --- a/lib/useAbilityEffects/useAbilityEffects.ts +++ b/lib/useAbilityEffects/index.ts @@ -20,8 +20,8 @@ export interface Explanation { } interface useAbilityEffectsArgs { - weapon: Weapon; - buildsAbilities: ViewSlotsAbilities; + weapon: string; + abilities: ViewSlotsAbilities; bonusAp?: Partial>; 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, diff --git a/package-lock.json b/package-lock.json index 76d2e0908..32b5e7cf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 86edd2d14..78be331e3 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pages/_app.tsx b/pages/_app.tsx index 590925452..222a4ea46 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -46,6 +46,11 @@ const extendedTheme = extendTheme({ colorScheme: "theme", }, }, + Badge: { + defaultProps: { + colorScheme: "theme", + }, + }, Modal: { baseStyle: (props) => ({ dialog: { diff --git a/pages/analyzer.tsx b/pages/analyzer.tsx new file mode 100644 index 000000000..c10964d25 --- /dev/null +++ b/pages/analyzer.tsx @@ -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({ + ...defaultBuild, + }); + const [otherBuild, setOtherBuild] = useState({ + ...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> + >({}); + const [otherBonusAp, setOtherBonusAp] = useState< + Partial> + >({}); + 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 ( + <> + + + Patch {CURRENT_PATCH} + + + AP = Ability Point = Mains * 10 + Subs * 3 + + + + + + + + + + {weapon && ( + { + setOtherFocused(!otherFocused); + }} + otherFocused={otherFocused} + bonusAp={bonusAp} + setBonusAp={setBonusAp} + otherBonusAp={otherBonusAp} + setOtherBonusAp={setOtherBonusAp} + lde={lde} + setLde={setLde} + otherLde={otherLde} + setOtherLde={setOtherLde} + /> + )} + + + + + {showSettings && ( + + setHideExtra(!hideExtra)} + mr="0.5em" + /> + + {t`Hide stats at base value`} + + + + + setShowNotActualProgress(!showNotActualProgress) + } + mr="0.5em" + /> + + {t`Progress bars show progress to max value`} + + + + setStartChartsAtZero(!startChartsAtZero)} + mr="0.5em" + /> + + {t`Start charts Y axis from zero`} + + + + )} + + + + + + + ); +}; + +export default BuildAnalyzerPage;