rotations first working version
14
index.js
|
|
@ -2,6 +2,7 @@ require('dotenv').config()
|
|||
const { ApolloServer, UserInputError, AuthenticationError, gql } = require('apollo-server-express')
|
||||
const mongoose = require('mongoose')
|
||||
const express = require('express')
|
||||
const axios = require('axios')
|
||||
const cors = require('cors')
|
||||
const Placement = require('./models/placement')
|
||||
const Player = require('./models/player')
|
||||
|
|
@ -13,6 +14,8 @@ const path = require('path')
|
|||
mongoose.set('useFindAndModify', false)
|
||||
mongoose.set('useCreateIndex', true)
|
||||
|
||||
let rotationData = {timestamp: 0}
|
||||
|
||||
console.log('connecting to MongoDB')
|
||||
|
||||
mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, dbName: "production" })
|
||||
|
|
@ -110,6 +113,7 @@ const typeDefs = gql`
|
|||
playerInfo(uid: String!): PlayerWithPlacements!
|
||||
searchForPlayers(name: String! exact: Boolean): [Placement]!
|
||||
maplists: [Maplist!]!
|
||||
rotationData: String
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
|
|
@ -392,6 +396,16 @@ const resolvers = {
|
|||
invalidArgs: args,
|
||||
})
|
||||
})
|
||||
},
|
||||
rotationData: async (root, args) => {
|
||||
if (Math.floor(Date.now() / 1000) - rotationData.timestamp > 7200) { //only refetching data if two hours have passed
|
||||
const result = await axios.get('https://splatoon2.ink/data/schedules.json', { headers: { 'User-Agent': 'sendou.ink - owner: @Sendouc on Twitter'}})
|
||||
rotationData = result.data
|
||||
rotationData.timestamp = Math.floor(Date.now() / 1000)
|
||||
return JSON.stringify(rotationData)
|
||||
} else {
|
||||
return JSON.stringify(rotationData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
30
package-lock.json
generated
|
|
@ -497,6 +497,15 @@
|
|||
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
|
||||
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.3.0",
|
||||
"is-buffer": "^1.1.5"
|
||||
}
|
||||
},
|
||||
"backo2": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
||||
|
|
@ -1432,6 +1441,24 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz",
|
||||
"integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==",
|
||||
"requires": {
|
||||
"debug": "^3.2.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"for-in": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
||||
|
|
@ -2252,8 +2279,7 @@
|
|||
"is-buffer": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
|
||||
},
|
||||
"is-callable": {
|
||||
"version": "1.1.4",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node index.js",
|
||||
"watch": "nodemon index.js"
|
||||
},
|
||||
"author": "Sendou",
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
"dependencies": {
|
||||
"apollo-server": "^2.4.8",
|
||||
"apollo-server-express": "^2.4.8",
|
||||
"axios": "^0.18.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^7.0.0",
|
||||
"graphql": "^14.2.1",
|
||||
|
|
|
|||
4
react-ui/src/App.js
vendored
|
|
@ -11,6 +11,7 @@ import InfoPlayer from './components/XSearch/InfoPlayer'
|
|||
import InfoWeapon from './components/XSearch/InfoWeapon'
|
||||
import ScrollToTop from './utils/ScrollToTop'
|
||||
import MapListGenerator from './components/Tools/MapListGenerator'
|
||||
import Rotations from './components/Tools/Rotations'
|
||||
|
||||
const App = () => {
|
||||
const [menuSelection, setMenuSelection] = useState('home')
|
||||
|
|
@ -34,6 +35,9 @@ const App = () => {
|
|||
<Route exact path="/maps" render={() =>
|
||||
<MapListGenerator setMenuSelection={setMenuSelection} />
|
||||
} />
|
||||
<Route exact path="/rotation" render={() =>
|
||||
<Rotations setMenuSelection={setMenuSelection} />
|
||||
} />
|
||||
<Route path="/404" render={() => <NotFound />} />
|
||||
<Route path="*" render={() => <NotFound />} />
|
||||
</Switch>
|
||||
|
|
|
|||
8
react-ui/src/components/Misc/MainMenu.js
vendored
|
|
@ -19,6 +19,14 @@ const MainMenu = withRouter(({ history, menuSelection, setMenuSelection }) => {
|
|||
setMenuSelection('maplists')
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
name='rotations'
|
||||
active={menuSelection === 'rotations'}
|
||||
onClick={() => {
|
||||
history.push('/rotation')
|
||||
setMenuSelection('rotations')
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
name='leaderboards'
|
||||
active={menuSelection === 'leaderboards'}
|
||||
|
|
|
|||
224
react-ui/src/components/Tools/Rotations.js
vendored
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useQuery } from 'react-apollo-hooks'
|
||||
import { rotationData } from '../../graphql/queries/rotationData'
|
||||
import { Loader, Segment, Header, Grid, Image, Popup } 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'
|
||||
|
||||
const mapIcons = {
|
||||
"Arowana Mall": arowana_mall,
|
||||
"Ancho-V Games": anchov_games,
|
||||
"Blackbelly Skatepark": blackbelly_skatepark,
|
||||
"Camp Triggerfish": camp_triggerfish,
|
||||
"Goby Arena": goby_arena,
|
||||
"Humpback Pump Track": humpback_pump_track,
|
||||
"Inkblot Art Academy": inkblot_art_academy,
|
||||
"Kelp Dome": kelp_dome,
|
||||
"MakoMart": makomart,
|
||||
"Manta Maria": manta_maria,
|
||||
"Moray Towers": moray_towers,
|
||||
"Musselforge Fitness": musselforge_fitness,
|
||||
"New Albacore Hotel": new_albacore_hotel,
|
||||
"Piranha Pit": piranha_pit,
|
||||
"Port Mackerel": port_mackerel,
|
||||
"Shellendorf Institute": shellendorf_institute,
|
||||
"Skipper Pavilion": skipper_pavilion,
|
||||
"Snapper Canal": snapper_canal,
|
||||
"Starfish Mainstage": starfish_mainstage,
|
||||
"Sturgeon Shipyard": sturgeon_shipyard,
|
||||
"The Reef": the_reef,
|
||||
"Wahoo World": wahoo_world,
|
||||
"Walleye Warehouse": walleye_warehouse
|
||||
}
|
||||
|
||||
const modeIcons = {
|
||||
"Splat Zones": szIcon,
|
||||
"Tower Control": tcIcon,
|
||||
"Rainmaker": rmIcon,
|
||||
"Clam Blitz": cbIcon
|
||||
}
|
||||
|
||||
const modeShort = {
|
||||
"Splat Zones": "SZ",
|
||||
"Tower Control": "TC",
|
||||
"Rainmaker": "RM",
|
||||
"Clam Blitz": "CB"
|
||||
}
|
||||
|
||||
const Rotations = ({ setMenuSelection }) => {
|
||||
const { data, error, loading } = useQuery(rotationData)
|
||||
const [ rotation, setRotation ] = useState([])
|
||||
const [ currentTime, setCurrentTime ] = useState(new Date(Math.floor(Date.now() / 1000)))
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
return
|
||||
}
|
||||
setMenuSelection('rotations')
|
||||
document.title = 'Maplist Generator - sendou.ink'
|
||||
setRotation(JSON.parse(data.rotationData))
|
||||
|
||||
}, [data, loading, setMenuSelection])
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setCurrentTime(new Date(Math.floor(Date.now() / 1000)))
|
||||
}, 1000)
|
||||
return () => {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}, [currentTime])
|
||||
|
||||
if (loading || rotation.length === 0) {
|
||||
return <div style={{"paddingTop": "25px", "paddingBottom": "20000px"}} ><Loader active inline='centered' /></div>
|
||||
}
|
||||
if (error) {
|
||||
return <div style={{"color": "red"}}>{error.message}</div>
|
||||
}
|
||||
console.log(rotation)
|
||||
console.log(currentTime)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{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'
|
||||
if (hours >= 2) {
|
||||
header = `In ${hours} hours ${minutes} minutes (${new Date(r.start_time * 1000).toLocaleString('en-GB')})`
|
||||
} else if (hours > 0) {
|
||||
header = `In ${hours} hours ${minutes} minutes ${seconds} seconds`
|
||||
} else if (minutes > 0) {
|
||||
header = `In ${minutes} minutes ${seconds} seconds`
|
||||
}
|
||||
return (
|
||||
<div key={r.start_time} style={{"paddingTop": "25px"}}>
|
||||
<Header size='small' disabled={r.rule.name === 'Clam Blitz'}>{header}</Header>
|
||||
<Segment inverted disabled={r.rule.name === 'Clam Blitz'}>
|
||||
<Grid columns={3} stackable>
|
||||
<Grid.Row>
|
||||
<Grid.Column>
|
||||
<Header textAlign='center' inverted>
|
||||
<Image src={leagueIcon} style={{"paddingBottom": "10px"}}/> LEAGUE
|
||||
<Header.Subheader style={{"paddingTop": "10px"}}><Image size="mini" src={modeIcons[rotation.league[i].rule.name]} avatar/>{rotation.league[i].rule.name}</Header.Subheader>
|
||||
</Header>
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<Header textAlign='center' inverted>
|
||||
<Image src={rankedIcon} style={{"paddingBottom": "10px"}}/> RANKED
|
||||
<Header.Subheader style={{"paddingTop": "10px"}}><Image size="mini" src={modeIcons[r.rule.name]} avatar/>{r.rule.name}</Header.Subheader>
|
||||
</Header>
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<Header textAlign='center' inverted>
|
||||
<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
|
||||
/>}
|
||||
content={r.stage_a.name}
|
||||
basic
|
||||
/>
|
||||
<Popup
|
||||
trigger={<Image
|
||||
src={mapIcons[r.stage_b.name]}
|
||||
rounded
|
||||
/>}
|
||||
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>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Rotations
|
||||
BIN
react-ui/src/components/img/mapIcons/ancho-v_games.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
react-ui/src/components/img/mapIcons/arowana_mall.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
react-ui/src/components/img/mapIcons/blackbelly_skatepark.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
react-ui/src/components/img/mapIcons/camp_triggerfish.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
react-ui/src/components/img/mapIcons/goby_arena.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
react-ui/src/components/img/mapIcons/humpback_pump_track.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
react-ui/src/components/img/mapIcons/inkblot_art_academy.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
react-ui/src/components/img/mapIcons/kelp_dome.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
react-ui/src/components/img/mapIcons/makomart.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
react-ui/src/components/img/mapIcons/manta_maria.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
react-ui/src/components/img/mapIcons/moray_towers.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
react-ui/src/components/img/mapIcons/musselforge_fitness.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
react-ui/src/components/img/mapIcons/new_albacore_hotel.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
react-ui/src/components/img/mapIcons/piranha_pit.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
react-ui/src/components/img/mapIcons/port_mackerel.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
react-ui/src/components/img/mapIcons/shellendorf_institute.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
react-ui/src/components/img/mapIcons/skipper_pavilion.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
react-ui/src/components/img/mapIcons/snapper_canal.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
react-ui/src/components/img/mapIcons/starfish_mainstage.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
react-ui/src/components/img/mapIcons/sturgeon_shipyard.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
react-ui/src/components/img/mapIcons/the_reef.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
react-ui/src/components/img/mapIcons/wahoo_world.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
react-ui/src/components/img/mapIcons/walleye_warehouse.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
react-ui/src/components/img/modeIcons/league.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
react-ui/src/components/img/modeIcons/ranked.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
react-ui/src/components/img/modeIcons/regular.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
react-ui/src/components/img/modeIcons/splatfest.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
7
react-ui/src/graphql/queries/rotationData.js
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { gql } from 'apollo-boost'
|
||||
|
||||
export const rotationData = gql`
|
||||
{
|
||||
rotationData
|
||||
}
|
||||
`
|
||||
|
|
@ -12,4 +12,15 @@ addFlex: async () => {
|
|||
}
|
||||
] )
|
||||
|
||||
return "ok"
|
||||
return "ok"
|
||||
|
||||
-- React timer --
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setCurrentTime(new Date(Math.floor(Date.now() / 1000)))
|
||||
}, 1000)
|
||||
return () => {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}, [currentTime])
|
||||