x search and x trends

This commit is contained in:
Sendou 2019-10-12 14:24:32 +03:00
parent e9f3924d9d
commit e5d85eced4
18 changed files with 1448 additions and 40 deletions

View File

@ -4113,6 +4113,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.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.0.tgz",
"integrity": "sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg=="
},
"d3-format": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.1.tgz",
"integrity": "sha512-TUswGe6hfguUX1CtKxyG2nymO+1lyThbkS1ifLX0Sr+dOQtAD5gkrffpHnx+yHNKUZ0Bmg5T4AjUQwugPDrm0g=="
},
"d3-interpolate": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz",
"integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==",
"requires": {
"d3-color": "1"
}
},
"d3-path": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.8.tgz",
"integrity": "sha512-J6EfUNwcMQ+aM5YPOB8ZbgAZu6wc82f/0WFxrxwV6Ll8wBwLaHLKCqQ5Imub02JriCVVdPjgI+6P3a4EWJCxAg=="
},
"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.5",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz",
"integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==",
"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.1.3",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz",
"integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==",
"requires": {
"d3-time": "1"
}
},
"damerau-levenshtein": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz",
@ -4166,6 +4233,11 @@
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"decimal.js-light": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.0.tgz",
"integrity": "sha512-b3VJCbd2hwUpeRGG3Toob+CRo8W22xplipNhP3tN7TSVB/cyMX71P1vM2Xjc9H74uV6dS2hDDmo/rHq8L87Upg=="
},
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
@ -4402,6 +4474,14 @@
"utila": "~0.4"
}
},
"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": "0.2.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz",
@ -8137,6 +8217,11 @@
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0="
},
"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.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -8164,6 +8249,11 @@
"lodash._reinterpolate": "^3.0.0"
}
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"lodash.unescape": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
@ -8260,6 +8350,11 @@
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
},
"math-expression-evaluator": {
"version": "1.2.17",
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
"integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw="
},
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -10618,6 +10713,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.1.tgz",
"integrity": "sha512-BXUMf9sIOPXXZWqr7+c5SeOKJykyVr2u0UDzEf4LNGc6taGkQe1A9DFD07umCIXz45RLr9oAAwZbAJ0Pkknfaw=="
},
"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-popper": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.4.tgz",
@ -10631,6 +10731,17 @@
"warning": "^4.0.2"
}
},
"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-router": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz",
@ -10748,6 +10859,28 @@
"react-dom": "^16.6.1"
}
},
"react-smooth": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-1.0.2.tgz",
"integrity": "sha512-pIGzL1g9VGAsRsdZQokIK0vrCkcdKtnOnS1gyB2rrowdLy69lNSWoIjCTWAfgbiYvria8tm5hEZqj+jwXMkV4A==",
"requires": {
"lodash": "~4.17.4",
"prop-types": "^15.6.0",
"raf": "^3.4.0",
"react-transition-group": "^2.5.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"
}
},
"reactcss": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
@ -10827,6 +10960,39 @@
"util.promisify": "^1.0.0"
}
},
"recharts": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-1.7.1.tgz",
"integrity": "sha512-i4vK/ZSICr+dXGmaijuNybc+xhctiX0464xnqauY+OvE6WvU5v+0GYciQvD/HJSObkKG4wY8aRtiuUL9YtXnHQ==",
"requires": {
"classnames": "^2.2.5",
"core-js": "^2.5.1",
"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.0",
"recharts-scale": "^0.4.2",
"reduce-css-calc": "^1.3.0"
},
"dependencies": {
"core-js": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz",
"integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A=="
}
}
},
"recharts-scale": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.2.tgz",
"integrity": "sha512-p/cKt7j17D1CImLgX2f5+6IXLbRHGUQkogIp06VUoci/XkhOQiGSzUrsD1uRmiI7jha4u8XNFOjkHkzzBPivMg==",
"requires": {
"decimal.js-light": "^2.4.1"
}
},
"recursive-readdir": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz",
@ -10835,6 +11001,31 @@
"minimatch": "3.0.4"
}
},
"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"
}
},
"regenerate": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
@ -11064,6 +11255,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"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.12.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",

View File

@ -13,6 +13,7 @@
"react-router-dom": "^5.1.2",
"react-scripts": "3.1.2",
"react-sketch": "^0.5.1",
"recharts": "^1.7.1",
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^0.88.1"
},

View File

