// Generated, do not edit. sha256:f7ff570558c71c4401b25418a5f1ab453ce90d9a53873cee3fc08aace66e3174 import { t, localeTag } from '../i18n/index.js'; import { CLOCKS } from '../../shared/config.js'; import { durationParts, exchangeStatus, localClock } from './exchange-times.js'; const WEEKDAY_NUM = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 }; const pad2 = n => String(n).padStart(2, '0'); const hhmm = (h, m) => `${pad2(h)}:${pad2(m)}`; const cityFromTz = tz => tz.split('/').pop().replace(/_/g, ' '); const _dateFmt = new Map(); function tzDate(tz, now) { const key = `${localeTag()}|${tz}`; let f = _dateFmt.get(key); if (!f) { f = new Intl.DateTimeFormat(localeTag(), { timeZone: tz, weekday: 'short', day: 'numeric', month: 'short' }); _dateFmt.set(key, f); } return f.format(now); } function clockHint(isLocal, own, h, m) { if (isLocal) return t('clocks.hoverLocal'); let d = ((h * 60 + m) - (own.h * 60 + own.m)) % 1440; if (d > 720) d -= 1440; else if (d <= -720) d += 1440; if (d === 0) return t('clocks.hoverSame'); const abs = Math.abs(d), uur = Math.floor(abs / 60), min = abs % 60; const dur = []; if (uur) dur.push(`${uur} uur`); if (min) dur.push(`${min} ${min === 1 ? 'minuut' : 'minuten'}`); return `${dur.join(' en ')} ${d < 0 ? 'achter' : 'voor'}`; } const _offFmt = new Map(); function tzOffsetMinutes(tz, now) { let f = _offFmt.get(tz); if (!f) { f = new Intl.DateTimeFormat('en-GB', { timeZone: tz, hourCycle: 'h23', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); _offFmt.set(tz, f); } const p = {}; for (const part of f.formatToParts(now)) p[part.type] = part.value; const asUTC = Date.UTC(+p.year, +p.month - 1, +p.day, +p.hour, +p.minute, +p.second); return Math.round((asUTC - now.getTime()) / 60000); } const norm1440 = o => ((o % 1440) + 1440) % 1440; const circDist = (a, b) => { const d = Math.abs(a - b); return Math.min(d, 1440 - d); }; function spreadCityKeys(now) { const localOff = norm1440(tzOffsetMinutes(Intl.DateTimeFormat().resolvedOptions().timeZone, now)); const chosen = [localOff]; const cand = CLOCKS.CITIES.map(c => ({ key: c.key, off: norm1440(tzOffsetMinutes(c.tz, now)) })); const picked = []; for (let i = 0; i < 3 && cand.length; i++) { let best = -1, bestMin = -1; cand.forEach((c, idx) => { let m = Infinity; for (const o of chosen) m = Math.min(m, circDist(c.off, o)); if (m > bestMin) { bestMin = m; best = idx; } }); if (best < 0) break; chosen.push(cand[best].off); picked.push(cand[best].key); cand.splice(best, 1); } return picked; } function clockKeys(instance) { const p = instance && instance.params; const c = p && p.cities; return Array.isArray(c) ? c : ['local', ...spreadCityKeys(new Date())]; } function faceSvg({ seconds = false, mini = false } = {}) { let ticks = ''; for (let i = 0; i < 12; i++) { const major = i % 3 === 0; if (mini && !major) continue; const a = (i * 30) * Math.PI / 180; const r1 = major ? 38 : 42.5; const x1 = (50 + Math.sin(a) * r1).toFixed(2), y1 = (50 - Math.cos(a) * r1).toFixed(2); const x2 = (50 + Math.sin(a) * 46).toFixed(2), y2 = (50 - Math.cos(a) * 46).toFixed(2); ticks += ``; } const sec = seconds ? '' : ''; return ``; } function setHands(svg, h, m, s) { if (!svg) return; const hourA = ((h % 12) + m / 60) * 30; const minA = (m + (s == null ? 0 : s) / 60) * 6; const hour = svg.querySelector('.clockw-hour'); const min = svg.querySelector('.clockw-min'); if (hour) hour.setAttribute('transform', `rotate(${hourA.toFixed(2)} 50 50)`); if (min) min.setAttribute('transform', `rotate(${minA.toFixed(2)} 50 50)`); const sec = svg.querySelector('.clockw-sec'); if (sec && s != null) sec.setAttribute('transform', `rotate(${(s * 6).toFixed(2)} 50 50)`); } function tzParts(tz, now) { const parts = new Intl.DateTimeFormat('en-US', { timeZone: tz, hour12: false, weekday: 'short', hour: '2-digit', minute: '2-digit', second: '2-digit', }).formatToParts(now); const part = type => parts.find(p => p.type === type)?.value; let h = Number(part('hour')); if (h === 24) h = 0; return { wd: WEEKDAY_NUM[part('weekday')], h, m: Number(part('minute')), s: Number(part('second')) }; } function exchangeState(ex, now) { const { h, m } = tzParts(ex.tz, now); const st = exchangeStatus(ex, now); const minutes = (a, b) => (a - b) / 60000; return { ex, hh: h, mm: m, isOpen: st.isOpen, status: st, sinceOpen: st.isOpen ? minutes(now, st.openedAt) : null, untilOpen: st.opensAt ? minutes(st.opensAt, now) : Infinity, }; } const DURATION_KEY = { min: 'clocks.durMin', hourMin: 'clocks.durHourMin', hours: 'clocks.durHours', days: 'clocks.durDays' }; function durationText(ms) { const p = durationParts(ms); if (p.unit === 'days' && p.n === 1) return t('clocks.durDay'); return t(DURATION_KEY[p.unit], { n: p.n, m: p.m }); } function rowTexts(s, now) { const st = s.status; if (st.isOpen) { return { sub: t('clocks.openUntil', { tijd: localClock(st.closesAt, now).time }), right: t('clocks.closesIn', { duur: durationText(st.closesAt - now) }) }; } if (!st.opensAt) return { sub: '', right: '' }; const c = localClock(st.opensAt, now); const sub = c.otherDay ? t('clocks.opensOn', { dag: c.weekday, tijd: c.time }) : t('clocks.opensAt', { tijd: c.time }); return { sub, right: t('clocks.opensIn', { duur: durationText(st.opensAt - now) }) }; } function orderStates(states) { const open = states.filter(s => s.isOpen).sort((a, b) => a.sinceOpen - b.sinceOpen); const shut = states.filter(s => !s.isOpen).sort((a, b) => a.untilOpen - b.untilOpen); return [...open, ...shut]; } function dedupeStates(states) { const bySig = new Map(); for (const s of states) { const sig = `${s.ex.tz}|${s.ex.open}|${s.ex.close}|${s.ex.days.join('')}`; const hit = bySig.get(sig); if (hit) hit.names.push(s.ex.name); else bySig.set(sig, { ...s, names: [s.ex.name] }); } return [...bySig.values()].map(s => ({ ...s, ex: { ...s.ex, name: s.names.join(' / ') } })); } function worldEntries(instance) { const localTz = Intl.DateTimeFormat().resolvedOptions().timeZone; const resolve = k => k === 'local' ? { key: 'local', name: cityFromTz(localTz), tz: localTz, local: true } : (CLOCKS.CITIES.find(c => c.key === k) ? { ...CLOCKS.CITIES.find(c => c.key === k), local: false } : null); const entries = clockKeys(instance).map(resolve).filter(Boolean); const local = entries.find(e => e.local); const others = entries.filter(e => !e.local); if (!local) return others; const mid = Math.floor(others.length / 2); return [...others.slice(0, mid), local, ...others.slice(mid)]; } function worldClockMarkup(e) { return `
${faceSvg({ seconds: e.local })}
${e.name}
`; } function rowMarkup(s) { return `
${faceSvg({ mini: true })} ${s.ex.name}
`; } function applyRow(row, s, now) { setHands(row.querySelector('.clockw-face'), s.hh, s.mm, null); row.querySelector('.clockw-face').classList.toggle('is-open', s.isOpen); row.classList.toggle('is-open', s.isOpen); const { sub, right } = rowTexts(s, now); row.querySelector('.clockw-ex-sub').textContent = sub; row.querySelector('.clockw-open').textContent = right; } function flashRow(listEl, key) { const row = listEl.querySelector(`[data-ex="${key}"]`); if (!row) return; row.classList.remove('is-flash'); void row.offsetWidth; row.classList.add('is-flash'); setTimeout(() => row.classList.remove('is-flash'), 1600); } function showExchanges(instance) { const p = instance && instance.params; return !!(p && p.showExchanges === true); } export function renderClocksWidget(host, instance) { if (host._clockTimer) { clearInterval(host._clockTimer); host._clockTimer = null; } const entries = worldEntries(instance); const showEx = showExchanges(instance); host.innerHTML = `
${t('dashboard.widget.clocks')}
${entries.map(worldClockMarkup).join('')}
${showEx ? `
${t('clocks.listHead')}
` : ''}
`; const listEl = host.querySelector('[data-clock-list]'); const prevOpen = host._exOpen || {}; let shownKeys = ''; const update = () => { if (!host.isConnected) { clearInterval(host._clockTimer); host._clockTimer = null; return; } const now = new Date(); const own = tzParts(Intl.DateTimeFormat().resolvedOptions().timeZone, now); host.querySelectorAll('.clockw-clock').forEach(el => { const { h, m, s } = tzParts(el.dataset.tz, now); setHands(el.querySelector('.clockw-face'), h, m, el.dataset.local === '1' ? s : null); const time = el.querySelector('.clockw-clock-time'); if (time) time.textContent = hhmm(h, m); const date = el.querySelector('.clockw-clock-date'); if (date) date.textContent = tzDate(el.dataset.tz, now); el.title = clockHint(el.dataset.local === '1', own, h, m); }); if (!listEl) return; const states = dedupeStates(CLOCKS.EXCHANGES.map(ex => exchangeState(ex, now))); const ordered = orderStates(states).slice(0, CLOCKS.LIST_MAX); const keys = ordered.map(s => s.ex.key).join(','); if (keys !== shownKeys) { shownKeys = keys; listEl.innerHTML = ordered.map(rowMarkup).join(''); } ordered.forEach(s => { const row = listEl.querySelector(`[data-ex="${s.ex.key}"]`); if (row) applyRow(row, s, now); }); states.forEach(s => { if (s.isOpen && prevOpen[s.ex.key] === false) flashRow(listEl, s.ex.key); }); states.forEach(s => { prevOpen[s.ex.key] = s.isOpen; }); host._exOpen = prevOpen; }; update(); host._clockTimer = setInterval(update, 1000); } export function renderClocksSettings(host, instance, commit) { const localTz = Intl.DateTimeFormat().resolvedOptions().timeZone; const selected = new Set(clockKeys(instance)); const showEx = showExchanges(instance); const entries = [{ key: 'local', name: `${t('clocks.localLabel')} (${cityFromTz(localTz)})` }, ...CLOCKS.CITIES]; host.innerHTML = `
${t('clocks.chooseClocks')}
${entries.map(e => ``).join('')}
`; host.querySelector('.clockw-settings').addEventListener('change', e => { const exToggle = e.target.closest('input[data-ex-toggle]'); if (exToggle) { instance.params = { ...(instance.params || {}), showExchanges: exToggle.checked }; commit({ resize: true }); return; } const cb = e.target.closest('input[data-city]'); if (!cb) return; const set = new Set(clockKeys(instance)); if (cb.checked) set.add(cb.dataset.city); else set.delete(cb.dataset.city); instance.params = { ...(instance.params || {}), cities: [...set] }; commit({ resize: true }); }); }