x trends code, map planner bug fix and new maps

This commit is contained in:
Sendou 2019-08-03 23:32:17 +03:00
parent 6396276378
commit d886dae750
62 changed files with 2714 additions and 1110 deletions

16
models/trend.js Normal file
View File

@ -0,0 +1,16 @@
const mongoose = require('mongoose')
const trendSchema = new mongoose.Schema({
weapon: {type: String, required: true},
counts: [
{
year: Number,
SZ: [Number],
TC: [Number],
RM: [Number],
CB: [Number]
}
]
})
module.exports = mongoose.model('Trend', trendSchema)

View File

@ -4048,6 +4048,73 @@
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA="
},
"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.3.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.3.0.tgz",
"integrity": "sha512-NHODMBlj59xPAwl2BDiO2Mog6V+PrGRtBfWKqKRrs9MCqlSkIEb0Z/SfY7jW29ReHTDC/j+vwXhnZcXI3+3fbg=="
},
"d3-format": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz",
"integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ=="
},
"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.0.11",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.11.tgz",
"integrity": "sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw=="
},
"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.4",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz",
@ -4101,6 +4168,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",
@ -4337,6 +4409,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.1.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
@ -7050,6 +7130,7 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -7957,6 +8038,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.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
@ -7994,6 +8080,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",
@ -8090,6 +8181,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",
@ -10375,6 +10471,22 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
},
"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-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.0.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.1.tgz",
@ -10489,6 +10601,17 @@
"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-sound": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/react-sound/-/react-sound-1.2.0.tgz",
@ -10498,6 +10621,17 @@
"soundmanager2": "^2.97.20170602"
}
},
"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",
@ -10557,6 +10691,39 @@
"util.promisify": "^1.0.0"
}
},
"recharts": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-1.6.2.tgz",
"integrity": "sha512-NqVN8Hq5wrrBthTxQB+iCnZjup1dc+AYRIB6Q9ck9UjdSJTt4PbLepGpudQEYJEN5iIpP/I2vThC4uiTJa7xUQ==",
"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",
@ -10565,6 +10732,38 @@
"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.2",
"resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz",
"integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=",
"requires": {
"balanced-match": "^0.4.2"
},
"dependencies": {
"balanced-match": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
"integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
}
}
},
"regenerate": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
@ -10811,6 +11010,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.10.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",

View File

@ -18,6 +18,7 @@
"react-scripts": "3.0.0",
"react-sketch": "^0.5.1",
"react-sound": "^1.2.0",
"recharts": "^1.6.2",
"semantic-ui-react": "^0.86.0"
},
"scripts": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

10
react-ui/src/App.js vendored
View File

