add progress bar

This commit is contained in:
2023-05-26 15:41:49 +02:00
parent c565d1593b
commit 9764f65cf6
5 changed files with 154 additions and 36 deletions
+25
View File
@@ -0,0 +1,25 @@
<template>
<div class="loadingComponent">
<progress class="loadingBar" max="1" :value="value"></progress>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: {
value: Number
}
})
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.loadingComponent {
display:flex;
align-items: center;
justify-content: center;
height: calc(100% - 3em);
}
</style>
+12 -3
View File
@@ -1,7 +1,7 @@
<template> <template>
<div class="playerChart" ref="container"> <div class="playerChart" ref="container">
<!-- <Scatter :data="chart" :options="chartOptions" /> --> <LoadingBar v-if="progress!==1" :value="progress"></LoadingBar>
<Scatter :data="chart" :options="chartOptions" /> <Scatter :data="chart" :options="chartOptions" v-if="progress===1" />
</div> </div>
</template> </template>
@@ -26,6 +26,8 @@ import { defineComponent, PropType, Ref, toRaw, triggerRef, 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'
import LoadingBar from './LoadingBar.vue'
ChartJS.register(LinearScale, PointElement, LineElement, Tooltip, Legend, annotationPlugin, dataLabel) ChartJS.register(LinearScale, PointElement, LineElement, Tooltip, Legend, annotationPlugin, dataLabel)
export default defineComponent({ export default defineComponent({
@@ -43,12 +45,19 @@ export default defineComponent({
}, },
emits: ['highlightPlayer'], emits: ['highlightPlayer'],
components: { components: {
Scatter Scatter, LoadingBar
}, },
mounted () { mounted () {
this.refreshColors++ this.refreshColors++
}, },
computed: { computed: {
progress () {
if (this.filters) {
const { player: _, ...withoutPlayer } = this.filters
return this.store.getPlayerList(withoutPlayer)?.value.progress
}
return 0
},
playerList (): (Player & {id:string})[] { playerList (): (Player & {id:string})[] {
const data = this.store.getPlayerList(this.filters || {})?.value.data 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) })) if (!data) return Object.entries(this.store.fetchPlayers(this.filters || {}).value.data).map(e => ({ id: e[0], ...toRaw(e[1].value) }))
+8 -3
View File
@@ -12,7 +12,8 @@
<span v-on:click="updateSort('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>
<VirtualList :list="playerList" :row-height="32" v-slot="slotProps" :highlighted="playerHighlighted"> <LoadingBar v-if="progress!==1" :value="progress"></LoadingBar>
<VirtualList :list="playerList" :row-height="32" v-slot="slotProps" :highlighted="playerHighlighted" v-if="progress===1">
<div :class="'playerRow ' + (slotProps.data.id === playerHighlighted ? 'selected ' : '') + ((slotProps.index + 1)%2 ? 'odd ':'uneven ')" v-on:click="$emit('highlightPlayer', slotProps.data.id)"> <div :class="'playerRow ' + (slotProps.data.id === playerHighlighted ? 'selected ' : '') + ((slotProps.index + 1)%2 ? 'odd ':'uneven ')" v-on:click="$emit('highlightPlayer', slotProps.data.id)">
<div><span>{{ slotProps.index+1 }}</span></div> <div><span>{{ slotProps.index+1 }}</span></div>
<div><span>{{ slotProps.data.value.username }}</span></div> <div><span>{{ slotProps.data.value.username }}</span></div>
@@ -30,9 +31,10 @@
import { defineComponent, PropType, Ref } from 'vue' import { defineComponent, PropType, Ref } from 'vue'
import { useKillStore, Player, Filters } from '@/stores/kill' import { useKillStore, Player, Filters } from '@/stores/kill'
import VirtualList from './List/VirtualList.vue' import VirtualList from './List/VirtualList.vue'
import LoadingBar from './LoadingBar.vue'
export default defineComponent({ export default defineComponent({
components: { VirtualList }, components: { VirtualList, LoadingBar },
props: { props: {
filters: { type: Object as PropType<Filters>, default: () => ({}) }, filters: { type: Object as PropType<Filters>, default: () => ({}) },
playerHighlighted: String playerHighlighted: String
@@ -45,6 +47,10 @@ export default defineComponent({
} }
}, },
computed: { computed: {
progress () {
const { player: _, ...withoutPlayer } = this.filters
return this.store.getPlayerList(withoutPlayer)?.value.progress
},
players (): { [key: string]: Ref<Player> } { players (): { [key: string]: Ref<Player> } {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { player: _, ...withoutPlayer } = this.filters const { player: _, ...withoutPlayer } = this.filters
@@ -61,7 +67,6 @@ export default defineComponent({
return player return player
}) as (Ref<Player> & { id: string })[] }) as (Ref<Player> & { id: string })[]
const date = new Date()
if (this.sortingData.argument === 'username') { if (this.sortingData.argument === 'username') {
const collator = new Intl.Collator('en', { numeric: true, sensitivity: 'base' }) const collator = new Intl.Collator('en', { numeric: true, sensitivity: 'base' })
players.sort((a, b) => { players.sort((a, b) => {
+9 -2
View File
@@ -1,6 +1,7 @@
<template> <template>
<div class="weaponChart" ref="container"> <div class="weaponChart" ref="container">
<Doughnut :data="chart" :options="chartOptions" /> <LoadingBar v-if="progress !== 1" :value="progress"></LoadingBar>
<Doughnut :data="chart" :options="chartOptions" v-if="progress === 1"/>
</div> </div>
</template> </template>
@@ -12,6 +13,8 @@ import { Doughnut } from 'vue-chartjs'
import { defineComponent, PropType, Ref } from 'vue' import { defineComponent, PropType, Ref } from 'vue'
import weapons from '../stores/weapons.json' import weapons from '../stores/weapons.json'
import LoadingBar from './LoadingBar.vue'
ChartJS.register(ArcElement, Tooltip, Legend, dataLabel) ChartJS.register(ArcElement, Tooltip, Legend, dataLabel)
export default defineComponent({ export default defineComponent({
@@ -22,7 +25,7 @@ export default defineComponent({
}, },
emits: ['highlightPlayer'], emits: ['highlightPlayer'],
components: { components: {
Doughnut Doughnut, LoadingBar
}, },
mounted () { mounted () {
this.refreshColors++ this.refreshColors++
@@ -34,6 +37,10 @@ export default defineComponent({
} }
}, },
computed: { computed: {
progress () {
const { weapon: _, ...withoutWeapon } = this.filters || {}
return this.store.getWeaponList(withoutWeapon)?.value.progress
},
colors () { colors () {
// eslint-disable-next-line no-unused-expressions // eslint-disable-next-line no-unused-expressions
this.refreshColors this.refreshColors
+100 -28
View File
@@ -27,6 +27,59 @@ function objectEqual (a: Filters, b: Filters) {
} }
} }
function fetchWithLoading (url: string, progress: (percentage: number) => void) {
return fetch(url).then(response => {
if (!response.ok) {
throw Error(response.status + ' ' + response.statusText)
}
if (!response.body) {
throw Error('ReadableStream not yet supported in this browser.')
}
// to access headers, server must send CORS header "Access-Control-Expose-Headers: content-encoding, content-length x-file-size"
// server must send custom x-file-size header if gzip or other content-encoding is used
const contentEncoding = response.headers.get('Content-Encoding')
const contentLength = response.headers.get(contentEncoding ? 'X-File-Size' : 'content-length')
if (contentLength === null) {
throw Error('Response size header unavailable')
}
const total = parseInt(contentLength, 10)
let loaded = 0
return new Response(
new ReadableStream({
start (controller) {
const reader = response.body!.getReader()
read()
function read () {
reader.read().then(({ done, value }) => {
if (done) {
controller.close()
return
}
if (value) {
loaded += value.byteLength
progress(loaded / total)
controller.enqueue(value)
}
read()
}).catch(error => {
console.error(error)
controller.error(error)
})
}
}
})
)
})
.catch(error => {
console.error(error)
})
}
export interface Kill { export interface Kill {
deaths: number; deaths: number;
kills: number; kills: number;
@@ -38,6 +91,7 @@ export interface Kill {
export interface KillData<T extends Kill> { export interface KillData<T extends Kill> {
filter: Filters; filter: Filters;
data: { [key: string]: Ref<T> }; data: { [key: string]: Ref<T> };
progress?: number;
} }
export interface Player extends Kill { export interface Player extends Kill {
@@ -56,15 +110,15 @@ export interface Server extends Kill {
} }
export interface NSServer { export interface NSServer {
name:string; name: string;
region:string; region: string;
description:string; description: string;
playerCount:number; playerCount: number;
maxPlayers:number; maxPlayers: number;
map:string; map: string;
playlist:string; playlist: string;
hasPassword:boolean; hasPassword: boolean;
modInfo:{Mods:[]} modInfo: { Mods: [] }
} }
// define your typings for the store state // define your typings for the store state
export interface State { export interface State {
@@ -77,7 +131,7 @@ export interface State {
nsServers: NSServer[] | undefined nsServers: NSServer[] | undefined
} }
let serverInterval:number let serverInterval: number
export const useKillStore = defineStore('kill', { export const useKillStore = defineStore('kill', {
state: (): State => ({ state: (): State => ({
servers: [], servers: [],
@@ -106,14 +160,20 @@ export const useKillStore = defineStore('kill', {
entry = shallowRef({ filter, data: {} }) entry = shallowRef({ filter, data: {} })
this.players.push(entry) this.players.push(entry)
} }
fetch( fetchWithLoading(
'https://tone.sleepycat.date/v2/client/players?' + 'https://tone.sleepycat.date/v2/client/players?' +
new URLSearchParams(filter as Record<keyof Filters, string>) new URLSearchParams(filter as Record<keyof Filters, string>),
(progress) => {
if (entry) {
unref(entry).progress = progress
if (progress !== 1) triggerRef(entry)
}
}
).then(async response => { ).then(async response => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion if (entry) {
unref(entry)!.data = Object.fromEntries(Object.entries(await response.json()).map(e => [e[0], ref(e[1] as Player)])) 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)
triggerRef(entry!) }
}) })
return entry return entry
}, },
@@ -125,14 +185,20 @@ export const useKillStore = defineStore('kill', {
this.weapons.push(entry) this.weapons.push(entry)
} }
fetch( fetchWithLoading(
'https://tone.sleepycat.date/v2/client/weapons?' + 'https://tone.sleepycat.date/v2/client/weapons?' +
new URLSearchParams(filter as Record<keyof Filters, string>) new URLSearchParams(filter as Record<keyof Filters, string>),
(progress) => {
if (entry) {
unref(entry).progress = progress
if (progress !== 1) triggerRef(entry)
}
}
).then(async response => { ).then(async response => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion if (entry) {
unref(entry)!.data = Object.fromEntries(Object.entries(await response.json()).map(e => [e[0], ref(e[1] as Weapon)])) 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)
triggerRef(entry!) }
}) })
return entry return entry
}, },
@@ -143,14 +209,20 @@ export const useKillStore = defineStore('kill', {
entry = shallowRef({ filter, data: {} }) entry = shallowRef({ filter, data: {} })
this.servers.push(entry) this.servers.push(entry)
} }
fetch( fetchWithLoading(
'https://tone.sleepycat.date/v2/client/servers?' + 'https://tone.sleepycat.date/v2/client/servers?' +
new URLSearchParams(filter as Record<keyof Filters, string>) new URLSearchParams(filter as Record<keyof Filters, string>),
(progress) => {
if (entry) {
unref(entry).progress = progress
if (progress !== 1) triggerRef(entry)
}
}
).then(async response => { ).then(async response => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion if (entry) {
unref(entry)!.data = Object.fromEntries(Object.entries(await response.json()).map(e => [e[0], ref(e[1] as Server)])) 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)
triggerRef(entry!) }
}) })
return entry return entry
}, },