@ -5,7 +5,7 @@ import { weapons } from "../../utils/lists"
import { wpnSmall } from "../../assets/imageImports"
import weaponDict from "../../utils/english_internal.json"
const WeaponDropdown = ({ onChange, value, showImages = true }) => {
const WeaponDropdown = ({ value, onChange, showImages = true }) => {
return (
<Dropdown
placeholder="Choose a weapon"

View File

@ -26,17 +26,29 @@ const Footer = () => {
<Grid.Column width={3}>
<Header inverted as="h4" content="Follow Sendou" />
<List link inverted>
<List.Item as="a">Twitter</List.Item>
<List.Item as="a">Twitch</List.Item>
<List.Item as="a">YouTube</List.Item>
<List.Item as="a">Discord</List.Item>
<List.Item as="a" href="https://twitter.com/sendouc">
Twitter
</List.Item>
<List.Item as="a" href="https://www.twitch.tv/sendou">
Twitch
</List.Item>
<List.Item as="a" href="https://www.youtube.com/sendou">
YouTube
</List.Item>
<List.Item as="a" href="https://discordapp.com/invite/J6NqUvt">
Discord
</List.Item>
</List>
</Grid.Column>
<Grid.Column width={3}>
<Header inverted as="h4" content="Splatoon elsewhere" />
<List link inverted>
<List.Item as="a">Splatoonwiki</List.Item>
<List.Item as="a">Some random</List.Item>
<List.Item as="a" href="https://splatoonwiki.org/">
Inkipedia
</List.Item>
<List.Item as="a" href="https://stat.ink/">
stat.ink
</List.Item>
<List.Item as="a">Links</List.Item>
<List.Item as="a">More</List.Item>
</List>

View File

@ -1,6 +1,6 @@
import React from "react"
import { Menu, Container, Image, Dropdown, Button } from "semantic-ui-react"
import { NavLink } from "react-router-dom"
import { Menu, Container, Image, Dropdown, Icon } from "semantic-ui-react"
import { Link, NavLink } from "react-router-dom"
import sink_logo from "../../assets/sink_logo.png"
const dropdownStyle = {
@ -11,7 +11,7 @@ const dropdownStyle = {
const MainMenu = () => {
return (
<Menu inverted attached="top" stackable>
<Menu inverted secondary attached="top" stackable>
<Container>
<Menu.Item as="a" header>
<Image src={sink_logo} style={{ height: "40px", width: "auto" }} />
@ -53,8 +53,10 @@ const MainMenu = () => {
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<Menu.Item position="right">
<Button style={{ background: "#7289DA" }}>Log in via Discord</Button>
<Menu.Item as={Link} to="/" position="right">
<Icon name="discord" size="large" style={{ paddingRight: "0.2em" }} />
Log in via Discord
</Menu.Item>
</Container>
</Menu>

View File

@ -1,10 +1,8 @@
import React, { useState } from "react"
import { Comment } from "semantic-ui-react"
import { clothingGear, shoesGear, headGear } from "../../utils/lists"
import { choose } from "../../utils/helperFunctions"
import { abilityIcons } from "../../assets/imageImports"
import murchpfp from "../../assets/murchpfp.png"
import head from "../../utils/head.json"
import clothes from "../../utils/clothes.json"
import shoes from "../../utils/shoes.json"
@ -37,6 +35,8 @@ const subAbilityStyle = {
}
const gearStyle = { maxWidth: "50px", height: "auto" }
const paddingStyle = { paddingLeft: "0.7em" }
const subAbilities = [
abilityIcons.ISM,
abilityIcons.ISS,
@ -167,7 +167,6 @@ const RollSim = () => {
choose(subAbilities)
])
const [rolling, setRolling] = useState(false)
const [rollCount, setRollCount] = useState(0)
const setSubs = (json, gear) => {
let gearName = gear.split("_")[1]
@ -239,7 +238,6 @@ const RollSim = () => {
}
setSubsState(setSubs(json, gear)) //only using this method in the last roll that matters for optimization purposes
setRolling(false)
setRollCount(rollCount + 1)
}
return (
@ -250,7 +248,9 @@ const RollSim = () => {
style={gearStyle}
alt=""
/>
<img src={headMain} style={mainAbilityStyle} alt="" />
<span style={paddingStyle}>
<img src={headMain} style={mainAbilityStyle} alt="" />
</span>
<img
src={headSubs[0]}
style={subAbilityStyle}
@ -276,7 +276,9 @@ const RollSim = () => {
style={gearStyle}
alt=""
/>
<img src={clothingMain} style={mainAbilityStyle} alt="" />
<span style={paddingStyle}>
<img src={clothingMain} style={mainAbilityStyle} alt="" />
</span>
<img
src={clothingSubs[0]}
style={subAbilityStyle}
@ -302,7 +304,9 @@ const RollSim = () => {
style={gearStyle}
alt=""
/>
<img src={shoesMain} style={mainAbilityStyle} alt="" />
<span style={paddingStyle}>
<img src={shoesMain} style={mainAbilityStyle} alt="" />
</span>
<img
src={shoesSubs[0]}
style={subAbilityStyle}
@ -322,20 +326,6 @@ const RollSim = () => {
onClick={() => roll("SHOES")}
/>
</div>
{rollCount > 0 && (
<Comment.Group>
<Comment>
<Comment.Avatar as="a" src={murchpfp} />
<Comment.Content>
<Comment.Author style={{ color: "white" }}>Murch</Comment.Author>
<Comment.Text style={{ color: "white" }}>
You have rolled {rollCount} {rollCount === 1 ? "time" : "times"}
, chum.
</Comment.Text>
</Comment.Content>
</Comment>
</Comment.Group>
)}
</div>
)
}

View File

@ -7,6 +7,8 @@ const Rotations = lazy(() => import("../rotation/Rotations"))
const MapPlanner = lazy(() => import("../plans/MapPlanner"))
const Calendar = lazy(() => import("../calendar/Calendar"))
const XLeaderboard = lazy(() => import("../xleaderboard/XLeaderboard"))
const PlayerXRankStats = lazy(() => import("../xsearch/PlayerXRankStats"))
const XTrends = lazy(() => import("../xtrends/XTrends"))
const Routes = () => {
return (
@ -27,6 +29,12 @@ const Routes = () => {
<Route path="/xleaderboard">
<XLeaderboard />
</Route>
<Route path="/xsearch/p/:uid">
<PlayerXRankStats />
</Route>
<Route path="/trends">
<XTrends />
</Route>
</Switch>
</Suspense>
)

View File

@ -39,7 +39,7 @@ const Rotations = ({ setMenuSelection }) => {
}, [data, loading, monthly.loading])
if (loading || monthly.loading || rotation.length === 0) return <Loading />
if (error || monthly.error) return <Error />
if (error || monthly.error) return <Error errorMessage={error.message} />
const monthlyMaps = monthly.data.maplists[0]

View File

@ -1,5 +1,5 @@
import React, { useEffect } from "react"
import { Table, Icon, Popup, Segment } from "semantic-ui-react"
import { Table, Icon, Popup } from "semantic-ui-react"
import { useQuery } from "@apollo/react-hooks"
import { Link } from "react-router-dom"
@ -21,7 +21,7 @@ const FlexLeaderboard = () => {
)
}
if (result.error) {
return <Error />
return <Error errorMessage={result.error.message} />
}
const leaderboard = result.data["topFlex"]

View File

@ -22,7 +22,7 @@ const WeaponLeaderboard = ({ query, queryName, scoreField, weaponsField }) => {
)
}
if (result.error) {
return <Error />
return <Error errorMessage={result.error.message} />
}
const leaderboard = result.data[queryName]

View File

@ -0,0 +1,61 @@
import React from "react"
import { Table, Header, Image } from "semantic-ui-react"
import { wpnSmall } from "../../assets/imageImports"
import szIcon from "../../assets/sz.png"
import tcIcon from "../../assets/tc.png"
import rmIcon from "../../assets/rm.png"
import cbIcon from "../../assets/cb.png"
import { months } from "../../utils/lists"
import weaponDict from "../../utils/english_internal.json"
const MonthsTable = ({ placements }) => {
//data received is ordered chronologically and sz->tc->rm->cb
const modeIcons = [null, szIcon, tcIcon, rmIcon, cbIcon]
let lastMonth = placements[0].month
let toggle = false
return (
<Table basic="very" celled collapsing>
<Table.Header>
<Table.Row>
<Table.HeaderCell></Table.HeaderCell>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>X Power</Table.HeaderCell>
<Table.HeaderCell>Placement</Table.HeaderCell>
<Table.HeaderCell>Weapon</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{placements.map(p => {
if (lastMonth !== p.month) {
lastMonth = p.month
toggle = !toggle
}
return (
<Table.Row key={p.id} active={toggle}>
<Table.Cell>
<Header as="h4" image>
<Image src={modeIcons[p.mode]} rounded size="mini" />
<Header.Content>
{months[p.month]}
<Header.Subheader>{p.year}</Header.Subheader>
</Header.Content>
</Header>
</Table.Cell>
<Table.Cell>{p.name}</Table.Cell>
<Table.Cell>{p.x_power}</Table.Cell>
<Table.Cell>{p.rank}</Table.Cell>
<Table.Cell>
<img src={wpnSmall[weaponDict[p.weapon]]} alt={p.weapon} />
</Table.Cell>
</Table.Row>
)
})}
</Table.Body>
</Table>
)
}
export default MonthsTable

View File

@ -0,0 +1,146 @@
import React, { useEffect, useState } from "react"
import { useQuery } from "@apollo/react-hooks"
import { Header, Image, Icon, List, Segment } from "semantic-ui-react"
import { Link, Redirect, useParams } from "react-router-dom"
import { playerInfo } from "../../graphql/queries/playerInfo"
import TopPlacementsTable from "./TopPlacementsTable"
import WpnPlayedTable from "./WpnPlayedTable"
import MonthsTable from "./MonthsTable"
import Loading from "../common/Loading"
import Error from "../common/Error"
const addXRankHelp = (
<div>
This user has no X Rank profile set up. If you are the owner of this user
page here is how you can set it up:
<div style={{ paddingTop: "5px" }}>
<List ordered>
<List.Item>
Finish an X rank season in the Top 500 in at least one mode.
</List.Item>
<List.Item>Send your Twitter handle to Sendou via DM.</List.Item>
<List.Item>
Add your Twitter account to your profile on Discord, verify it and
make sure it's set to appear publicly.
</List.Item>
<List.Item>Log in to sendou.ink</List.Item>
</List>
</div>
</div>
)
const PlayerXRankStats = ({ twitter, tabMode = false }) => {
let searchVariables = {}
const { uid } = useParams()
if (uid) searchVariables = { uid }
if (twitter) searchVariables = { twitter }
const { data, error, loading } = useQuery(playerInfo, {
variables: searchVariables
})
const [top, setTop] = useState([])
useEffect(() => {
if (loading) {
return
}
if (data && data.playerInfo) {
document.title = `${data.playerInfo.player.name} - X Rank - sendou.ink`
const placements = data.playerInfo.placements
//reducing placements to top sz, tc etc. rank and x power
const tops = ["", "szTop", "tcTop", "rmTop", "cbTop"]
const exs = ["", "szX", "tcX", "rmX", "cbX"]
setTop(
placements.reduce(
(acc, cur) => {
const topKey = tops[cur.mode]
const xKey = exs[cur.mode]
if (!acc[xKey]) {
acc[xKey] = cur
acc[topKey] = cur
return acc
}
if (acc[xKey].x_power < cur.x_power) {
acc[xKey] = cur
}
if (acc[topKey].rank > cur.rank) {
acc[topKey] = cur
}
return acc
},
{
szX: null,
szTop: null,
tcX: null,
tcTop: null,
rmX: null,
rmTop: null,
cbX: null,
cbTop: null
}
)
)
}
}, [data, loading])
if (!uid && !twitter) return addXRankHelp
if (loading) return <Loading />
if (error) {
if (error.message === "GraphQL error: player not found") {
if (tabMode) return addXRankHelp
else return <Redirect to="/404" />
}
return <Error errorMessage={error.message} />
}
const playerData = data.playerInfo.player
return (
<>
{!tabMode ? (
<Header as="h2">
{playerData.twitter ? (
<Image
circular
src={`https://avatars.io/twitter/${playerData.twitter}`}
/>
) : null}{" "}
{playerData.alias ? playerData.alias : playerData.name}
{playerData.twitter ? (
<a href={`https://twitter.com/${playerData.twitter}`}>
<Icon
style={{ paddingLeft: "5px" }}
size="small"
name="twitter"
/>
</a>
) : null}
</Header>
) : null}
{tabMode && playerData.discord_id ? (
<>
<Link to={`/u/${playerData.discord_id}`}>User page</Link>
<br />
</>
) : null}
<Segment compact>
<div style={{ padding: "5px" }}>
<TopPlacementsTable top={top} />
</div>
</Segment>
<Segment compact>
<div style={{ padding: "5px" }}>
<Header dividing>All Top 500 placements</Header>
<MonthsTable placements={data.playerInfo.placements} />
</div>
</Segment>
<Segment compact>
<div style={{ padding: "5px" }}>
<Header dividing>Weapons reached Top 500 with</Header>
<WpnPlayedTable weapons={playerData.weapons} />
</div>
</Segment>
</>
)
}
export default PlayerXRankStats

View File

@ -0,0 +1,118 @@
import React from "react"
import { Header, Image, Table } from "semantic-ui-react"
import { wpnSmall } from "../../assets/imageImports"
import weaponDict from "../../utils/english_internal.json"
import { months } from "../../utils/lists"
import szIcon from "../../assets/sz.png"
import tcIcon from "../../assets/tc.png"
import rmIcon from "../../assets/rm.png"
import cbIcon from "../../assets/cb.png"
const TopPlacementsTable = ({ top }) => {
const returnRow = (x, placement, mode) => {
if (!x) {
return null
}
const modeIcon = ["", szIcon, tcIcon, rmIcon, cbIcon][mode]
const modeName = [
"",
"Splat Zones",
"Tower Control",
"Rainmaker",
"Clam Blitz"
][mode]
if (x === placement) {
return (
<Table.Body>
<Table.Row>
<Table.Cell>
<Header as="h4" image>
<Image src={modeIcon} size="mini" />
<Header.Content>
{modeName}
<Header.Subheader>
Highest X Power & Placement
</Header.Subheader>
</Header.Content>
</Header>
</Table.Cell>
<Table.Cell>{x.x_power}</Table.Cell>
<Table.Cell>{x.rank}</Table.Cell>
<Table.Cell>
<img src={wpnSmall[weaponDict[x.weapon]]} alt={x.weapon} />
</Table.Cell>
<Table.Cell>{months[x.month]}</Table.Cell>
<Table.Cell>{x.year}</Table.Cell>
</Table.Row>
</Table.Body>
)
}
return (
<Table.Body>
<Table.Row>
<Table.Cell>
<Header as="h4" image>
<Image src={modeIcon} size="mini" />
<Header.Content>
{modeName}
<Header.Subheader>Highest X Power</Header.Subheader>
</Header.Content>
</Header>
</Table.Cell>
<Table.Cell>{x.x_power}</Table.Cell>
<Table.Cell>{x.rank}</Table.Cell>
<Table.Cell>
<img src={wpnSmall[weaponDict[x.weapon]]} alt={x.weapon} />
</Table.Cell>
<Table.Cell>{months[x.month]}</Table.Cell>
<Table.Cell>{x.year}</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Header as="h4" image>
<Image src={modeIcon} size="mini" />
<Header.Content>
{modeName}
<Header.Subheader>Highest Placement</Header.Subheader>
</Header.Content>
</Header>
</Table.Cell>
<Table.Cell>{placement.x_power}</Table.Cell>
<Table.Cell>{placement.rank}</Table.Cell>
<Table.Cell>
<img
src={wpnSmall[weaponDict[placement.weapon]]}
alt={placement.weapon}
/>
</Table.Cell>
<Table.Cell>{months[placement.month]}</Table.Cell>
<Table.Cell>{placement.year}</Table.Cell>
</Table.Row>
</Table.Body>
)
}
return (
<Table basic="very" celled collapsing>
<Table.Header>
<Table.Row>
<Table.HeaderCell></Table.HeaderCell>
<Table.HeaderCell>X Power</Table.HeaderCell>
<Table.HeaderCell>Placement</Table.HeaderCell>
<Table.HeaderCell>Weapon</Table.HeaderCell>
<Table.HeaderCell>Month</Table.HeaderCell>
<Table.HeaderCell>Year</Table.HeaderCell>
</Table.Row>
</Table.Header>
{returnRow(top.szX, top.szTop, 1)}
{returnRow(top.tcX, top.tcTop, 2)}
{returnRow(top.rmX, top.rmTop, 3)}
{returnRow(top.cbX, top.cbTop, 4)}
</Table>
)
}
export default TopPlacementsTable

View File

@ -0,0 +1,63 @@
import React from "react"
import { weaponsByCategory } from "../../utils/lists"
import { categoryKeys } from "../../utils/lists"
import weaponDict from "../../utils/english_internal.json"
import { wpnSmall } from "../../assets/imageImports"
import { Table, Header, Popup } from "semantic-ui-react"
const WpnPlayedTable = ({ weapons }) => {
const weaponStyle = wpnName => {
const activeStyle = {}
const inactiveStyle = { filter: "grayscale(1)", opacity: "0.3" }
return weapons.includes(wpnName) ? activeStyle : inactiveStyle
}
return (
<Table basic="very" celled collapsing>
{categoryKeys.map(c => {
return (
<Table.Body key={c}>
<Table.Row>
<Table.Cell>
<Header as="h4">
<Header.Content>{c}</Header.Content>
</Header>
</Table.Cell>
<Table.Cell>
{weaponsByCategory[c].map(w => {
return (
<Popup
key={w}
trigger={
<img
key={w}
style={weaponStyle(w)}
src={wpnSmall[weaponDict[w]]}
alt={w}
/>
}
content={w}
/>
)
})}
</Table.Cell>
</Table.Row>
</Table.Body>
)
})}
<Table.Body>
<Table.Row>
<Table.Cell>
<Header as="h4">
<Header.Content>Total</Header.Content>
</Header>
</Table.Cell>
<Table.Cell>{weapons.length} / 129</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
)
}
export default WpnPlayedTable

View File

@ -0,0 +1,350 @@
import React, { useState, useEffect } from "react"
import {
Button,
Form,
Radio,
List,
Image,
Grid,
Dropdown,
Message
} from "semantic-ui-react"
import { wpnSmall } from "../../assets/imageImports"
import useTrends from "../../utils/useTrends"
import useWindowDimensions from "../../utils/useWindowDimensions"
import english_internal from "../../utils/english_internal.json"
import { months } from "../../utils/lists"
import WeaponDropdown from "../common/WeaponDropdown"
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
Brush
} from "recharts"
const customToolTipStyle = {
width: "200px",
margin: "0",
lineHeight: "24px",
border: "1px solid #f5f5f5",
backgroundColor: "hsla(0,0%,100%,.8)",
padding: "10px"
}
const labelStyle = {
margin: "0",
color: "#666",
fontWeight: "700"
}
const introStyle = {
borderTop: "1px solid #f5f5f5",
margin: "0"
}
const descStyle = {
margin: 0,
color: "#999"
}
const XTrends = () => {
const [weaponForm, setWeaponForm] = useState(null)
const [combineFormLeft, setCombineFormLeft] = useState(null)
const [combineFormRight, setCombineFormRight] = useState(null)
const [weaponForDispatch, setWeaponForDispatch] = useState(null)
const [mode, setMode] = useState("ALL")
const [modeForDispatch, setModeForDispatch] = useState(null)
const { loading, error, plotData, dispatch } = useTrends(
weaponForDispatch,
modeForDispatch
)
const { containerWidth } = useWindowDimensions()
useEffect(() => {
document.title = "X Rank Trends - sendou.ink"
}, [])
const CustomTooltip = ({ active, payload }) => {
if (active) {
if (payload.length === 0) return null
const monthNumber = payload[0].payload.name
const yearNumber = payload[0].payload.year
let patchDescription = null
if (payload[0].payload.hasOwnProperty("patch")) {
if (monthNumber === 4 && yearNumber === 2019) {
patchDescription = "Patches 4.6 and 4.7 were released."
} else {
patchDescription = `Patch ${payload[0].payload.patch} was released.`
}
}
return (
<div style={customToolTipStyle}>
<p style={labelStyle}>{`${months[monthNumber]} (${yearNumber})`}</p>
{payload.map(p => {
return (
<p style={{ ...introStyle, color: p.stroke }} key={p.dataKey}>
{p.payload[p.dataKey]} - {p.dataKey}
</p>
)
})}
{patchDescription && <p style={descStyle}>{patchDescription}</p>}
</div>
)
}
return null
}
if (error) {
return <div style={{ color: "red" }}>{error.message}</div>
}
return (
<>
<WeaponDropdown
value={weaponForm}
onChange={(e, { value }) => setWeaponForm(value)}
/>
<div style={{ paddingTop: "10px" }}>
<Form>
<Form.Field>
<Radio
label="All Modes"
name="radioGroup"
value="ALL"
checked={mode === "ALL"}
onChange={() => setMode("ALL")}
/>
</Form.Field>
<Form.Field>
<Radio
label="Splat Zones"
name="radioGroup"
value="SZ"
checked={mode === "SZ"}
onChange={() => setMode("SZ")}
/>
</Form.Field>
<Form.Field>
<Radio
label="Tower Control"
name="radioGroup"
value="TC"
checked={mode === "TC"}
onChange={() => setMode("TC")}
/>
</Form.Field>
<Form.Field>
<Radio
label="Rainmaker"
name="radioGroup"
value="RM"
checked={mode === "RM"}
onChange={() => setMode("RM")}
/>
</Form.Field>
<Form.Field>
<Radio
label="Clam Blitz"
name="radioGroup"
value="CB"
checked={mode === "CB"}
onChange={() => setMode("CB")}
/>
</Form.Field>
</Form>
</div>
<div style={{ paddingTop: "10px" }}>
<Button
loading={loading}
onClick={() => {
setWeaponForDispatch(weaponForm)
setModeForDispatch(mode)
const wpnForCombine =
mode === "ALL" ? weaponForm : `${weaponForm} (${mode})`
setCombineFormLeft(wpnForCombine)
if (plotData.keys.length >= 1)
setCombineFormRight(
plotData.keys[plotData.keys.length - 1].weapon
)
}}
disabled={!weaponForm}
>
Add to plot as new
</Button>
{plotData.keys.length >= 2 && (
<div style={{ paddingTop: "10px" }}>
<Button
disabled={
!combineFormLeft ||
!combineFormRight ||
combineFormLeft === combineFormRight
}
onClick={() => {
dispatch({
type: "combine",
left: combineFormLeft,
right: combineFormRight
})
}}
>
Combine...
</Button>
<span>
{" "}
<Dropdown
inline
options={plotData.keys
.map(k => {
return { text: k.weapon, value: k.weapon }
})
.filter(k => k.text !== combineFormRight)}
onChange={(event, { value }) => {
setCombineFormLeft(value)
}}
value={combineFormLeft}
/>{" "}
with{" "}
<Dropdown
inline
options={plotData.keys
.map(k => {
return { text: k.weapon, value: k.weapon }
})
.filter(k => k.text !== combineFormLeft)}
onChange={(event, { value }) => {
setCombineFormRight(value)
}}
value={combineFormRight}
/>
</span>
</div>
)}
</div>
{plotData.keys.length > 0 && (
<div style={{ paddingTop: "10px" }}>
<LineChart
width={containerWidth}
height={500}
data={plotData.data}
margin={{
top: 5,
right: 50,
left: 0,
bottom: 5
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="xLabel" tickLine={false} />
<XAxis
dataKey="patch"
tickLine={true}
scale="band"
axisLine={false}
height={1}
xAxisId="patch"
/>
<YAxis
allowDecimals={false}
label={{
value: "X rank top 500 placements",
angle: -90,
position: "insideLeft",
textAnchor: "middle"
}}
/>
<Tooltip content={<CustomTooltip />} />
<Legend verticalAlign="top" />
<Brush dataKey="xLabel" height={75} stroke="#000000" />
{plotData.keys.map(keyObj => {
const w = keyObj.weapon
const color = keyObj.color
return <Line key={w} type="monotone" dataKey={w} stroke={color} />
})}
</LineChart>
<Grid columns={2} style={{ paddingTop: "10px" }}>
<Grid.Column>
<List divided verticalAlign="middle">
{plotData.keys.map(keyObj => {
const w = keyObj.weapon
const color = keyObj.color
return (
<List.Item key={w}>
<List.Content floated="right">
<Button
circular
size="mini"
negative
icon="trash"
onClick={() =>
dispatch({ type: "delete", weapon: w })
}
/>
</List.Content>
{w.indexOf("+") === -1 && (
<Image
avatar
src={
wpnSmall[
english_internal[
w
.replace(" (SZ)", "")
.replace(" (TC)", "")
.replace(" (RM)", "")
.replace(" (CB)", "")
]
]
}
/>
)}
<List.Content>
<span
style={{
color: color,
background: "none",
border: "none",
cursor: "pointer",
WebkitUserSelect: "none",
MozUserSelect: "none",
MsUserSelect: "none",
userSelect: "none"
}}
onClick={() =>
dispatch({ type: "randomizeColor", weapon: w })
}
>
{w}
</span>
</List.Content>
</List.Item>
)
})}
</List>
</Grid.Column>
<Grid.Column />
</Grid>
</div>
)}
<Message>
<Message.List>
<Message.Item>
You can check out all the patch notes{" "}
<a href="https://splatoonwiki.org/wiki/List_of_updates_in_Splatoon_2">
here
</a>
</Message.Item>
<Message.Item>
For alternative take on X Rank trends check out{" "}
<a href="https://www.splatmeta.ink">Splat Meta</a>
</Message.Item>
</Message.List>
</Message>
</>
)
}
export default XTrends

View File

@ -6,7 +6,7 @@ body {
height: 100%;
margin: 0;
background: #79f1a4;
background-image: linear-gradient(135deg, #79f1a4 10%, #0e5cad 100%);
background: linear-gradient(to right, #0f2027, #203a43, #2c5364);
background-repeat: no-repeat;
background-attachment: fixed;
}

View File

@ -154,6 +154,191 @@ export const weapons = [
"Kensa Undercover Brella"
]
export const shooters = [
"Sploosh-o-matic",
"Neo Sploosh-o-matic",
"Sploosh-o-matic 7",
"Splattershot Jr.",
"Custom Splattershot Jr.",
"Kensa Splattershot Jr.",
"Splash-o-matic",
"Neo Splash-o-matic",
"Aerospray MG",
"Aerospray RG",
"Aerospray PG",
"Splattershot",
"Tentatek Splattershot",
"Kensa Splattershot",
".52 Gal",
".52 Gal Deco",
"Kensa .52 Gal",
"N-ZAP '85",
"N-ZAP '89",
"N-ZAP '83",
"Splattershot Pro",
"Forge Splattershot Pro",
"Kensa Splattershot Pro",
".96 Gal",
".96 Gal Deco",
"Jet Squelcher",
"Custom Jet Squelcher"
]
export const semiauto = [
"L-3 Nozzlenose",
"L-3 Nozzlenose D",
"Kensa L-3 Nozzlenose",
"H-3 Nozzlenose",
"H-3 Nozzlenose D",
"Cherry H-3 Nozzlenose",
"Squeezer",
"Foil Squeezer"
]
export const blasters = [
"Luna Blaster",
"Luna Blaster Neo",
"Kensa Luna Blaster",
"Blaster",
"Custom Blaster",
"Range Blaster",
"Custom Range Blaster",
"Grim Range Blaster",
"Rapid Blaster",
"Rapid Blaster Deco",
"Kensa Rapid Blaster",
"Rapid Blaster Pro",
"Rapid Blaster Pro Deco",
"Clash Blaster",
"Clash Blaster Neo"
]
export const rollers = [
"Carbon Roller",
"Carbon Roller Deco",
"Splat Roller",
"Krak-On Splat Roller",
"Kensa Splat Roller",
"Dynamo Roller",
"Gold Dynamo Roller",
"Kensa Dynamo Roller",
"Flingza Roller",
"Foil Flingza Roller"
]
export const brushes = [
"Inkbrush",
"Inkbrush Nouveau",
"Permanent Inkbrush",
"Octobrush",
"Octobrush Nouveau",
"Kensa Octobrush"
]
export const chargers = [
"Classic Squiffer",
"New Squiffer",
"Fresh Squiffer",
"Splat Charger",
"Firefin Splat Charger",
"Kensa Charger",
"Splatterscope",
"Firefin Splatterscope",
"Kensa Splatterscope",
"E-liter 4K",
"Custom E-liter 4K",
"E-liter 4K Scope",
"Custom E-liter 4K Scope",
"Bamboozler 14 Mk I",
"Bamboozler 14 Mk II",
"Bamboozler 14 Mk III",
"Goo Tuber",
"Custom Goo Tuber"
]
export const sloshers = [
"Slosher",
"Slosher Deco",
"Soda Slosher",
"Tri-Slosher",
"Tri-Slosher Nouveau",
"Sloshing Machine",
"Sloshing Machine Neo",
"Kensa Sloshing Machine",
"Bloblobber",
"Bloblobber Deco",
"Explosher",
"Custom Explosher"
]
export const splatlings = [
"Mini Splatling",
"Zink Mini Splatling",
"Kensa Mini Splatling",
"Heavy Splatling",
"Heavy Splatling Deco",
"Heavy Splatling Remix",
"Hydra Splatling",
"Custom Hydra Splatling",
"Ballpoint Splatling",
"Ballpoint Splatling Nouveau",
"Nautilus 47",
"Nautilus 79"
]
export const dualies = [
"Dapple Dualies",
"Dapple Dualies Nouveau",
"Clear Dapple Dualies",
"Splat Dualies",
"Enperry Splat Dualies",
"Kensa Splat Dualies",
"Glooga Dualies",
"Glooga Dualies Deco",
"Kensa Glooga Dualies",
"Dualie Squelchers",
"Custom Dualie Squelchers",
"Dark Tetra Dualies",
"Light Tetra Dualies"
]
export const brellas = [
"Splat Brella",
"Sorella Brella",
"Tenta Brella",
"Tenta Sorella Brella",
"Tenta Camo Brella",
"Undercover Brella",
"Undercover Sorella Brella",
"Kensa Undercover Brella"
]
export const weaponsByCategory = {
Shooters: shooters,
"Semi-automatic Shooters": semiauto,
Blasters: blasters,
Rollers: rollers,
Brushes: brushes,
Chargers: chargers,
Sloshers: sloshers,
Splatlings: splatlings,
Dualies: dualies,
Brellas: brellas
}
export const categoryKeys = [
"Shooters",
"Semi-automatic Shooters",
"Blasters",
"Rollers",
"Brushes",
"Chargers",
"Sloshers",
"Splatlings",
"Dualies",
"Brellas"
]
export const clothingGear = [
"Clt_AMB000",
"Clt_AMB001",
@ -442,7 +627,6 @@ export const headGear = [
"Hed_CAP011",
"Hed_CAP012",
"Hed_CAP014",
"Hed_CAP015",
"Hed_CAP018",
"Hed_CAP019",
"Hed_CAP020",
@ -569,7 +753,6 @@ export const headGear = [
"Hed_NCP004",
"Hed_NCP005",
"Hed_NCP006",
"Hed_NCP007",
"Hed_NCP008",
"Hed_NCP009",
"Hed_NCP010",

278
react-ui/src/utils/useTrends.js vendored Normal file
View File

@ -0,0 +1,278 @@
import { useReducer, useEffect } from "react"
import { useQuery } from "@apollo/react-hooks"
import { searchForTrend } from "../graphql/queries/seachForTrend"
const month = []
month[0] = null
month[1] = "Jan"
month[2] = "Feb"
month[3] = "Mar"
month[4] = "Apr"
month[5] = "May"
month[6] = "Jun"
month[7] = "Jul"
month[8] = "Aug"
month[9] = "Sep"
month[10] = "Oct"
month[11] = "Nov"
month[12] = "Dec"
const patches = {
5: {
2018: {
name: "3.0",
link: "https://splatoonwiki.org/wiki/Version_3.0.0_(Splatoon_2)"
},
2019: {
name: "4.8",
link: "https://splatoonwiki.org/wiki/Version_4.8.0_(Splatoon_2)"
}
},
6: {
2018: {
name: "3.1",
link: "https://splatoonwiki.org/wiki/Version_3.1.0_(Splatoon_2)"
},
2019: {
name: "4.9",
link: "https://splatoonwiki.org/wiki/Version_4.9.0_(Splatoon_2)"
}
},
7: {
2018: {
name: "3.2",
link: "https://splatoonwiki.org/wiki/Version_3.2.0_(Splatoon_2)"
},
2019: {
name: "5.0",
link: "https://splatoonwiki.org/wiki/Version_5.0.0_(Splatoon_2)"
}
},
9: {
2018: {
name: "4.0",
link: "https://splatoonwiki.org/wiki/Version_4.0.0_(Splatoon_2)"
}
},
10: {
2018: {
name: "4.1",
link: "https://splatoonwiki.org/wiki/Version_4.1.0_(Splatoon_2)"
}
},
11: {
2018: {
name: "4.2",
link: "https://splatoonwiki.org/wiki/Version_4.2.0_(Splatoon_2)"
}
},
12: {
2018: {
name: "4.3",
link: "https://splatoonwiki.org/wiki/Version_4.3.0_(Splatoon_2)"
}
},
1: {
2019: {
name: "4.4",
link: "https://splatoonwiki.org/wiki/Version_4.4.0_(Splatoon_2)"
}
},
3: {
2019: {
name: "4.5",
link: "https://splatoonwiki.org/wiki/Version_4.5.0_(Splatoon_2)"
}
},
4: {
2019: {
name: "4.6+4.7",
link: "https://splatoonwiki.org/wiki/List_of_updates_in_Splatoon_2"
}
}
}
const presetColors = [
"#FF00FF",
"#008000",
"#FF0000",
"#0000FF",
"#FFA500",
"#800080",
"#A52A2A",
"#1BC5CD",
"#000080",
"#5BCCA0"
]
const setPlotDataInitial = () => {
const arr_to_return = []
for (let i = 5; i < 13; i++) {
if (patches.hasOwnProperty(i) && patches[i].hasOwnProperty(2018)) {
arr_to_return.push({
name: i,
year: 2018,
xLabel: month[i],
patch: patches[i][2018].name
})
} else {
arr_to_return.push({ name: i, year: 2018, xLabel: month[i] })
}
}
const d = new Date()
const year = d.getFullYear()
const currentMonth = d.getMonth() + 1
for (let i = 2019; i < year + 1; i++) {
for (let j = 1; j < 13; j++) {
// break the loop when we reach the future
if (i === year && j === currentMonth) break
const xLabel = j === 1 ? `Jan (${year})` : month[j]
if (patches.hasOwnProperty(j) && patches[j].hasOwnProperty(i)) {
arr_to_return.push({
name: j,
year: i,
xLabel,
patch: patches[j][i].name
})
} else {
arr_to_return.push({ name: j, year: i, xLabel })
}
}
}
return arr_to_return
}
const mergeModeArrays = countObj => {
const sz_arr = countObj["SZ"]
const tc_arr = countObj["TC"]
const rm_arr = countObj["RM"]
const cb_arr = countObj["CB"]
const arr_to_return = new Array(12).fill(0)
for (let i = 1; i < 13; i++) {
arr_to_return[i] = sz_arr[i] + tc_arr[i] + rm_arr[i] + cb_arr[i]
}
return arr_to_return
}
// monthIndex is between 1 and 12 (inclusive)
const resolveStartIndex = (monthIndex, year) => {
// resolves start index for an array where index 0 is always May 2018
if (year === 2018) {
return monthIndex - 5
} else if (year === 2019) {
return 7 + monthIndex
} else {
return 7 + monthIndex + (year - 2019) * 12
}
}
const getColor = state => {
if (state.keys.length < 9) {
return presetColors[state.keys.length]
}
return "#000000".replace(/0/g, function() {
return (~~(Math.random() * 16)).toString(16)
})
}
const reducer = (state, action) => {
switch (action.type) {
case "add":
const trend = action.trendDocument
const mode = action.mode
const modeLabel = mode === "ALL" ? "" : ` (${mode})`
const weapon = `${action.trendDocument.weapon}${modeLabel}`
// don't add duplicate plots
if (state.keys.indexOf(weapon) !== -1) return state
const toPlotData = [...state.data]
for (let index = 0; index < trend.counts.length; index++) {
let year = trend.counts[index].year
let year_arr = null
if (mode === "ALL") {
year_arr = mergeModeArrays(trend.counts[index])
} else {
year_arr = trend.counts[index][mode]
}
for (let i = 1; i < 13; i++) {
// if year is 2018 skipping to the index where values are found
if (year === 2018 && i < 5) {
i = 4
continue
}
const plotIndex = resolveStartIndex(i, year)
if (plotIndex === toPlotData.length) break
toPlotData[plotIndex][weapon] = year_arr[i]
}
}
const keyObj = {
weapon,
color: getColor(state)
}
return { data: toPlotData, keys: [...state.keys, keyObj] }
case "delete":
const weaponToDelete = action.weapon
const newKeys = [...state.keys].filter(k => k.weapon !== weaponToDelete)
return { data: state.data, keys: newKeys }
case "combine":
const weaponLeft = action.left
const weaponRight = action.right
const newKey = `${weaponLeft} + ${weaponRight}`
const newKeysWithCombined = [
...state.keys,
{ weapon: newKey, color: getColor(state) }
].filter(k => k.weapon !== weaponLeft && k.weapon !== weaponRight)
const newDataWithCombined = [...state.data].map(d => {
const dataObj = { ...d }
let weaponLeftCount = 0
let weaponRightCount = 0
if (dataObj.hasOwnProperty(weaponLeft))
weaponLeftCount = dataObj[weaponLeft]
if (dataObj.hasOwnProperty(weaponRight))
weaponRightCount = dataObj[weaponRight]
dataObj[newKey] = weaponLeftCount + weaponRightCount
delete dataObj[weaponLeft]
delete dataObj[weaponRight]
return dataObj
})
return { data: newDataWithCombined, keys: newKeysWithCombined }
case "randomizeColor":
const keysWithNewColor = [...state.keys].map(k => {
if (k.weapon !== action.weapon) {
return k
}
return {
...k,
color: "#000000".replace(/0/g, function() {
return (~~(Math.random() * 16)).toString(16)
})
}
})
return { data: state.data, keys: keysWithNewColor }
default:
throw new Error()
}
}
export default function useTrends(weapon, mode) {
const [plotData, dispatch] = useReducer(reducer, {
data: setPlotDataInitial(),
keys: []
})
// Skip query if there is no weapon provided
const { data, loading, error } = useQuery(searchForTrend, {
skip: !weapon,
variables: { weapon }
})
useEffect(() => {
if (loading || !data) return
dispatch({ type: "add", trendDocument: data.searchForTrend, mode })
}, [loading, data, mode])
return { loading, error, plotData, dispatch }
}