Merge c25b51cecd
into d22ac6a7a4
This commit is contained in:
commit
38453ff199
6 changed files with 228 additions and 1 deletions
|
@ -39,6 +39,7 @@
|
|||
- [Twitch Top Games](#twitch-top-games)
|
||||
- [iframe](#iframe)
|
||||
- [HTML](#html)
|
||||
- [Count Timer](#count-timer)
|
||||
|
||||
|
||||
## Preconfigured page
|
||||
|
@ -2458,3 +2459,45 @@ Example:
|
|||
```
|
||||
|
||||
Note the use of `|` after `source:`, this allows you to insert a multi-line string.
|
||||
|
||||
### Count Timer
|
||||
Adds a counting timer. Counts up or down from/to a specific time. This widget is dynamic and updates every second.
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
- type: count-timer
|
||||
event-title: Christmas 2025
|
||||
date: 2025-12-25T00:00:00Z
|
||||
- type: count-timer
|
||||
event-title: New Year 2022 (US EST)
|
||||
date: 2022-01-01T00:00:00-05:00
|
||||
- type: count-timer
|
||||
title: Conf 2022
|
||||
date: 2022-06-04T00:00:00Z
|
||||
href: https://conf.com
|
||||
```
|
||||
|
||||
Preview:
|
||||
|
||||

|
||||
|
||||
#### Properties
|
||||
| Name | Type | Required | Default | Description |
|
||||
| ---- | ---- | -------- | ------- | ----------- |
|
||||
| date | time | yes | | Time, including timezone info |
|
||||
| title | str | no | | Choose one between `titie` end `event-title` |
|
||||
| event-title | str | no | | Choose one between `titie` end `event-title` |
|
||||
| href | str(URL) | no | | |
|
||||
|
||||
#### `date`
|
||||
The target date to count to. ISO 8601 format.
|
||||
|
||||
#### `event-title`
|
||||
The event title. ` ⋅ PAST` or ` ⋅ FUTURE` will be added to form the widget title.
|
||||
|
||||
#### `title`
|
||||
If `event-title` is not set or empty, this title shows instead.
|
||||
|
||||
#### `href`
|
||||
If set, the counter will have a hyperlink to this URL.
|
||||
|
||||
|
|
BIN
docs/images/count-timer.png
Normal file
BIN
docs/images/count-timer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
|
@ -653,6 +653,98 @@ function setupTruncatedElementTitles() {
|
|||
}
|
||||
}
|
||||
|
||||
function setUpCountdowns() {
|
||||
const countdowns = document.getElementsByClassName("widget-type-count-timer");
|
||||
|
||||
if (countdowns.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updateCallbacks = [];
|
||||
|
||||
for (var i = 0; i < countdowns.length; i++) {
|
||||
const countdown = countdowns[i];
|
||||
|
||||
const datasetMeta = countdown.getElementsByTagName("meta")[0];
|
||||
const targetDate = new Date(datasetMeta.getAttribute("target"));
|
||||
const title = datasetMeta.getAttribute("title");
|
||||
const eventTitle = datasetMeta.getAttribute("event");
|
||||
const h2Element = countdown.getElementsByTagName("h2")[0];
|
||||
|
||||
const dayTd = countdown.getElementsByClassName("count-days")[0];
|
||||
const hourTd = countdown.getElementsByClassName("count-hours")[0];
|
||||
const minuteTd = countdown.getElementsByClassName("count-minutes")[0];
|
||||
const secondTd = countdown.getElementsByClassName("count-seconds")[0];
|
||||
|
||||
// Create update callback fn
|
||||
const updateCountdown = (now) => {
|
||||
var diff = targetDate - now;
|
||||
|
||||
// Remove all colors
|
||||
dayTd.classList.remove("color-primary")
|
||||
dayTd.classList.remove("color-highlight")
|
||||
hourTd.classList.remove("color-primary")
|
||||
hourTd.classList.remove("color-highlight")
|
||||
minuteTd.classList.remove("color-primary")
|
||||
minuteTd.classList.remove("color-highlight")
|
||||
secondTd.classList.remove("color-primary")
|
||||
secondTd.classList.remove("color-highlight")
|
||||
|
||||
if(diff > 0) {
|
||||
// Set color to primary
|
||||
dayTd.classList.add("color-primary")
|
||||
hourTd.classList.add("color-primary")
|
||||
minuteTd.classList.add("color-primary")
|
||||
secondTd.classList.add("color-primary")
|
||||
} else {
|
||||
// Set color to highlight
|
||||
dayTd.classList.add("color-highlight")
|
||||
hourTd.classList.add("color-highlight")
|
||||
minuteTd.classList.add("color-highlight")
|
||||
secondTd.classList.add("color-highlight")
|
||||
}
|
||||
|
||||
if(eventTitle & (eventTitle != "")) {
|
||||
if(diff > 0) {
|
||||
h2Element.textContent = eventTitle + " ⋅ FUTURE"
|
||||
} else {
|
||||
h2Element.textContent = eventTitle + " ⋅ PAST"
|
||||
}
|
||||
} else {
|
||||
h2Element.textContent = title;
|
||||
}
|
||||
|
||||
if(diff < 0) {
|
||||
diff = -diff;
|
||||
}
|
||||
|
||||
const diffDays = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
const diffHours = String(Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))).padStart(2, '0');
|
||||
const diffMinutes = String(Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))).padStart(2, '0');
|
||||
const diffSeconds = String(Math.floor((diff % (1000 * 60)) / 1000)).padStart(2, '0');
|
||||
|
||||
// Set up countdown
|
||||
dayTd.innerHTML = diffDays;
|
||||
hourTd.innerHTML = diffHours;
|
||||
minuteTd.innerHTML = diffMinutes;
|
||||
secondTd.innerHTML = diffSeconds;
|
||||
}
|
||||
|
||||
updateCallbacks.push(updateCountdown);
|
||||
}
|
||||
|
||||
const updateCountdowns = () => {
|
||||
const now = new Date();
|
||||
|
||||
for (var i = 0; i < updateCallbacks.length; i++)
|
||||
updateCallbacks[i](now);
|
||||
|
||||
setTimeout(updateCountdowns, 1000);
|
||||
}
|
||||
|
||||
updateCountdowns();
|
||||
}
|
||||
|
||||
async function setupPage() {
|
||||
const pageElement = document.getElementById("page");
|
||||
const pageContentElement = document.getElementById("page-content");
|
||||
|
@ -662,7 +754,8 @@ async function setupPage() {
|
|||
|
||||
try {
|
||||
setupPopovers();
|
||||
setupClocks()
|
||||
setupClocks();
|
||||
setUpCountdowns();
|
||||
await setupCalendars();
|
||||
setupCarousels();
|
||||
setupSearchBoxes();
|
||||
|
|
33
internal/glance/templates/count-timer.html
Normal file
33
internal/glance/templates/count-timer.html
Normal file
|
@ -0,0 +1,33 @@
|
|||
{{ template "widget-base.html" . }}
|
||||
|
||||
{{ define "widget-header" }}
|
||||
<h2 class="uppercase"> {{ .RenderedTitle }}</h2>
|
||||
{{ end }}
|
||||
|
||||
{{ define "widget-content" }}
|
||||
{{ if .Href }}
|
||||
<a href="{{ .Href }}" target="_blank" rel="noreferrer" text-decoration: none; color: inherit;>
|
||||
{{ end }}
|
||||
<div class="count-timer=body">
|
||||
<meta id="dataset" target="{{ .TargetDate }}" event="{{ .EventTitle }}" title="{{.Title}}" />
|
||||
<table style="width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="title size-h1 count-days" style="text-align: center"> {{ .Days }} </td>
|
||||
<td class="title size-h1 count-hours" style="text-align: center"> {{ .Hours }} </td>
|
||||
<td class="title size-h1 count-minutes" style="text-align: center"> {{ .Minutes }} </td>
|
||||
<td class="title size-h1 count-seconds" style="text-align: center"> {{ .Seconds }} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="color-secondary" style="text-align: center">DAYS</td>
|
||||
<td class="color-secondary" style="text-align: center">HRS</td>
|
||||
<td class="color-secondary" style="text-align: center">MIN</td>
|
||||
<td class="color-secondary" style="text-align: center">SEC</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ if .Href }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
56
internal/glance/widget-count-timer.go
Normal file
56
internal/glance/widget-count-timer.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package glance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
var countTimerWidgetTemplate = mustParseTemplate("count-timer.html", "widget-base.html")
|
||||
|
||||
type countTimerWidget struct {
|
||||
widgetBase `yaml:",inline"`
|
||||
cachedHTML template.HTML `yaml:"-"`
|
||||
EventTitle string `yaml:"event-title"`
|
||||
TargetDate time.Time `yaml:"date"`
|
||||
Href string `yaml:"href"`
|
||||
RenderedTitle string `yaml:"-"`
|
||||
DiffSeconds int `yaml:"-"`
|
||||
Days int `yaml:"-"`
|
||||
Hours int `yaml:"-"`
|
||||
Minutes int `yaml:"-"`
|
||||
Seconds int `yaml:"-"`
|
||||
}
|
||||
|
||||
func (w *countTimerWidget) update(ctx context.Context) {
|
||||
now := time.Now()
|
||||
target := w.TargetDate
|
||||
|
||||
diff := target.Sub(now)
|
||||
if diff < 0 {
|
||||
w.RenderedTitle = w.EventTitle + " ⋅ PAST"
|
||||
diff = -diff
|
||||
} else {
|
||||
w.RenderedTitle = w.EventTitle + " ⋅ FUTURE"
|
||||
}
|
||||
if w.EventTitle == "" {
|
||||
w.RenderedTitle = w.Title
|
||||
}
|
||||
w.Days = int(diff.Hours()) / 24
|
||||
w.Hours = int(diff.Hours()) % 24
|
||||
w.Minutes = int(diff.Minutes()) % 60
|
||||
w.Seconds = int(diff.Seconds()) % 60
|
||||
|
||||
w.cachedHTML = w.renderTemplate(w, countTimerWidgetTemplate)
|
||||
}
|
||||
|
||||
func (w *countTimerWidget) initialize() error {
|
||||
w.update(context.Background())
|
||||
w.withTitle(w.RenderedTitle).withError(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *countTimerWidget) Render() template.HTML {
|
||||
w.update(context.TODO())
|
||||
return w.cachedHTML
|
||||
}
|
|
@ -79,6 +79,8 @@ func newWidget(widgetType string) (widget, error) {
|
|||
w = &dockerContainersWidget{}
|
||||
case "server-stats":
|
||||
w = &serverStatsWidget{}
|
||||
case "count-timer":
|
||||
w = &countTimerWidget{}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown widget type: %s", widgetType)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue