// 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 = ``;
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 });
});
}