69 lines
1.8 KiB
Vue
69 lines
1.8 KiB
Vue
<template>
|
|
<span id="connection-banner" v-if="!connectionState.connected" class="inline-notification critical user-info-connection" role="status">{{ bannerText }}</span>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, watch, onUnmounted } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { connectionState } from '../stores/connectionState.js'
|
|
|
|
const { t } = useI18n()
|
|
|
|
function formatShortRelative(ms) {
|
|
if (ms < 0) return '0s'
|
|
const secs = Math.floor(ms / 1000)
|
|
const mins = Math.floor(secs / 60)
|
|
const hours = Math.floor(mins / 60)
|
|
if (hours > 0) return `${hours}h`
|
|
if (mins > 0) return `${mins}m`
|
|
return `${secs}s`
|
|
}
|
|
|
|
function formatShortTime(ts) {
|
|
if (ts == null) return '--:--'
|
|
return new Date(ts).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })
|
|
}
|
|
|
|
const now = ref(Date.now())
|
|
let ticker = null
|
|
watch(() => connectionState.connected, (connected) => {
|
|
if (ticker) {
|
|
clearInterval(ticker)
|
|
ticker = null
|
|
}
|
|
if (!connected) {
|
|
now.value = Date.now()
|
|
ticker = setInterval(() => { now.value = Date.now() }, 1000)
|
|
}
|
|
}, { immediate: true })
|
|
|
|
onUnmounted(() => {
|
|
if (ticker) {
|
|
clearInterval(ticker)
|
|
ticker = null
|
|
}
|
|
})
|
|
|
|
const bannerText = computed(() => {
|
|
const at = connectionState.disconnectedAt
|
|
const next = connectionState.nextReconnectAt
|
|
const n = now.value
|
|
const disconnectedSince = formatShortTime(at)
|
|
if (next != null && next > n) {
|
|
const reconnectIn = formatShortRelative(next - n)
|
|
return t('disconnected-banner', { disconnectedSince, reconnectIn })
|
|
}
|
|
return t('disconnected-banner-reconnecting', { disconnectedSince })
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
#connection-banner.user-info-connection {
|
|
font-weight: 500;
|
|
}
|
|
.inline-notification {
|
|
border: 0;
|
|
margin: 0;
|
|
}
|
|
</style>
|