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 charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<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">
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 276 KiB |
+102
-87
@@ -17,12 +17,12 @@ import {
|
|||||||
PluginChartOptions,
|
PluginChartOptions,
|
||||||
ScaleChartOptions
|
ScaleChartOptions
|
||||||
} from 'chart.js'
|
} from 'chart.js'
|
||||||
import annotationPlugin from 'chartjs-plugin-annotation'
|
import annotationPlugin, { AnnotationPluginOptions } from 'chartjs-plugin-annotation'
|
||||||
import dataLabel from 'chartjs-plugin-datalabels'
|
import dataLabel from 'chartjs-plugin-datalabels'
|
||||||
import { useKillStore, Player, Filters } from '@/stores/kill'
|
import { useKillStore, Player, Filters } from '@/stores/kill'
|
||||||
|
|
||||||
import { Scatter } from 'vue-chartjs'
|
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 { ChartEvent } from 'chart.js/dist/core/core.plugins'
|
||||||
import { _DeepPartialObject } from 'chart.js/dist/types/utils'
|
import { _DeepPartialObject } from 'chart.js/dist/types/utils'
|
||||||
|
|
||||||
@@ -37,7 +37,8 @@ export default defineComponent({
|
|||||||
data: () => {
|
data: () => {
|
||||||
return {
|
return {
|
||||||
store: useKillStore(),
|
store: useKillStore(),
|
||||||
refreshColors: 0
|
refreshColors: 0,
|
||||||
|
playerData: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['highlightPlayer'],
|
emits: ['highlightPlayer'],
|
||||||
@@ -48,6 +49,15 @@ export default defineComponent({
|
|||||||
this.refreshColors++
|
this.refreshColors++
|
||||||
},
|
},
|
||||||
computed: {
|
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 () {
|
colors () {
|
||||||
// eslint-disable-next-line no-unused-expressions
|
// eslint-disable-next-line no-unused-expressions
|
||||||
this.refreshColors
|
this.refreshColors
|
||||||
@@ -64,39 +74,35 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
return colors
|
return colors
|
||||||
},
|
},
|
||||||
|
|
||||||
chart () {
|
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 {
|
return {
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Players',
|
label: 'Players',
|
||||||
labels: this.playerIdList || [] as string[],
|
labels: this.playerList.map(e => e.id) || [] as string[],
|
||||||
borderColor: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? this.colors.orange : this.colors.cyan,
|
borderColor,
|
||||||
backgroundColor: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? this.colors.orange : this.colors.cyan,
|
backgroundColor,
|
||||||
pointRadius: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? 20 : 1,
|
pointRadius,
|
||||||
pointStyle: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? 'crossRot' : 'circle',
|
pointStyle,
|
||||||
hoverRadius: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.index]) ? 30 : 4,
|
hoverRadius,
|
||||||
data: this.playerIdList.map((id) => ({ y: this.players[id].kills, x: this.players[id].deaths }))
|
data: this.playerList.map((player) => ({ y: player.kills, x: player.deaths }))
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
chartOptions (): _DeepPartialObject<CoreChartOptions<'scatter'> & PluginChartOptions<'scatter'> & ScaleChartOptions<'scatter'>> {
|
chartStaticOptions () {
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
@@ -124,53 +130,8 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: {
|
|
||||||
tooltip: {
|
|
||||||
callbacks: {
|
|
||||||
label: (ctx: any) => {
|
|
||||||
let label: string
|
|
||||||
if (!this.players) {
|
|
||||||
label = ctx.dataset.labels[ctx.dataIndex]
|
|
||||||
} else {
|
|
||||||
label = this.players[ctx.dataset.labels[ctx.dataIndex]].username
|
|
||||||
}
|
|
||||||
label += ' (' + ctx.parsed.y + ' kills ' + ctx.parsed.x + ' deaths)'
|
|
||||||
return label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
annotation: {
|
|
||||||
annotations: {
|
|
||||||
line: {
|
|
||||||
type: 'line',
|
|
||||||
yMin: 0,
|
|
||||||
xMin: 0,
|
|
||||||
yMax: endY,
|
|
||||||
xMax: endX,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: this.colors.orange,
|
|
||||||
borderDash: [1, 5]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: { display: false },
|
|
||||||
datalabels: {
|
|
||||||
formatter: (value: any, context: any) => {
|
|
||||||
return this.players[this.playerIdList[context.dataIndex]].username
|
|
||||||
},
|
|
||||||
backgroundColor: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.dataIndex]) ? this.colors.orange : null,
|
|
||||||
borderColor: this.colors.fg,
|
|
||||||
borderWidth: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.dataIndex]) ? 1 : 0,
|
|
||||||
borderRadius: 5,
|
|
||||||
display: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerIdList[context.dataIndex]) ? '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) => {
|
onClick: (e: ChartEvent, element: any) => {
|
||||||
this.$emit('highlightPlayer', element.length > 0 ? this.playerIdList[element[0].index] : undefined)
|
this.$emit('highlightPlayer', element.length > 0 ? this.playerList[element[0].index].id : undefined)
|
||||||
},
|
},
|
||||||
onHover: (e: any, element: any) => {
|
onHover: (e: any, element: any) => {
|
||||||
if (!e.native.target) return
|
if (!e.native.target) return
|
||||||
@@ -178,22 +139,76 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
players (): { [key: string]: Player } {
|
chartStaticPlugins () {
|
||||||
const data = this.store.getPlayerList(this.filters || {})?.data
|
let endX = 0
|
||||||
if (!data) return {}
|
let endY = 0
|
||||||
const cut = Object.entries(data).sort((a, b) => {
|
if (this.playerList) {
|
||||||
if (a[1].kills < b[1].kills) {
|
const maxX = Math.max(...this.playerList.map(e => e.deaths))
|
||||||
return 1
|
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
|
||||||
}
|
}
|
||||||
if (a[1].kills > b[1].kills) {
|
}
|
||||||
return -1
|
return {
|
||||||
}
|
tooltip: {
|
||||||
return 0
|
callbacks: {
|
||||||
}).slice(0, 200)
|
label: (ctx: any) => {
|
||||||
return Object.fromEntries(cut)
|
let label: string
|
||||||
|
if (!this.playerList) {
|
||||||
|
label = ctx.dataset.labels[ctx.dataIndex]
|
||||||
|
} else {
|
||||||
|
label = this.playerList[ctx.dataIndex].username
|
||||||
|
}
|
||||||
|
label += ' (' + ctx.parsed.y + ' kills ' + ctx.parsed.x + ' deaths)'
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
annotation: {
|
||||||
|
annotations: {
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
yMin: 0,
|
||||||
|
xMin: 0,
|
||||||
|
yMax: endY,
|
||||||
|
xMax: endX,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: this.colors.orange,
|
||||||
|
borderDash: [1, 5]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as _DeepPartialObject<AnnotationPluginOptions>,
|
||||||
|
legend: { display: false }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
playerIdList (): string[] { return Object.keys(this.players) }
|
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.playerList[context.dataIndex].username
|
||||||
|
},
|
||||||
|
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.playerList[context.dataIndex].id) ? 1 : 0,
|
||||||
|
borderRadius: 5,
|
||||||
|
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.playerList[context.dataIndex].id) ? this.colors.bg : this.colors.fg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,32 +2,32 @@
|
|||||||
<div class="playerTable" v-on:keydown="selectNextPlayer" tabindex="0">
|
<div class="playerTable" v-on:keydown="selectNextPlayer" tabindex="0">
|
||||||
<div class="playerHeaders">
|
<div class="playerHeaders">
|
||||||
<span></span>
|
<span></span>
|
||||||
<span v-on:click="sortPlayerList('username')"
|
<span v-on:click="updateSort('username')"
|
||||||
:class="sortingData.argument == 'username' ? 'selected' : ''">Username</span>
|
:class="sortingData.argument == 'username' ? 'selected' : ''">Username</span>
|
||||||
<span v-on:click="sortPlayerList('kills')" :class="sortingData.argument == 'kills' ? 'selected' : ''">K</span>
|
<span v-on:click="updateSort('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="updateSort('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="updateSort('k/d')" :class="sortingData.argument == 'k/d' ? 'selected' : ''">K/D</span>
|
||||||
<span v-on:click="sortPlayerList('max_distance')"
|
<span v-on:click="updateSort('max_distance')"
|
||||||
:class="sortingData.argument == 'max_distance' ? 'selected' : ''">max distance</span>
|
: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>
|
:class="sortingData.argument == 'avg_distance' ? 'selected' : ''">average distance</span>
|
||||||
</div>
|
</div>
|
||||||
<div :class="'playerRow ' + (playerId === $props.playerHighlighted ? 'selected' : '')"
|
<div :class="'playerRow ' + (player.id === $props.playerHighlighted ? 'selected' : '')"
|
||||||
v-for="(playerId, index) in playerIdList" v-bind:key="playerId" v-on:click="$emit('highlightPlayer', playerId)"
|
v-for="(player, index) in playerList" v-bind:key="player.id" v-on:click="$emit('highlightPlayer', player.id)"
|
||||||
:ref="`player:` + playerId">
|
:ref="`player:` + player.id">
|
||||||
<div><span>{{ index+1 }}</span></div>
|
<div><span>{{ index+1 }}</span></div>
|
||||||
<div><span>{{ players[playerId].username }}</span></div>
|
<div><span>{{ player.value.username }}</span></div>
|
||||||
<div><span>{{ players[playerId].kills }}</span></div>
|
<div><span>{{ player.value.kills }}</span></div>
|
||||||
<div><span>{{ players[playerId].deaths }}</span></div>
|
<div><span>{{ player.value.deaths }}</span></div>
|
||||||
<div><span>{{ Math.round(players[playerId].kills / Math.max(1, players[playerId].deaths) * 100) / 100 }}</span></div>
|
<div><span>{{ Math.round(player.value.kills / Math.max(1, player.value.deaths) * 100) / 100 }}</span></div>
|
||||||
<div><span>{{ players[playerId].max_distance }}</span></div>
|
<div><span>{{ player.value.max_distance }}</span></div>
|
||||||
<div><span>{{ Math.round(((players[playerId].total_distance / players[playerId].kills) || 0) * 100) / 100 }}</span></div>
|
<div><span>{{ Math.round(((player.value.total_distance / player.value.kills) || 0) * 100) / 100 }}</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue'
|
import { defineComponent, PropType, Ref, toRaw, unref } from 'vue'
|
||||||
import { useKillStore, Player, Filters } from '@/stores/kill'
|
import { useKillStore, Player, Filters } from '@/stores/kill'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -39,74 +39,67 @@ export default defineComponent({
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
sortingData: { direction: -1, argument: 'kills' as keyof Player | 'k/d' | 'avg_distance' },
|
sortingData: { direction: -1, argument: 'kills' as keyof Player | 'k/d' | 'avg_distance' },
|
||||||
playerIdList: [] as string[],
|
|
||||||
store: useKillStore()
|
store: useKillStore()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
players (): { [key: string]: Player } {
|
players (): { [key: string]: Ref<Player> } {
|
||||||
const { player: _, ...withoutPlayer } = this.filters
|
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: {
|
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) {
|
playerHighlighted (newval) {
|
||||||
const newElement = this.$refs['player:' + newval] as HTMLElement[]
|
this.scrollToPlayer(newval)
|
||||||
if (newElement && newElement[0]) { newElement[0].scrollIntoView({ behavior: 'smooth', block: 'center' }) }
|
},
|
||||||
|
playerList () {
|
||||||
|
if (this.playerHighlighted) this.scrollToPlayer(this.playerHighlighted)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updated () {
|
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
|
if (!element || !element[0]) return
|
||||||
element[0].scrollIntoView({ behavior: 'smooth', block: 'center' })
|
element[0].scrollIntoView({ behavior: 'smooth', block: 'center' }) */
|
||||||
},
|
},
|
||||||
methods: {
|
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) {
|
selectNextPlayer (e: KeyboardEvent) {
|
||||||
const values = {
|
const values = {
|
||||||
Home: -Infinity,
|
Home: -Infinity,
|
||||||
@@ -121,11 +114,26 @@ export default defineComponent({
|
|||||||
if (!this.$props.playerHighlighted) return
|
if (!this.$props.playerHighlighted) return
|
||||||
if (!Object.keys(values).includes(e.key)) return
|
if (!Object.keys(values).includes(e.key)) return
|
||||||
e.preventDefault()
|
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])
|
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
|
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 dataLabel from 'chartjs-plugin-datalabels'
|
||||||
import { Weapon, Filters, useKillStore } from '@/stores/kill'
|
import { Weapon, Filters, useKillStore } from '@/stores/kill'
|
||||||
import { Doughnut } from 'vue-chartjs'
|
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)
|
ChartJS.register(ArcElement, Tooltip, Legend, dataLabel)
|
||||||
|
|
||||||
@@ -86,7 +87,7 @@ export default defineComponent({
|
|||||||
if (!this.weapons) {
|
if (!this.weapons) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return this.weapons[e].kills
|
return this.weapons[e].value.kills
|
||||||
})
|
})
|
||||||
return chartData
|
return chartData
|
||||||
},
|
},
|
||||||
@@ -101,7 +102,9 @@ export default defineComponent({
|
|||||||
plugins: {
|
plugins: {
|
||||||
datalabels: {
|
datalabels: {
|
||||||
formatter: (value: any, context: any) => {
|
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) => {
|
backgroundColor: (context: any) => {
|
||||||
return context.dataset.backgroundColor(context)
|
return context.dataset.backgroundColor(context)
|
||||||
@@ -126,7 +129,7 @@ export default defineComponent({
|
|||||||
} else {
|
} else {
|
||||||
label = this.sortedWeaponList[ctx.dataIndex]
|
label = this.sortedWeaponList[ctx.dataIndex]
|
||||||
}
|
}
|
||||||
label += ' (' + this.weapons[label].kills + ' kills)'
|
label += ' (' + this.weapons[label].value.kills + ' kills)'
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,23 +138,20 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
return options
|
return options
|
||||||
},
|
},
|
||||||
weapons (): { [key: string]: Weapon } {
|
weapons (): { [key: string]: Ref<Weapon> } {
|
||||||
const { weapon: _, ...withoutWeapon } = this.filters || {}
|
const { weapon: _, ...withoutWeapon } = this.filters || {}
|
||||||
const data = this.store.getWeaponList(withoutWeapon)?.data
|
const data = this.store.getWeaponList(withoutWeapon)?.value.data
|
||||||
if (!data) {
|
if (!data) return this.store.fetchWeapons(withoutWeapon).value.data
|
||||||
this.store.fetchWeapons(withoutWeapon)
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
sortedWeaponList (): string[] {
|
sortedWeaponList (): string[] {
|
||||||
if (!this.weapons) return []
|
if (!this.weapons) return []
|
||||||
const weapons = Object.keys(this.weapons)
|
const weapons = Object.keys(this.weapons)
|
||||||
weapons.sort((a, b) => {
|
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
|
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 1
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+102
-1
@@ -1,8 +1,109 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp, ref, unref } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
import { useKillStore } from './stores/kill'
|
||||||
|
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
|
|
||||||
createApp(App).use(pinia).use(router).mount('#app')
|
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 { defineStore } from 'pinia'
|
||||||
import * as assert from 'assert'
|
import * as assert from 'assert'
|
||||||
|
import { Ref, reactive, ref, shallowRef, triggerRef, unref } from 'vue'
|
||||||
|
|
||||||
function removeNullEntries (a:any) {
|
export interface Filters {
|
||||||
return Object.fromEntries(Object.entries(a).filter(e => e[1] !== undefined && e[1] !== null))
|
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 {
|
try {
|
||||||
a = removeNullEntries(a)
|
a = removeNullEntries(a)
|
||||||
b = removeNullEntries(b)
|
b = removeNullEntries(b)
|
||||||
@@ -14,14 +26,6 @@ function objectEqual (a:any, b:any) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface Filters {
|
|
||||||
server?: string;
|
|
||||||
player?: string;
|
|
||||||
weapon?: string;
|
|
||||||
host?: string;
|
|
||||||
map?: string;
|
|
||||||
gamemode?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Kill {
|
export interface Kill {
|
||||||
deaths: number;
|
deaths: number;
|
||||||
@@ -33,13 +37,13 @@ export interface Kill {
|
|||||||
}
|
}
|
||||||
export interface KillData<T extends Kill> {
|
export interface KillData<T extends Kill> {
|
||||||
filter: Filters;
|
filter: Filters;
|
||||||
data: { [key: string]: T };
|
data: { [key: string]: Ref<T> };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Player extends Kill {
|
export interface Player extends Kill {
|
||||||
username: string;
|
username: string;
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
deaths_while_equipped?: number;
|
// deaths_while_equipped?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Weapon extends Kill {
|
export interface Weapon extends Kill {
|
||||||
@@ -53,11 +57,11 @@ export interface Server extends Kill {
|
|||||||
|
|
||||||
// define your typings for the store state
|
// define your typings for the store state
|
||||||
export interface State {
|
export interface State {
|
||||||
servers: KillData<Server>[];
|
servers: Ref<KillData<Server>>[];
|
||||||
players: KillData<Player>[];
|
players: Ref<KillData<Player>>[];
|
||||||
weapons: KillData<Weapon>[];
|
weapons: Ref<KillData<Weapon>>[];
|
||||||
maps: KillData<Kill>[];
|
maps: Ref<KillData<Kill>>[];
|
||||||
gamemodes: KillData<Kill>[];
|
gamemodes: Ref<KillData<Kill>>[];
|
||||||
hosts: { [key: number]: string };
|
hosts: { [key: number]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,43 +75,69 @@ export const useKillStore = defineStore('kill', {
|
|||||||
hosts: {}
|
hosts: {}
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getPlayerList: (state) => (filters: Filters) => state.players.find((e) => objectEqual(e.filter, filters)),
|
getPlayerList: (state) => (filters: Filters) => {
|
||||||
getWeaponList: (state) => (filters: Filters) => state.weapons.find((e) => objectEqual(e.filter, filters)),
|
return state.players.find((e) => objectEqual(unref(e).filter, filters))
|
||||||
getServerList: (state) => (filters: Filters) => state.servers.find((e) => objectEqual(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: {
|
actions: {
|
||||||
async fetchPlayers (filter: Filters) {
|
fetchPlayers (filter: Filters) {
|
||||||
filter = removeNullEntries(filter)
|
filter = removeNullEntries(filter)
|
||||||
const response = await fetch(
|
let entry = this.players.find((e) => objectEqual(unref(e).filter, filter))
|
||||||
'https://tone.sleepycat.date/v2_test/client/players?' +
|
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>)
|
new URLSearchParams(filter as Record<keyof Filters, string>)
|
||||||
)
|
).then(async response => {
|
||||||
const data = await response.json()
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const entry = this.players.find((e) => objectEqual(e.filter, filter))
|
unref(entry)!.data = Object.fromEntries(Object.entries(await response.json()).map(e => [e[0], ref(e[1] as Player)]))
|
||||||
if (!entry) return this.players.push({ filter, data })
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
entry.data = data
|
triggerRef(entry!)
|
||||||
|
})
|
||||||
|
return entry
|
||||||
},
|
},
|
||||||
async fetchWeapons (filter: Filters) {
|
fetchWeapons (filter: Filters) {
|
||||||
filter = removeNullEntries(filter)
|
filter = removeNullEntries(filter)
|
||||||
const response = await fetch(
|
let entry = this.weapons.find((e) => objectEqual(unref(e).filter, filter))
|
||||||
'https://tone.sleepycat.date/v2_test/client/weapons?' +
|
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>)
|
new URLSearchParams(filter as Record<keyof Filters, string>)
|
||||||
)
|
).then(async response => {
|
||||||
const data = await response.json()
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const entry = this.weapons.find((e) => objectEqual(e.filter, filter))
|
unref(entry)!.data = Object.fromEntries(Object.entries(await response.json()).map(e => [e[0], ref(e[1] as Weapon)]))
|
||||||
if (!entry) return this.weapons.push({ filter, data })
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
entry.data = data
|
triggerRef(entry!)
|
||||||
|
})
|
||||||
|
return entry
|
||||||
},
|
},
|
||||||
async fetchServers (filter: Filters) {
|
fetchServers (filter: Filters) {
|
||||||
filter = removeNullEntries(filter)
|
filter = removeNullEntries(filter)
|
||||||
const response = await fetch(
|
let entry = this.servers.find((e) => objectEqual(unref(e).filter, filter))
|
||||||
'https://tone.sleepycat.date/v2_test/client/servers?' +
|
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>)
|
new URLSearchParams(filter as Record<keyof Filters, string>)
|
||||||
)
|
).then(async response => {
|
||||||
const data = await response.json()
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const entry = this.servers.find((e) => objectEqual(e.filter, filter))
|
unref(entry)!.data = Object.fromEntries(Object.entries(await response.json()).map(e => [e[0], ref(e[1] as Server)]))
|
||||||
if (!entry) return this.servers.push({ filter, data })
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
entry.data = data
|
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></option>
|
||||||
<option v-for="weaponId in sortedWeaponList" v-bind:key="weaponId" :value="weaponId">{{ weaponId }}</option>
|
<option v-for="weaponId in sortedWeaponList" v-bind:key="weaponId" :value="weaponId">{{ weaponId }}</option>
|
||||||
</select> -->
|
</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"
|
<span class="multiselect-wrapper">
|
||||||
: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
|
||||||
|
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>
|
||||||
|
<button @click="playerHighlighted = undefined" :disabled="!playerHighlighted">X</button>
|
||||||
<VueMultiselect selectLabel="" deselectLabel="remove" placeholder="Select weapon" v-model="model.weapon"
|
</span>
|
||||||
: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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="playerView">
|
<div id="playerView">
|
||||||
<PlayerList :filters="filters" v-on:highlight-player="playerHighlighted = $event"
|
<PlayerList
|
||||||
:playerHighlighted="playerHighlighted"></PlayerList>
|
:filters="filters"
|
||||||
<PlayerChart :filters="filters" v-on:highlight-player="playerHighlighted = $event"
|
v-on:highlightPlayer="highlight_player"
|
||||||
:playerHighlighted="playerHighlighted"></PlayerChart>
|
:playerHighlighted="playerHighlighted?.id"
|
||||||
<WeaponChart :filters="{player: playerHighlighted, ...filters}" :playerHighlighted="playerHighlighted"></WeaponChart>
|
>
|
||||||
|
</PlayerList>
|
||||||
|
<PlayerChart
|
||||||
|
:filters="filters"
|
||||||
|
v-on:highlightPlayer="highlight_player"
|
||||||
|
:playerHighlighted="playerHighlighted?.id"
|
||||||
|
>
|
||||||
|
</PlayerChart>
|
||||||
|
<WeaponChart
|
||||||
|
:filters="{ player: playerHighlighted?.id, ...filters }"
|
||||||
|
:playerHighlighted="playerHighlighted?.id"
|
||||||
|
>
|
||||||
|
</WeaponChart>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -38,58 +86,75 @@ import { Player, Weapon, Server, useKillStore, Filters } from '@/stores/kill'
|
|||||||
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 WeaponChart from '@/components/WeaponChart.vue'
|
import WeaponChart from '@/components/WeaponChart.vue'
|
||||||
import { defineComponent } from 'vue'
|
import { Ref, defineComponent, toRaw, unref } from 'vue'
|
||||||
import VueMultiselect from 'vue-multiselect'
|
import VueMultiselect from 'vue-multiselect'
|
||||||
import 'vue-multiselect/dist/vue-multiselect.css'
|
import 'vue-multiselect/dist/vue-multiselect.css'
|
||||||
|
import weapons from '../stores/weapons.json'
|
||||||
|
import router from '@/router'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'PlayerView',
|
name: 'PlayerView',
|
||||||
components: {
|
components: {
|
||||||
VueMultiselect, PlayerList, PlayerChart, WeaponChart
|
VueMultiselect,
|
||||||
|
PlayerList,
|
||||||
|
PlayerChart,
|
||||||
|
WeaponChart
|
||||||
},
|
},
|
||||||
|
|
||||||
data () {
|
data () {
|
||||||
return {
|
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,
|
filters: {} as Filters,
|
||||||
store: useKillStore(),
|
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: {
|
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 { server: _, player: _1, ...withoutServer } = this.filters
|
||||||
const data = this.store.getServerList(withoutServer)?.data
|
const data = this.store.getServerList(withoutServer)?.value.data
|
||||||
if (!data) {
|
if (!data) return unref(this.store.fetchServers(withoutServer)).data
|
||||||
this.store.fetchServers(withoutServer)
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
groupedServers () {
|
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) => {
|
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[] })
|
const server = unref(e[1])
|
||||||
hosts.find(host => host.id === e[1].host + 'sfdsdfsd')?.servers.push({ name: e[0], ...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
|
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 { weapon: _, player: _1, ...withoutWeapons } = this.filters
|
||||||
const data = this.store.getWeaponList(withoutWeapons)?.data
|
const data = this.store.getWeaponList(withoutWeapons)?.value.data
|
||||||
if (!data) {
|
if (!data) return this.store.fetchWeapons(withoutWeapons).value.data
|
||||||
this.store.fetchWeapons(withoutWeapons)
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
return 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 { player: _, ...withoutPlayers } = this.filters
|
||||||
const data = this.store.getPlayerList(withoutPlayers)?.data
|
const data = this.store.getPlayerList(withoutPlayers)?.value.data
|
||||||
if (!data) {
|
if (!data) return this.store.fetchPlayers(withoutPlayers).value.data
|
||||||
this.store.fetchPlayers(withoutPlayers)
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
sortedWeaponList (): string[] {
|
sortedWeaponList (): string[] {
|
||||||
@@ -97,22 +162,22 @@ export default defineComponent({
|
|||||||
const weapons = Object.keys(this.weapons)
|
const weapons = Object.keys(this.weapons)
|
||||||
return weapons.sort()
|
return weapons.sort()
|
||||||
},
|
},
|
||||||
sortedServerList ():string[] {
|
sortedServerList (): string[] {
|
||||||
if (!this.servers) return []
|
if (!this.servers) return []
|
||||||
const servers = Object.keys(this.servers)
|
const servers = Object.keys(this.servers)
|
||||||
return servers.sort()
|
return servers.sort()
|
||||||
},
|
},
|
||||||
sortedPlayerList (): string[] {
|
sortedPlayerList (): Player[] {
|
||||||
if (!this.players) return []
|
if (!this.players) return []
|
||||||
const players = Object.keys(this.players)
|
const players = Object.entries(this.players).map((e) => ({
|
||||||
return players.sort((a: string, b: string) => {
|
id: e[0],
|
||||||
if (!this.players) {
|
...toRaw(e[1].value)
|
||||||
return 0
|
}))
|
||||||
}
|
return players.sort((a: Player, b: Player) => {
|
||||||
if (this.players[a].username < this.players[b].username) {
|
if (a.username < b.username) {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
if (this.players[a].username > this.players[b].username) {
|
if (a.username > b.username) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
@@ -120,12 +185,57 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
players () {
|
||||||
|
const player = this.$route.query.player?.toString()
|
||||||
|
if (player && this.playerHighlighted?.id !== player) {
|
||||||
|
this.highlight_player(player)
|
||||||
|
}
|
||||||
|
},
|
||||||
model: {
|
model: {
|
||||||
handler (newModel) {
|
handler (newModel) {
|
||||||
this.filters.server = newModel.server?.name
|
this.filters.server = newModel.server?.name
|
||||||
this.filters.weapon = newModel.weapon
|
this.filters.weapon = newModel.weapon
|
||||||
},
|
},
|
||||||
deep: true
|
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>
|
<style scoped>
|
||||||
.multiselect {
|
.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 {
|
#playerView {
|
||||||
@@ -142,8 +261,8 @@ export default defineComponent({
|
|||||||
display: grid;
|
display: grid;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
'list chart'
|
"list chart"
|
||||||
'list info';
|
"list info";
|
||||||
grid-template-columns: 50% 50%;
|
grid-template-columns: 50% 50%;
|
||||||
grid-template-rows: 50% 50%;
|
grid-template-rows: 50% 50%;
|
||||||
}
|
}
|
||||||
@@ -154,16 +273,27 @@ export default defineComponent({
|
|||||||
/* height:100%; */
|
/* height:100%; */
|
||||||
display: grid;
|
display: grid;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
grid-template-areas:
|
grid-template-areas: "list list";
|
||||||
'list list';
|
|
||||||
grid-template-columns: 100%;
|
grid-template-columns: 100%;
|
||||||
grid-template-rows: 100%;
|
grid-template-rows: 100%;
|
||||||
width: calc(100vw - 1em);
|
width: calc(100vw - 1em);
|
||||||
margin: 0 0rem 1rem 1rem;
|
margin: 0 0rem 1rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.multiselect-wrapper button {
|
||||||
|
margin: 0 0.5em 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
.multiselect {
|
.multiselect {
|
||||||
width: calc(100% - 1em)
|
width: calc(100% - 1em);
|
||||||
|
}
|
||||||
|
|
||||||
|
.playerChart {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weaponChart {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"resolveJsonModule":true,
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user