Change data loading to centralized store

This commit is contained in:
2023-03-23 13:00:57 +01:00
parent eaa773b7ec
commit 5e8bdd7d4b
5 changed files with 168 additions and 101 deletions
+25 -16
View File
@@ -14,6 +14,8 @@ import {
Legend Legend
} from 'chart.js' } from 'chart.js'
import annotationPlugin from 'chartjs-plugin-annotation' import annotationPlugin from 'chartjs-plugin-annotation'
import { Store, useStore } from 'vuex'
import { Player, Weapon } from '@/store/index'
import { Scatter } from 'vue-chartjs' import { Scatter } from 'vue-chartjs'
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
@@ -23,7 +25,7 @@ ChartJS.register(LinearScale, PointElement, LineElement, Tooltip, Legend, annota
export default defineComponent({ export default defineComponent({
name: 'PlayerChart', name: 'PlayerChart',
props: { props: {
players: Object filters: Object
}, },
components: { components: {
Scatter Scatter
@@ -37,15 +39,24 @@ export default defineComponent({
labels: [] as string[], labels: [] as string[],
borderColor: '#f87979', borderColor: '#f87979',
backgroundColor: '#f87979', backgroundColor: '#f87979',
pointRadius: [1],
pointStyle: ['dot'],
data: [] as {x:number, y:number}[] data: [] as {x:number, y:number}[]
} }
] ]
} }
if (!this.$props.players) { if (!this.players) {
return chartData return chartData
} }
chartData.datasets[0].labels = Object.keys(this.$props.players) chartData.datasets[0].labels = this.playerIdList
chartData.datasets[0].data = Object.values(this.$props.players).map((e:any) => ({ x: e.deaths, y: e.kills })) chartData.datasets[0].data = this.playerIdList.map(e => {
if (!this.players) {
return { x: 0, y: 0 }
}
return { x: this.players[e].deaths, y: this.players[e].kills }
})
chartData.datasets[0].pointRadius = [1].fill(1, 0, chartData.datasets[0].labels.length)
chartData.datasets[0].pointStyle = ['dot'].fill('dot', 0, chartData.datasets[0].labels.length)
return chartData return chartData
}, },
chartOptions () { chartOptions () {
@@ -56,10 +67,10 @@ export default defineComponent({
callbacks: { callbacks: {
label: (ctx:any) => { label: (ctx:any) => {
let label:string let label:string
if (!this.$props.players) { if (!this.players) {
label = ctx.dataset.labels[ctx.dataIndex] label = ctx.dataset.labels[ctx.dataIndex]
} else { } else {
label = this.$props.players[ctx.dataset.labels[ctx.dataIndex]].username label = this.players[ctx.dataset.labels[ctx.dataIndex]].username
} }
label += ' (' + ctx.parsed.y + ' kills ' + ctx.parsed.x + ' deaths)' label += ' (' + ctx.parsed.y + ' kills ' + ctx.parsed.x + ' deaths)'
return label return label
@@ -69,11 +80,11 @@ export default defineComponent({
annotation: {} annotation: {}
} }
} }
if (!this.$props.players) { if (!this.players) {
return options return options
} }
const maxX = Math.max(...Object.values(this.$props.players).map(e => e.deaths)) const maxX = Math.max(...Object.values(this.players).map(e => e.deaths))
const maxY = Math.max(...Object.values(this.$props.players).map(e => e.kills)) const maxY = Math.max(...Object.values(this.players).map(e => e.kills))
let endX, endY let endX, endY
if (maxY > maxX) { if (maxY > maxX) {
endX = maxX endX = maxX
@@ -97,16 +108,14 @@ export default defineComponent({
} }
} }
return options return options
} },
}, players ():{[key:string]:Player} { return this.store.getters.getPlayerList(this.filters) },
/* watch: { playerIdList ():string[] { return Object.keys(this.players) }
players: function (newval: any, oldval: any): void {
} },
}, */
data () { data () {
return { return {
store: useStore()
} }
} }
}) })
+28 -17
View File
@@ -9,7 +9,7 @@
<span v-on:click="sortPlayerList('avg_kill_distance')">average kill distance</span> <span v-on:click="sortPlayerList('avg_kill_distance')">average kill distance</span>
</div> </div>
<div class="player" v-for="playerId in playerIdList" v-bind:key="playerId"> <div class="player" v-for="playerId in playerIdList" v-bind:key="playerId">
<span>{{ $props.players[playerId].username }}</span> <span>{{ players[playerId].username }}</span>
<span>{{ players[playerId].kills }}</span> <span>{{ players[playerId].kills }}</span>
<span>{{ players[playerId].deaths }}</span> <span>{{ players[playerId].deaths }}</span>
<span>{{ Math.round(players[playerId].kills / players[playerId].deaths * 100) / 100 }}</span> <span>{{ Math.round(players[playerId].kills / players[playerId].deaths * 100) / 100 }}</span>
@@ -21,27 +21,37 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { Player } from '@/store/index'
import { useStore } from 'vuex'
export default defineComponent({ export default defineComponent({
props: { props: {
players: Object filters: Object
}, },
data () { data () {
return { return {
sortingData: { direction: -1, argument: '' }, sortingData: { direction: -1, argument: '' as keyof Player | 'k/d' },
playerIdList: [] as string[] playerIdList: [] as string[],
store: useStore()
} }
}, },
computed: {
players ():{[key:string]:Player} { return this.store.getters.getPlayerList(this.filters) }
},
watch: { watch: {
players: function (newval: any, oldval: any): void { players: {
this.playerIdList = [] handler (newval:{[key:string]:Player}): void {
this.sortingData.direction *= -1 if (!newval) return
this.playerIdList = Object.keys(newval) this.playerIdList = []
this.sortPlayerList(this.sortingData.argument) this.sortingData.direction *= -1
this.playerIdList = Object.keys(newval)
this.sortPlayerList(this.sortingData.argument)
},
immediate: true
} }
}, },
methods: { methods: {
sortPlayerList (arg: string) { sortPlayerList (arg: ((keyof Player) | 'k/d')) {
if (arg === this.sortingData.argument) { if (arg === this.sortingData.argument) {
this.sortingData.direction *= -1 this.sortingData.direction *= -1
} else { } else {
@@ -51,15 +61,17 @@ export default defineComponent({
} }
this.sortingData.argument = arg this.sortingData.argument = arg
this.playerIdList.sort((a:any, b:any) => { this.playerIdList.sort((a:string, b:string) => {
if (!this.$props.players) { if (!this.players) {
return 0 return 0
} }
let varA = this.$props.players[a][arg] let varA, varB
let varB = this.$props.players[b][arg]
if (arg === 'k/d') { if (arg === 'k/d') {
varA = this.$props.players[a].kills / this.$props.players[a].deaths varA = this.players[a].kills / this.players[a].deaths
varB = this.$props.players[b].kills / this.$props.players[b].deaths varB = this.players[b].kills / this.players[b].deaths
} else {
varA = this.players[a][arg]
varB = this.players[b][arg]
} }
if (varA < varB) { if (varA < varB) {
@@ -68,7 +80,6 @@ export default defineComponent({
if (varA > varB) { if (varA > varB) {
return 1 * this.sortingData.direction return 1 * this.sortingData.direction
} }
// names must be equal
return 0 return 0
}) })
} }
+4
View File
@@ -4,3 +4,7 @@ import router from './router'
import store from './store' import store from './store'
createApp(App).use(store).use(router).mount('#app') createApp(App).use(store).use(router).mount('#app')
store.dispatch('fetchServers', {})
store.dispatch('fetchPlayers', {})
store.dispatch('fetchWeapons', {})
+74 -9
View File
@@ -2,34 +2,99 @@
import { InjectionKey } from 'vue' import { InjectionKey } from 'vue'
import { createStore, Store } from 'vuex' import { createStore, Store } from 'vuex'
interface Player { export interface Player {
deaths: number; deaths: number;
username: string; username: string;
maxKillDistance: number; // eslint-disable-next-line camelcase
avgKillDistance: number; max_kill_distance: number;
// eslint-disable-next-line camelcase
avg_kill_distance: number;
kills: number; kills: number;
} }
export interface Weapon {
// eslint-disable-next-line camelcase
max_kill_distance: number;
// eslint-disable-next-line camelcase
avg_kill_distance: number;
kills: number;
}
export interface Server{
name:string;
id:number;
description:string
}
// define your typings for the store state // define your typings for the store state
export interface State { export interface State {
players: Player[]; servers: Server[]
players: { [key: string]: { [key: string]: Player } };
weapons: { [key: string]:{ [key: string]: Weapon }};
} }
// define injection key // define injection key
// eslint-disable-next-line symbol-description // eslint-disable-next-line symbol-description
export const key: InjectionKey<Store<State>> = Symbol() export const key: InjectionKey<Store<State>> = Symbol()
export default createStore({ export default createStore<State>({
state: { state: {
servers: [], servers: [],
players: {}, players: {},
weapons: {} weapons: {}
}, },
getters: { getters: {
getPlayerList (weapon, server) { getPlayerList: (state) => ({ server, weapon }: { server?: number, weapon?: string }) => {
console.log('e') return state.players[(server || '') + (weapon || '')]
},
getWeaponList: (state) => ({ server, player }: { server?: number, player?: string }) => {
return state.weapons[(server || '') + (player || '')]
}
},
mutations: {
setPlayers (state, data: { data: { [key: string]: Player }, filters: { weapon: string, server: number } }) {
state.players[(data.filters.server || '') + (data.filters.weapon || '')] = data.data
},
setWeapons (state, data:{data: {[key:string]:Weapon}, filters:{server:string, player:string}}) {
state.weapons[(data.filters.server || '') + (data.filters.player || '')] = data.data
},
setServers (state, data) {
state.servers = data
}
},
actions: {
async fetchPlayers (context, { weapon, server }) {
const searchParams: Record<string, string> = {}
if (weapon?.toString()) { searchParams.weapon = weapon }
if (server?.toString()) { searchParams.server = server }
const response = await fetch(
'https://tone.sleepycat.date/v1/client/players?' + new URLSearchParams(searchParams)
)
const data = await response.json()
Object.keys(data).forEach(e => {
data[e].kills = Number(data[e].kills)
data[e].deaths = Number(data[e].deaths)
data[e].max_kill_distance = Number(data[e].max_kill_distance)
data[e].avg_kill_distance = Number(data[e].avg_kill_distance)
})
context.commit('setPlayers', { data, filters: { weapon, server } })
},
async fetchWeapons (context, { player, server }) {
const searchParams: Record<string, string> = {}
if (player?.toString()) { searchParams.player = player }
if (server?.toString()) { searchParams.server = server }
const response = await fetch(
'https://tone.sleepycat.date/v1/client/weapons?' + new URLSearchParams(searchParams)
)
const data = await response.json()
context.commit('setWeapons', { data, filters: { player, server } })
},
async fetchServers (context) {
const response = await fetch(
'https://tone.sleepycat.date/v1/client/servers'
)
const data = await response.json()
context.commit('setServers', data)
} }
}, },
mutations: {},
actions: {},
modules: {} modules: {}
}) })
+37 -59
View File
@@ -1,82 +1,60 @@
<template> <template>
<select v-model="filters.server" v-on:change="fetchPlayers(filters)"> <select v-on:change="changeFilter({ server: ($event.target as HTMLInputElement).value })">
<option></option> <option></option>
<option v-for="(serverData, serverId) in servers" v-bind:key="serverId" :value="serverId">{{ serverData.name }}</option> <option v-for="(serverData) in servers" v-bind:key="serverData.id" :value="serverData.id">{{ serverData.name }}
</option>
</select> </select>
<select v-model="filters.weapon" v-on:change="fetchPlayers(filters)"> <select v-on:change="changeFilter({ weapon: ($event.target as HTMLInputElement).value })">
<option></option> <option></option>
<option v-for="(weaponData, weaponId) in weapons" v-bind:key="weaponId" :value="weaponId">{{ weaponId }}</option> <option v-for="(weaponData, weaponId) in weapons" v-bind:key="weaponId" :value="weaponId">{{ weaponId }}</option>
</select> </select>
<div class="playersData"> <div class="playersData">
<PlayerList :players="players"></PlayerList> <PlayerList :filters="filters"></PlayerList>
<PlayerChart :players="players"></PlayerChart> <PlayerChart :filters="filters"></PlayerChart>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Options, Vue } from 'vue-class-component' import { Store, useStore } from 'vuex'
import { Player, Weapon, Server } from '@/store/index'
import PlayerList from '@/components/PlayerList.vue' import PlayerList from '@/components/PlayerList.vue'
import PlayerChart from '@/components/PlayerChart.vue' import PlayerChart from '@/components/PlayerChart.vue'
import { defineComponent } from 'vue'
@Options({ export default defineComponent({
name: 'PlayerView',
components: { components: {
PlayerList, PlayerChart PlayerList, PlayerChart
} },
})
export default class PlayerView extends Vue {
players: any = {}
weapons: any = {}
servers: any = {}
filters: any = {}
created () {
this.fetchPlayers({})
this.fetchWeapons({})
this.fetchServers()
}
async fetchWeapons ({ server }: { server?: string }) {
const searchParams: Record<string, string> = {}
if (server) { searchParams.server = server }
const response = await fetch(
'https://tone.sleepycat.date/v1/client/weapons?' + new URLSearchParams(searchParams)
)
const data = await response.json()
this.weapons = data
}
async fetchServers () {
const response = await fetch(
'https://tone.sleepycat.date/v1/client/servers'
)
const data = await response.json()
this.servers = data
console.log(this.servers)
}
data () { data () {
return { a: 1 } return {
filters: {},
store: useStore()
} as { filters: { weapon?: string, server?: string }, store: Store<any> }
},
computed: {
servers (): Server[] { return this.store.state.servers },
players (): { [key: string]: Player } { return this.store.getters.getPlayerList(this.filters) },
weapons (): { [key: string]: Weapon } { return this.store.getters.getWeaponList(this.filters) }
},
methods: {
async changeFilter ({ weapon, server }: { weapon?: string, server?: string }) {
const filters = JSON.parse(JSON.stringify(this.filters))
if (weapon !== undefined) filters.weapon = weapon
if (server !== undefined) filters.server = server
if (weapon === '') delete filters.weapon
if (server === '') delete filters.server
const promises = []
if (!this.store.getters.getPlayerList(filters)) promises.push(this.store.dispatch('fetchPlayers', filters))
if (!this.store.getters.getWeaponList(filters)) promises.push(this.store.dispatch('fetchWeapons', filters))
await Promise.all(promises)
// Delay the update of filters propery after we fetch the data to the API as changing it will cause subcomponents to reload before data is fetched
this.filters = filters
}
} }
})
async fetchPlayers ({ weapon, server }: { weapon?: string, server?: string }) {
const searchParams: Record<string, string> = {}
if (weapon?.toString()) { searchParams.weapon = weapon }
if (server?.toString()) { searchParams.server = server }
console.log(server, weapon)
console.log('https://tone.sleepycat.date/v1/client/players?' + new URLSearchParams(searchParams))
const response = await fetch(
'https://tone.sleepycat.date/v1/client/players?' + new URLSearchParams(searchParams)
)
const data = await response.json()
Object.keys(data).forEach(e => {
data[e].kills = Number(data[e].kills)
data[e].deaths = Number(data[e].deaths)
data[e].max_kill_distance = Number(data[e].max_kill_distance)
data[e].avg_kill_distance = Number(data[e].avg_kill_distance)
})
this.players = data
}
}
</script> </script>
<style scoped> <style scoped>