diff --git a/docs/configuration.md b/docs/configuration.md index 5e317a8..44e803f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -966,18 +966,50 @@ Whether to open the link in the same tab or a new one. Whether to hide the colored arrow on each link. ### Clock -Display a clock showing the current time. +Display a clock showing the current time and date. Optionally, also display the the time in other timezones. Example: ```yaml - type: clock + hour-format: 24h + timezones: + - timezone: Europe/Paris + label: Paris + - timezone: America/New_York + label: New York + - timezone: Asia/Tokyo + label: Tokyo ``` Preview:  +#### Properties + +| Name | Type | Required | Default | +| ---- | ---- | -------- | ------- | +| hour-format | string | no | 24h | +| timezones | array | no | | + +##### `hour-format` +Whether to show the time in 12 or 24 hour format. Possible values are `12h` and `24h`. + +#### Properties for each timezone + +| Name | Type | Required | Default | +| ---- | ---- | -------- | ------- | +| timezone | string | yes | | +| label | string | no | | + +##### `timezone` +A timezone identifier such as `Europe/London`, `America/New_York`, etc. The full list of available identifiers can be found [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). + +##### `label` +Optionally, override the display value for the timezone to something more meaningful such as "Home", "Work" or anything else. + + ### Calendar Display a calendar. diff --git a/docs/images/clock-widget-preview.png b/docs/images/clock-widget-preview.png index 346a6ed..bf809c5 100644 Binary files a/docs/images/clock-widget-preview.png and b/docs/images/clock-widget-preview.png differ diff --git a/internal/assets/static/main.css b/internal/assets/static/main.css index 75f2f41..6cf820d 100644 --- a/internal/assets/static/main.css +++ b/internal/assets/static/main.css @@ -849,6 +849,10 @@ body { transform: translate(-50%, -50%); } +.clock-time span { + color: var(--color-text-highlight); +} + .monitor-site-icon { display: block; opacity: 0.8; diff --git a/internal/assets/static/main.js b/internal/assets/static/main.js index e5287c0..ccb2ab3 100644 --- a/internal/assets/static/main.js +++ b/internal/assets/static/main.js @@ -103,7 +103,7 @@ function updateRelativeTimeForElements(elements) if (timestamp === undefined) continue - element.innerText = relativeTimeSince(timestamp); + element.textContent = relativeTimeSince(timestamp); } } @@ -341,18 +341,110 @@ function afterContentReady(callback) { contentReadyCallbacks.push(callback); } -function updateClocks(elements, formatter) { - const currentDate = new Date(); - for (const elem of elements) { - elem.textContent = formatter.format(currentDate); +const weekDayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; +const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + +function makeSettableTimeElement(element, hourFormat) { + const fragment = document.createDocumentFragment(); + const hour = document.createElement('span'); + const minute = document.createElement('span'); + const amPm = document.createElement('span'); + fragment.append(hour, document.createTextNode(':'), minute); + + if (hourFormat == '12h') { + fragment.append(document.createTextNode(' '), amPm); } + + element.append(fragment); + + return (date) => { + const hours = date.getHours(); + + if (hourFormat == '12h') { + amPm.textContent = hours < 12 ? 'AM' : 'PM'; + hour.textContent = hours % 12 || 12; + } else { + hour.textContent = hours < 10 ? '0' + hours : hours; + } + + const minutes = date.getMinutes(); + minute.textContent = minutes < 10 ? '0' + minutes : minutes; + }; +}; + +function timeInZone(now, zone) { + let timeInZone; + + try { + timeInZone = new Date(now.toLocaleString('en-US', { timeZone: zone })); + } catch (e) { + // TODO: indicate to the user that this is an invalid timezone + console.error(e); + timeInZone = now + } + + const diffInHours = Math.round((timeInZone.getTime() - now.getTime()) / 1000 / 60 / 60); + + return { time: timeInZone, diffInHours: diffInHours }; } function setupClocks() { - const clockFormatter = new Intl.DateTimeFormat(undefined, {minute: "numeric", hour: "numeric"}); - const elements = document.getElementsByClassName("glance-clock"); - updateClocks(elements, clockFormatter) - setInterval(() => {updateClocks(elements, clockFormatter)}, 1000); + const clocks = document.getElementsByClassName('clock'); + + if (clocks.length == 0) { + return; + } + + const updateCallbacks = []; + + for (var i = 0; i < clocks.length; i++) { + const clock = clocks[i]; + const hourFormat = clock.dataset.hourFormat; + const localTimeContainer = clock.querySelector('[data-local-time]'); + const localDateElement = localTimeContainer.querySelector('[data-date]'); + const localWeekdayElement = localTimeContainer.querySelector('[data-weekday]'); + const localYearElement = localTimeContainer.querySelector('[data-year]'); + const timeZoneContainers = clock.querySelectorAll('[data-time-in-zone]'); + + const setLocalTime = makeSettableTimeElement( + localTimeContainer.querySelector('[data-time]'), + hourFormat + ); + + updateCallbacks.push((now) => { + setLocalTime(now); + localDateElement.textContent = now.getDate() + ' ' + monthNames[now.getMonth()]; + localWeekdayElement.textContent = weekDayNames[now.getDay()]; + localYearElement.textContent = now.getFullYear(); + }); + + for (var z = 0; z < timeZoneContainers.length; z++) { + const timeZoneContainer = timeZoneContainers[z]; + const diffElement = timeZoneContainer.querySelector('[data-time-diff]'); + + const setZoneTime = makeSettableTimeElement( + timeZoneContainer.querySelector('[data-time]'), + hourFormat + ); + + updateCallbacks.push((now) => { + const { time, diffInHours } = timeInZone(now, timeZoneContainer.dataset.timeInZone); + setZoneTime(time); + diffElement.textContent = (diffInHours <= 0 ? diffInHours : '+' + diffInHours) + 'h'; + }); + } + } + + const updateClocks = () => { + const now = new Date(); + + for (var i = 0; i < updateCallbacks.length; i++) + updateCallbacks[i](now); + + setTimeout(updateClocks, (60 - now.getSeconds()) * 1000); + }; + + updateClocks(); } async function setupPage() { diff --git a/internal/assets/templates/clock.html b/internal/assets/templates/clock.html index 5116782..2be2d1c 100644 --- a/internal/assets/templates/clock.html +++ b/internal/assets/templates/clock.html @@ -1,5 +1,30 @@ {{ template "widget-base.html" . }} {{ define "widget-content" }} -
+