ConnectionBanner.vue 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. <template>
  2. <span id="connection-banner" v-if="!connectionState.connected" class="inline-notification critical user-info-connection">
  3. <span class="connection-banner-sr-only" role="status">{{ staticAnnouncement }}</span>
  4. <span aria-hidden="true">{{ bannerText }}</span>
  5. </span>
  6. </template>
  7. <script setup>
  8. import { ref, computed, watch, onUnmounted } from 'vue'
  9. import { useI18n } from 'vue-i18n'
  10. import { connectionState } from '../stores/connectionState.js'
  11. const { t } = useI18n()
  12. function formatShortRelative(ms) {
  13. if (ms < 0) return '0s'
  14. const secs = Math.floor(ms / 1000)
  15. const mins = Math.floor(secs / 60)
  16. const hours = Math.floor(mins / 60)
  17. if (hours > 0) return `${hours}h`
  18. if (mins > 0) return `${mins}m`
  19. return `${secs}s`
  20. }
  21. function formatShortTime(ts) {
  22. if (ts == null) return '--:--'
  23. return new Date(ts).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })
  24. }
  25. const now = ref(Date.now())
  26. let ticker = null
  27. watch(() => connectionState.connected, (connected) => {
  28. if (ticker) {
  29. clearInterval(ticker)
  30. ticker = null
  31. }
  32. if (!connected) {
  33. now.value = Date.now()
  34. ticker = setInterval(() => { now.value = Date.now() }, 1000)
  35. }
  36. }, { immediate: true })
  37. onUnmounted(() => {
  38. if (ticker) {
  39. clearInterval(ticker)
  40. ticker = null
  41. }
  42. })
  43. const staticAnnouncement = computed(() => t('disconnected-banner-announcement'))
  44. const bannerText = computed(() => {
  45. const at = connectionState.disconnectedAt
  46. const next = connectionState.nextReconnectAt
  47. const n = now.value
  48. const disconnectedSince = formatShortTime(at)
  49. if (next != null && next > n) {
  50. const reconnectIn = formatShortRelative(next - n)
  51. return t('disconnected-banner', { disconnectedSince, reconnectIn })
  52. }
  53. return t('disconnected-banner-reconnecting', { disconnectedSince })
  54. })
  55. </script>
  56. <style scoped>
  57. #connection-banner.user-info-connection {
  58. font-weight: 500;
  59. }
  60. .inline-notification {
  61. border: 0;
  62. margin: 0;
  63. }
  64. .connection-banner-sr-only {
  65. position: absolute;
  66. width: 1px;
  67. height: 1px;
  68. padding: 0;
  69. margin: -1px;
  70. overflow: hidden;
  71. clip: rect(0, 0, 0, 0);
  72. white-space: nowrap;
  73. border: 0;
  74. }
  75. </style>