Newer
Older
navi-1 / webclient / src / composables / useTime.js
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { locale, t } from '@/i18n/index.js'

/**
 * Format a timestamp as a relative/short label (same logic as old client).
 * < 1 min  → "just now"
 * < 1 hour → "5 min ago"
 * today    → "14:32"
 * older    → "12.04.2024"
 */
export function formatTimeLabel(ts) {
  if (!ts) return ''
  const d = new Date(ts)
  if (isNaN(d)) return ''
  const diff = Date.now() - d
  if (diff < 60_000)     return t('common.justNow')
  if (diff < 3_600_000)  return t('common.minutesAgo', { n: Math.floor(diff / 60_000) })
  if (diff < 86_400_000) return d.toLocaleTimeString(locale.value, { hour: '2-digit', minute: '2-digit' })
  return d.toLocaleDateString(locale.value)
}

// One clock for every label on screen. Each useTimeLabel() used to install its
// own setInterval: a sidebar of 30 sessions plus every message timestamp meant
// dozens of timers waking independently. They all read the same "now", so one
// shared ticker is enough; it runs only while at least one label is mounted.
const now = ref(Date.now())
let timer = null
let subscribers = 0

function subscribe() {
  subscribers++
  if (subscribers === 1) {
    now.value = Date.now()
    timer = setInterval(() => { now.value = Date.now() }, 30_000)
  }
}

function unsubscribe() {
  subscribers = Math.max(0, subscribers - 1)
  if (subscribers === 0 && timer) {
    clearInterval(timer)
    timer = null
  }
}

/**
 * Reactive time label that auto-updates every 30 s.
 * Returns a computed that ticks so "5 min ago" stays current. It also tracks
 * the interface language: formatTimeLabel() reads locale, so a language change
 * recomputes every label without a separate watcher.
 */
export function useTimeLabel(tsRef) {
  const label = computed(() => {
    now.value // register the shared ticker as a dependency
    return formatTimeLabel(tsRef.value)
  })

  onMounted(subscribe)
  onUnmounted(unsubscribe)

  return label
}