diff options
| -rw-r--r-- | app/static/block_time.js | 85 | ||||
| -rw-r--r-- | app/static/script.js | 18 | ||||
| -rw-r--r-- | app/static/style.css | 33 | ||||
| -rw-r--r-- | app/templates/index.html | 5 |
4 files changed, 120 insertions, 21 deletions
diff --git a/app/static/block_time.js b/app/static/block_time.js index d6510ae..8841e7b 100644 --- a/app/static/block_time.js +++ b/app/static/block_time.js @@ -1,13 +1,86 @@ function init() { - initTimeVisualizer('timeVisualizer'); + initTimeVisualizer("time-timeVisualizer"); } export function updateTime() { - fetch("/time") - .then(res => res.json()) - .then(data => { - document.getElementById("weatherSummary").innerText = data.time; - }); + const cities = [ + { name: "Tokyo", tz: "Asia/Tokyo", note: "JST" }, + { name: "Berlin", tz: "Europe/Berlin", note: "CET, CEST from last Sunday in March at 02:00 to last Sunday in October at 03:00" }, + { name: "London", tz: "Europe/London", note: "GMT, BST from last Sunday of March to last Sunday of October" }, + { name: "Lima", tz: "America/Lima", note: "aka: \"PET\""}, + { name: "Santiago", tz: "America/Santiago", note: "aka: \"CLT\""}, + { name: "New York", tz: "America/New_York", note: "EST, EDT from second Sunday in March at 02:00 to first Sunday in November at 02:00" }, + { name: "Chicago", tz: "America/Chicago", note: "CST, CDT from second Sunday in March at 02:00 to first Sunday in November at 02:00" }, + { name: "Denver", tz: "America/Denver", note: "MST, MDT from second Sunday in March at 02:00 to first Sunday in November at 02:00" }, + { name: "Los Angeles", tz: "America/Los_Angeles", note: "PST, PDT from second Sunday in March at 02:00 to first Sunday in November at 02:00" }, + { name: "Arizona", tz: "America/Denver", note: "MST" }, + { name: "Anchorage", tz: "America/Anchorage", note: "AKST, AKDT from second Sunday in March at 02:00 to first Sunday in November at 02:00" }, + { name: "Honolulu", tz: "Pacific/Honolulu", note: "HST" }, + ]; + + const escapeHtml = str => str.replace(/[&<>"']/g, c => `&#${c.charCodeAt(0)};`); + + const now = new Date(); + const locale = "en-US"; + let output = "<ul>"; + + // Tokyo datetime header + const tokyoDate = new Intl.DateTimeFormat("ja-JP", { + calendar: 'japanese', era: 'long', + year: "numeric", month: "numeric", day: "numeric", weekday: "long", + hour: "2-digit", minute: "2-digit", hour12: false, + timeZone: "Asia/Tokyo" + }).format(now); + const qw = getDateInfo(now); // quarter and ISO8601 week + let s = `Tokyo: (Q${qw.quarter}) (W${qw.isoWeek.toString().padStart(2, '0')}) ${tokyoDate}\n`; + document.getElementById("time-localClock").innerText = s; + + // Time in other cities + cities.forEach(city => { + const formatter = new Intl.DateTimeFormat(locale, { + year: "numeric", month: "2-digit", day: "2-digit", weekday: "short", + hour: "2-digit", minute: "2-digit", hour12: false, + timeZoneName: "shortOffset", + timeZone: city.tz + }) + const parts = formatter.formatToParts(now); + const t = { + time: parts.slice(8, -1).map(part => part.value).join('').trim(), + offset: parts.find(part => part.type === "timeZoneName")?.value.replace("GMT", "UTC"), + timeZoneName: new Intl.DateTimeFormat(locale, { timeZoneName: 'long', timeZone: city.tz }) + .formatToParts(now).find(part => part.type === "timeZoneName")?.value, + } + t.dst = ["Daylight", "Summer"].some(sub => t.timeZoneName.includes(sub)); + //const timeData = parts.reduce((obj, part) => { obj[part.type] = obj[part.value]; return obj; }, {}) + const offsetAndDST = `${t.offset}` + (t.dst ? "+1" : ""); + output += `<li>${city.name}: ${t.time} (<span title="${escapeHtml(city.note)}">${offsetAndDST}</span>) </li>`; + }); + output += "</ul>" + + document.getElementById("time-worldClock").innerHTML = output.trim(); +} + +function getQuarter(date) { + const month = date.getMonth(); // 0-11 (January is 0) + return Math.floor(month / 3) + 1; // Returns 1-4 +} + +function getISOWeek(date) { + const tempDate = new Date(date); + tempDate.setHours(0, 0, 0, 0); + tempDate.setDate(tempDate.getDate() + 3 - ((tempDate.getDay() + 6) % 7)); // Move to nearest Thursday + const firstThursday = new Date(tempDate.getFullYear(), 0, 4); // First Thursday of the year + firstThursday.setDate(firstThursday.getDate() + 3 - ((firstThursday.getDay() + 6) % 7)); + const weekNumber = Math.round(((tempDate - firstThursday) / 86400000) / 7) + 1; + return weekNumber; +} + +function getDateInfo(date) { + return { + quarter: getQuarter(date), + isoWeek: getISOWeek(date), + year: date.getFullYear() + }; } function initTimeVisualizer(containerId) { diff --git a/app/static/script.js b/app/static/script.js index efd6836..84230a5 100644 --- a/app/static/script.js +++ b/app/static/script.js @@ -1,24 +1,22 @@ +"use strict"; import { updateTime } from './block_time.js'; import { updateWeather } from './block_weather.js'; const config = { - polling: true + polling: true, + pollingDelay: 500, // in ms. To be light on CPU make it >= 500ms } function init() { const blocks = { - time: { interval: 30 * 1000, lastUpdate: 0, update: updateTime }, // 30s + time: { interval: 30 * 1000, lastUpdate: 0, update: updateTime }, // 30s weather: { interval: 30 * 60000, lastUpdate: 0, update: updateWeather } // 30min // Add more: { interval: X, lastUpdate: 0, update: updateFunction } }; initHeaderControls(blocks) - - // Initial load - // Poll every 500ms to check intervals (fast enough for 1s updates, light on CPU) - // maybe the 1s updates should be special case, but for now let's keep it simple reloadAll(blocks); - setInterval(() => pollUpdates(blocks), 500); + setInterval(() => pollUpdates(blocks), config.pollingDelay); } function reloadAll(blocks) { @@ -45,6 +43,12 @@ function initHeaderControls(blocks) { document.getElementById("pause").addEventListener("change", (e) => { config.polling = e.target.checked; }); + config.polling = document.getElementById("pause").checked; + document.getElementById("city").addEventListener("keypress", (e) => { + if (e.key === "Enter") { + updateWeather() + } + }); } init() diff --git a/app/static/style.css b/app/static/style.css index a186f24..9089bc6 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -1,4 +1,12 @@ /* MASU */ +/* Design decisions: + * * Minimum viewport resolution: 440x440px + * * simple is better + * + * Good and simple UI sites: + * * https://archlinux.org/packages/ (https://github.com/archlinux/archweb) + * * https://suckless.org/ + */ body { margin: 0; padding: 0; @@ -47,12 +55,25 @@ header button, header label { } } +/* tooltip: span element with "title" attribute (KISS) */ +span[title] { + /*border-bottom: 1px dotted;*/ + text-decoration: dotted underline; +} + /* block_time */ #time .block { justify-content: center; } -#timeVisualizer .grid { +#time-localClock { +} + +#time-worldClock { + font-size: 12px; +} + +#time-timeVisualizer .grid { display: grid; grid-template-columns: repeat(8, 42px); grid-template-rows: repeat(3, 42px); @@ -63,7 +84,7 @@ header button, header label { margin: 0 auto; } -#timeVisualizer .cell { +#time-timeVisualizer .cell { width: 42px; height: 42px; background: #ffffff; @@ -71,15 +92,15 @@ header button, header label { border: 1px solid #ddd; } -#timeVisualizer .sleep { +#time-timeVisualizer .sleep { background: #e6f3ff; } -#timeVisualizer .past { +#time-timeVisualizer .past { background: #cccccc; } -#timeVisualizer .current::after { +#time-timeVisualizer .current::after { content: ''; position: absolute; left: 0; @@ -89,7 +110,7 @@ header button, header label { background: #cccccc; } -#timeVisualizer .timeline { +#time-timeVisualizer .timeline { position: absolute; width: 2px; height: 100%; diff --git a/app/templates/index.html b/app/templates/index.html index 4647619..0d10afa 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -14,8 +14,9 @@ <div id="container"> <!-- TODO: Use Jinja2 blocks --> <div class="block" id="time"> - <p id="weatherSummary"></p> - <div id="timeVisualizer"></div> + <div id="time-localClock"></div> + <div id="time-worldClock"></div> + <div id="time-timeVisualizer"></div> </div> <div class="block" id="weather"> <img id="weather-icon" src="" alt="weather icon"> |
