responsive and some finishing touches

This commit is contained in:
2023-03-28 22:11:19 +02:00
parent 8ade9d79a1
commit b72bf04d49
7 changed files with 167 additions and 54 deletions
+10
View File
@@ -15,6 +15,7 @@
"vue": "^3.2.13", "vue": "^3.2.13",
"vue-chartjs": "^5.2.0", "vue-chartjs": "^5.2.0",
"vue-class-component": "^8.0.0-0", "vue-class-component": "^8.0.0-0",
"vue-multiselect": "^3.0.0-beta.1",
"vue-router": "^4.0.3", "vue-router": "^4.0.3",
"vuex": "^4.0.0" "vuex": "^4.0.0"
}, },
@@ -12498,6 +12499,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/vue-multiselect": {
"version": "3.0.0-beta.1",
"resolved": "https://registry.npmjs.org/vue-multiselect/-/vue-multiselect-3.0.0-beta.1.tgz",
"integrity": "sha512-V+jpydtjyHcQ+yjHsEWEBrDAopOx/pufNkSAXNVDAGQ+ESDEJ7wYejNd9H1RiCnFOYK4yf1XSGqE+Mp3HJXmdg==",
"engines": {
"node": ">= 4.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/vue-router": { "node_modules/vue-router": {
"version": "4.1.6", "version": "4.1.6",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz",
+1
View File
@@ -15,6 +15,7 @@
"vue": "^3.2.13", "vue": "^3.2.13",
"vue-chartjs": "^5.2.0", "vue-chartjs": "^5.2.0",
"vue-class-component": "^8.0.0-0", "vue-class-component": "^8.0.0-0",
"vue-multiselect": "^3.0.0-beta.1",
"vue-router": "^4.0.3", "vue-router": "^4.0.3",
"vuex": "^4.0.0" "vuex": "^4.0.0"
}, },
+16 -5
View File
@@ -2,11 +2,11 @@
<header> <header>
<nav> <nav>
<!-- <div class="navbar-element">hamburger</div> --> <!-- <div class="navbar-element">hamburger</div> -->
<div class="navbar-element"><span>logo&nbsp;</span>TONE&nbsp;API</div> <div class="navbar-element"><!--<span>logo&nbsp;</span>--->TONE&nbsp;API</div>
<div class="spacer"></div> <div class="spacer"></div>
<router-link to="/" class="navbar-element">Home</router-link> <!-- <router-link to="/" class="navbar-element">Home</router-link>
<router-link to="/about" class="navbar-element">About</router-link> <router-link to="/about" class="navbar-element">About</router-link> -->
<router-link to="/players" class="navbar-element">Players</router-link> <router-link to="/" class="navbar-element">Players</router-link>
</nav> </nav>
</header> </header>
<router-view /> <router-view />
@@ -31,7 +31,7 @@ body {
#app { #app {
height: 100vh; height: 100vh;
width: 100vw; width: 100vw;
font-family: Avenir, Helvetica, Arial, sans-serif; font-family: 'Helvetica', 'Arial', sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
text-align: center; text-align: center;
@@ -46,6 +46,17 @@ body {
grid-template-rows: auto 1fr auto auto; grid-template-rows: auto 1fr auto auto;
} }
@media only screen and (max-width: 922px) {
#app{
grid-template-areas:
'header header'
'filters filters'
'body body ';
grid-template-rows: auto auto 1fr;
}
}
header { header {
grid-area: header; grid-area: header;
height: 64px; height: 64px;
+22 -4
View File
@@ -44,7 +44,9 @@ export default defineComponent({
if (this.$refs.container) { if (this.$refs.container) {
const style = getComputedStyle(this.$refs.container as Element) const style = getComputedStyle(this.$refs.container as Element)
colors.fg = style.getPropertyValue('--foreground') colors.fg = style.getPropertyValue('--foreground')
colors.bg = style.getPropertyValue('--bg-color')
colors.orange = style.getPropertyValue('--orange') colors.orange = style.getPropertyValue('--orange')
colors.cyan = style.getPropertyValue('--cyan')
colors.currentLine = style.getPropertyValue('--current-line') colors.currentLine = style.getPropertyValue('--current-line')
} else { } else {
colors.fg = '#ffffff' colors.fg = '#ffffff'
@@ -78,7 +80,7 @@ export default defineComponent({
}) })
chartData.datasets[0].pointRadius = Array(chartData.datasets[0].labels.length).fill(1) chartData.datasets[0].pointRadius = Array(chartData.datasets[0].labels.length).fill(1)
chartData.datasets[0].pointStyle = Array(chartData.datasets[0].labels.length).fill('dot') chartData.datasets[0].pointStyle = Array(chartData.datasets[0].labels.length).fill('dot')
chartData.datasets[0].borderColor = Array(chartData.datasets[0].labels.length).fill(this.colors.fg) chartData.datasets[0].borderColor = Array(chartData.datasets[0].labels.length).fill(this.colors.cyan)
chartData.datasets[0].backgroundColor = chartData.datasets[0].borderColor chartData.datasets[0].backgroundColor = chartData.datasets[0].borderColor
chartData.datasets[0].hoverRadius = Array(chartData.datasets[0].labels.length).fill(4) chartData.datasets[0].hoverRadius = Array(chartData.datasets[0].labels.length).fill(4)
if (this.$props.playerHighlighted) { if (this.$props.playerHighlighted) {
@@ -102,13 +104,21 @@ export default defineComponent({
y: { y: {
title: { title: {
text: 'Kills', text: 'Kills',
display: true display: true,
color: this.colors.fg
},
ticks: {
color: this.colors.fg
} }
}, },
x: { x: {
title: { title: {
text: 'Deaths', text: 'Deaths',
display: true display: true,
color: this.colors.fg
},
ticks: {
color: this.colors.fg
} }
} }
}, },
@@ -128,6 +138,7 @@ export default defineComponent({
} }
}, },
annotation: {}, annotation: {},
legend: { display: false },
datalabels: { datalabels: {
formatter: (value:any, context:any) => { formatter: (value:any, context:any) => {
return this.players[this.playerIdList[context.dataIndex]].username return this.players[this.playerIdList[context.dataIndex]].username
@@ -139,7 +150,8 @@ export default defineComponent({
display: Array(this.playerIdList.length).fill('auto'), display: Array(this.playerIdList.length).fill('auto'),
align: '-45', align: '-45',
anchor: 'end', anchor: 'end',
clamp: true clamp: true,
color: Array(this.playerIdList.length).fill(this.colors.fg)
} }
}, },
onClick: (e:Event, element:any) => { onClick: (e:Event, element:any) => {
@@ -183,6 +195,7 @@ export default defineComponent({
options.plugins.datalabels.display[index] = true options.plugins.datalabels.display[index] = true
options.plugins.datalabels.backgroundColor[index] = this.colors.orange options.plugins.datalabels.backgroundColor[index] = this.colors.orange
options.plugins.datalabels.borderWidth[index] = 1 options.plugins.datalabels.borderWidth[index] = 1
options.plugins.datalabels.color[index] = this.colors.bg
} }
return options return options
}, },
@@ -203,4 +216,9 @@ export default defineComponent({
canvas { canvas {
background: var(--accent); background: var(--accent);
} }
@media only screen and (max-width: 922px) {
canvas{
display:none !important
}
}
</style> </style>
+42 -27
View File
@@ -2,22 +2,22 @@
<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')" :class="sortingData.argument == 'username' ? 'selected' : ''">username</span> <span v-on:click="sortPlayerList('username')" :class="sortingData.argument == 'username' ? 'selected' : ''">Username</span>
<span v-on:click="sortPlayerList('kills')" :class="sortingData.argument == 'kills' ? 'selected' : ''">kills</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' : ''">deaths</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('k/d')" :class="sortingData.argument == 'k/d' ? 'selected' : ''">K/D</span>
<span v-on:click="sortPlayerList('max_kill_distance')" :class="sortingData.argument == 'max_kill_distance' ? 'selected' : ''">max kill distance</span> <span v-on:click="sortPlayerList('max_kill_distance')" :class="sortingData.argument == 'max_kill_distance' ? 'selected' : ''">max distance</span>
<span v-on:click="sortPlayerList('avg_kill_distance')" :class="sortingData.argument == 'avg_kill_distance' ? 'selected' : ''">average kill distance</span> <span v-on:click="sortPlayerList('avg_kill_distance')" :class="sortingData.argument == 'avg_kill_distance' ? 'selected' : ''">average distance</span>
</div> </div>
<div class="playerRow" v-for="(playerId, index) in playerIdList" v-bind:key="playerId" <div class="playerRow" v-for="(playerId, index) in playerIdList" v-bind:key="playerId"
v-on:click="$emit('highlightPlayer', playerId)" :ref="`player:` + playerId"> v-on:click="$emit('highlightPlayer', playerId)" :ref="`player:` + playerId">
<span>{{ index }}</span> <div><span>{{ index }}</span></div>
<span>{{ players[playerId].username }}</span> <div><span>{{ players[playerId].username }}</span></div>
<span>{{ players[playerId].kills }}</span> <div><span>{{ players[playerId].kills }}</span></div>
<span>{{ players[playerId].deaths }}</span> <div><span>{{ players[playerId].deaths }}</span></div>
<span>{{ Math.round(players[playerId].kills / players[playerId].deaths * 100) / 100 }}</span> <div><span>{{ Math.round(players[playerId].kills / players[playerId].deaths * 100) / 100 }}</span></div>
<span>{{ players[playerId].max_kill_distance }}</span> <div><span>{{ players[playerId].max_kill_distance }}</span></div>
<span>{{ Math.round(players[playerId].avg_kill_distance * 100) / 100 }}</span> <div><span>{{ Math.round(players[playerId].avg_kill_distance * 100) / 100 }}</span></div>
</div> </div>
</div> </div>
</template> </template>
@@ -147,21 +147,30 @@ export default defineComponent({
height: 100%; height: 100%;
} }
.playerHeaders {
position: sticky;
top: 0px;
}
.playerRow, .playerRow,
.playerHeaders { .playerHeaders {
box-sizing: border-box;
display: grid; display: grid;
grid-template-columns: 4ch 2fr 6ch 6ch 6ch 10ch 1fr; grid-template-columns: 4ch 20ch 6ch 6ch 6ch 10ch 1fr;
background: var(--bg-color); background: var(--bg-color);
text-align: left; text-align: left;
cursor: pointer; cursor: pointer;
margin: 0; margin: 0;
/* border: 2px solid transparent; */ /* border: 2px solid transparent; */
transition: border .25s; transition: border .25s;
height:2em;
}
.playerHeaders{
position:sticky;
top:0;
height:3em;
}
.playerHeaders>span{
padding:.5em .8em 0 .25em;
}
.playerRow>div {
padding:.5em .8em 0 .25em;
} }
.playerHeaders, .playerHeaders,
@@ -173,13 +182,13 @@ export default defineComponent({
background: var(--accent); background: var(--accent);
} }
.playerHeaders span:hover, .playerHeaders div:hover,
.playerRow:hover { .playerRow:hover {
background: var(--bg-color); background: var(--bg-color);
} }
.playerRow>span:not(:last-child), .playerRow>div:not(:last-child),
.playerHeaders>span:not(:last-child) { .playerHeaders>div:not(:last-child) {
border-right: solid var(--bg-color) 2px; border-right: solid var(--bg-color) 2px;
} }
@@ -187,10 +196,14 @@ export default defineComponent({
width:100%; width:100%;
} }
span { @media only screen and (max-width: 600px) {
text-overflow: ellipsis; .playerRow>div:nth-child(6), .playerHeaders>span:nth-child(6),.playerRow>div:nth-child(7), .playerHeaders>span:nth-child(7) {
overflow: hidden; display: none;
user-select: none; }
.playerRow,
.playerHeaders{
grid-template-columns: 4ch 20ch 6ch 6ch 1fr;
}
} }
.player span { .player span {
@@ -199,4 +212,6 @@ span {
.selected { .selected {
background: var(--comment) !important; background: var(--comment) !important;
}</style> }
</style>
+13 -13
View File
@@ -6,21 +6,21 @@ const routes: Array<RouteRecordRaw> = [
{ {
path: '/', path: '/',
name: 'home', name: 'home',
component: HomeView
},
{
path: '/players',
name: 'players',
component: PlayerView component: PlayerView
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
} }
// {
// path: '/players',
// name: 'players',
// component: PlayerView
// },
// {
// path: '/about',
// name: 'about',
// // route level code-splitting
// // this generates a separate chunk (about.[hash].js) for this route
// // which is lazy-loaded when the route is visited.
// component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
// }
] ]
const router = createRouter({ const router = createRouter({
+63 -5
View File
@@ -1,8 +1,8 @@
<template> <template>
<div id="filters"> <div id="filters">
<input type="number" min="0" v-model="filters.minKills"> <!-- <input type="number" min="0" v-model="filters.minKills">
<input type="number" min="0" v-model="filters.minDeaths"> <input type="number" min="0" v-model="filters.minDeaths"> -->
<select v-on:change="changeFilter({ server: ($event.target as HTMLInputElement).value })"> <!-- <select v-on:change="changeFilter({ server: ($event.target as HTMLInputElement).value })">
<option></option> <option></option>
<option v-for="(serverData) in servers" v-bind:key="serverData.id" :value="serverData.id">{{ serverData.name }} <option v-for="(serverData) in servers" v-bind:key="serverData.id" :value="serverData.id">{{ serverData.name }}
</option> </option>
@@ -10,7 +10,18 @@
<select v-on:change="changeFilter({ weapon: ($event.target as HTMLInputElement).value })"> <select v-on:change="changeFilter({ weapon: ($event.target as HTMLInputElement).value })">
<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> -->
<VueMultiselect :showLabels="false" placeholder="Select server" v-model="models.server" :options="servers"
:allow-empty="true" :custom-label="((e) => e.name)" @select="changeFilter({ server: $event.id })"
@remove="changeFilter({ server: '' })"></VueMultiselect>
<VueMultiselect :showLabels="false" selectLabel="" placeholder="Select weapon" v-model="models.weapon"
:options="sortedWeaponList" :allow-empty="true" @select="changeFilter({ weapon: $event })"
@remove="changeFilter({ weapon: '' })"></VueMultiselect>
<VueMultiselect :showLabels="false" selectLabel="" placeholder="Search player" v-model="playerHighlighted"
:options="sortedPlayerList" :allow-empty="true" :custom-label="((e) => players[e]?.username)"></VueMultiselect>
</div> </div>
<div id="playerView"> <div id="playerView">
@@ -27,15 +38,18 @@ import { Player, Weapon, Server } from '@/store/index'
import PlayerList from '@/components/PlayerList.vue' import PlayerList from '@/components/PlayerList.vue'
import PlayerChart from '@/components/PlayerChart.vue' import PlayerChart from '@/components/PlayerChart.vue'
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import VueMultiselect from 'vue-multiselect'
import 'vue-multiselect/dist/vue-multiselect.css'
export default defineComponent({ export default defineComponent({
name: 'PlayerView', name: 'PlayerView',
components: { components: {
PlayerList, PlayerChart PlayerList, PlayerChart, VueMultiselect
}, },
data () { data () {
return { return {
models: {},
filters: { minKills: 100, minDeaths: 100 }, filters: { minKills: 100, minDeaths: 100 },
store: useStore(), store: useStore(),
playerHighlighted: undefined playerHighlighted: undefined
@@ -45,12 +59,37 @@ export default defineComponent({
servers (): Server[] { return this.store.state.servers }, servers (): Server[] { return this.store.state.servers },
weapons (): { [key: string]: Weapon } { return this.store.getters.getWeaponList(this.filters) }, weapons (): { [key: string]: Weapon } { return this.store.getters.getWeaponList(this.filters) },
sortedWeaponList (): string[] { sortedWeaponList (): string[] {
if (!this.weapons) return []
const weapons = Object.keys(this.weapons) const weapons = Object.keys(this.weapons)
return weapons.sort() return weapons.sort()
},
players (): { [key: string]: Player } { return this.store.getters.getPlayerList(this.filters) },
sortedPlayerList (): string[] {
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) {
return -1
}
if (this.players[a].username > this.players[b].username) {
return 1
}
return 0
})
}
},
watch: {
test: function (newval) {
console.log(newval)
} }
}, },
methods: { methods: {
async changeFilter ({ weapon, server }: { weapon?: string, server?: string }) { async changeFilter ({ weapon, server }: { weapon?: string, server?: string }) {
console.log(weapon)
const filters = JSON.parse(JSON.stringify(this.filters)) const filters = JSON.parse(JSON.stringify(this.filters))
if (weapon !== undefined) filters.weapon = weapon if (weapon !== undefined) filters.weapon = weapon
if (server !== undefined) filters.server = server if (server !== undefined) filters.server = server
@@ -68,6 +107,10 @@ export default defineComponent({
</script> </script>
<style scoped> <style scoped>
.multiselect {
margin: 0 .5em 1em .5em;
}
#playerView { #playerView {
margin: 0 1rem 1rem 1rem; margin: 0 1rem 1rem 1rem;
/* height:100%; */ /* height:100%; */
@@ -76,6 +119,21 @@ export default defineComponent({
grid-template-columns: 50% 50%; grid-template-columns: 50% 50%;
} }
@media only screen and (max-width: 922px) {
#playerView {
margin: 0 1rem 1rem 1rem;
/* height:100%; */
display: grid;
overflow: auto;
grid-template-columns: 100%;
width: calc(100vw - 1em);
margin: 0 0rem 1rem 1rem;
}
.multiselect{
width: calc(100% - 1em)
}
}
#filters { #filters {
grid-area: filters; grid-area: filters;
} }