@ -12,7 +12,6 @@ import InfoWeapon from './components/XSearch/InfoWeapon'
import ScrollToTop from './utils/ScrollToTop'
import MapListGenerator from './components/Tools/MapListGenerator'
import Rotations from './components/Tools/Rotations'
import Comps from './components/Tools/Comps'
import MapPlanner from './components/Tools/MapPlanner'
import Links from './components/Misc/Links'
import UserPage from './components/SoloLadder/UserPage'
@ -20,6 +19,7 @@ import HomePage from './components/Misc/HomePage'
import BuildSearch from './components/SoloLadder/BuildSearch'
import Admin from './components/Misc/Admin'
import Calendar from './components/Tools/Calendar'
import XTrends from './components/XSearch/XTrends'
const App = () => {
const [menuSelection, setMenuSelection] = useState('home')
@ -61,9 +61,6 @@ const App = () => {
weaponFromUrl={match.params.weapon.replace(/_/g, ' ')}
/>
} />
<Route exact path="/comps" render={() =>
<Comps />
} />
<Route exact path="/plans" render={() =>
<MapPlanner
setMenuSelection={setMenuSelection}
@ -74,6 +71,11 @@ const App = () => {
setMenuSelection={setMenuSelection}
/>
} />
<Route exact path="/trends" render={() =>
<XTrends
setMenuSelection={setMenuSelection}
/>
} />
<Route path="/404" render={() => <NotFound />} />
<Route path="/admin" render={() => <Admin />} />
<Route path="*" render={() => <NotFound />} />

View File

@ -1,52 +1,86 @@
import React from 'react'
import { Header, Image } from 'semantic-ui-react'
import { Link } from 'react-router-dom'
import koshienBanner from '../img/misc/koshienBanner.png'
import React from "react"
import { Header, Image, Segment } from "semantic-ui-react"
import { Link } from "react-router-dom"
import koshienBanner from "../img/misc/koshienBanner.png"
const HomePage = ({ setMenuSelection }) => {
setMenuSelection('home')
setMenuSelection("home")
return (
<div>
<div style={{'textAlign': 'center'}}>
<Header as='h2'>Welcome to sendou.ink!<Header sub>Competitive Splatoon Hub</Header></Header>
</div>
<div style={{'paddingTop': '20px'}}>
<b><Link to='/plans'>Map planner</Link></b> - Draw on Splatoon 2 maps to show your teammates or anyone else what your plan is. Supports free drawing, shapes and inserting any weapon from the game on the canvas.
</div>
<div style={{'paddingTop': '20px'}}>
<b><Link to='/maps'>Maplists</Link></b> - Generate a maplist to use when scrimming.
You can choose the map pool to be used including monthly ranked maps or an upcoming event.
Any maps you don't feel like you need to practice can be excluded from the pool.
</div>
<div style={{'paddingTop': '15px'}}>
<b><Link to='/rotation'>Rotation</Link></b> - View upcoming Splatoon 2 rotations up to 24 hours in advance.
Have some maps you'd rather not play in ranked? You can set those as unfavored maps (log in not required).
If a rotation contains your unfavored maps you can easily tell at a glance.
</div>
<div style={{'paddingTop': '15px'}}>
<b><Link to='/xleaderboard'>Leaderboards</Link></b> - Browse through X Rank Top 500 Leaderboards.
There is a leaderboard for each weapon class where players are ranked by the average of their top four powers.
</div>
<div style={{'paddingTop': '15px'}}>
<b><Link to='/xsearch'>Top 500 Search</Link></b> - Search through X Rank Top 500 results.
You can choose any weapon to find out the top performing players with it.
Another way is to search for a player name if you are curious how a specific player has performed in the past.
</div>
<div style={{'paddingTop': '15px'}}>
<b><Link to='/calendar'>Calendar</Link></b> - Discover all the upcoming events in competitive Splatoon.
</div>
<div style={{'paddingTop': '15px'}}>
<b><Link to='/links'>Links</Link></b> - Discover useful resources related to competitive Splatoon.
</div>
<div style={{'paddingTop': '15px'}}>
If you <b>login</b> you can add gear builds on your user page. As a reference you can take a look at
my <Link to='u/79237403620945920'>page.</Link>
</div>
<div style={{'paddingTop': '10px'}}>
<Image rounded src={koshienBanner} size='huge' centered/>
</div>
<Segment>
<div style={{"padding": "5px"}}>
<div style={{ textAlign: "center"}}>
<Header as="h2">
Welcome to sendou.ink!<Header sub>Competitive Splatoon Hub</Header>
</Header>
</div>
<div style={{ paddingTop: "20px" }}>
<b>
<Link to="/plans">Map planner</Link>
</b>{" "}
- Draw on Splatoon 2 maps to show your teammates or anyone else what
your plan is. Supports free drawing, shapes and inserting any weapon
from the game on the canvas.
</div>
<div style={{ paddingTop: "20px" }}>
<b>
<Link to="/maps">Maplists</Link>
</b>{" "}
- Generate a maplist to use when scrimming. You can choose the map
pool to be used including monthly ranked maps or an upcoming event.
Any maps you don't feel like you need to practice can be excluded from
the pool.
</div>
<div style={{ paddingTop: "15px" }}>
<b>
<Link to="/rotation">Rotation</Link>
</b>{" "}
- View upcoming Splatoon 2 rotations up to 24 hours in advance. Have
some maps you'd rather not play in ranked? You can set those as
unfavored maps (log in not required). If a rotation contains your
unfavored maps you can easily tell at a glance.
</div>
<div style={{ paddingTop: "15px" }}>
<b>
<Link to="/xleaderboard">Leaderboards</Link>
</b>{" "}
- Browse through X Rank Top 500 Leaderboards. There is a leaderboard
for each weapon class where players are ranked by the average of their
top four powers.
</div>
<div style={{ paddingTop: "15px" }}>
<b>
<Link to="/xsearch">Top 500 Search</Link>
</b>{" "}
- Search through X Rank Top 500 results. You can choose any weapon to
find out the top performing players with it. Another way is to search
for a player name if you are curious how a specific player has
performed in the past.
</div>
<div style={{ paddingTop: "15px" }}>
<b>
<Link to="/calendar">Calendar</Link>
</b>{" "}
- Discover all the upcoming events in competitive Splatoon.
</div>
<div style={{ paddingTop: "15px" }}>
<b>
<Link to="/links">Links</Link>
</b>{" "}
- Discover useful resources related to competitive Splatoon.
</div>
<div style={{ paddingTop: "15px" }}>
If you <b>login</b> you can add gear builds on your user page. As a
reference you can take a look at my{" "}
<Link to="u/79237403620945920">page.</Link>
</div>
<div style={{ paddingTop: "10px" }}>
<Image rounded src={koshienBanner} size="huge" centered />
</div>
</div>
</Segment>
</div>
)
}
export default HomePage
export default HomePage

View File

@ -1,89 +1,97 @@
import React from 'react'
import { Icon, Header, Loader, List } from 'semantic-ui-react'
import { useQuery } from 'react-apollo-hooks'
import { links } from '../../graphql/queries/links'
import React from "react"
import { Icon, Header, Loader, List, Segment } from "semantic-ui-react"
import { useQuery } from "react-apollo-hooks"
import { links } from "../../graphql/queries/links"
const Links = ({ setMenuSelection }) => {
const { data, error, loading } = useQuery(links)
setMenuSelection('links')
document.title = 'Links - sendou.ink'
setMenuSelection("links")
document.title = "Links - sendou.ink"
if (loading) {
return <div style={{"paddingTop": "25px", "paddingBottom": "20000px"}} ><Loader active inline='centered' /></div>
return (
<div style={{ paddingTop: "25px", paddingBottom: "20000px" }}>
<Loader active inline="centered" />
</div>
)
}
if (error) {
return <div style={{"color": "red"}}>{error.message}</div>
return <div style={{ color: "red" }}>{error.message}</div>
}
const linksData = data.links
return (
<div>
<div style={{'paddingTop': '10px'}}>
<Header as='h1'>
<Icon name='discord' />
<Header.Content>Discord</Header.Content>
</Header>
<List divided verticalAlign='middle'>
{linksData.map(l => {
if (l.type !== 'DISCORD') {
return null
}
return (
<List.Item key={l.title} style={{'padding': '6px'}}>
<List.Header size='small' as='a' href={l.url}>{l.title}</List.Header>
<List.Description>
{l.description}
</List.Description>
</List.Item>
)
})}
</List>
<div>
<Segment>
<div style={{ padding: "5px" }}>
<div style={{ paddingTop: "10px" }}>
<Header as="h1">
<Icon name="discord" />
<Header.Content>Discord</Header.Content>
</Header>
<List divided verticalAlign="middle">
{linksData.map(l => {
if (l.type !== "DISCORD") {
return null
}
return (
<List.Item key={l.title} style={{ padding: "6px" }}>
<List.Header size="small" as="a" href={l.url}>
{l.title}
</List.Header>
<List.Description>{l.description}</List.Description>
</List.Item>
)
})}
</List>
</div>
<div style={{ paddingTop: "20px" }}>
<Header as="h1">
<Icon name="book" />
<Header.Content>Guides</Header.Content>
</Header>
<List divided verticalAlign="middle">
{linksData.map(l => {
if (l.type !== "GUIDE") {
return null
}
return (
<List.Item key={l.title} style={{ padding: "6px" }}>
<List.Header size="small" as="a" href={l.url}>
{l.title}
</List.Header>
<List.Description>{l.description}</List.Description>
</List.Item>
)
})}
</List>
</div>
<div style={{ paddingTop: "20px" }}>
<Header as="h1">
<Icon name="folder open" />
<Header.Content>Misc</Header.Content>
</Header>
<List divided verticalAlign="middle">
{linksData.map(l => {
if (l.type !== "MISC") {
return null
}
return (
<List.Item key={l.title} style={{ padding: "6px" }}>
<List.Header size="small" as="a" href={l.url}>
{l.title}
</List.Header>
<List.Description>{l.description}</List.Description>
</List.Item>
)
})}
</List>
</div>
</div>
</Segment>
</div>
<div style={{'paddingTop': '20px'}}>
<Header as='h1'>
<Icon name='book' />
<Header.Content>Guides</Header.Content>
</Header>
<List divided verticalAlign='middle'>
{linksData.map(l => {
if (l.type !== 'GUIDE') {
return null
}
return (
<List.Item key={l.title} style={{'padding': '6px'}}>
<List.Header size='small' as='a' href={l.url}>{l.title}</List.Header>
<List.Description>
{l.description}
</List.Description>
</List.Item>
)
})}
</List>
</div>
<div style={{'paddingTop': '20px'}}>
<Header as='h1'>
<Icon name='folder open' />
<Header.Content>Misc</Header.Content>
</Header>
<List divided verticalAlign='middle'>
{linksData.map(l => {
if (l.type !== 'MISC') {
return null
}
return (
<List.Item key={l.title} style={{'padding': '6px'}}>
<List.Header size='small' as='a' href={l.url}>{l.title}</List.Header>
<List.Description>
{l.description}
</List.Description>
</List.Item>
)
})}
</List>
</div>
</div>
)
}
export default Links
export default Links

View File

@ -109,6 +109,11 @@ const MainMenu = withRouter(({ history, menuSelection, setMenuSelection }) => {
active={menuSelection === 'search'}
onClick={() => { history.push('/xsearch') }}
/>
<Menu.Item
name='trends'
active={menuSelection === 'trends'}
onClick={() => { history.push('/trends') }}
/>
</Dropdown.Menu>
</Dropdown>
<Menu.Item

View File

@ -1,100 +1,174 @@
import React, { useState } from 'react'
import { Grid, Comment } from 'semantic-ui-react'
import { clothingGear, shoesGear, headGear, choose } from '../../utils/lists'
import Sound from 'react-sound'
import useWindowDimensions from '../hooks/useWindowDimensions'
import React, { useState } from "react"
import { Grid, Comment } from "semantic-ui-react"
import { clothingGear, shoesGear, headGear, choose } from "../../utils/lists"
import Sound from "react-sound"
import useWindowDimensions from "../hooks/useWindowDimensions"
import BDU from '../img/abilityIcons/BDU.png'
import BRU from '../img/abilityIcons/BRU.png'
import CB from '../img/abilityIcons/CB.png'
import DR from '../img/abilityIcons/DR.png'
import H from '../img/abilityIcons/H.png'
import ISM from '../img/abilityIcons/ISM.png'
import ISS from '../img/abilityIcons/ISS.png'
import LDE from '../img/abilityIcons/LDE.png'
import MPU from '../img/abilityIcons/MPU.png'
import NS from '../img/abilityIcons/NS.png'
import OG from '../img/abilityIcons/OG.png'
import QR from '../img/abilityIcons/QR.png'
import QSJ from '../img/abilityIcons/QSJ.png'
import REC from '../img/abilityIcons/REC.png'
import RES from '../img/abilityIcons/RES.png'
import RP from '../img/abilityIcons/RP.png'
import RSU from '../img/abilityIcons/RSU.png'
import SCU from '../img/abilityIcons/SCU.png'
import SJ from '../img/abilityIcons/SJ.png'
import SPU from '../img/abilityIcons/SPU.png'
import SS from '../img/abilityIcons/SS.png'
import SSU from '../img/abilityIcons/SSU.png'
import T from '../img/abilityIcons/T.png'
import TI from '../img/abilityIcons/TI.png'
import OS from '../img/abilityIcons/OS.png'
import murchpfp from '../img/misc/murchpfp.png'
import reroll from '../sounds/reroll.mp3'
import booyah from '../sounds/nice.mp3'
import head from '../../utils/head.json'
import clothes from '../../utils/clothes.json'
import shoes from '../../utils/shoes.json'
import BDU from "../img/abilityIcons/BDU.png"
import BRU from "../img/abilityIcons/BRU.png"
import CB from "../img/abilityIcons/CB.png"
import DR from "../img/abilityIcons/DR.png"
import H from "../img/abilityIcons/H.png"
import ISM from "../img/abilityIcons/ISM.png"
import ISS from "../img/abilityIcons/ISS.png"
import LDE from "../img/abilityIcons/LDE.png"
import MPU from "../img/abilityIcons/MPU.png"
import NS from "../img/abilityIcons/NS.png"
import OG from "../img/abilityIcons/OG.png"
import QR from "../img/abilityIcons/QR.png"
import QSJ from "../img/abilityIcons/QSJ.png"
import REC from "../img/abilityIcons/REC.png"
import RES from "../img/abilityIcons/RES.png"
import RP from "../img/abilityIcons/RP.png"
import RSU from "../img/abilityIcons/RSU.png"
import SCU from "../img/abilityIcons/SCU.png"
import SJ from "../img/abilityIcons/SJ.png"
import SPU from "../img/abilityIcons/SPU.png"
import SS from "../img/abilityIcons/SS.png"
import SSU from "../img/abilityIcons/SSU.png"
import T from "../img/abilityIcons/T.png"
import TI from "../img/abilityIcons/TI.png"
import OS from "../img/abilityIcons/OS.png"
import murchpfp from "../img/misc/murchpfp.png"
import reroll from "../sounds/reroll.mp3"
import booyah from "../sounds/nice.mp3"
import head from "../../utils/head.json"
import clothes from "../../utils/clothes.json"
import shoes from "../../utils/shoes.json"
const mainAbilityStyle = { //https://github.com/loadout-ink/splat2-calc
"zIndex": "2",
"borderRadius": "50%",
"width": "40px",
"height": "40px",
"background": "#000",
"border": "2px solid #888",
"borderRight": "0px",
"borderBottom": "0px",
"backgroundSize": "100%",
"boxShadow": "0 0 0 1px #000"
const mainAbilityStyle = {
//https://github.com/loadout-ink/splat2-calc
zIndex: "2",
borderRadius: "50%",
width: "40px",
height: "40px",
background: "#000",
border: "2px solid #888",
borderRight: "0px",
borderBottom: "0px",
backgroundSize: "100%",
boxShadow: "0 0 0 1px #000"
}
const subAbilityStyle = { //https://github.com/loadout-ink/splat2-calc
"zIndex": "2",
"borderRadius": "50%",
"width": "30px",
"height": "30px",
"background": "#000",
"border": "2px solid #888",
"borderRight": "0px",
"borderBottom": "0px",
"backgroundSize": "100%",
"boxShadow": "0 0 0 1px #000"
const subAbilityStyle = {
//https://github.com/loadout-ink/splat2-calc
zIndex: "2",
borderRadius: "50%",
width: "30px",
height: "30px",
background: "#000",
border: "2px solid #888",
borderRight: "0px",
borderBottom: "0px",
backgroundSize: "100%",
boxShadow: "0 0 0 1px #000"
}
const speechBubble = {
"content": '',
"position": "absolute",
"left": "0",
"top": "50%",
"width": "0",
"height": "0",
"border": "26px solid transparent",
"borderRightColor": "#000000",
"borderLeft": "0",
"marginTop": "-26px",
"marginLeft": "-26px",
}
const gearStyle = { "maxWidth": "50px", "height": "auto" }
const gearStyle = { maxWidth: "50px", height: "auto" }
const subAbilities = [ISM,ISS,REC,RSU,SSU,QSJ,RES,BDU,MPU,QR,SCU,SS,SPU,BRU,ISM,ISS,REC,RSU,SSU,QSJ,RES,BDU,MPU,QR,SCU,SS,SPU,BRU]
const headAbilities = [ISM,ISS,REC,RSU,SSU,QSJ,RES,BDU,MPU,QR,SCU,SS,SPU,BRU,OG,LDE,CB,T]
const clothingAbilities = [ISM,ISS,REC,RSU,SSU,QSJ,RES,BDU,MPU,QR,SCU,SS,SPU,BRU,H,NS,TI,RP]
const shoeAbilities = [ISM,ISS,REC,RSU,SSU,QSJ,RES,BDU,MPU,QR,SCU,SS,SPU,BRU,DR,SJ,OS]
const subAbilities = [
ISM,
ISS,
REC,
RSU,
SSU,
QSJ,
RES,
BDU,
MPU,
QR,
SCU,
SS,
SPU,
BRU,
ISM,
ISS,
REC,
RSU,
SSU,
QSJ,
RES,
BDU,
MPU,
QR,
SCU,
SS,
SPU,
BRU
]
const headAbilities = [
ISM,
ISS,
REC,
RSU,
SSU,
QSJ,
RES,
BDU,
MPU,
QR,
SCU,
SS,
SPU,
BRU,
OG,
LDE,
CB,
T
]
const clothingAbilities = [
ISM,
ISS,
REC,
RSU,
SSU,
QSJ,
RES,
BDU,
MPU,
QR,
SCU,
SS,
SPU,
BRU,
H,
NS,
TI,
RP
]
const shoeAbilities = [
ISM,
ISS,
REC,
RSU,
SSU,
QSJ,
RES,
BDU,
MPU,
QR,
SCU,
SS,
SPU,
BRU,
DR,
SJ,
OS
]
const internalToAbility = {
"MainInk_Save": ISM,
"SubInk_Save": ISS,
"InkRecovery_Up": REC,
"HumanMove_Up": RSU,
"SquidMove_Up": SSU,
"JumpTime_Save": QSJ,
"RespawnTime_Save": QR,
"OpInkEffect_Reduction": RES,
"BombDamage_Reduction": BDU,
"MarkingTime_Reduction": MPU,
"RespawnSpecialGauge_Save": SS,
"SpecialIncrease_Up": SCU,
"SpecialTime_Up": SPU,
"BombDistance_Up": BRU
MainInk_Save: ISM,
SubInk_Save: ISS,
InkRecovery_Up: REC,
HumanMove_Up: RSU,
SquidMove_Up: SSU,
JumpTime_Save: QSJ,
RespawnTime_Save: QR,
OpInkEffect_Reduction: RES,
BombDamage_Reduction: BDU,
MarkingTime_Reduction: MPU,
RespawnSpecialGauge_Save: SS,
SpecialIncrease_Up: SCU,
SpecialTime_Up: SPU,
BombDistance_Up: BRU
}
const RollSim = () => {
@ -104,20 +178,34 @@ const RollSim = () => {
const [audio, setAudio] = useState(Sound.status.STOPPED)
const [nice, setNice] = useState(Sound.status.STOPPED)
const [headMain] = useState(choose(headAbilities))
const [headSubs, setHeadSubs] = useState([choose(subAbilities), choose(subAbilities), choose(subAbilities)])
const [headSubs, setHeadSubs] = useState([
choose(subAbilities),
choose(subAbilities),
choose(subAbilities)
])
const [clothingMain] = useState(choose(clothingAbilities))
const [clothingSubs, setClothingSubs] = useState([choose(subAbilities), choose(subAbilities), choose(subAbilities)])
const [clothingSubs, setClothingSubs] = useState([
choose(subAbilities),
choose(subAbilities),
choose(subAbilities)
])
const [shoesMain] = useState(choose(shoeAbilities))
const [shoesSubs, setShoesSubs] = useState([choose(subAbilities), choose(subAbilities), choose(subAbilities)])
const [shoesSubs, setShoesSubs] = useState([
choose(subAbilities),
choose(subAbilities),
choose(subAbilities)
])
const [rolling, setRolling] = useState(false)
const [rollCount, setRollCount] = useState(0)
const { containerWidth } = useWindowDimensions()
const setSubs = (json, gear) => {
let gearName = gear.split("_")[1]
if (!json.hasOwnProperty(gearName)) return [choose(subAbilities), choose(subAbilities), choose(subAbilities)]
if (!json.hasOwnProperty(gearName))
return [choose(subAbilities), choose(subAbilities), choose(subAbilities)]
const prefArray = json[gearName]
if (prefArray[0] === prefArray[1]) { //neutral brand
if (prefArray[0] === prefArray[1]) {
//neutral brand
return [choose(subAbilities), choose(subAbilities), choose(subAbilities)]
}
@ -140,16 +228,17 @@ const RollSim = () => {
const ability1 = choose(adjustedAbilities)
const ability2 = choose(adjustedAbilities)
const ability3 = choose(adjustedAbilities)
if (ability1 === ability2 && ability1 === ability3) setNice(Sound.status.PLAYING)
if (ability1 === ability2 && ability1 === ability3)
setNice(Sound.status.PLAYING)
return [ability1, ability2, ability3]
}
const sleep = (milliseconds) => {
const sleep = milliseconds => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
const roll = async (mode) => {
const roll = async mode => {
if (rolling) return
let setSubsState = null
@ -184,58 +273,122 @@ const RollSim = () => {
setSubsState(setSubs(json, gear)) //only using this method in the last roll that matters for optimization purposes
setAudio(Sound.status.STOPPED)
setRolling(false)
setRollCount(rollCount+1)
setRollCount(rollCount + 1)
}
return (
<div>
<Sound url={reroll} playStatus={audio} loop={true} volume={10} />
<Sound url={booyah} playStatus={nice} volume={10} onFinishedPlaying={() => setNice(Sound.status.STOPPED)} />
<Grid columns='equal' stackable>
<Sound
url={booyah}
playStatus={nice}
volume={10}
onFinishedPlaying={() => setNice(Sound.status.STOPPED)}
/>
<Grid columns="equal" stackable>
<Grid.Row>
<Grid.Column textAlign={containerWidth < 768 ? null : "left"}>
<div style={{'float': 'none', 'whiteSpace': 'nowrap'}}>
<img src={`https://raw.githubusercontent.com/Leanny/leanny.github.io/master/splat2/gear/${headLink}.png`} style={gearStyle} alt="" />
<img src={headMain} style={mainAbilityStyle} alt=""/>
<img src={headSubs[0]} style={subAbilityStyle} onClick={() => roll("HEAD")} alt="" />
<img src={headSubs[1]} style={subAbilityStyle} onClick={() => roll("HEAD")} alt="" />
<img src={headSubs[2]} style={subAbilityStyle} onClick={() => roll("HEAD")} alt="" />
<div style={{ float: "none", whiteSpace: "nowrap" }}>
<img
src={`https://raw.githubusercontent.com/Leanny/leanny.github.io/master/splat2/gear/${headLink}.png`}
style={gearStyle}
alt=""
/>
<img src={headMain} style={mainAbilityStyle} alt="" />
<img
src={headSubs[0]}
style={subAbilityStyle}
onClick={() => roll("HEAD")}
alt=""
/>
<img
src={headSubs[1]}
style={subAbilityStyle}
onClick={() => roll("HEAD")}
alt=""
/>
<img
src={headSubs[2]}
style={subAbilityStyle}
onClick={() => roll("HEAD")}
alt=""
/>
</div>
</Grid.Column>
<Grid.Column textAlign={containerWidth < 768 ? null : "center"}>
<div style={{'float': 'none', 'whiteSpace': 'nowrap'}}>
<img src={`https://raw.githubusercontent.com/Leanny/leanny.github.io/master/splat2/gear/${clothingLink}.png`} style={gearStyle} alt="" />
<div style={{ float: "none", whiteSpace: "nowrap" }}>
<img
src={`https://raw.githubusercontent.com/Leanny/leanny.github.io/master/splat2/gear/${clothingLink}.png`}
style={gearStyle}
alt=""
/>
<img src={clothingMain} style={mainAbilityStyle} alt="" />
<img src={clothingSubs[0]} style={subAbilityStyle} alt="" onClick={() => roll("CLOTHING")} />
<img src={clothingSubs[1]} style={subAbilityStyle} alt="" onClick={() => roll("CLOTHING")} />
<img src={clothingSubs[2]} style={subAbilityStyle} alt="" onClick={() => roll("CLOTHING")} />
<img
src={clothingSubs[0]}
style={subAbilityStyle}
alt=""
onClick={() => roll("CLOTHING")}
/>
<img
src={clothingSubs[1]}
style={subAbilityStyle}
alt=""
onClick={() => roll("CLOTHING")}
/>
<img
src={clothingSubs[2]}
style={subAbilityStyle}
alt=""
onClick={() => roll("CLOTHING")}
/>
</div>
</Grid.Column>
<Grid.Column textAlign={containerWidth < 768 ? null : "right"}>
<div style={{'float': 'none', 'whiteSpace': 'nowrap'}}>
<img src={`https://raw.githubusercontent.com/Leanny/leanny.github.io/master/splat2/gear/${shoesLink}.png`} style={gearStyle} alt="" />
<img src={shoesMain} style={mainAbilityStyle} alt=""/>
<img src={shoesSubs[0]} style={subAbilityStyle} alt="" onClick={() => roll("SHOES")} />
<img src={shoesSubs[1]} style={subAbilityStyle} alt="" onClick={() => roll("SHOES")} />
<img src={shoesSubs[2]} style={subAbilityStyle} alt="" onClick={() => roll("SHOES")} />
<div style={{ float: "none", whiteSpace: "nowrap" }}>
<img
src={`https://raw.githubusercontent.com/Leanny/leanny.github.io/master/splat2/gear/${shoesLink}.png`}
style={gearStyle}
alt=""
/>
<img src={shoesMain} style={mainAbilityStyle} alt="" />
<img
src={shoesSubs[0]}
style={subAbilityStyle}
alt=""
onClick={() => roll("SHOES")}
/>
<img
src={shoesSubs[1]}
style={subAbilityStyle}
alt=""
onClick={() => roll("SHOES")}
/>
<img
src={shoesSubs[2]}
style={subAbilityStyle}
alt=""
onClick={() => roll("SHOES")}
/>
</div>
</Grid.Column>
</Grid.Row>
</Grid>
{rollCount > 0 &&
<Comment.Group>
<Comment>
<Comment.Avatar as='a' src={murchpfp} />
<Comment.Content>
<Comment.Author>Murch</Comment.Author>
<Comment.Text>
You have rolled {rollCount} {rollCount === 1 ? "time" : "times"}, chum.
</Comment.Text>
</Comment.Content>
</Comment>
</Comment.Group>}
{rollCount > 0 && (
<Comment.Group>
<Comment>
<Comment.Avatar as="a" src={murchpfp} />
<Comment.Content>
<Comment.Author>Murch</Comment.Author>
<Comment.Text>
You have rolled {rollCount} {rollCount === 1 ? "time" : "times"}
, chum.
</Comment.Text>
</Comment.Content>
</Comment>
</Comment.Group>
)}
</div>
)
}
export default RollSim
export default RollSim

View File

@ -1,69 +0,0 @@
import React from 'react'
import Tournament from './Tournament'
const Comps = () => {
const mockData = [{
id: 'a',
name: "In The Zone X",
bracket: "https://sendous.challonge.com/InTheZone10",
vod: "https://www.twitch.tv/videos/446387554",
weapons: ["Heavy Splatling Remix", "Tenta Camo Brella", "Tentatek Splattershot", "Soda Slosher", "Splattershot Jr.", "Kensa Splattershot Pro", "Splatterscope", "Custom Dualie Squelchers", "Splat Brella", "Firefin Splatterscope", "Kensa Splattershot"],
team_count: 42,
japanese: false,
patch: '4.9.0',
timestamp: 1561852800,
alpha_team: ["15919661737096779013", "17188550932108025730", "14175924256374209434", "10344205398411576675",], //either uids or nicknames order matters kaji plontro grey kiver
bravo_team: ["12385180454540543327", "14434881234947959524", "15316460862005315247", "7140457213492687321"], //erza brian sorin zekken
alpha_team_players: ["Kaji", "plontro", "Grey", "Kiver"],
bravo_team_players: ["Erza", "Brian", "Sorin", "Zekken"],
alpha_team_name: ["Kraken Paradise"],
bravo_team_name: ["Team Olive"],
rounds: [{
winner: "BRAVO", //enum
map: "MakoMart",
mode: "Splat Zones", //enum
alpha_weapons: ["Heavy Splatling Remix", "Soda Slosher", "Splattershot Jr.", "Kensa Splattershot Pro"],
bravo_weapons: ["Soda Slosher", "Splatterscope", "Splattershot Jr.", "Kensa Splattershot Pro"]
},
{
winner: "BRAVO", //enum
map: "Wahoo World",
mode: "Splat Zones", //enum
alpha_weapons: ["Heavy Splatling Remix", "Soda Slosher", "Splattershot Jr.", "Custom Dualie Squelchers"],
bravo_weapons: ["Splat Brella", "Firefin Splatterscope", "Splattershot Jr.", "Kensa Splattershot"]
},
{
winner: "ALPHA",
map: "Skipper Pavilion",
mode: "Splat Zones",
alpha_weapons: ["Heavy Splatling Remix", "Soda Slosher", "Tenta Camo Brella", "Custom Dualie Squelchers"],
bravo_weapons: ["Kensa Sloshing Machine", "Splatterscope", "Splattershot Jr.", "Kensa Splattershot Pro"]
},
{
winner: "BRAVO",
map: "Manta Maria",
mode: "Splat Zones",
alpha_weapons: ["Heavy Splatling Remix", "Soda Slosher", "Tenta Camo Brella", "Tentatek Splattershot"],
bravo_weapons: ["Splat Brella", "Firefin Splatterscope", "Splattershot Jr.", "Kensa Splattershot"]
}]
}]
return (
<div>
<div>
Search bar here.
</div>
<div>
{mockData.map(t => {
return (
<Tournament tournament={t} key={t.id}/>
)
})}
</div>
<div>
Pagination here
</div>
</div>
)
}
export default Comps

View File

@ -1,62 +1,79 @@
import React, { useEffect, useState, useRef } from 'react'
import { Loader, Checkbox, Form, Header, List, Image, Divider, Icon, Grid, Input, Button, Label } from 'semantic-ui-react'
import { useQuery } from 'react-apollo-hooks'
import { maplists } from '../../graphql/queries/maplists'
import szIcon from '../img/modeIcons/sz.png'
import tcIcon from '../img/modeIcons/tc.png'
import rmIcon from '../img/modeIcons/rm.png'
import cbIcon from '../img/modeIcons/cb.png'
import React, { useEffect, useState, useRef } from "react"
import {
Loader,
Checkbox,
Form,
Header,
List,
Image,
Divider,
Icon,
Grid,
Input,
Button,
Label,
Segment
} from "semantic-ui-react"
import { useQuery } from "react-apollo-hooks"
import { maplists } from "../../graphql/queries/maplists"
import szIcon from "../img/modeIcons/sz.png"
import tcIcon from "../img/modeIcons/tc.png"
import rmIcon from "../img/modeIcons/rm.png"
import cbIcon from "../img/modeIcons/cb.png"
const MapListGenerator = ({ setMenuSelection }) => {
const { data, error, loading } = useQuery(maplists)
const [ maps, setMaps ] = useState([])
const [ boxValue, setBoxValue ] = useState(0)
const [ mapValues, setMapValues ] = useState([])
const [ amountToGenerate, setAmountToGenerate ] = useState(12)
const [ generatedMaps, setGeneratedMaps ] = useState([])
const [ generationType, setGenerationType] = useState('rotate')
const [copySuccess, setCopySuccess] = useState('')
const [maps, setMaps] = useState([])
const [boxValue, setBoxValue] = useState(0)
const [mapValues, setMapValues] = useState([])
const [amountToGenerate, setAmountToGenerate] = useState(12)
const [generatedMaps, setGeneratedMaps] = useState([])
const [generationType, setGenerationType] = useState("rotate")
const [copySuccess, setCopySuccess] = useState("")
const textAreaRef = useRef(null)
useEffect(() => {
if (loading) {
return
}
setMenuSelection('maplists')
document.title = 'Maplist Generator - sendou.ink'
setMenuSelection("maplists")
document.title = "Maplist Generator - sendou.ink"
setMaps(data.maplists)
setMapValues(data.maplists.reduce((acc, cur) => {
return (
acc.concat({
setMapValues(
data.maplists.reduce((acc, cur) => {
return acc.concat({
sz: new Array(cur.sz.length).fill(true),
tc: new Array(cur.tc.length).fill(true),
rm: new Array(cur.rm.length).fill(true),
cb: new Array(cur.cb.length).fill(true),
cb: new Array(cur.cb.length).fill(true)
})
)
}, []))
}, [])
)
}, [data, loading, setMenuSelection])
if (loading || maps.length === 0) {
return <div style={{"paddingTop": "25px", "paddingBottom": "20000px"}} ><Loader active inline='centered' /></div>
return (
<div style={{ paddingTop: "25px", paddingBottom: "20000px" }}>
<Loader active inline="centered" />
</div>
)
}
if (error) {
return <div style={{"color": "red"}}>{error.message}</div>
return <div style={{ color: "red" }}>{error.message}</div>
}
function copyToClipboard(e) {
textAreaRef.current.select()
document.execCommand('copy')
document.execCommand("copy")
e.target.focus()
setCopySuccess('Copied!')
setCopySuccess("Copied!")
}
function shuffle(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
const j = Math.floor(Math.random() * (i + 1))
;[a[i], a[j]] = [a[j], a[i]]
}
return a
}
@ -66,13 +83,16 @@ const MapListGenerator = ({ setMenuSelection }) => {
const tcMaps = maps[boxValue].tc.filter((m, i) => mapValues[boxValue].tc[i])
const rmMaps = maps[boxValue].rm.filter((m, i) => mapValues[boxValue].rm[i])
const cbMaps = maps[boxValue].cb.filter((m, i) => mapValues[boxValue].cb[i])
if (szMaps.length+tcMaps.length+rmMaps.length+cbMaps.length === 0) {
if (szMaps.length + tcMaps.length + rmMaps.length + cbMaps.length === 0) {
return
}
let mapsWithModes = [...szMaps.map(m => `Splat Zones on ${m}`),
...tcMaps.map(m => `Tower Control on ${m}`), ...rmMaps.map(m => `Rainmaker on ${m}`),
...cbMaps.map(m => `Clam Blitz on ${m}`)]
let mapsWithModes = [
...szMaps.map(m => `Splat Zones on ${m}`),
...tcMaps.map(m => `Tower Control on ${m}`),
...rmMaps.map(m => `Rainmaker on ${m}`),
...cbMaps.map(m => `Clam Blitz on ${m}`)
]
const toSetAsState = []
let usedMaps = []
shuffle(mapsWithModes)
@ -84,7 +104,7 @@ const MapListGenerator = ({ setMenuSelection }) => {
usedMaps = []
}
const modeMap = mapsWithModes.pop()
toSetAsState.push(`${toSetAsState.length+1}) ${modeMap}`)
toSetAsState.push(`${toSetAsState.length + 1}) ${modeMap}`)
usedMaps.push(modeMap)
}
@ -131,7 +151,7 @@ const MapListGenerator = ({ setMenuSelection }) => {
let szRemainder = modes.length + 1
if (szEveryOther) {
szRemainder = 2
shuffle(modes) //not shuffled if sz->tc->cb->rm for host convenience
shuffle(modes) //not shuffled if sz->tc->cb->rm for host convenience
}
for (let i = amountToGenerate; i > 0; i--) {
@ -154,16 +174,17 @@ const MapListGenerator = ({ setMenuSelection }) => {
usedCb = []
}
if (i % szRemainder === 0 && szMapMode.length !== 0) { //latter check is for if user selected 0 sz maps
if (i % szRemainder === 0 && szMapMode.length !== 0) {
//latter check is for if user selected 0 sz maps
const modeMap = szMapMode.shift()
toSetAsState.push(`${toSetAsState.length+1}) ${modeMap}`)
toSetAsState.push(`${toSetAsState.length + 1}) ${modeMap}`)
usedSz.push(modeMap)
continue
}
if (modes[0] === "TOWER CONTROL" && tcMapMode.length !== 0) {
const modeMap = tcMapMode.shift()
toSetAsState.push(`${toSetAsState.length+1}) ${modeMap}`)
toSetAsState.push(`${toSetAsState.length + 1}) ${modeMap}`)
usedTc.push(modeMap)
modes.push(modes.splice(0, 1)[0]) // move the mode to last
continue
@ -171,7 +192,7 @@ const MapListGenerator = ({ setMenuSelection }) => {
if (modes[0] === "RAINMAKER" && rmMapMode.length !== 0) {
const modeMap = rmMapMode.shift()
toSetAsState.push(`${toSetAsState.length+1}) ${modeMap}`)
toSetAsState.push(`${toSetAsState.length + 1}) ${modeMap}`)
usedRm.push(modeMap)
modes.push(modes.splice(0, 1)[0])
continue
@ -179,7 +200,7 @@ const MapListGenerator = ({ setMenuSelection }) => {
if (modes[0] === "CLAM BLITZ" && cbMapMode.length !== 0) {
const modeMap = cbMapMode.shift()
toSetAsState.push(`${toSetAsState.length+1}) ${modeMap}`)
toSetAsState.push(`${toSetAsState.length + 1}) ${modeMap}`)
usedCb.push(modeMap)
modes.push(modes.splice(0, 1)[0])
}
@ -189,191 +210,208 @@ const MapListGenerator = ({ setMenuSelection }) => {
}
return (
<div style={{"paddingTop": "5px"}}>
<Divider horizontal>
<Header as='h4'>
<Icon name='map' />
Choose the map pool to use
</Header>
</Divider>
<Form>
{maps.map((m, i) => {
return (
<Form.Field key={m.name}>
<Checkbox
radio
label={m.name}
name='map'
checked={i === boxValue}
onChange={() => setBoxValue(i)}
/>
</Form.Field>
)
})}
</Form>
<div>
<Segment>
<div style={{"padding": "5px"}}>
<Divider horizontal>
<Header as='h4'>
<Icon name='checkmark box' />
Choose the maps to include
<Header as="h4">
<Icon name="map" />
Choose the map pool to use
</Header>
</Divider>
<Grid relaxed='very' columns={4} stackable>
<Grid.Column>
<Image src={szIcon} size="tiny" /><br />
<List>
{maps[boxValue].sz.map((m, i) => {
return (
<List.Item key={m}>
<Checkbox
label={m}
checked={mapValues[boxValue].sz[i]}
onChange={() => {
const copy = [...mapValues]
copy[boxValue].sz[i] = !copy[boxValue].sz[i]
setMapValues(copy)
}}
/>
</List.Item>
)
})}
</List>
</Grid.Column>
<Grid.Column>
<Image src={tcIcon} size="tiny" /><br />
<List>
{maps[boxValue].tc.map((m, i) => {
return (
<List.Item key={m}>
<Checkbox
label={m}
checked={mapValues[boxValue].tc[i]}
onChange={() => {
const copy = [...mapValues]
copy[boxValue].tc[i] = !copy[boxValue].tc[i]
setMapValues(copy)
}}
/>
</List.Item>
)
})}
</List>
</Grid.Column>
<Grid.Column>
<Image src={rmIcon} size="tiny" /><br />
<List>
{maps[boxValue].rm.map((m, i) => {
return (
<List.Item key={m}>
<Checkbox
label={m}
checked={mapValues[boxValue].rm[i]}
onChange={() => {
const copy = [...mapValues]
copy[boxValue].rm[i] = !copy[boxValue].rm[i]
setMapValues(copy)
}}
/>
</List.Item>
)
})}
</List>
</Grid.Column>
<Grid.Column>
<Image src={cbIcon} size="tiny" /><br />
<List>
{maps[boxValue].cb.map((m, i) => {
return (
<List.Item key={m}>
<Checkbox
label={m}
checked={mapValues[boxValue].cb[i]}
onChange={() => {
const copy = [...mapValues]
copy[boxValue].cb[i] = !copy[boxValue].cb[i]
setMapValues(copy)
}}
/>
</List.Item>
)
})}
</List>
</Grid.Column>
</Grid>
</div>
<div style={{"paddingTop": "5px"}}>
<Divider horizontal>
<Header as='h4'>
<Icon name='clipboard list' />
Set the final preferences & generate!
</Header>
</Divider>
<Input
type='number'
placeholder='Choose amount'
label='Choose the amount of maps to generate'
value={amountToGenerate}
onChange={(e) => setAmountToGenerate(e.target.value)}
error={amountToGenerate < 1 || amountToGenerate > 100}
/>
{(amountToGenerate < 1 || amountToGenerate > 100) ? <Label basic color='red' pointing='left'>
Amount of maps to generate has to be between 1 and 100
</Label> : null}
</div>
<div style={{"paddingTop": "5px"}}>
<Form.Field>
<Checkbox
radio
label="Rotate mode SZ->TC->CB->RM"
name='rotateMode'
checked={generationType === 'rotate'}
onChange={() => setGenerationType('rotate')}
/><br/>
<Checkbox
radio
label="SZ every other map"
name='szEveryOther'
checked={generationType === 'everyOther'}
onChange={() => setGenerationType('everyOther')}
/><br/>
<Checkbox
radio
label="Total randomness"
name='random'
checked={generationType === 'random'}
onChange={() => setGenerationType('random')}
<Form>
{maps.map((m, i) => {
return (
<Form.Field key={m.name}>
<Checkbox
radio
label={m.name}
name="map"
checked={i === boxValue}
onChange={() => setBoxValue(i)}
/>
</Form.Field>
)
})}
</Form>
<div>
<Divider horizontal>
<Header as="h4">
<Icon name="checkmark box" />
Choose the maps to include
</Header>
</Divider>
<Grid relaxed="very" columns={4} stackable>
<Grid.Column>
<Image src={szIcon} size="tiny" />
<br />
<List>
{maps[boxValue].sz.map((m, i) => {
return (
<List.Item key={m}>
<Checkbox
label={m}
checked={mapValues[boxValue].sz[i]}
onChange={() => {
const copy = [...mapValues]
copy[boxValue].sz[i] = !copy[boxValue].sz[i]
setMapValues(copy)
}}
/>
</List.Item>
)
})}
</List>
</Grid.Column>
<Grid.Column>
<Image src={tcIcon} size="tiny" />
<br />
<List>
{maps[boxValue].tc.map((m, i) => {
return (
<List.Item key={m}>
<Checkbox
label={m}
checked={mapValues[boxValue].tc[i]}
onChange={() => {
const copy = [...mapValues]
copy[boxValue].tc[i] = !copy[boxValue].tc[i]
setMapValues(copy)
}}
/>
</List.Item>
)
})}
</List>
</Grid.Column>
<Grid.Column>
<Image src={rmIcon} size="tiny" />
<br />
<List>
{maps[boxValue].rm.map((m, i) => {
return (
<List.Item key={m}>
<Checkbox
label={m}
checked={mapValues[boxValue].rm[i]}
onChange={() => {
const copy = [...mapValues]
copy[boxValue].rm[i] = !copy[boxValue].rm[i]
setMapValues(copy)
}}
/>
</List.Item>
)
})}
</List>
</Grid.Column>
<Grid.Column>
<Image src={cbIcon} size="tiny" />
<br />
<List>
{maps[boxValue].cb.map((m, i) => {
return (
<List.Item key={m}>
<Checkbox
label={m}
checked={mapValues[boxValue].cb[i]}
onChange={() => {
const copy = [...mapValues]
copy[boxValue].cb[i] = !copy[boxValue].cb[i]
setMapValues(copy)
}}
/>
</List.Item>
)
})}
</List>
</Grid.Column>
</Grid>
</div>
<div style={{ paddingTop: "5px" }}>
<Divider horizontal>
<Header as="h4">
<Icon name="clipboard list" />
Set the final preferences & generate!
</Header>
</Divider>
<Input
type="number"
placeholder="Choose amount"
label="Choose the amount of maps to generate"
value={amountToGenerate}
onChange={e => setAmountToGenerate(e.target.value)}
error={amountToGenerate < 1 || amountToGenerate > 100}
/>
</Form.Field>
</div>
<div style={{"paddingTop": "10px"}}>
<Button
disabled={amountToGenerate < 1 || amountToGenerate > 100}
onClick={() => {generationType === 'random' ? generateRandomMapPool() : generateEvenMapPool(generationType === 'everyOther')}}>Go!
</Button>
</div>
<div style={{"paddingTop": "5px"}}>
{generatedMaps.length !== 0 ?
<div>
<textarea
rows={generatedMaps.length + 2}
style={{"resize": "none", "width": "300px"}}
readOnly
value={generatedMaps.join("\n")}
ref={textAreaRef}
/><br/>
{
document.queryCommandSupported('copy') &&
{amountToGenerate < 1 || amountToGenerate > 100 ? (
<Label basic color="red" pointing="left">
Amount of maps to generate has to be between 1 and 100
</Label>
) : null}
</div>
<div style={{ paddingTop: "5px" }}>
<Form.Field>
<Checkbox
radio
label="Rotate mode SZ->TC->CB->RM"
name="rotateMode"
checked={generationType === "rotate"}
onChange={() => setGenerationType("rotate")}
/>
<br />
<Checkbox
radio
label="SZ every other map"
name="szEveryOther"
checked={generationType === "everyOther"}
onChange={() => setGenerationType("everyOther")}
/>
<br />
<Checkbox
radio
label="Total randomness"
name="random"
checked={generationType === "random"}
onChange={() => setGenerationType("random")}
/>
</Form.Field>
</div>
<div style={{ paddingTop: "10px" }}>
<Button
disabled={amountToGenerate < 1 || amountToGenerate > 100}
onClick={() => {
generationType === "random"
? generateRandomMapPool()
: generateEvenMapPool(generationType === "everyOther")
}}
>
Go!
</Button>
</div>
<div style={{ paddingTop: "5px" }}>
{generatedMaps.length !== 0 ? (
<div>
<textarea
rows={generatedMaps.length + 2}
style={{ resize: "none", width: "300px" }}
readOnly
value={generatedMaps.join("\n")}
ref={textAreaRef}
/>
<br />
{document.queryCommandSupported("copy") && (
<div>
<Button onClick={copyToClipboard}>Copy maps to clipboard</Button>
<Button onClick={copyToClipboard}>
Copy maps to clipboard
</Button>
{copySuccess}
</div>
}
</div> :
null
}
</div>
</div>
)}
</div>
) : null}
</div>
</div>
</Segment>
)
}
export default MapListGenerator
export default MapListGenerator

View File

@ -66,13 +66,12 @@ const MapPlanner = ({ setMenuSelection }) => {
const [canUndo, setCanUndo] = useState(false)
const [canRedo, setCanRedo] = useState(false)
const [text, setText] = useState('')
const [bg, setBg] = useState(reef)
const [bg, setBg] = useState(null)
const [uploadError, setUploadError] = useState(null)
const [controlledValue, setControlledValue] = useState(defaultValue)
useEffect(() => {
if (!sketch) return
sketch.setBackgroundFromDataUrl(bg)
setMenuSelection('plans')
document.title = 'Planner - sendou.ink'
}, [sketch, setMenuSelection, bg])

View File

@ -1,39 +1,48 @@
import React, { useEffect, useState } from 'react'
import { useQuery } from 'react-apollo-hooks'
import { rotationData } from '../../graphql/queries/rotationData'
import { maplists } from '../../graphql/queries/maplists'
import { Loader, Segment, Header, Grid, Image, Popup, Button, Checkbox } from 'semantic-ui-react'
import React, { useEffect, useState } from "react"
import { useQuery } from "react-apollo-hooks"
import { rotationData } from "../../graphql/queries/rotationData"
import { maplists } from "../../graphql/queries/maplists"
import {
Loader,
Segment,
Header,
Grid,
Image,
Popup,
Button,
Checkbox
} from "semantic-ui-react"
import arowana_mall from '../img/mapIcons/arowana_mall.png'
import anchov_games from '../img/mapIcons/ancho-v_games.png'
import blackbelly_skatepark from '../img/mapIcons/blackbelly_skatepark.png'
import camp_triggerfish from '../img/mapIcons/camp_triggerfish.png'
import goby_arena from '../img/mapIcons/goby_arena.png'
import humpback_pump_track from '../img/mapIcons/humpback_pump_track.png'
import inkblot_art_academy from '../img/mapIcons/inkblot_art_academy.png'
import kelp_dome from '../img/mapIcons/kelp_dome.png'
import makomart from '../img/mapIcons/makomart.png'
import manta_maria from '../img/mapIcons/manta_maria.png'
import moray_towers from '../img/mapIcons/moray_towers.png'
import musselforge_fitness from '../img/mapIcons/musselforge_fitness.png'
import new_albacore_hotel from '../img/mapIcons/new_albacore_hotel.png'
import piranha_pit from '../img/mapIcons/piranha_pit.png'
import port_mackerel from '../img/mapIcons/port_mackerel.png'
import shellendorf_institute from '../img/mapIcons/shellendorf_institute.png'
import skipper_pavilion from '../img/mapIcons/skipper_pavilion.png'
import snapper_canal from '../img/mapIcons/snapper_canal.png'
import starfish_mainstage from '../img/mapIcons/starfish_mainstage.png'
import sturgeon_shipyard from '../img/mapIcons/sturgeon_shipyard.png'
import the_reef from '../img/mapIcons/the_reef.png'
import wahoo_world from '../img/mapIcons/wahoo_world.png'
import walleye_warehouse from '../img/mapIcons/walleye_warehouse.png'
import rankedIcon from '../img/modeIcons/ranked.png'
import regularIcon from '../img/modeIcons/regular.png'
import leagueIcon from '../img/modeIcons/league.png'
import szIcon from '../img/modeIcons/sz.png'
import tcIcon from '../img/modeIcons/tc.png'
import rmIcon from '../img/modeIcons/rm.png'
import cbIcon from '../img/modeIcons/cb.png'
import arowana_mall from "../img/mapIcons/arowana_mall.png"
import anchov_games from "../img/mapIcons/ancho-v_games.png"
import blackbelly_skatepark from "../img/mapIcons/blackbelly_skatepark.png"
import camp_triggerfish from "../img/mapIcons/camp_triggerfish.png"
import goby_arena from "../img/mapIcons/goby_arena.png"
import humpback_pump_track from "../img/mapIcons/humpback_pump_track.png"
import inkblot_art_academy from "../img/mapIcons/inkblot_art_academy.png"
import kelp_dome from "../img/mapIcons/kelp_dome.png"
import makomart from "../img/mapIcons/makomart.png"
import manta_maria from "../img/mapIcons/manta_maria.png"
import moray_towers from "../img/mapIcons/moray_towers.png"
import musselforge_fitness from "../img/mapIcons/musselforge_fitness.png"
import new_albacore_hotel from "../img/mapIcons/new_albacore_hotel.png"
import piranha_pit from "../img/mapIcons/piranha_pit.png"
import port_mackerel from "../img/mapIcons/port_mackerel.png"
import shellendorf_institute from "../img/mapIcons/shellendorf_institute.png"
import skipper_pavilion from "../img/mapIcons/skipper_pavilion.png"
import snapper_canal from "../img/mapIcons/snapper_canal.png"
import starfish_mainstage from "../img/mapIcons/starfish_mainstage.png"
import sturgeon_shipyard from "../img/mapIcons/sturgeon_shipyard.png"
import the_reef from "../img/mapIcons/the_reef.png"
import wahoo_world from "../img/mapIcons/wahoo_world.png"
import walleye_warehouse from "../img/mapIcons/walleye_warehouse.png"
import rankedIcon from "../img/modeIcons/ranked.png"
import regularIcon from "../img/modeIcons/regular.png"
import leagueIcon from "../img/modeIcons/league.png"
import szIcon from "../img/modeIcons/sz.png"
import tcIcon from "../img/modeIcons/tc.png"
import rmIcon from "../img/modeIcons/rm.png"
import cbIcon from "../img/modeIcons/cb.png"
const mapIcons = {
"Arowana Mall": arowana_mall,
@ -64,31 +73,40 @@ const mapIcons = {
const modeIcons = {
"Splat Zones": szIcon,
"Tower Control": tcIcon,
"Rainmaker": rmIcon,
Rainmaker: rmIcon,
"Clam Blitz": cbIcon
}
const modeShort = {
"Splat Zones": "sz",
"Tower Control": "tc",
"Rainmaker": "rm",
Rainmaker: "rm",
"Clam Blitz": "cb"
}
const Rotations = ({ setMenuSelection }) => {
const { data, error, loading } = useQuery(rotationData)
const monthly = useQuery(maplists)
const [ rotation, setRotation ] = useState([])
const [ preferences, setPreferences ] = useState({sz: {}, tc: {}, rm: {}, cb: {}})
const [ show, setShow ] = useState(false)
const [ currentTime, setCurrentTime ] = useState(new Date(Math.floor(Date.now() / 1000)))
const [rotation, setRotation] = useState([])
const [preferences, setPreferences] = useState({
sz: {},
tc: {},
rm: {},
cb: {}
})
const [show, setShow] = useState(false)
const [currentTime, setCurrentTime] = useState(
new Date(Math.floor(Date.now() / 1000))
)
useEffect(() => {
if (loading || monthly.loading) {
return
}
const rotationPreferencesFromDb = window.localStorage.getItem('rotationPreferences')
const rotationPreferencesFromDb = window.localStorage.getItem(
"rotationPreferences"
)
if (rotationPreferencesFromDb) {
setPreferences(JSON.parse(rotationPreferencesFromDb))
}
@ -106,15 +124,24 @@ const Rotations = ({ setMenuSelection }) => {
}, [currentTime])
useEffect(() => {
setMenuSelection('rotations')
document.title = 'Rotations - sendou.ink'
setMenuSelection("rotations")
document.title = "Rotations - sendou.ink"
}, [setMenuSelection])
if (loading || monthly.loading || rotation.length === 0) {
return <div style={{"paddingTop": "25px", "paddingBottom": "20000px"}} ><Loader active inline='centered' /></div>
return (
<div style={{ paddingTop: "25px", paddingBottom: "20000px" }}>
<Loader active inline="centered" />
</div>
)
}
if (error || monthly.error) {
return <div style={{"color": "red"}}>{error ? error.message : null} {monthly.error? monthly.error.message : null}</div>
return (
<div style={{ color: "red" }}>
{error ? error.message : null}{" "}
{monthly.error ? monthly.error.message : null}
</div>
)
}
const monthlyMaps = monthly.data.maplists[0]
@ -122,106 +149,121 @@ const Rotations = ({ setMenuSelection }) => {
return (
<div>
<div>
<Button icon='cog' onClick={() => setShow(!show)}/>
{show ?
<div>
Uncheck a map in the ranked rotation to mark it as a disliked map.
<Grid relaxed columns={4}>
<Grid.Column>
<b>Splat Zones</b>
{monthlyMaps.sz.map(m => {
return (
<div key={m}>
<Checkbox
label={m}
checked={!preferences.sz[m]}
onChange={() => {
preferences.sz[m] = !preferences.sz[m]
window.localStorage.setItem(
'rotationPreferences', JSON.stringify(preferences)
)
}}
/><br/>
</div>
)
})}
</Grid.Column>
<Grid.Column>
<b>Tower Control</b>
{monthlyMaps.tc.map(m => {
return (
<div key={m}>
<Checkbox
label={m}
checked={!preferences.tc[m]}
onChange={() => {
preferences.tc[m] = !preferences.tc[m]
window.localStorage.setItem(
'rotationPreferences', JSON.stringify(preferences)
)
}}
/><br/>
</div>
)
})}
</Grid.Column>
<Grid.Column>
<b>Rainmaker</b>
{monthlyMaps.rm.map(m => {
return (
<div key={m}>
<Checkbox
label={m}
checked={!preferences.rm[m]}
onChange={() => {
preferences.rm[m] = !preferences.rm[m]
window.localStorage.setItem(
'rotationPreferences', JSON.stringify(preferences)
)
}}
/><br/>
</div>
)
})}
</Grid.Column>
<Grid.Column>
<b>Clam Blitz</b>
{monthlyMaps.cb.map(m => {
return (
<div key={m}>
<Checkbox
label={m}
checked={!preferences.cb[m]}
onChange={() => {
preferences.cb[m] = !preferences.cb[m]
window.localStorage.setItem(
'rotationPreferences', JSON.stringify(preferences)
)
}}
/><br/>
</div>
)
})}
</Grid.Column>
</Grid>
</div>
: null}
<Button icon="cog" onClick={() => setShow(!show)} />
{show ? (
<Segment>
<div style={{"padding": "5px"}}>
Uncheck a map in the ranked rotation to mark it as a disliked map.
<Grid relaxed columns={4}>
<Grid.Column>
<b>Splat Zones</b>
{monthlyMaps.sz.map(m => {
return (
<div key={m}>
<Checkbox
label={m}
checked={!preferences.sz[m]}
onChange={() => {
preferences.sz[m] = !preferences.sz[m]
window.localStorage.setItem(
"rotationPreferences",
JSON.stringify(preferences)
)
}}
/>
<br />
</div>
)
})}
</Grid.Column>
<Grid.Column>
<b>Tower Control</b>
{monthlyMaps.tc.map(m => {
return (
<div key={m}>
<Checkbox
label={m}
checked={!preferences.tc[m]}
onChange={() => {
preferences.tc[m] = !preferences.tc[m]
window.localStorage.setItem(
"rotationPreferences",
JSON.stringify(preferences)
)
}}
/>
<br />
</div>
)
})}
</Grid.Column>
<Grid.Column>
<b>Rainmaker</b>
{monthlyMaps.rm.map(m => {
return (
<div key={m}>
<Checkbox
label={m}
checked={!preferences.rm[m]}
onChange={() => {
preferences.rm[m] = !preferences.rm[m]
window.localStorage.setItem(
"rotationPreferences",
JSON.stringify(preferences)
)
}}
/>
<br />
</div>
)
})}
</Grid.Column>
<Grid.Column>
<b>Clam Blitz</b>
{monthlyMaps.cb.map(m => {
return (
<div key={m}>
<Checkbox
label={m}
checked={!preferences.cb[m]}
onChange={() => {
preferences.cb[m] = !preferences.cb[m]
window.localStorage.setItem(
"rotationPreferences",
JSON.stringify(preferences)
)
}}
/>
<br />
</div>
)
})}
</Grid.Column>
</Grid>
</div>
</Segment>
) : null}
</div>
<div>
{rotation.gachi.map((r, i) => { //league maps references like so: rotation.league[i] - turf: rotation.normal[i]
{rotation.gachi.map((r, i) => {
//league maps references like so: rotation.league[i] - turf: rotation.normal[i]
if (r.end_time < currentTime) {
return null
}
const timeUntil = r.start_time - currentTime
const hours = Math.floor(timeUntil / 3600)
const minutes = Math.floor((timeUntil % 3600) / 60)
const seconds = timeUntil % 3600 % 60
let header = 'Current'
const seconds = (timeUntil % 3600) % 60
let header = "Current"
if (hours >= 2) {
if (minutes === 1) {
header = `In ${hours} hours ${minutes} minute (${new Date(r.start_time * 1000).toLocaleString('en-GB')})`
header = `In ${hours} hours ${minutes} minute (${new Date(
r.start_time * 1000
).toLocaleString("en-GB")})`
} else {
header = `In ${hours} hours ${minutes} minutes (${new Date(r.start_time * 1000).toLocaleString('en-GB')})`
header = `In ${hours} hours ${minutes} minutes (${new Date(
r.start_time * 1000
).toLocaleString("en-GB")})`
}
} else if (hours > 0) {
if (minutes === 1) {
@ -237,97 +279,175 @@ const Rotations = ({ setMenuSelection }) => {
}
}
return (
<div key={r.start_time} style={{"paddingTop": "25px"}}>
<Header size='small' disabled={preferences[modeShort[r.rule.name]][r.stage_a.name] && preferences[modeShort[r.rule.name]][r.stage_b.name]}>{header}</Header>
<Segment disabled={preferences[modeShort[r.rule.name]][r.stage_a.name] && preferences[modeShort[r.rule.name]][r.stage_b.name]} raised>
<Grid columns={3} stackable>
<Grid.Row>
<Grid.Column>
<Header textAlign='center'>
<Image src={leagueIcon} style={{"paddingBottom": "10px"}}/> LEAGUE
<Header.Subheader style={{"paddingTop": "10px"}}><Image size="mini" src={modeIcons[rotation.league[i].rule.name]} avatar/><b>{rotation.league[i].rule.name}</b></Header.Subheader>
</Header>
</Grid.Column>
<Grid.Column>
<Header textAlign='center'>
<Image src={rankedIcon} style={{"paddingBottom": "10px"}}/> RANKED
<Header.Subheader style={{"paddingTop": "10px"}}><Image size="mini" src={modeIcons[r.rule.name]} avatar/><b>{r.rule.name}</b></Header.Subheader>
</Header>
</Grid.Column>
<Grid.Column>
<Header textAlign='center'>
<Image src={regularIcon} style={{"paddingBottom": "10px"}}/>REGULAR
<Header.Subheader style={{"paddingTop": "10px"}}></Header.Subheader> {/*this is needed for formatting reasons.*/}
</Header>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column>
<div style={{display: 'flex', justifyContent:'center', alignItems:'center'}}>
<Popup
trigger={<Image
src={mapIcons[rotation.league[i].stage_a.name]}
rounded
/>}
content={rotation.league[i].stage_a.name}
basic
/>
<Popup
trigger={<Image
src={mapIcons[rotation.league[i].stage_b.name]}
rounded
/>}
content={rotation.league[i].stage_b.name}
basic
/>
</div>
</Grid.Column>
<Grid.Column>
<div style={{display: 'flex', justifyContent:'center', alignItems:'center'}}>
<Popup
trigger={<Image
src={mapIcons[r.stage_a.name]}
rounded
disabled={preferences[modeShort[r.rule.name]][r.stage_a.name]}
/>}
content={r.stage_a.name}
basic
/>
<Popup
trigger={<Image
src={mapIcons[r.stage_b.name]}
rounded
disabled={preferences[modeShort[r.rule.name]][r.stage_b.name]}
/>}
content={r.stage_b.name}
basic
/>
</div>
</Grid.Column>
<Grid.Column>
<div style={{display: 'flex', justifyContent:'center', alignItems:'center'}}>
<Popup
trigger={<Image
src={mapIcons[rotation.regular[i].stage_a.name]}
rounded
/>}
content={rotation.regular[i].stage_a.name}
basic
/>
<Popup
trigger={<Image
src={mapIcons[rotation.regular[i].stage_b.name]}
rounded
/>}
content={rotation.regular[i].stage_b.name}
basic
/>
</div>
</Grid.Column>
</Grid.Row>
</Grid>
<div key={r.start_time} style={{ paddingTop: "25px" }}>
<Header
size="small"
disabled={
preferences[modeShort[r.rule.name]][r.stage_a.name] &&
preferences[modeShort[r.rule.name]][r.stage_b.name]
}
>
{header}
</Header>
<Segment
disabled={
preferences[modeShort[r.rule.name]][r.stage_a.name] &&
preferences[modeShort[r.rule.name]][r.stage_b.name]
}
raised
>
<Grid columns={3} stackable>
<Grid.Row>
<Grid.Column>
<Header textAlign="center">
<Image
src={leagueIcon}
style={{ paddingBottom: "10px" }}
/>{" "}
LEAGUE
<Header.Subheader style={{ paddingTop: "10px" }}>
<Image
size="mini"
src={modeIcons[rotation.league[i].rule.name]}
avatar
/>
<b>{rotation.league[i].rule.name}</b>
</Header.Subheader>
</Header>
</Grid.Column>
<Grid.Column>
<Header textAlign="center">
<Image
src={rankedIcon}
style={{ paddingBottom: "10px" }}
/>{" "}
RANKED
<Header.Subheader style={{ paddingTop: "10px" }}>
<Image
size="mini"
src={modeIcons[r.rule.name]}
avatar
/>
<b>{r.rule.name}</b>
</Header.Subheader>
</Header>
</Grid.Column>
<Grid.Column>
<Header textAlign="center">
<Image
src={regularIcon}
style={{ paddingBottom: "10px" }}
/>
REGULAR
<Header.Subheader style={{ paddingTop: "10px" }} />{" "}
{/*this is needed for formatting reasons.*/}
</Header>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center"
}}
>
<Popup
trigger={
<Image
src={mapIcons[rotation.league[i].stage_a.name]}
rounded
/>
}
content={rotation.league[i].stage_a.name}
basic
/>
<Popup
trigger={
<Image
src={mapIcons[rotation.league[i].stage_b.name]}
rounded
/>
}
content={rotation.league[i].stage_b.name}
basic
/>
</div>
</Grid.Column>
<Grid.Column>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center"
}}
>
<Popup
trigger={
<Image
src={mapIcons[r.stage_a.name]}
rounded
disabled={
preferences[modeShort[r.rule.name]][
r.stage_a.name
]
}
/>
}
content={r.stage_a.name}
basic
/>
<Popup
trigger={
<Image
src={mapIcons[r.stage_b.name]}
rounded
disabled={
preferences[modeShort[r.rule.name]][
r.stage_b.name
]
}
/>
}
content={r.stage_b.name}
basic
/>
</div>
</Grid.Column>
<Grid.Column>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center"
}}
>
<Popup
trigger={
<Image
src={mapIcons[rotation.regular[i].stage_a.name]}
rounded
/>
}
content={rotation.regular[i].stage_a.name}
basic
/>
<Popup
trigger={
<Image
src={mapIcons[rotation.regular[i].stage_b.name]}
rounded
/>
}
content={rotation.regular[i].stage_b.name}
basic
/>
</div>
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
</div>
)
})}
@ -336,4 +456,4 @@ const Rotations = ({ setMenuSelection }) => {
)
}
export default Rotations
export default Rotations

View File

@ -1,149 +0,0 @@
import React, { useState } from 'react'
import { Segment, Header, Divider, Popup, Flag, Image, Button, Table } from 'semantic-ui-react'
import { Link } from 'react-router-dom'
import weaponDict from '../../utils/english_internal.json'
import szIcon from '../img/modeIcons/sz.png'
import tcIcon from '../img/modeIcons/tc.png'
import rmIcon from '../img/modeIcons/rm.png'
import cbIcon from '../img/modeIcons/cb.png'
const Tournament = ({ tournament }) => {
const [expanded, setExpanded] = useState(false)
const t = tournament
let date = null
let day = null
let month = null
let year = null
let alphaCount = 0
let bravoCount = 0
const modeIcons = {
"Splat Zones": szIcon,
"Tower Control": tcIcon,
"Rainmaker": rmIcon,
"Clam Blitz": cbIcon
}
if (t.timestamp) {
const monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
]
date = new Date(t.timestamp * 1000)
day = date.getDate()
month = monthNames[date.getMonth()]
year = date.getFullYear()
}
return (
<div>
<Segment raised compact padded>
<Header>
{t.name}
{t.japanese ? <><br/>{<Flag name='jp'/>}</> : null}
<Header.Subheader>{t.timestamp ? <>{`${day} ${month} ${year}`}</> : null}{t.patch ? <> | Patch {t.patch}</>: null}</Header.Subheader>
<Header.Subheader>
{t.bracket ? <a href={t.bracket}>Bracket</a> : null}
{t.vod ? <> | <a href={t.vod}>VoD</a></> : null}<br />
</Header.Subheader>
<Header.Subheader>{t.team_count ? <>{t.team_count} teams</> : null}</Header.Subheader>
</Header>
<Divider />
<b style={{'padding': '3px'}}>{t.alpha_team_name}</b><br/>
{t.alpha_team_players.map((p, i) => {
if (t.alpha_team[i]) return <span style={{"padding": "3px"}}><Link to={`/xsearch/p/${t.alpha_team[i]}`}>{p}</Link></span>
return (
<span style={{"padding": "3px"}}>{p}</span>
)
})}<br/>
<span style={{'padding': '3px'}}>vs.</span><br/>
<b style={{'padding': '3px'}}>{t.bravo_team_name}</b><br/>
{t.bravo_team_players.map((p, i) => {
if (t.bravo_team[i]) return <span style={{"padding": "3px"}}><Link to={`/xsearch/p/${t.bravo_team[i]}`}>{p}</Link></span>
return (
<span style={{"padding": "3px"}}>{p}</span>
)
})}
<div style={{'paddingTop': '3px'}}>
{t.weapons.map((w, i) => {
return (
<span key={w}>
<Popup trigger={
<Image inline src={process.env.PUBLIC_URL + `/wpnSmall/Wst_${weaponDict[w]}.png`}></Image>
}>
{w}
</Popup>
{i % 10 === 0 && i !== 0 ? <br/> : null}
</span>
)
})}
</div>
<div style={{'paddingTop': '10px'}}>
<Button onClick={() => setExpanded(!expanded)}>
{expanded ? 'Hide battles' : 'Show battles'}
</Button>
</div>
{expanded ?
<div style={{"paddingTop": "10px"}}>
<Table basic='very' celled collapsing>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Mode</Table.HeaderCell>
<Table.HeaderCell>Map</Table.HeaderCell>
<Table.HeaderCell>{t.alpha_team_name}<br/>{t.bravo_team_name}</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{t.rounds.map(r => {
if (r.winner === 'ALPHA') alphaCount++
if (r.winner === 'BRAVO') bravoCount++
return (
<Table.Row>
<Table.Cell>
<Image size="mini" src={modeIcons[r.mode]} />
</Table.Cell>
<Table.Cell>
{r.map}
</Table.Cell>
<Table.Cell>
{r.alpha_weapons.map((w, i) => {
return (
<Popup trigger={
<Image
style={{"maxWidth": "100px", "height": "auto", "width": "auto"}}
inline
src={process.env.PUBLIC_URL + `/wpnSmall/Wst_${weaponDict[w]}.png`} />}>
{t.alpha_team_players[i]}
</Popup>
)
})} {r.winner === 'ALPHA' ? '🏆' : null}<br/>
{r.bravo_weapons.map((w, i) => {
return (
<Popup trigger={
<Image
style={{"maxWidth": "100px", "height": "auto", "width": "auto"}}
inline
src={process.env.PUBLIC_URL + `/wpnSmall/Wst_${weaponDict[w]}.png`} />}>
{t.bravo_team_players[i]}
</Popup>
)
})} {r.winner === 'BRAVO' ? '🏆' : null}
</Table.Cell>
</Table.Row>
)
})}
<Table.Row>
<Table.Cell></Table.Cell>
<Table.Cell>
{t.alpha_team_name} {alphaCount} - {bravoCount} {t.bravo_team_name}
</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
</div>
: null}
</Segment>
</div>
)
}
export default Tournament

View File

@ -1,5 +1,5 @@
import React from 'react'
import { Table, Icon, Popup } from 'semantic-ui-react'
import { Table, Icon, Popup, Segment } from 'semantic-ui-react'
import { useQuery } from 'react-apollo-hooks'
import { Loader } from 'semantic-ui-react'
import { Link } from 'react-router-dom'
@ -20,40 +20,44 @@ const FlexLeaderboard = ({ setActiveItem }) => {
const leaderboard = result.data['topFlex']
return (
<Table style={{"paddingTop": "5px", "paddingBottom": "5px"}} basic='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell></Table.HeaderCell>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>
Flex Power
<Popup
trigger={<Icon name='question circle'/>}
content="Flex Power is the amount of unique weapons the player has reached top 500 with. Different kits count as unique weapons but reskins (e.g. Tentatek & Octoshot) don't."
position='bottom center'
/>
</Table.HeaderCell>
<Table.HeaderCell>Total Power</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Segment>
<div style={{"padding": "5px"}}>
<Table basic='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell></Table.HeaderCell>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>
Flex Power
<Popup
trigger={<Icon name='question circle'/>}
content="Flex Power is the amount of unique weapons the player has reached top 500 with. Different kits count as unique weapons but reskins (e.g. Tentatek & Octoshot) don't."
position='bottom center'
/>
</Table.HeaderCell>
<Table.HeaderCell>Total Power</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{leaderboard.map((player, index) =>
<Table.Row key={player.id}>
<Table.Cell>{index + 1}</Table.Cell>
<Table.Cell><Link to={`/xsearch/p/${player.unique_id}`} style={{"color": "black"}}><u>{player.alias ? player.alias : player.name}</u></Link>
{player.twitter ?
<a href={`https://twitter.com/${player.twitter}`}><Icon style={{"paddingLeft": "5px"}} name="twitter"/></a>
: null}
</Table.Cell>
<Table.Cell>{player.weaponsCount}</Table.Cell>
<Table.Cell>
{player.topTotalScore}
</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table>
<Table.Body>
{leaderboard.map((player, index) =>
<Table.Row key={player.id}>
<Table.Cell>{index + 1}</Table.Cell>
<Table.Cell><Link to={`/xsearch/p/${player.unique_id}`} style={{"color": "black"}}><u>{player.alias ? player.alias : player.name}</u></Link>
{player.twitter ?
<a href={`https://twitter.com/${player.twitter}`}><Icon style={{"paddingLeft": "5px"}} name="twitter"/></a>
: null}
</Table.Cell>
<Table.Cell>{player.weaponsCount}</Table.Cell>
<Table.Cell>
{player.topTotalScore}
</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table>
</div>
</Segment>
)
}

View File

@ -1,5 +1,5 @@
import React from 'react'
import { Table, Icon, Popup } from 'semantic-ui-react'
import { Table, Icon, Popup, Segment } from 'semantic-ui-react'
import { useQuery } from 'react-apollo-hooks'
import { Loader } from 'semantic-ui-react'
import { Link } from 'react-router-dom'
@ -20,40 +20,44 @@ const WeaponLeaderboard = ({ query, queryName, scoreField, weaponsField, setActi
const leaderboard = result.data[queryName]
return (
<Table style={{"paddingTop": "5px", "paddingBottom": "5px"}} basic='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell></Table.HeaderCell>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>
{weaponsField.substring(3)} Power
<Popup
trigger={<Icon name='question circle'/>}
content='Power is the average of the top 4 best X powers. Hover over the weapon to receive information about that placement.'
position='bottom center'
/>
</Table.HeaderCell>
<Table.HeaderCell>Weapons</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Segment>
<div style={{"padding": "5px"}}>
<Table basic='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell></Table.HeaderCell>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>
{weaponsField.substring(3)} Power
<Popup
trigger={<Icon name='question circle'/>}
content='Power is the average of the top 4 best X powers. Hover over the weapon to receive information about that placement.'
position='bottom center'
/>
</Table.HeaderCell>
<Table.HeaderCell>Weapons</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{leaderboard.map((player, index) =>
<Table.Row key={player.id}>
<Table.Cell>{index + 1}</Table.Cell>
<Table.Cell><Link to={`/xsearch/p/${player.unique_id}`} style={{"color": "black"}}><u>{player.alias ? player.alias : player.name}</u></Link>
{player.twitter ?
<a href={`https://twitter.com/${player.twitter}`}><Icon style={{"paddingLeft": "5px"}} name="twitter"/></a>
: null}
</Table.Cell>
<Table.Cell>{player[scoreField]}</Table.Cell>
<Table.Cell>
<FourWeapons weapons={player[weaponsField]} />
</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table>
<Table.Body>
{leaderboard.map((player, index) =>
<Table.Row key={player.id}>
<Table.Cell>{index + 1}</Table.Cell>
<Table.Cell><Link to={`/xsearch/p/${player.unique_id}`} style={{"color": "black"}}><u>{player.alias ? player.alias : player.name}</u></Link>
{player.twitter ?
<a href={`https://twitter.com/${player.twitter}`}><Icon style={{"paddingLeft": "5px"}} name="twitter"/></a>
: null}
</Table.Cell>
<Table.Cell>{player[scoreField]}</Table.Cell>
<Table.Cell>
<FourWeapons weapons={player[weaponsField]} />
</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table>
</div>
</Segment>
)
}

View File

@ -1,18 +1,20 @@
import React, { useEffect, useState } from 'react'
import { useQuery } from 'react-apollo-hooks'
import { Loader, Header, Image, Icon, List } from 'semantic-ui-react'
import { Link } from 'react-router-dom'
import React, { useEffect, useState } from "react"
import { useQuery } from "react-apollo-hooks"
import { Loader, Header, Image, Icon, List, Segment } from "semantic-ui-react"
import { Link } from "react-router-dom"
import { playerInfo } from '../../graphql/queries/playerInfo'
import TopPlacementTable from './TopPlacementsTable'
import WpnPlayedTable from './WpnPlayedTable'
import MonthsTable from './MonthsTable'
import { playerInfo } from "../../graphql/queries/playerInfo"
import TopPlacementTable from "./TopPlacementsTable"
import WpnPlayedTable from "./WpnPlayedTable"
import MonthsTable from "./MonthsTable"
const InfoPlayer = ({ uid, setMenuSelection, twitter }) => {
let searchVariables = {}
if (uid) searchVariables = { uid }
if (twitter) searchVariables = { twitter }
const { data, error, loading } = useQuery(playerInfo, {variables: searchVariables})
const { data, error, loading } = useQuery(playerInfo, {
variables: searchVariables
})
const [top, setTop] = useState([])
useEffect(() => {
@ -20,8 +22,9 @@ const InfoPlayer = ({ uid, setMenuSelection, twitter }) => {
return
}
if (setMenuSelection) { //if we're on /xsearch
setMenuSelection('search')
if (setMenuSelection) {
//if we're on /xsearch
setMenuSelection("search")
}
if (data.playerInfo) {
const placements = data.playerInfo.placements
@ -29,84 +32,178 @@ const InfoPlayer = ({ uid, setMenuSelection, twitter }) => {
//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
}
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
}))
return acc
},
{
szX: null,
szTop: null,
tcX: null,
tcTop: null,
rmX: null,
rmTop: null,
cbX: null,
cbTop: null
}
)
)
}
}, [data, loading, setMenuSelection])
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>
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>
)
if (!uid && !twitter) return addXRankHelp
if (loading) {
return <div style={{"paddingTop": "25px", "paddingBottom": "20000px"}} ><Loader active inline='centered' /></div>
return (
<div style={{ paddingTop: "25px", paddingBottom: "20000px" }}>
<Loader active inline="centered" />
</div>
)
}
if (error) {
if (error.message === 'GraphQL error: player not found') {
if (error.message === "GraphQL error: player not found") {
return addXRankHelp
}
return <div style={{"color": "red"}}>{error.message}</div>
return <div style={{ color: "red" }}>{error.message}</div>
}
const playerData = data.playerInfo.player
return (
<div style={{"paddingTop": "20px"}}>
{setMenuSelection ?
<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}
<div style={{ paddingTop: "20px" }}>
{setMenuSelection ? (
<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}
{(setMenuSelection && playerData.discord_id) ? <><Link to={`/u/${playerData.discord_id}`}>User page</Link><br/></> : null}
{playerData.topTotalScore ? <><i>Total Power: {playerData.topTotalScore}</i><br /></> : null}
{playerData.topShooterScore ? <><i>Shooter Power: {playerData.topShooterScore}</i><br /></> : null}
{playerData.topBlasterScore ? <><i>Blaster Power: {playerData.topBlasterScore}</i><br /></> : null}
{playerData.topRollerScore ? <><i>Roller Power: {playerData.topRollerScore}</i><br /></> : null}
{playerData.topChargerScore ? <><i>Charger Power: {playerData.topChargerScore}</i><br /></> : null}
{playerData.topSlosherScore ? <><i>Slosher Power: {playerData.topSlosherScore}</i><br /></> : null}
{playerData.topSplatlingScore ? <><i>Splatling Power: {playerData.topSplatlingScore}</i><br /></> : null}
{playerData.topDualiesScore ? <><i>Dualies Power: {playerData.topDualiesScore}</i><br /></> : null}
{playerData.topBrellaScore ? <><i>Brella Power: {playerData.topBrellaScore}</i><br /></> : null}
<TopPlacementTable top={top} />
<Header dividing style={{"paddingTop": "5px"}}>All Top 500 placements</Header>
<MonthsTable placements={data.playerInfo.placements} />
<Header dividing style={{"paddingTop": "5px"}}>Weapons reached Top 500 with</Header>
<WpnPlayedTable weapons={playerData.weapons} />
) : null}
{setMenuSelection && playerData.discord_id ? (
<>
<Link to={`/u/${playerData.discord_id}`}>User page</Link>
<br />
</>
) : null}
{playerData.topTotalScore ? (
<>
<i>Total Power: {playerData.topTotalScore}</i>
<br />
</>
) : null}
{playerData.topShooterScore ? (
<>
<i>Shooter Power: {playerData.topShooterScore}</i>
<br />
</>
) : null}
{playerData.topBlasterScore ? (
<>
<i>Blaster Power: {playerData.topBlasterScore}</i>
<br />
</>
) : null}
{playerData.topRollerScore ? (
<>
<i>Roller Power: {playerData.topRollerScore}</i>
<br />
</>
) : null}
{playerData.topChargerScore ? (
<>
<i>Charger Power: {playerData.topChargerScore}</i>
<br />
</>
) : null}
{playerData.topSlosherScore ? (
<>
<i>Slosher Power: {playerData.topSlosherScore}</i>
<br />
</>
) : null}
{playerData.topSplatlingScore ? (
<>
<i>Splatling Power: {playerData.topSplatlingScore}</i>
<br />
</>
) : null}
{playerData.topDualiesScore ? (
<>
<i>Dualies Power: {playerData.topDualiesScore}</i>
<br />
</>
) : null}
{playerData.topBrellaScore ? (
<>
<i>Brella Power: {playerData.topBrellaScore}</i>
<br />
</>
) : null}
<Segment compact>
<div style={{"padding": "5px"}}>
<TopPlacementTable 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>
</div>
)
}
export default InfoPlayer
export default InfoPlayer

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'
import { useQuery } from 'react-apollo-hooks'
import { topPlayersOfWeapon } from '../../graphql/queries/topPlayersOfWeapon'
import weaponDictReversed from '../../utils/internal_english.json'
import { Loader, Header, Table, Checkbox } from 'semantic-ui-react'
import { Loader, Header, Table, Checkbox, Segment } from 'semantic-ui-react'
import { withRouter, Link } from 'react-router-dom'
import szIcon from '../img/modeIcons/sz.png'
@ -58,22 +58,24 @@ const InfoWeapon = withRouter(({ history, wpn }) => {
return (
<div>
<div style={{"paddingTop": "20px", "paddingBottom": "20px"}}>
<Header size='large'>
<img
src={`https://raw.githubusercontent.com/Leanny/leanny.github.io/master/splat2/weapons/Wst_${wpn}.png`}
alt={weaponDictReversed[wpn]}
/> {weaponDictReversed[wpn]} Leaderboard
</Header>
<Header size='tiny'>
<b>Times in Top 500:</b>
<img src={modeIcons[1]} style={{"paddingLeft": "10px"}} alt="Splat Zones logo" /> {modeCount[1]}
<img src={modeIcons[2]} style={{"paddingLeft": "10px"}} alt="Tower Control logo" /> {modeCount[2]}
<img src={modeIcons[3]} style={{"paddingLeft": "10px"}} alt="Rainmaker logo" /> {modeCount[3]}
<img src={modeIcons[4]} style={{"paddingLeft": "10px"}} alt="Clam Blitz logo" /> {modeCount[4]}<br/>
Total {modeCount[0]}
</Header>
</div>
<Segment compact>
<div style={{"padding": "5px"}}>
<Header size='large'>
<img
src={`https://raw.githubusercontent.com/Leanny/leanny.github.io/master/splat2/weapons/Wst_${wpn}.png`}
alt={weaponDictReversed[wpn]}
/> {weaponDictReversed[wpn]} Leaderboard
</Header>
<Header size='tiny'>
<b>Times in Top 500:</b>
<img src={modeIcons[1]} style={{"paddingLeft": "10px"}} alt="Splat Zones logo" /> {modeCount[1]}
<img src={modeIcons[2]} style={{"paddingLeft": "10px"}} alt="Tower Control logo" /> {modeCount[2]}
<img src={modeIcons[3]} style={{"paddingLeft": "10px"}} alt="Rainmaker logo" /> {modeCount[3]}
<img src={modeIcons[4]} style={{"paddingLeft": "10px"}} alt="Clam Blitz logo" /> {modeCount[4]}<br/>
Total {modeCount[0]}
</Header>
</div>
</Segment>
<div>
<Checkbox
radio

View File

@ -1,48 +1,369 @@
import React, { useState } from 'react'
import { Dropdown, Button } from 'semantic-ui-react'
import { withRouter } from 'react-router-dom'
import { weapons } from '../../utils/lists'
import weaponDict from '../../utils/english_internal.json'
import React, { useState } from "react"
import { Dropdown, Button } from "semantic-ui-react"
import { withRouter } from "react-router-dom"
import { weapons } from "../../utils/lists"
import weaponDict from "../../utils/english_internal.json"
const WeaponForm = withRouter(({ history, weaponForm, setWeaponForm, showImages=true, push=false, setPage }) => {
return (
<div>
<Dropdown
placeholder='Choose a weapon'
fluid
search
selection
options={weapons.map(w =>
(
{key: w,
text: w,
value: w,
image: showImages ? {src: process.env.PUBLIC_URL + `/wpnSmall/Wst_${weaponDict[w]}.png`} : null
const WeaponForm = withRouter(
({
history,
weaponForm,
setWeaponForm,
showImages = true,
push = false,
setPage,
showSubSpecials = false
}) => {
const subSpecialOptions = [
{
key: "Splat Bomb",
text: "Splat Bomb",
value: "Splat Bomb",
image: showImages
? {
src:
process.env.PUBLIC_URL + `/wpnSmall/Wst_Bomb_Splash.png`
}
)
)
}
onChange={(event, { value }) => {
setWeaponForm(value)
if (push) history.push(`/builds/${value.replace(/ /g, '_')}`)
if (setPage) setPage(1)
}}
value={weaponForm}
/>
</div>
)
})
: null
},
{
key: "Suction Bomb",
text: "Suction Bomb",
value: "Suction Bomb",
image: showImages
? {
src:
process.env.PUBLIC_URL + `/wpnSmall/Wst_Bomb_Suction.png`
}
: null
},
{
key: "Burst Bomb",
text: "Burst Bomb",
value: "Burst Bomb",
image: showImages
? {
src:
process.env.PUBLIC_URL + `/wpnSmall/Wst_Bomb_Quick.png`
}
: null
},
{
key: "Curling Bomb",
text: "Curling Bomb",
value: "Curling Bomb",
image: showImages
? {
src:
process.env.PUBLIC_URL + `/wpnSmall/Wst_Bomb_Curling.png`
}
: null
},
{
key: "Autobomb",
text: "Autobomb",
value: "Autobomb",
image: showImages
? {
src: process.env.PUBLIC_URL + `/wpnSmall/Wst_Bomb_Robo.png`
}
: null
},
{
key: "Ink Mine",
text: "Ink Mine",
value: "Ink Mine",
image: showImages
? {
src: process.env.PUBLIC_URL + `/wpnSmall/Wst_TimerTrap.png`
}
: null
},
{
key: "Toxic Mist",
text: "Toxic Mist",
value: "Toxic Mist",
image: showImages
? {
src: process.env.PUBLIC_URL + `/wpnSmall/Wst_PoisonFog.png`
}
: null
},
{
key: "Point Sensor",
text: "Point Sensor",
value: "Point Sensor",
image: showImages
? {
src:
process.env.PUBLIC_URL + `/wpnSmall/Wst_PointSensor.png`
}
: null
},
{
key: "Splash Wall",
text: "Splash Wall",
value: "Splash Wall",
image: showImages
? {
src: process.env.PUBLIC_URL + `/wpnSmall/Wst_Shield.png`
}
: null
},
{
key: "Sprinkler",
text: "Sprinkler",
value: "Sprinkler",
image: showImages
? {
src: process.env.PUBLIC_URL + `/wpnSmall/Wst_Sprinkler.png`
}
: null
},
{
key: "Squid Beakon",
text: "Squid Beakon",
value: "Squid Beakon",
image: showImages
? {
src: process.env.PUBLIC_URL + `/wpnSmall/Wst_Flag.png`
}
: null
},
{
key: "Fizzy Bomb",
text: "Fizzy Bomb",
value: "Fizzy Bomb",
image: showImages
? {
src: process.env.PUBLIC_URL + `/wpnSmall/Wst_Bomb_Piyo.png`
}
: null
},
{
key: "Torpedo",
text: "Torpedo",
value: "Torpedo",
image: showImages
? {
src: process.env.PUBLIC_URL + `/wpnSmall/Wst_Bomb_Tako.png`
}
: null
},
{
key: "Tenta Missiles",
text: "Tenta Missiles",
value: "Tenta Missiles",
image: showImages
? {
src:
process.env.PUBLIC_URL + `/wpnSmall/Wst_SuperMissile.png`
}
: null
},
{
key: "Sting Ray",
text: "Sting Ray",
value: "Sting Ray",
image: showImages
? {
src:
process.env.PUBLIC_URL + `/wpnSmall/Wst_WaterCutter.png`
}
: null
},
{
key: "Inkjet",
text: "Inkjet",
value: "Inkjet",
image: showImages
? {
src: process.env.PUBLIC_URL + `/wpnSmall/Wst_Jetpack.png`
}
: null
},
{
key: "Splashdown",
text: "Splashdown",
value: "Splashdown",
image: showImages
? {
src:
process.env.PUBLIC_URL + `/wpnSmall/Wst_SuperLanding.png`
}
: null
},
{
key: "Ink Armor",
text: "Ink Armor",
value: "Ink Armor",
image: showImages
? {
src:
process.env.PUBLIC_URL + `/wpnSmall/Wst_SuperArmor.png`
}
: null
},
{
key: "Autobomb Launcher",
text: "Autobomb Launcher",
value: "Autobomb Launcher",
image: showImages
? {
src:
process.env.PUBLIC_URL + `/wpnSmall/Wst_LauncherRobo.png`
}
: null
},
{
key: "Burst-Bomb Launcher",
text: "Burst-Bomb Launcher",
value: "Burst-Bomb Launcher",
image: showImages
? {
src:
process.env.PUBLIC_URL +
`/wpnSmall/Wst_LauncherQuick.png`
}
: null
},
{
key: "Curling-Bomb Launcher",
text: "Curling-Bomb Launcher",
value: "Curling-Bomb Launcher",
image: showImages
? {
src:
process.env.PUBLIC_URL +
`/wpnSmall/Wst_LauncherCurling.png`
}
: null
},
{
key: "Splat-Bomb Launcher",
text: "Splat-Bomb Launcher",
value: "Splat-Bomb Launcher",
image: showImages
? {
src:
process.env.PUBLIC_URL +
`/wpnSmall/Wst_LauncherSplash.png`
}
: null
},
{
key: "Suction-Bomb Launcher",
text: "Suction-Bomb Launcher",
value: "Suction-Bomb Launcher",
image: showImages
? {
src:
process.env.PUBLIC_URL +
`/wpnSmall/Wst_LauncherSuction.png`
}
: null
},
{
key: "Ink Storm",
text: "Ink Storm",
value: "Ink Storm",
image: showImages
? {
src: process.env.PUBLIC_URL + `/wpnSmall/Wst_RainCloud.png`
}
: null
},
{
key: "Baller",
text: "Baller",
value: "Baller",
image: showImages
? {
src: process.env.PUBLIC_URL + `/wpnSmall/Wst_AquaBall.png`
}
: null
},
{
key: "Bubble Blower",
text: "Bubble Blower",
value: "Bubble Blower",
image: showImages
? {
src:
process.env.PUBLIC_URL + `/wpnSmall/Wst_SuperBubble.png`
}
: null
},
{
key: "Booyah Bomb",
text: "Booyah Bomb",
value: "Booyah Bomb",
image: showImages
? {
src: process.env.PUBLIC_URL + `/wpnSmall/Wst_SuperBall.png`
}
: null
},
{
key: "Ultra Stamp",
text: "Ultra Stamp",
value: "Ultra Stamp",
image: showImages
? {
src:
process.env.PUBLIC_URL + `/wpnSmall/Wst_SuperStamp.png`
}
: null
}
]
let options = weapons.map(w => ({
key: w,
text: w,
value: w,
image: showImages
? { src: process.env.PUBLIC_URL + `/wpnSmall/Wst_${weaponDict[w]}.png` }
: null
}))
if (showSubSpecials) options = options.concat(subSpecialOptions)
return (
<div>
<Dropdown
placeholder="Choose a weapon"
fluid
search
selection
options={options}
onChange={(event, { value }) => {
setWeaponForm(value)
if (push) history.push(`/builds/${value.replace(/ /g, "_")}`)
if (setPage) setPage(1)
}}
value={weaponForm}
/>
</div>
)
}
)
export const WeaponFormWithButton = withRouter(({ history }) => {
const [weaponForm, setWeaponForm] = useState('')
const [weaponForm, setWeaponForm] = useState("")
return (
<div>
<WeaponForm weaponForm={weaponForm} setWeaponForm={setWeaponForm} />
<div style={{"paddingTop": "13px"}}>
<Button disabled={weaponForm === ''} onClick={() => history.push(`/xsearch/w/${weaponDict[weaponForm].replace(/_/g, '-')}`)}>Search for a weapon</Button>
<div style={{ paddingTop: "13px" }}>
<Button
disabled={weaponForm === ""}
onClick={() =>
history.push(
`/xsearch/w/${weaponDict[weaponForm].replace(/_/g, "-")}`
)
}
>
Search for a weapon
</Button>
</div>
</div>
)
})
export default WeaponForm
export default WeaponForm

View File

@ -19,7 +19,7 @@ const XSearchResults = withRouter(({ history, name, exact }) => {
error
header='Found 0 players who reached Top 500 with your search'
list={[
'You can\'t search for a player that has never finished in the Top 500',
'You can\'t search for a player who has never finished in the Top 500',
'Be careful about special characters: ρ is not the same as p for example',
'Some characters can\'t be used in the search such as )'
]}

View File

@ -0,0 +1,359 @@
import React, { useState, useEffect } from "react"
import {
Button,
Form,
Radio,
Segment,
List,
Image,
Grid,
Dropdown
} from "semantic-ui-react"
import WeaponForm from "./WeaponForm"
import useTrends from "../hooks/useTrends"
import useWindowDimensions from "../hooks/useWindowDimensions"
import english_internal from "../../utils/english_internal.json"
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
Brush
} from "recharts"
const circleStyle = {
width: "25px",
height: "25px",
WebkitBorderRadius: "12.5px",
MozBorderRadius: "12.5px",
borderRadius: "12.5px"
}
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 months = [
null,
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
]
const XTrends = ({ setMenuSelection }) => {
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(() => {
setMenuSelection("trends")
document.title = "X Rank Trends - sendou.ink"
}, [setMenuSelection])
const CustomTooltip = ({ active, payload, label }) => {
if (active) {
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
}
return (
<div>
<Segment>
<div style={{ padding: "5px" }}>
<WeaponForm
showSubSpecials
weaponForm={weaponForm}
setWeaponForm={setWeaponForm}
/>
<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/Wst_${
english_internal[
w
.replace(" (SZ)", "")
.replace(" (TC)", "")
.replace(" (RM)", "")
.replace(" (CB)", "")
]
}.png`}
/>
)}
<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>
)}
</div>
</Segment>
</div>
)
}
export default XTrends

View File

@ -0,0 +1,278 @@
import { useReducer, useEffect } from "react"
import { useQuery } from "react-apollo-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 }
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 820 KiB

After

Width:  |  Height:  |  Size: 828 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 KiB

After

Width:  |  Height:  |  Size: 825 KiB

View File

@ -0,0 +1,16 @@
import { gql } from 'apollo-boost'
export const searchForTrend = gql`
query searchForTrend($weapon: String!) {
searchForTrend(weapon: $weapon) {
weapon
counts {
year
SZ
TC
RM
CB
}
}
}
`

View File

@ -9,6 +9,7 @@ const { Player, playerResolvers } = require('./schemas/player')
const { Rotation, rotationResolvers } = require('./schemas/rotation')
const { User, userResolvers } = require('./schemas/user')
const { Link, linkResolvers } = require('./schemas/link')
const { Trend, trendResolvers } = require('./schemas/trend')
const Query = gql`
type Query {
@ -34,7 +35,8 @@ const schema = makeExecutableSchema({
Player,
Rotation,
User,
Link ],
Link,
Trend ],
resolvers: merge(
resolvers,
buildResolvers,
@ -43,7 +45,8 @@ const schema = makeExecutableSchema({
playerResolvers,
rotationResolvers,
userResolvers,
linkResolvers
linkResolvers,
trendResolvers
)
})

42
schemas/trend.js Normal file
View File

@ -0,0 +1,42 @@
const { UserInputError, gql } = require('apollo-server-express')
const Trend = require('../models/trend')
const typeDef = gql`
extend type Query {
searchForTrend(weapon: String!): Trend
}
type Year {
year: Int!
"Array that has length of 13. 0 index = null. Other indexes correspond months e.g. index 1 = January."
SZ: [Int]!
"Array that has length of 13. 0 index = null. Other indexes correspond months e.g. index 1 = January."
TC: [Int]!
"Array that has length of 13. 0 index = null. Other indexes correspond months e.g. index 1 = January."
RM: [Int]!
"Array that has length of 13. 0 index = null. Other indexes correspond months e.g. index 1 = January."
CB: [Int]!
}
type Trend {
weapon: String!
counts: [Year!]!
}
`
const resolvers = {
Query: {
searchForTrend: (root, args) => {
return Trend
.findOne({ weapon: args.weapon })
.catch(e => {
throw new UserInputError(e.message, {
invalidArgs: args,
})
})
}
}
}
module.exports = {
Trend: typeDef,
trendResolvers: resolvers
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,57 @@
import json
weapons = ["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",
"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",
"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",
"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", "Inkbrush", "Inkbrush Nouveau",
"Permanent Inkbrush", "Octobrush", "Octobrush Nouveau", "Kensa Octobrush",
"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", "Slosher", "Slosher Deco", "Soda Slosher", "Tri-Slosher",
"Tri-Slosher Nouveau", "Sloshing Machine", "Sloshing Machine Neo",
"Kensa Sloshing Machine", "Bloblobber", "Bloblobber Deco", "Explosher",
"Custom Explosher", "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",
"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",
"Splat Brella", "Sorella Brella", "Tenta Brella", "Tenta Sorella Brella",
"Tenta Camo Brella", "Undercover Brella", "Undercover Sorella Brella", "Kensa Undercover Brella"]
weapon_info = {}
lang_dict = json.loads(open('lang_dict_EUen.json').read())
wpn_list = json.loads(open('WeaponInfo_Main_5_0.json').read())
for wpn_obj in wpn_list:
Name = lang_dict[wpn_obj['Name']].strip()
if Name not in weapons:
print(Name)
continue
Sub = lang_dict[wpn_obj['Sub']]
Special = lang_dict[wpn_obj['Special']]
Range = wpn_obj['Range']
SpecialCost = wpn_obj['SpecialCost']
weapon_info[Name] = {"Sub": Sub, "Special": Special, "Range": Range, "SpecialCost": SpecialCost}
with open('weapon_info.json', 'w') as fp:
json.dump(weapon_info, fp)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,56 @@
#!/usr/bin/env python3
from config import uri
import pymongo
import json
def main():
client = pymongo.MongoClient(uri)
db = client.production
placements = db.placements.find({ })
wpn_dict = json.loads(open('weapon_info.json').read())
trends = {}
modes = {1: "SZ", 2: "TC", 3: "RM", 4: "CB"}
for p in placements:
year = p['year']
weapon = p['weapon']
mode = modes[p['mode']]
month = p['month']
weapon_obj = trends.get(weapon, {})
year_obj = weapon_obj.get(year, {"SZ": [None,0,0,0,0,0,0,0,0,0,0,0,0], "TC": [None,0,0,0,0,0,0,0,0,0,0,0,0], "RM": [None,0,0,0,0,0,0,0,0,0,0,0,0], "CB": [None,0,0,0,0,0,0,0,0,0,0,0,0]})
year_obj[mode][month] = year_obj[mode][month] + 1
weapon_obj[year] = year_obj
trends[weapon] = weapon_obj
sub = wpn_dict[weapon]["Sub"]
special = wpn_dict[weapon]["Special"]
weapon_obj = trends.get(sub, {})
year_obj = weapon_obj.get(year, {"SZ": [None,0,0,0,0,0,0,0,0,0,0,0,0], "TC": [None,0,0,0,0,0,0,0,0,0,0,0,0], "RM": [None,0,0,0,0,0,0,0,0,0,0,0,0], "CB": [None,0,0,0,0,0,0,0,0,0,0,0,0]})
year_obj[mode][month] = year_obj[mode][month] + 1
weapon_obj[year] = year_obj
trends[sub] = weapon_obj
weapon_obj = trends.get(special, {})
year_obj = weapon_obj.get(year, {"SZ": [None,0,0,0,0,0,0,0,0,0,0,0,0], "TC": [None,0,0,0,0,0,0,0,0,0,0,0,0], "RM": [None,0,0,0,0,0,0,0,0,0,0,0,0], "CB": [None,0,0,0,0,0,0,0,0,0,0,0,0]})
year_obj[mode][month] = year_obj[mode][month] + 1
weapon_obj[year] = year_obj
trends[special] = weapon_obj
to_bulk_add = []
for key in trends:
trend_obj = {"weapon": key, "counts": []}
for i in range(2018, 2024):
if i in trends[key]:
modes_obj = trends[key][i]
modes_obj["year"] = i
trend_obj['counts'].append(modes_obj)
to_bulk_add.append(trend_obj)
db.trends.insert_many(to_bulk_add)
print("done")
if __name__ == "__main__":
main()

1
scripts/weapon_info.json Normal file

File diff suppressed because one or more lines are too long