Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e263211eb | ||
|
|
20b0e09cca | ||
|
|
78a4cc8a1b | ||
|
|
32efc16ae1 | ||
|
|
33bd50081e | ||
|
|
4824657f44 | ||
|
|
50759f129c | ||
|
|
fd6ad61923 | ||
|
|
4e56f70fec | ||
|
|
f815cb3d1a | ||
|
|
ac57843c48 | ||
|
|
93096daee3 | ||
|
|
c6ea1ce032 | ||
|
|
f65e4b20af | ||
|
|
d41b16e697 | ||
|
|
5f1611998a | ||
|
|
e155cb1013 | ||
|
|
b78072d385 | ||
|
|
91977ded3d | ||
|
|
b9fb1ce5a1 | ||
|
|
4b400348e6 | ||
|
|
bc1e23255d | ||
|
|
dd075d2982 | ||
|
|
0be6adb7d2 | ||
|
|
0e0404ce0a |
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/ToneAPI_webclient/favicon.ico"><title>toneapi_webclient</title><script defer="defer" src="/ToneAPI_webclient/js/chunk-vendors.3f4f208d.js"></script><script defer="defer" src="/ToneAPI_webclient/js/app.f8f821bc.js"></script><link href="/ToneAPI_webclient/css/app.bbc51a59.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but toneapi_webclient doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><meta content="Tone API" property="og:title"/><meta content="Northstar kill statistics" property="og:description"/><meta content="https://toneapi.github.io/ToneAPI_webclient/" property="og:url"/><meta content="https://toneapi.github.io/ToneAPI_webclient/og_image.png" property="og:image"/><meta name="twitter:card" content="summary_large_image"><link rel="icon" href="/ToneAPI_webclient/favicon.ico"><title>toneapi_webclient</title><script defer="defer" src="/ToneAPI_webclient/js/chunk-vendors.3f4f208d.js"></script><script defer="defer" src="/ToneAPI_webclient/js/app.13330013.js"></script><link href="/ToneAPI_webclient/css/app.a9b40eb3.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but toneapi_webclient doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
Vendored
+2
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
-2
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 276 KiB |
@@ -4,6 +4,11 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<meta content="Tone API" property="og:title" />
|
||||
<meta content="Northstar kill statistics" property="og:description" />
|
||||
<meta content="https://toneapi.github.io/ToneAPI_webclient/" property="og:url" />
|
||||
<meta content="https://toneapi.github.io/ToneAPI_webclient/og_image.png" property="og:image" />
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 276 KiB |
@@ -17,12 +17,12 @@ import {
|
||||
PluginChartOptions,
|
||||
ScaleChartOptions
|
||||
} from 'chart.js'
|
||||
import annotationPlugin from 'chartjs-plugin-annotation'
|
||||
import annotationPlugin, { AnnotationPluginOptions } from 'chartjs-plugin-annotation'
|
||||
import dataLabel from 'chartjs-plugin-datalabels'
|
||||
import { useKillStore, Player, Filters } from '@/stores/kill'
|
||||
|
||||
import { Scatter } from 'vue-chartjs'
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import { defineComponent, PropType, toRaw, unref } from 'vue'
|
||||
import { ChartEvent } from 'chart.js/dist/core/core.plugins'
|
||||
import { _DeepPartialObject } from 'chart.js/dist/types/utils'
|
||||
|
||||
@@ -37,7 +37,8 @@ export default defineComponent({
|
||||
data: () => {
|
||||
return {
|
||||
store: useKillStore(),
|
||||
refreshColors: 0
|
||||
refreshColors: 0,
|
||||
playerData: []
|
||||
}
|
||||
},
|
||||
emits: ['highlightPlayer'],
|
||||
@@ -48,6 +49,15 @@ export default defineComponent({
|
||||
this.refreshColors++
|
||||
},
|
||||
computed: {
|
||||
playerList (): (Player & {id:string})[] {
|
||||
const data = this.store.getPlayerList(this.filters || {})?.value.data
|
||||
if (!data) return Object.entries(this.store.fetchPlayers(this.filters || {}).value.data).map(e => ({ id: e[0], ...toRaw(e[1].value) }))
|
||||
const cut = Object.entries(data).map(e => ({ id: e[0], ...toRaw(e[1].value) }))
|
||||
cut.sort((a, b) => {
|
||||
return b.kills - a.kills
|
||||
})
|
||||
return cut.slice(0, 200)
|
||||
},
|
||||
colors () {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
this.refreshColors
|
||||
@@ -64,39 +74,35 @@ export default defineComponent({
|
||||
}
|
||||
return colors
|
||||
},
|
||||
|
||||
chart () {
|
||||
const playerIndex = this.playerList.findIndex(e => e.id === this.$props.playerHighlighted)
|
||||
const defaultColor = this.colors.cyan
|
||||
const borderColor = new Array(this.playerList.length).fill(defaultColor)
|
||||
borderColor[playerIndex] = this.colors.orange
|
||||
const backgroundColor = borderColor
|
||||
const pointRadius = new Array(this.playerList.length).fill(1)
|
||||
pointRadius[playerIndex] = 20
|
||||
const hoverRadius = new Array(this.playerList.length).fill(4)
|
||||
hoverRadius[playerIndex] = 30
|
||||
const pointStyle = new Array(this.playerList.length).fill('circle')
|
||||
pointStyle[playerIndex] = 'crossRot'
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Players',
|
||||
labels: this.playerIdList || [] as string[],
|
||||
borderColor: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? this.colors.orange : this.colors.cyan,
|
||||
backgroundColor: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? this.colors.orange : this.colors.cyan,
|
||||
pointRadius: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? 20 : 1,
|
||||
pointStyle: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? 'crossRot' : 'circle',
|
||||
hoverRadius: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? 30 : 4,
|
||||
data: this.playerIdList.map((id) => ({ y: this.players[id].kills, x: this.players[id].deaths }))
|
||||
labels: this.playerList.map(e => e.id) || [] as string[],
|
||||
borderColor,
|
||||
backgroundColor,
|
||||
pointRadius,
|
||||
pointStyle,
|
||||
hoverRadius,
|
||||
data: this.playerList.map((player) => ({ y: player.kills, x: player.deaths }))
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
chartOptions (): _DeepPartialObject<CoreChartOptions<'scatter'> & PluginChartOptions<'scatter'> & ScaleChartOptions<'scatter'>> {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
this.$props.playerHighlighted
|
||||
let endX = 0
|
||||
let endY = 0
|
||||
if (this.players) {
|
||||
const maxX = Math.max(...Object.values(this.players).map(e => e.deaths))
|
||||
const maxY = Math.max(...Object.values(this.players).map(e => e.kills))
|
||||
|
||||
if (maxY > maxX) {
|
||||
endX = maxX
|
||||
endY = (maxX / maxY) * maxY
|
||||
} else {
|
||||
endY = maxY
|
||||
endX = (maxY / maxX) * maxX
|
||||
}
|
||||
}
|
||||
chartStaticOptions () {
|
||||
return {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
@@ -124,15 +130,38 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
onClick: (e: ChartEvent, element: any) => {
|
||||
this.$emit('highlightPlayer', element.length > 0 ? this.playerList[element[0].index].id : undefined)
|
||||
},
|
||||
onHover: (e: any, element: any) => {
|
||||
if (!e.native.target) return
|
||||
(e.native.target as HTMLElement).style.cursor = element[0] ? 'pointer' : 'default'
|
||||
}
|
||||
}
|
||||
},
|
||||
chartStaticPlugins () {
|
||||
let endX = 0
|
||||
let endY = 0
|
||||
if (this.playerList) {
|
||||
const maxX = Math.max(...this.playerList.map(e => e.deaths))
|
||||
const maxY = Math.max(...this.playerList.map(e => e.kills))
|
||||
if (maxY > maxX) {
|
||||
endX = maxX
|
||||
endY = (maxX / maxY) * maxY
|
||||
} else {
|
||||
endY = maxY
|
||||
endX = (maxY / maxX) * maxX
|
||||
}
|
||||
}
|
||||
return {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (ctx: any) => {
|
||||
let label: string
|
||||
if (!this.players) {
|
||||
if (!this.playerList) {
|
||||
label = ctx.dataset.labels[ctx.dataIndex]
|
||||
} else {
|
||||
label = this.players[ctx.dataset.labels[ctx.dataIndex]].username
|
||||
label = this.playerList[ctx.dataIndex].username
|
||||
}
|
||||
label += ' (' + ctx.parsed.y + ' kills ' + ctx.parsed.x + ' deaths)'
|
||||
return label
|
||||
@@ -152,48 +181,34 @@ export default defineComponent({
|
||||
borderDash: [1, 5]
|
||||
}
|
||||
}
|
||||
} as _DeepPartialObject<AnnotationPluginOptions>,
|
||||
legend: { display: false }
|
||||
}
|
||||
},
|
||||
legend: { display: false },
|
||||
chartOptions (): _DeepPartialObject<CoreChartOptions<'scatter'> & PluginChartOptions<'scatter'> & ScaleChartOptions<'scatter'>> {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
this.$props.playerHighlighted
|
||||
return {
|
||||
...this.chartStaticOptions,
|
||||
plugins: {
|
||||
...this.chartStaticPlugins,
|
||||
datalabels: {
|
||||
formatter: (value: any, context: any) => {
|
||||
return this.players[this.playerIdList[context.dataIndex]].username
|
||||
return this.playerList[context.dataIndex].username
|
||||
},
|
||||
backgroundColor: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.dataIndex]) ? this.colors.orange : null,
|
||||
backgroundColor: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerList[context.dataIndex].id) ? this.colors.orange : null,
|
||||
borderColor: this.colors.fg,
|
||||
borderWidth: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.dataIndex]) ? 1 : 0,
|
||||
borderWidth: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerList[context.dataIndex].id) ? 1 : 0,
|
||||
borderRadius: 5,
|
||||
display: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.dataIndex]) ? 'true' : 'auto',
|
||||
display: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerList[context.dataIndex].id) ? 'true' : 'auto',
|
||||
align: -45,
|
||||
anchor: 'end',
|
||||
clamp: true,
|
||||
color: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.dataIndex]) ? this.colors.bg : this.colors.fg
|
||||
}
|
||||
},
|
||||
onClick: (e: ChartEvent, element: any) => {
|
||||
this.$emit('highlightPlayer', element.length > 0 ? this.playerIdList[element[0].index] : undefined)
|
||||
},
|
||||
onHover: (e: any, element: any) => {
|
||||
if (!e.native.target) return
|
||||
(e.native.target as HTMLElement).style.cursor = element[0] ? 'pointer' : 'default'
|
||||
color: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerList[context.dataIndex].id) ? this.colors.bg : this.colors.fg
|
||||
}
|
||||
}
|
||||
},
|
||||
players (): { [key: string]: Player } {
|
||||
const data = this.store.getPlayerList(this.filters || {})?.data
|
||||
if (!data) return {}
|
||||
const cut = Object.entries(data).sort((a, b) => {
|
||||
if (a[1].kills < b[1].kills) {
|
||||
return 1
|
||||
}
|
||||
if (a[1].kills > b[1].kills) {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}).slice(0, 200)
|
||||
return Object.fromEntries(cut)
|
||||
},
|
||||
playerIdList (): string[] { return Object.keys(this.players) }
|
||||
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -2,32 +2,32 @@
|
||||
<div class="playerTable" v-on:keydown="selectNextPlayer" tabindex="0">
|
||||
<div class="playerHeaders">
|
||||
<span></span>
|
||||
<span v-on:click="sortPlayerList('username')"
|
||||
<span v-on:click="updateSort('username')"
|
||||
:class="sortingData.argument == 'username' ? 'selected' : ''">Username</span>
|
||||
<span v-on:click="sortPlayerList('kills')" :class="sortingData.argument == 'kills' ? 'selected' : ''">K</span>
|
||||
<span v-on:click="sortPlayerList('deaths')" :class="sortingData.argument == 'deaths' ? 'selected' : ''">D</span>
|
||||
<span v-on:click="sortPlayerList('k/d')" :class="sortingData.argument == 'k/d' ? 'selected' : ''">K/D</span>
|
||||
<span v-on:click="sortPlayerList('max_distance')"
|
||||
<span v-on:click="updateSort('kills')" :class="sortingData.argument == 'kills' ? 'selected' : ''">K</span>
|
||||
<span v-on:click="updateSort('deaths')" :class="sortingData.argument == 'deaths' ? 'selected' : ''">D</span>
|
||||
<span v-on:click="updateSort('k/d')" :class="sortingData.argument == 'k/d' ? 'selected' : ''">K/D</span>
|
||||
<span v-on:click="updateSort('max_distance')"
|
||||
:class="sortingData.argument == 'max_distance' ? 'selected' : ''">max distance</span>
|
||||
<span v-on:click="sortPlayerList('avg_distance')"
|
||||
<span v-on:click="updateSort('avg_distance')"
|
||||
:class="sortingData.argument == 'avg_distance' ? 'selected' : ''">average distance</span>
|
||||
</div>
|
||||
<div :class="'playerRow ' + (playerId === $props.playerHighlighted ? 'selected' : '')"
|
||||
v-for="(playerId, index) in playerIdList" v-bind:key="playerId" v-on:click="$emit('highlightPlayer', playerId)"
|
||||
:ref="`player:` + playerId">
|
||||
<div :class="'playerRow ' + (player.id === $props.playerHighlighted ? 'selected' : '')"
|
||||
v-for="(player, index) in playerList" v-bind:key="player.id" v-on:click="$emit('highlightPlayer', player.id)"
|
||||
:ref="`player:` + player.id">
|
||||
<div><span>{{ index+1 }}</span></div>
|
||||
<div><span>{{ players[playerId].username }}</span></div>
|
||||
<div><span>{{ players[playerId].kills }}</span></div>
|
||||
<div><span>{{ players[playerId].deaths }}</span></div>
|
||||
<div><span>{{ Math.round(players[playerId].kills / Math.max(1, players[playerId].deaths) * 100) / 100 }}</span></div>
|
||||
<div><span>{{ players[playerId].max_distance }}</span></div>
|
||||
<div><span>{{ Math.round(((players[playerId].total_distance / players[playerId].kills) || 0) * 100) / 100 }}</span></div>
|
||||
<div><span>{{ player.value.username }}</span></div>
|
||||
<div><span>{{ player.value.kills }}</span></div>
|
||||
<div><span>{{ player.value.deaths }}</span></div>
|
||||
<div><span>{{ Math.round(player.value.kills / Math.max(1, player.value.deaths) * 100) / 100 }}</span></div>
|
||||
<div><span>{{ player.value.max_distance }}</span></div>
|
||||
<div><span>{{ Math.round(((player.value.total_distance / player.value.kills) || 0) * 100) / 100 }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import { defineComponent, PropType, Ref, toRaw, unref } from 'vue'
|
||||
import { useKillStore, Player, Filters } from '@/stores/kill'
|
||||
|
||||
export default defineComponent({
|
||||
@@ -39,74 +39,67 @@ export default defineComponent({
|
||||
data () {
|
||||
return {
|
||||
sortingData: { direction: -1, argument: 'kills' as keyof Player | 'k/d' | 'avg_distance' },
|
||||
playerIdList: [] as string[],
|
||||
store: useKillStore()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
players (): { [key: string]: Player } {
|
||||
players (): { [key: string]: Ref<Player> } {
|
||||
const { player: _, ...withoutPlayer } = this.filters
|
||||
return this.store.getPlayerList(withoutPlayer)?.data || {}
|
||||
const data = this.store.getPlayerList(withoutPlayer)?.value.data
|
||||
if (!data) return this.store.fetchPlayers(withoutPlayer).value.data
|
||||
return data
|
||||
},
|
||||
playerList ():(Ref<Player> & {id:string})[] {
|
||||
if (!this.players) return []
|
||||
let players = Object.entries(this.players).map(e => {
|
||||
const target = {} as ProxyHandler<Ref<Player>>
|
||||
const player = new Proxy(e[1], target) as Ref<Player> & {id:string}
|
||||
player.id = e[0]
|
||||
return player
|
||||
}) as (Ref<Player> & {id:string})[]
|
||||
|
||||
if (this.sortingData.argument === 'username') {
|
||||
const collator = new Intl.Collator('en', { numeric: true, sensitivity: 'base' })
|
||||
players.sort((a: Ref<Player>, b: Ref<Player>) => {
|
||||
return collator.compare(toRaw(a.value).username, toRaw(b.value).username)
|
||||
})
|
||||
} else if (this.sortingData.argument === 'k/d') {
|
||||
players.sort((a: Ref<Player>, b: Ref<Player>) => {
|
||||
return toRaw(a.value).kills / Math.max(1, toRaw(a.value).deaths) - toRaw(b.value).kills / Math.max(1, toRaw(b.value).deaths)
|
||||
})
|
||||
} else if (this.sortingData.argument === 'avg_distance') {
|
||||
players.sort((a: Ref<Player>, b: Ref<Player>) => {
|
||||
return ((toRaw(a.value).total_distance / toRaw(a.value).kills) || 0) - ((toRaw(b.value).total_distance / toRaw(b.value).kills) || 0)
|
||||
})
|
||||
} else {
|
||||
const argument = this.sortingData.argument
|
||||
players.sort((a: Ref<Player>, b: Ref<Player>) => {
|
||||
return toRaw(a.value)[argument] - toRaw(b.value)[argument]
|
||||
})
|
||||
}
|
||||
|
||||
if (this.sortingData.direction < 0) {
|
||||
players.reverse()
|
||||
}
|
||||
|
||||
players = players.slice(0, 500)
|
||||
return players
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
players: {
|
||||
handler (newval: { [key: string]: Player }): void {
|
||||
if (!newval) return
|
||||
this.playerIdList = []
|
||||
this.sortingData.direction *= -1
|
||||
this.playerIdList = Object.keys(newval)
|
||||
this.sortPlayerList(this.sortingData.argument)
|
||||
this.playerIdList = this.playerIdList.slice(0, 500)
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
playerHighlighted (newval) {
|
||||
const newElement = this.$refs['player:' + newval] as HTMLElement[]
|
||||
if (newElement && newElement[0]) { newElement[0].scrollIntoView({ behavior: 'smooth', block: 'center' }) }
|
||||
this.scrollToPlayer(newval)
|
||||
},
|
||||
playerList () {
|
||||
if (this.playerHighlighted) this.scrollToPlayer(this.playerHighlighted)
|
||||
}
|
||||
},
|
||||
updated () {
|
||||
const element = this.$refs['player:' + this.$props.playerHighlighted] as HTMLElement[]
|
||||
/* const element = this.$refs['player:' + this.$props.playerHighlighted] as HTMLElement[]
|
||||
if (!element || !element[0]) return
|
||||
element[0].scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
element[0].scrollIntoView({ behavior: 'smooth', block: 'center' }) */
|
||||
},
|
||||
methods: {
|
||||
sortPlayerList (arg: ((keyof Player) | 'k/d' | 'avg_distance')) {
|
||||
if (arg === this.sortingData.argument) {
|
||||
this.sortingData.direction *= -1
|
||||
} else {
|
||||
if (arg === 'username') {
|
||||
this.sortingData.direction = 1
|
||||
}
|
||||
}
|
||||
this.sortingData.argument = arg
|
||||
|
||||
this.playerIdList.sort((a: string, b: string) => {
|
||||
if (!this.players) {
|
||||
return 0
|
||||
}
|
||||
let varA, varB
|
||||
if (arg === 'k/d') {
|
||||
varA = this.players[a].kills / Math.max(1, this.players[a].deaths)
|
||||
varB = this.players[b].kills / Math.max(1, this.players[b].deaths)
|
||||
} else if (arg === 'avg_distance') {
|
||||
varA = (this.players[a].total_distance / this.players[a].kills) || 0
|
||||
varB = (this.players[b].total_distance / this.players[b].kills) || 0
|
||||
} else {
|
||||
varA = this.players[a][arg]
|
||||
varB = this.players[b][arg]
|
||||
}
|
||||
if (varA === undefined || varB === undefined) return 0
|
||||
if (varA < varB) {
|
||||
return -1 * this.sortingData.direction
|
||||
}
|
||||
if (varA > varB) {
|
||||
return 1 * this.sortingData.direction
|
||||
}
|
||||
return 0
|
||||
})
|
||||
},
|
||||
selectNextPlayer (e: KeyboardEvent) {
|
||||
const values = {
|
||||
Home: -Infinity,
|
||||
@@ -121,11 +114,26 @@ export default defineComponent({
|
||||
if (!this.$props.playerHighlighted) return
|
||||
if (!Object.keys(values).includes(e.key)) return
|
||||
e.preventDefault()
|
||||
const index = this.playerIdList.indexOf(this.$props.playerHighlighted)
|
||||
const index = this.playerList.findIndex(e => e.id === this.$props.playerHighlighted)
|
||||
let nextIndex = (index + values[e.key])
|
||||
if (nextIndex >= this.playerIdList.length) nextIndex = this.playerIdList.length - 1
|
||||
if (nextIndex >= this.playerList.length) nextIndex = this.playerList.length - 1
|
||||
if (nextIndex < 0) nextIndex = 0
|
||||
this.$emit('highlightPlayer', this.playerIdList[nextIndex])
|
||||
this.$emit('highlightPlayer', this.playerList[nextIndex].id)
|
||||
},
|
||||
updateSort (argument:string) {
|
||||
if (this.sortingData.argument === argument) {
|
||||
this.sortingData.direction *= -1
|
||||
} else {
|
||||
this.sortingData.direction = -1
|
||||
}
|
||||
this.sortingData.argument = argument as typeof this.sortingData.argument
|
||||
},
|
||||
scrollToPlayer (playerid:string) {
|
||||
const newElement = this.$refs['player:' + playerid] as HTMLElement[]
|
||||
// Some hack because of some inconsistency
|
||||
setTimeout(function () {
|
||||
if (newElement && newElement[0]) { newElement[0].scrollIntoView({ behavior: 'smooth', block: 'center' }) }
|
||||
}, 10)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'
|
||||
import dataLabel from 'chartjs-plugin-datalabels'
|
||||
import { Weapon, Filters, useKillStore } from '@/stores/kill'
|
||||
import { Doughnut } from 'vue-chartjs'
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import { defineComponent, PropType, Ref, unref } from 'vue'
|
||||
import weapons from '../stores/weapons.json'
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend, dataLabel)
|
||||
|
||||
@@ -86,7 +87,7 @@ export default defineComponent({
|
||||
if (!this.weapons) {
|
||||
return 0
|
||||
}
|
||||
return this.weapons[e].kills
|
||||
return this.weapons[e].value.kills
|
||||
})
|
||||
return chartData
|
||||
},
|
||||
@@ -101,7 +102,9 @@ export default defineComponent({
|
||||
plugins: {
|
||||
datalabels: {
|
||||
formatter: (value: any, context: any) => {
|
||||
return this.sortedWeaponList[context.dataIndex]
|
||||
const weaponId = this.sortedWeaponList[context.dataIndex]
|
||||
// if (!(weapons as {[key:string]:string})[weaponId]) console.log(weaponId)
|
||||
return (weapons as {[key:string]:string})[weaponId] || weaponId
|
||||
},
|
||||
backgroundColor: (context: any) => {
|
||||
return context.dataset.backgroundColor(context)
|
||||
@@ -126,7 +129,7 @@ export default defineComponent({
|
||||
} else {
|
||||
label = this.sortedWeaponList[ctx.dataIndex]
|
||||
}
|
||||
label += ' (' + this.weapons[label].kills + ' kills)'
|
||||
label += ' (' + this.weapons[label].value.kills + ' kills)'
|
||||
return label
|
||||
}
|
||||
}
|
||||
@@ -135,23 +138,20 @@ export default defineComponent({
|
||||
}
|
||||
return options
|
||||
},
|
||||
weapons (): { [key: string]: Weapon } {
|
||||
weapons (): { [key: string]: Ref<Weapon> } {
|
||||
const { weapon: _, ...withoutWeapon } = this.filters || {}
|
||||
const data = this.store.getWeaponList(withoutWeapon)?.data
|
||||
if (!data) {
|
||||
this.store.fetchWeapons(withoutWeapon)
|
||||
return {}
|
||||
}
|
||||
const data = this.store.getWeaponList(withoutWeapon)?.value.data
|
||||
if (!data) return this.store.fetchWeapons(withoutWeapon).value.data
|
||||
return data
|
||||
},
|
||||
sortedWeaponList (): string[] {
|
||||
if (!this.weapons) return []
|
||||
const weapons = Object.keys(this.weapons)
|
||||
weapons.sort((a, b) => {
|
||||
if (Number(this.weapons[a].kills) < Number(this.weapons[b].kills)) {
|
||||
if (Number(this.weapons[a].value.kills) < Number(this.weapons[b].value.kills)) {
|
||||
return -1
|
||||
}
|
||||
if (Number(this.weapons[a].kills) > Number(this.weapons[b].kills)) {
|
||||
if (Number(this.weapons[a].value.kills) > Number(this.weapons[b].value.kills)) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
|
||||
+102
-1
@@ -1,8 +1,109 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createApp, ref, unref } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import { createPinia } from 'pinia'
|
||||
import { useKillStore } from './stores/kill'
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
createApp(App).use(pinia).use(router).mount('#app')
|
||||
|
||||
const websocketData = {
|
||||
attacker_id: '1010035351172',
|
||||
attacker_name: 'Iverral',
|
||||
cause_of_death: 'mp_weapon_car',
|
||||
victim_id: '1008553404453',
|
||||
victim_name: 'GURENTITANIUM',
|
||||
attacker_current_weapon: 'mp_weapon_car',
|
||||
victim_current_weapon: 'mp_weapon_sniper',
|
||||
distance: 300,
|
||||
game_mode: 'ps',
|
||||
servername: "fvnkhead's 3v3",
|
||||
map: 'lf_meadow',
|
||||
host: 1
|
||||
}
|
||||
|
||||
const store = useKillStore()
|
||||
setInterval(() => {
|
||||
const allPlayerArrays = store.$state.players.filter((e) => {
|
||||
return (
|
||||
(!e.value.filter.server ||
|
||||
e.value.filter.server === websocketData.servername) &&
|
||||
(!e.value.filter.weapon ||
|
||||
e.value.filter.weapon === websocketData.cause_of_death)
|
||||
// Handle other types of filters once those are implemented
|
||||
)
|
||||
})
|
||||
allPlayerArrays.forEach(e => {
|
||||
if (!e.value.data[websocketData.attacker_id]) {
|
||||
e.value.data[websocketData.attacker_id] = ref({
|
||||
deaths: 0,
|
||||
kills: 500,
|
||||
max_distance: 0,
|
||||
total_distance: 0,
|
||||
username: websocketData.attacker_name
|
||||
})
|
||||
}
|
||||
if (!e.value.data[websocketData.victim_id]) {
|
||||
e.value.data[websocketData.victim_id] = ref({
|
||||
deaths: 0,
|
||||
kills: 0,
|
||||
max_distance: 0,
|
||||
total_distance: 0,
|
||||
username: websocketData.victim_name
|
||||
})
|
||||
}
|
||||
// handle deaths with equipped, eventually
|
||||
/*
|
||||
if(e.value.filter.weapon){
|
||||
!e.value.data[websocketData.victim_id] = e.value.data[websocketData.victim_id] = {
|
||||
deaths: 0,
|
||||
kills: 0,
|
||||
max_distance: 0,
|
||||
total_distance: 0,
|
||||
username: websocketData.victim_name,
|
||||
}
|
||||
} */
|
||||
|
||||
unref(e.value.data[websocketData.victim_id]).deaths++
|
||||
unref(e.value.data[websocketData.victim_id]).username = websocketData.victim_name
|
||||
unref(e.value.data[websocketData.attacker_id]).username = websocketData.attacker_name
|
||||
unref(e.value.data[websocketData.attacker_id]).kills++
|
||||
unref(e.value.data[websocketData.attacker_id]).total_distance += websocketData.distance
|
||||
unref(e.value.data[websocketData.attacker_id]).max_distance = Math.max(websocketData.distance, unref(e.value.data[websocketData.attacker_id]).max_distance)
|
||||
})
|
||||
const allWeaponsArrays = store.$state.weapons.filter((e) => {
|
||||
return (
|
||||
(!e.value.filter.player || e.value.filter.player === websocketData.attacker_id)
|
||||
// Handle other types of filters once those are implemented
|
||||
)
|
||||
})
|
||||
|
||||
allWeaponsArrays.forEach(e => {
|
||||
if (!e.value.data[websocketData.cause_of_death]) {
|
||||
e.value.data[websocketData.cause_of_death] = ref({
|
||||
deaths: 0,
|
||||
kills: 500,
|
||||
max_distance: 0,
|
||||
total_distance: 0,
|
||||
deaths_while_equipped: 0
|
||||
})
|
||||
}
|
||||
// handle deaths with equipped, eventually
|
||||
/*
|
||||
if(e.value.filter.weapon){
|
||||
!e.value.data[websocketData.victim_id] = e.value.data[websocketData.victim_id] = {
|
||||
deaths: 0,
|
||||
kills: 0,
|
||||
max_distance: 0,
|
||||
total_distance: 0,
|
||||
username: websocketData.victim_name,
|
||||
}
|
||||
} */
|
||||
|
||||
// unref(e.value.data[websocketData.cause_of_death]).deaths++
|
||||
unref(e.value.data[websocketData.cause_of_death]).total_distance += websocketData.distance
|
||||
unref(e.value.data[websocketData.cause_of_death]).kills++
|
||||
unref(e.value.data[websocketData.cause_of_death]).max_distance = Math.max(websocketData.distance, unref(e.value.data[websocketData.cause_of_death]).max_distance)
|
||||
})
|
||||
}, 100)
|
||||
|
||||
+75
-45
@@ -1,10 +1,22 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import * as assert from 'assert'
|
||||
import { Ref, reactive, ref, shallowRef, triggerRef, unref } from 'vue'
|
||||
|
||||
function removeNullEntries (a:any) {
|
||||
return Object.fromEntries(Object.entries(a).filter(e => e[1] !== undefined && e[1] !== null))
|
||||
export interface Filters {
|
||||
server?: string;
|
||||
player?: string;
|
||||
weapon?: string;
|
||||
host?: string;
|
||||
map?: string;
|
||||
gamemode?: string;
|
||||
}
|
||||
function objectEqual (a:any, b:any) {
|
||||
|
||||
function removeNullEntries (a: Filters) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(a).filter((e) => e[1] !== undefined && e[1] !== null)
|
||||
)
|
||||
}
|
||||
function objectEqual (a: Filters, b: Filters) {
|
||||
try {
|
||||
a = removeNullEntries(a)
|
||||
b = removeNullEntries(b)
|
||||
@@ -14,14 +26,6 @@ function objectEqual (a:any, b:any) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
export interface Filters {
|
||||
server?: string;
|
||||
player?: string;
|
||||
weapon?: string;
|
||||
host?: string;
|
||||
map?: string;
|
||||
gamemode?: string;
|
||||
}
|
||||
|
||||
export interface Kill {
|
||||
deaths: number;
|
||||
@@ -33,13 +37,13 @@ export interface Kill {
|
||||
}
|
||||
export interface KillData<T extends Kill> {
|
||||
filter: Filters;
|
||||
data: { [key: string]: T };
|
||||
data: { [key: string]: Ref<T> };
|
||||
}
|
||||
|
||||
export interface Player extends Kill {
|
||||
username: string;
|
||||
// eslint-disable-next-line camelcase
|
||||
deaths_while_equipped?: number;
|
||||
// deaths_while_equipped?: number;
|
||||
}
|
||||
|
||||
export interface Weapon extends Kill {
|
||||
@@ -53,11 +57,11 @@ export interface Server extends Kill {
|
||||
|
||||
// define your typings for the store state
|
||||
export interface State {
|
||||
servers: KillData<Server>[];
|
||||
players: KillData<Player>[];
|
||||
weapons: KillData<Weapon>[];
|
||||
maps: KillData<Kill>[];
|
||||
gamemodes: KillData<Kill>[];
|
||||
servers: Ref<KillData<Server>>[];
|
||||
players: Ref<KillData<Player>>[];
|
||||
weapons: Ref<KillData<Weapon>>[];
|
||||
maps: Ref<KillData<Kill>>[];
|
||||
gamemodes: Ref<KillData<Kill>>[];
|
||||
hosts: { [key: number]: string };
|
||||
}
|
||||
|
||||
@@ -71,43 +75,69 @@ export const useKillStore = defineStore('kill', {
|
||||
hosts: {}
|
||||
}),
|
||||
getters: {
|
||||
getPlayerList: (state) => (filters: Filters) => state.players.find((e) => objectEqual(e.filter, filters)),
|
||||
getWeaponList: (state) => (filters: Filters) => state.weapons.find((e) => objectEqual(e.filter, filters)),
|
||||
getServerList: (state) => (filters: Filters) => state.servers.find((e) => objectEqual(e.filter, filters))
|
||||
getPlayerList: (state) => (filters: Filters) => {
|
||||
return state.players.find((e) => objectEqual(unref(e).filter, filters))
|
||||
},
|
||||
getWeaponList: (state) => (filters: Filters) =>
|
||||
state.weapons.find((e) => objectEqual(unref(e).filter, filters)),
|
||||
getServerList: (state) => (filters: Filters) =>
|
||||
state.servers.find((e) => objectEqual(unref(e).filter, filters))
|
||||
},
|
||||
actions: {
|
||||
async fetchPlayers (filter: Filters) {
|
||||
fetchPlayers (filter: Filters) {
|
||||
filter = removeNullEntries(filter)
|
||||
const response = await fetch(
|
||||
'https://tone.sleepycat.date/v2_test/client/players?' +
|
||||
let entry = this.players.find((e) => objectEqual(unref(e).filter, filter))
|
||||
if (entry === undefined) {
|
||||
entry = shallowRef({ filter, data: {} })
|
||||
this.players.push(entry)
|
||||
}
|
||||
fetch(
|
||||
'https://tone.sleepycat.date/v2/client/players?' +
|
||||
new URLSearchParams(filter as Record<keyof Filters, string>)
|
||||
)
|
||||
const data = await response.json()
|
||||
const entry = this.players.find((e) => objectEqual(e.filter, filter))
|
||||
if (!entry) return this.players.push({ filter, data })
|
||||
entry.data = data
|
||||
).then(async response => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
unref(entry)!.data = Object.fromEntries(Object.entries(await response.json()).map(e => [e[0], ref(e[1] as Player)]))
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
triggerRef(entry!)
|
||||
})
|
||||
return entry
|
||||
},
|
||||
async fetchWeapons (filter: Filters) {
|
||||
fetchWeapons (filter: Filters) {
|
||||
filter = removeNullEntries(filter)
|
||||
const response = await fetch(
|
||||
'https://tone.sleepycat.date/v2_test/client/weapons?' +
|
||||
let entry = this.weapons.find((e) => objectEqual(unref(e).filter, filter))
|
||||
if (!entry) {
|
||||
entry = shallowRef({ filter, data: {} })
|
||||
this.weapons.push(entry)
|
||||
}
|
||||
|
||||
fetch(
|
||||
'https://tone.sleepycat.date/v2/client/weapons?' +
|
||||
new URLSearchParams(filter as Record<keyof Filters, string>)
|
||||
)
|
||||
const data = await response.json()
|
||||
const entry = this.weapons.find((e) => objectEqual(e.filter, filter))
|
||||
if (!entry) return this.weapons.push({ filter, data })
|
||||
entry.data = data
|
||||
).then(async response => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
unref(entry)!.data = Object.fromEntries(Object.entries(await response.json()).map(e => [e[0], ref(e[1] as Weapon)]))
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
triggerRef(entry!)
|
||||
})
|
||||
return entry
|
||||
},
|
||||
async fetchServers (filter: Filters) {
|
||||
fetchServers (filter: Filters) {
|
||||
filter = removeNullEntries(filter)
|
||||
const response = await fetch(
|
||||
'https://tone.sleepycat.date/v2_test/client/servers?' +
|
||||
let entry = this.servers.find((e) => objectEqual(unref(e).filter, filter))
|
||||
if (!entry) {
|
||||
entry = shallowRef({ filter, data: {} })
|
||||
this.servers.push(entry)
|
||||
}
|
||||
fetch(
|
||||
'https://tone.sleepycat.date/v2/client/servers?' +
|
||||
new URLSearchParams(filter as Record<keyof Filters, string>)
|
||||
)
|
||||
const data = await response.json()
|
||||
const entry = this.servers.find((e) => objectEqual(e.filter, filter))
|
||||
if (!entry) return this.servers.push({ filter, data })
|
||||
entry.data = data
|
||||
).then(async response => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
unref(entry)!.data = Object.fromEntries(Object.entries(await response.json()).map(e => [e[0], ref(e[1] as Server)]))
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
triggerRef(entry!)
|
||||
})
|
||||
return entry
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"rodeo_battery_removal": "Rodeo Battery Removal",
|
||||
"human_execution": "Human Execution",
|
||||
"melee_pilot_kunai": "Kunai Melee",
|
||||
"execution": "Execution",
|
||||
"mp_weapon_car": "CAR",
|
||||
"mp_weapon_defender": "Charge Rifle",
|
||||
"mp_weapon_doubletake": "Double Take",
|
||||
"mp_weapon_epg": "EPG-1",
|
||||
"mp_weapon_hemlok_smg": "Volt",
|
||||
"mp_weapon_lstar": "L-STAR",
|
||||
"mp_weapon_r97": "R-97",
|
||||
"mp_weapon_rspn101": "R-201",
|
||||
"mp_weapon_smart_pistol": "Smart Pistol",
|
||||
"mp_weapon_sniper": "Kraber",
|
||||
"mp_weapon_vinson": "Flatline",
|
||||
"mp_weapon_autopistol": "RE-45 auto",
|
||||
"mp_weapon_alternator_smg": "Alternator",
|
||||
"mp_weapon_frag_grenade": "Frag Grenade",
|
||||
"mp_weapon_g2": "G2",
|
||||
"mp_weapon_lmg": "Spitfire",
|
||||
"mp_weapon_smr": "Sidewinder SMR",
|
||||
"mp_weapon_softball": "Softball",
|
||||
"mp_weapon_thermite_grenade": "Firestar",
|
||||
"pilot_emptyhanded": "Melee",
|
||||
"mp_weapon_mastiff": "Mastiff",
|
||||
"mp_weapon_mgl": "MGL",
|
||||
"mp_weapon_rspn101_og": "R-101",
|
||||
"mp_weapon_wingman": "Wingman",
|
||||
"invalid": "invalid",
|
||||
"mp_weapon_arc_launcher": "Thunderbolt",
|
||||
"mp_weapon_dmr": "DMR",
|
||||
"mp_weapon_grenade_electric_smoke": "Electric Smoke",
|
||||
"mp_weapon_pulse_lmg": "Cold War",
|
||||
"mp_weapon_satchel": "Satchel",
|
||||
"mp_weapon_shotgun": "EVA-8 Auto",
|
||||
"mp_weapon_shotgun_pistol": "Mozambique",
|
||||
"mp_weapon_wingman_n": "Wingman Elite",
|
||||
"mp_weapon_grenade_gravity": "Gravity Star",
|
||||
"mp_weapon_grenade_sonar": "Pulse Blade",
|
||||
"mp_weapon_rocket_launcher": "Archer",
|
||||
"outOfBounds": "OutOfBounds",
|
||||
"mind_crime": "Mind Crime",
|
||||
"mp_weapon_esaw": "Devotion",
|
||||
"fall": "Fall",
|
||||
"mp_weapon_semipistol": "P2016",
|
||||
"mp_weapon_grenade_emp": "Arc Grenade",
|
||||
"mp_weapon_hemlok": "Hemlok",
|
||||
"melee_pilot_emptyhanded": "Melee",
|
||||
"phase_shift": "Phase Shift",
|
||||
"damagedef_suicide": "Suicide",
|
||||
"damagedef_titan_hotdrop": "Crushed by Titanfall",
|
||||
"mp_titanweapon_arc_cannon": "Arc Cannon",
|
||||
"mp_weapon_peacekraber": "Peacekraber",
|
||||
"damagedef_crush": "Crushed",
|
||||
"mp_titanweapon_brute4_quad_rocket": "Quad Rocket Launcher",
|
||||
"mp_weapon_turretplasma": "Antititan Turret",
|
||||
"mp_titanweapon_xo16_vanguard": "XO-16",
|
||||
"mp_titanweapon_sticky_40mm": "40mm Cannon",
|
||||
"mp_titanweapon_predator_cannon": "Predator Cannon",
|
||||
"mp_titanweapon_leadwall": "Leadwall",
|
||||
"mp_titanweapon_sniper": "Plasma Railgun",
|
||||
"titan_execution": "Titan Execution",
|
||||
"titan_explosion": "Titan Explosion",
|
||||
"auto_titan_melee": "Autotitan Melee",
|
||||
"mp_titanweapon_vortex_shield_ion":"Vortex Shield",
|
||||
"mp_titanweapon_heat_shield":"Heat shield",
|
||||
"bubble_shield":"Bubble Shield",
|
||||
"mp_titancore_salvo_core":"Salvo Core",
|
||||
"mp_titancore_flame_wave":"Flame Wave",
|
||||
"mp_titanweapon_flightcore_rockets":"Flight Core",
|
||||
"mp_titancore_laser_cannon":"Laser Cannon",
|
||||
"mp_titancore_flame_wave_secondary":"Scorched Earth",
|
||||
"mp_titanweapon_flame_wall":"Flame Wall",
|
||||
"mp_titanweapon_arc_wave":"Arc Wave"
|
||||
}
|
||||
+184
-54
@@ -11,25 +11,73 @@
|
||||
<option></option>
|
||||
<option v-for="weaponId in sortedWeaponList" v-bind:key="weaponId" :value="weaponId">{{ weaponId }}</option>
|
||||
</select> -->
|
||||
<span class="multiselect-wrapper">
|
||||
<VueMultiselect
|
||||
selectLabel=""
|
||||
deselectLabel="remove"
|
||||
placeholder="Select server"
|
||||
v-model="model.server"
|
||||
:options="groupedServers"
|
||||
group-values="servers"
|
||||
group_label="id"
|
||||
:group-select="false"
|
||||
:allow-empty="true"
|
||||
:multiple="false"
|
||||
:custom-label="((e: any) => e.name)"
|
||||
track-by="name"
|
||||
label="name"
|
||||
>
|
||||
</VueMultiselect>
|
||||
<button @click="model.server = undefined" :disabled="!model.server">X</button>
|
||||
</span>
|
||||
|
||||
<VueMultiselect selectLabel="" deselectLabel="remove" placeholder="Select server" v-model="model.server"
|
||||
:options="groupedServers" group-values="servers" group_label="id" :group-select="false" :allow-empty="true" :multiple="false" :custom-label="((e:any) => e.name)" track-by="name" label="name"
|
||||
<span class="multiselect-wrapper">
|
||||
<VueMultiselect
|
||||
selectLabel=""
|
||||
deselectLabel="remove"
|
||||
placeholder="Select weapon"
|
||||
v-model="model.weapon"
|
||||
:custom-label="((e: any) => weaponLocale[e] || e)"
|
||||
:options="sortedWeaponList"
|
||||
:allow-empty="true"
|
||||
>
|
||||
</VueMultiselect>
|
||||
<button @click="model.weapon = undefined" :disabled="!model.weapon">X</button>
|
||||
</span>
|
||||
|
||||
<span class="multiselect-wrapper">
|
||||
<VueMultiselect
|
||||
:options-limit="20"
|
||||
selectLabel=""
|
||||
deselectLabel="remove"
|
||||
placeholder="Search player"
|
||||
v-model="playerHighlighted"
|
||||
:options="sortedPlayerList"
|
||||
:allow-empty="true"
|
||||
:custom-label="((e: Player) => e?.username)"
|
||||
></VueMultiselect>
|
||||
|
||||
<VueMultiselect selectLabel="" deselectLabel="remove" placeholder="Select weapon" v-model="model.weapon"
|
||||
:options="sortedWeaponList" :allow-empty="true"
|
||||
></VueMultiselect>
|
||||
|
||||
<VueMultiselect selectLabel="" deselectLabel="remove" placeholder="Search player" v-model="playerHighlighted"
|
||||
:options="sortedPlayerList" :allow-empty="true" :custom-label="((e:string) => players[e]?.username)"></VueMultiselect>
|
||||
<button @click="playerHighlighted = undefined" :disabled="!playerHighlighted">X</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div id="playerView">
|
||||
<PlayerList :filters="filters" v-on:highlight-player="playerHighlighted = $event"
|
||||
:playerHighlighted="playerHighlighted"></PlayerList>
|
||||
<PlayerChart :filters="filters" v-on:highlight-player="playerHighlighted = $event"
|
||||
:playerHighlighted="playerHighlighted"></PlayerChart>
|
||||
<WeaponChart :filters="{player: playerHighlighted, ...filters}" :playerHighlighted="playerHighlighted"></WeaponChart>
|
||||
<PlayerList
|
||||
:filters="filters"
|
||||
v-on:highlightPlayer="highlight_player"
|
||||
:playerHighlighted="playerHighlighted?.id"
|
||||
>
|
||||
</PlayerList>
|
||||
<PlayerChart
|
||||
:filters="filters"
|
||||
v-on:highlightPlayer="highlight_player"
|
||||
:playerHighlighted="playerHighlighted?.id"
|
||||
>
|
||||
</PlayerChart>
|
||||
<WeaponChart
|
||||
:filters="{ player: playerHighlighted?.id, ...filters }"
|
||||
:playerHighlighted="playerHighlighted?.id"
|
||||
>
|
||||
</WeaponChart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -38,58 +86,75 @@ import { Player, Weapon, Server, useKillStore, Filters } from '@/stores/kill'
|
||||
import PlayerList from '@/components/PlayerList.vue'
|
||||
import PlayerChart from '@/components/PlayerChart.vue'
|
||||
import WeaponChart from '@/components/WeaponChart.vue'
|
||||
import { defineComponent } from 'vue'
|
||||
import { Ref, defineComponent, toRaw, unref } from 'vue'
|
||||
import VueMultiselect from 'vue-multiselect'
|
||||
import 'vue-multiselect/dist/vue-multiselect.css'
|
||||
import weapons from '../stores/weapons.json'
|
||||
import router from '@/router'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PlayerView',
|
||||
components: {
|
||||
VueMultiselect, PlayerList, PlayerChart, WeaponChart
|
||||
VueMultiselect,
|
||||
PlayerList,
|
||||
PlayerChart,
|
||||
WeaponChart
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
model: { server: undefined, weapon: undefined } as unknown as {weapon:Weapon | Weapon[], server: (Server& {name?:string}) | (Server& {name?:string})[]},
|
||||
model: { server: undefined, weapon: undefined } as unknown as {
|
||||
weapon: string | undefined;
|
||||
server: { name?: string } | (Server & { name?: string })[] | undefined;
|
||||
},
|
||||
filters: {} as Filters,
|
||||
store: useKillStore(),
|
||||
playerHighlighted: undefined
|
||||
playerHighlighted: undefined as (Player & { id: string }) | undefined,
|
||||
weaponLocale: weapons as { [key: string]: string }
|
||||
}
|
||||
},
|
||||
created () {
|
||||
let server
|
||||
if (this.$route.query.server) server = { name: this.$route.query.server?.toString() }
|
||||
this.model = {
|
||||
server,
|
||||
weapon: this.$route.query.weapon?.toString()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
servers (): { [key: string]: Server } {
|
||||
servers (): { [key: string]: Ref<Server> } {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { server: _, player: _1, ...withoutServer } = this.filters
|
||||
const data = this.store.getServerList(withoutServer)?.data
|
||||
if (!data) {
|
||||
this.store.fetchServers(withoutServer)
|
||||
return {}
|
||||
}
|
||||
const data = this.store.getServerList(withoutServer)?.value.data
|
||||
if (!data) return unref(this.store.fetchServers(withoutServer)).data
|
||||
return data
|
||||
},
|
||||
groupedServers () {
|
||||
const hosts = [] as {id:string, servers:(Server& {name?:string})[] }[]
|
||||
const hosts = [] as {
|
||||
id: string;
|
||||
servers: (Server & { name?: string })[];
|
||||
}[]
|
||||
Object.entries(this.servers).forEach((e) => {
|
||||
if (!hosts.find(host => host.id === e[1].host + 'sfdsdfsd')) hosts.push({ id: e[1].host + 'sfdsdfsd', servers: [] as Server[] })
|
||||
hosts.find(host => host.id === e[1].host + 'sfdsdfsd')?.servers.push({ name: e[0], ...e[1] })
|
||||
const server = unref(e[1])
|
||||
if (!hosts.find((host) => host.id === server.host + 'sfdsdfsd')) { hosts.push({ id: server.host + 'sfdsdfsd', servers: [] as Server[] }) }
|
||||
hosts
|
||||
.find((host) => host.id === server.host + 'sfdsdfsd')
|
||||
?.servers.push({ name: e[0], ...server })
|
||||
})
|
||||
return hosts
|
||||
},
|
||||
weapons (): { [key: string]: Weapon } {
|
||||
weapons (): { [key: string]: Ref<Weapon> } {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { weapon: _, player: _1, ...withoutWeapons } = this.filters
|
||||
const data = this.store.getWeaponList(withoutWeapons)?.data
|
||||
if (!data) {
|
||||
this.store.fetchWeapons(withoutWeapons)
|
||||
return {}
|
||||
}
|
||||
const data = this.store.getWeaponList(withoutWeapons)?.value.data
|
||||
if (!data) return this.store.fetchWeapons(withoutWeapons).value.data
|
||||
return data
|
||||
},
|
||||
players (): { [key: string]: Player } {
|
||||
players (): { [key: string]: Ref<Player> } {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { player: _, ...withoutPlayers } = this.filters
|
||||
const data = this.store.getPlayerList(withoutPlayers)?.data
|
||||
if (!data) {
|
||||
this.store.fetchPlayers(withoutPlayers)
|
||||
return {}
|
||||
}
|
||||
const data = this.store.getPlayerList(withoutPlayers)?.value.data
|
||||
if (!data) return this.store.fetchPlayers(withoutPlayers).value.data
|
||||
return data
|
||||
},
|
||||
sortedWeaponList (): string[] {
|
||||
@@ -97,22 +162,22 @@ export default defineComponent({
|
||||
const weapons = Object.keys(this.weapons)
|
||||
return weapons.sort()
|
||||
},
|
||||
sortedServerList ():string[] {
|
||||
sortedServerList (): string[] {
|
||||
if (!this.servers) return []
|
||||
const servers = Object.keys(this.servers)
|
||||
return servers.sort()
|
||||
},
|
||||
sortedPlayerList (): string[] {
|
||||
sortedPlayerList (): Player[] {
|
||||
if (!this.players) return []
|
||||
const players = Object.keys(this.players)
|
||||
return players.sort((a: string, b: string) => {
|
||||
if (!this.players) {
|
||||
return 0
|
||||
}
|
||||
if (this.players[a].username < this.players[b].username) {
|
||||
const players = Object.entries(this.players).map((e) => ({
|
||||
id: e[0],
|
||||
...toRaw(e[1].value)
|
||||
}))
|
||||
return players.sort((a: Player, b: Player) => {
|
||||
if (a.username < b.username) {
|
||||
return -1
|
||||
}
|
||||
if (this.players[a].username > this.players[b].username) {
|
||||
if (a.username > b.username) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
@@ -120,12 +185,57 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
players () {
|
||||
const player = this.$route.query.player?.toString()
|
||||
if (player && this.playerHighlighted?.id !== player) {
|
||||
this.highlight_player(player)
|
||||
}
|
||||
},
|
||||
model: {
|
||||
handler (newModel) {
|
||||
this.filters.server = newModel.server?.name
|
||||
this.filters.weapon = newModel.weapon
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
filters: {
|
||||
handler (newValue) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { server: _, weapon: _2, ...withoutFilters } = this.$route.query
|
||||
router.push({ query: { ...newValue, ...withoutFilters } }).then(e => { console.log(e) })
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
playerHighlighted (newValue, oldValue) {
|
||||
if (newValue?.id === oldValue?.id) return
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { player: _, ...withoutPlayer } = this.$route.query
|
||||
router.push({ query: { player: newValue?.id, ...withoutPlayer } }).then(e => { console.log(e) })
|
||||
},
|
||||
'$route' (to) {
|
||||
if (this.playerHighlighted?.id !== to.query.player) {
|
||||
const playerId = to.query.player?.toString()
|
||||
if (playerId) {
|
||||
this.highlight_player(playerId)
|
||||
}
|
||||
}
|
||||
if (this.filters.weapon !== to.query.weapon) {
|
||||
this.model.weapon = to.query.weapon
|
||||
}
|
||||
if (this.filters.server !== to.query.server) {
|
||||
if (to.query.server) this.model.server = { name: to.query.server }
|
||||
else this.model.server = undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
highlight_player (playerid: string) {
|
||||
const player = this.players[playerid]
|
||||
if (player) {
|
||||
this.playerHighlighted = { id: playerid, ...unref(player) }
|
||||
return
|
||||
}
|
||||
this.playerHighlighted = undefined
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -133,7 +243,16 @@ export default defineComponent({
|
||||
|
||||
<style scoped>
|
||||
.multiselect {
|
||||
margin: 0 .5em 1em .5em;
|
||||
margin: 0 0.5em 1em 0.5em;
|
||||
}
|
||||
|
||||
.multiselect-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.multiselect-wrapper button {
|
||||
margin: 0 0 1em 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#playerView {
|
||||
@@ -142,8 +261,8 @@ export default defineComponent({
|
||||
display: grid;
|
||||
overflow: auto;
|
||||
grid-template-areas:
|
||||
'list chart'
|
||||
'list info';
|
||||
"list chart"
|
||||
"list info";
|
||||
grid-template-columns: 50% 50%;
|
||||
grid-template-rows: 50% 50%;
|
||||
}
|
||||
@@ -154,16 +273,27 @@ export default defineComponent({
|
||||
/* height:100%; */
|
||||
display: grid;
|
||||
overflow: auto;
|
||||
grid-template-areas:
|
||||
'list list';
|
||||
grid-template-areas: "list list";
|
||||
grid-template-columns: 100%;
|
||||
grid-template-rows: 100%;
|
||||
width: calc(100vw - 1em);
|
||||
margin: 0 0rem 1rem 1rem;
|
||||
}
|
||||
|
||||
.multiselect-wrapper button {
|
||||
margin: 0 0.5em 1em 0;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
width: calc(100% - 1em)
|
||||
width: calc(100% - 1em);
|
||||
}
|
||||
|
||||
.playerChart {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.weaponChart {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule":true,
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
|
||||
Reference in New Issue
Block a user