'use strict'; const api = window.clockApi; const els = { readoutTime: document.getElementById('readoutTime'), readoutMeta: document.getElementById('readoutMeta'), toggleClockWindow: document.getElementById('toggleClockWindow'), aotClock: document.getElementById('aotClock'), aotControl: document.getElementById('aotControl'), styleGroup: document.getElementById('styleGroup'), hh: document.getElementById('hh'), mm: document.getElementById('mm'), ss: document.getElementById('ss'), applyTime: document.getElementById('applyTime'), fillNow: document.getElementById('fillNow'), rate: document.getElementById('rate'), applyRate: document.getElementById('applyRate'), togglePause: document.getElementById('togglePause') }; let snapshot = { baseMs: Date.now(), baseRealMs: Date.now(), rate: 1, style: 'digital', paused: false, clockWindowOpen: false }; function pad2(value) { return String(value).padStart(2, '0'); } function displayMs(now) { return snapshot.baseMs + (now - snapshot.baseRealMs) * snapshot.rate; } function clampInt(value, min, max, fallback) { const n = Number.parseInt(value, 10); if (!Number.isFinite(n)) return fallback; return Math.min(max, Math.max(min, n)); } function renderReadout() { const ms = displayMs(Date.now()); const date = new Date(ms); els.readoutTime.textContent = `${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())}`; const rateText = snapshot.rate === 0 ? '暂停' : `×${snapshot.rate}`; const runText = snapshot.rate === 0 ? '已暂停' : snapshot.rate < 0 ? '倒转中' : '运行中'; els.readoutMeta.textContent = `${rateText} · ${runText} · 时钟窗口${snapshot.clockWindowOpen ? '已打开' : '已关闭'}`; requestAnimationFrame(renderReadout); } function syncControls() { els.toggleClockWindow.textContent = snapshot.clockWindowOpen ? '关闭时钟窗口' : '打开时钟窗口'; els.toggleClockWindow.classList.toggle('is-on', snapshot.clockWindowOpen); els.aotClock.checked = Boolean(snapshot.alwaysOnTop.clock); els.aotControl.checked = Boolean(snapshot.alwaysOnTop.control); for (const button of els.styleGroup.querySelectorAll('.seg')) { button.classList.toggle('is-active', button.dataset.style === snapshot.style); } // 用户正在输入时不要抢走他的光标和内容 if (document.activeElement !== els.rate) els.rate.value = String(snapshot.rate); if (snapshot.rate === 0) { els.togglePause.textContent = `继续(回到 ×${snapshot.lastRate})`; els.togglePause.classList.add('is-on'); } else { els.togglePause.textContent = '暂停'; els.togglePause.classList.remove('is-on'); } } function accept(next) { if (next) { snapshot = next; syncControls(); } } els.toggleClockWindow.addEventListener('click', async () => accept(await api.toggleClockWindow())); els.aotClock.addEventListener('change', async () => accept(await api.setAlwaysOnTop('clock', els.aotClock.checked))); els.aotControl.addEventListener('change', async () => accept(await api.setAlwaysOnTop('control', els.aotControl.checked))); for (const button of els.styleGroup.querySelectorAll('.seg')) { button.addEventListener('click', async () => accept(await api.setStyle(button.dataset.style))); } els.applyTime.addEventListener('click', async () => { const h = clampInt(els.hh.value, 0, 23, 0); const m = clampInt(els.mm.value, 0, 59, 0); const s = clampInt(els.ss.value, 0, 59, 0); els.hh.value = String(h); els.mm.value = String(m); els.ss.value = String(s); accept(await api.setTime(h, m, s)); }); els.fillNow.addEventListener('click', () => { // 三格都要补零:`String(0)` 是 "0",而输入框按 HH:MM:SS 解析, // 上午 9 点前会填出 "0:39:27" 这种非法值,点应用时解析失败。 const date = new Date(displayMs(Date.now())); els.hh.value = pad2(date.getHours()); els.mm.value = pad2(date.getMinutes()); els.ss.value = pad2(date.getSeconds()); }); function applyRateInput() { const value = Number(els.rate.value); if (!Number.isFinite(value)) { els.rate.value = String(snapshot.rate); return; } api.setRate(value).then(accept); } els.applyRate.addEventListener('click', applyRateInput); els.rate.addEventListener('keydown', (event) => { if (event.key === 'Enter') applyRateInput(); }); els.togglePause.addEventListener('click', async () => accept(await api.togglePause())); document.body.addEventListener('click', (event) => { const rate = event.target.dataset?.rate; if (rate !== undefined) { api.setRate(Number(rate)).then(accept); return; } const delta = event.target.dataset?.delta; if (delta !== undefined) { api.fastForward(Number(delta)).then(accept); } }); api.onState((next) => { snapshot = next; syncControls(); }); api.getState().then((initial) => { snapshot = initial; syncControls(); renderReadout(); });