diff --git a/MMM-CountEvents.js b/MMM-CountEvents.js
index e903558..2ace9ac 100644
--- a/MMM-CountEvents.js
+++ b/MMM-CountEvents.js
@@ -1,5 +1,4 @@
Module.register("MMM-CountEvents", {
-
defaults: {
refresh: 1000 * 60,
title: "nonamed",
@@ -19,125 +18,188 @@ Module.register("MMM-CountEvents", {
useQuarter: false,
onPassed: null,
onUpdated: null,
-
- events:[],
+ orderByTimeLeft: null, // 'asc', 'desc', or null for default list order
+ events: [],
},
- getStyles: function() {
- return ["MMM-CountEvents.css"]
+ getStyles: function () {
+ return ["MMM-CountEvents.css"];
},
- start: function() {
- this.config.identifier = this.config?.identifier ?? 'CE_' + this.identifier
- this.config.locale = this.config?.locale ?? config?.locale ?? "en"
+ start: function () {
+ this.config.identifier = this.config?.identifier ?? "CE_" + this.identifier;
+ this.config.locale = this.config?.locale ?? config?.locale ?? "en";
},
- getDom: function() {
- const wrapper = document.createElement("ul")
- wrapper.classList.add('CE')
- wrapper.id = this.config.identifier
- this.config.events.forEach(ev => {
- const event = this.regularize(ev)
- const mmt = this.mmTime(event)
- if (!mmt) return
+ getTargetTime: function (event) {
+ const t = new Date(event.targetTime);
+ const n = new Date(Date.now());
+ const tMonth = t.getMonth();
+ const tDate = t.getDate();
+ const tHour = t.getHours();
+ const tMinute = t.getMinutes();
+ const tSecond = t.getSeconds();
+ const tMilisecond = t.getMilliseconds();
+ const nYear = n.getFullYear();
+ const nMonth = n.getMonth();
+ const nDate = n.getDate();
+ const nHour = n.getHours();
- const eventWrapper = document.createElement("li")
- if (event.className) eventWrapper.classList.add(event.className)
- eventWrapper.innerHTML = event.output
- eventWrapper.querySelector(".title").innerHTML = event.title
- eventWrapper.querySelector(".output").appendChild(mmt)
- wrapper.appendChild(eventWrapper)
- })
- return wrapper
+ let nextT = new Date(t);
+ switch (event.repeat) {
+ case "yearly":
+ nextT = new Date(
+ nYear,
+ tMonth,
+ tDate,
+ tHour,
+ tMinute,
+ tSecond,
+ tMilisecond,
+ );
+ if (nextT.valueOf() < n.valueOf()) nextT.setFullYear(nYear + 1);
+ break;
+ case "monthly":
+ nextT = new Date(
+ nYear,
+ nMonth,
+ tDate,
+ tHour,
+ tMinute,
+ tSecond,
+ tMilisecond,
+ );
+ if (nextT.valueOf() < n.valueOf()) nextT.setMonth(nMonth + 1);
+ if (nextT.getDate() !== tDate) nextT.setDate(0);
+ break;
+ case "weekly":
+ nextT = new Date(
+ nYear,
+ nMonth,
+ nDate,
+ tHour,
+ tMinute,
+ tSecond,
+ tMilisecond,
+ );
+ const dayDiff = t.getDay() - nextT.getDay();
+ if (dayDiff === 0 && n.valueOf() > nextT.valueOf())
+ nextT.setDate(nDate + 7);
+ if (dayDiff !== 0) nextT.setDate(nDate + ((dayDiff + 7) % 7));
+ break;
+ case "daily":
+ nextT = new Date(
+ nYear,
+ nMonth,
+ nDate,
+ tHour,
+ tMinute,
+ tSecond,
+ tMilisecond,
+ );
+ if (nextT.valueOf() < n.valueOf()) nextT.setDate(nDate + 1);
+ break;
+ case "hourly":
+ nextT = new Date(
+ nYear,
+ nMonth,
+ nDate,
+ nHour,
+ tMinute,
+ tSecond,
+ tMilisecond,
+ );
+ if (nextT.valueOf() < n.valueOf()) nextT.setHours(nHour + 1);
+ break;
+ default:
+ break;
+ }
+ return nextT;
},
- regularize: function(event) {
- const { events, identifier, ...rest } = this.config
- return { ...rest, ...event }
- },
+ getDom: function () {
+ const wrapper = document.createElement("ul");
+ wrapper.classList.add("CE");
+ wrapper.id = this.config.identifier;
- mmTime: function(event) {
- const repeated = (event) => {
- const t = new Date(event.targetTime)
- const n = new Date(Date.now())
- const tMonth = t.getMonth()
- const tDate = t.getDate()
- const tHour = t.getHours()
- const tMinute = t.getMinutes()
- const tSecond = t.getSeconds()
- const tMilisecond = t.getMilliseconds()
- const nYear = n.getFullYear()
- const nMonth = n.getMonth()
- const nDate = n.getDate()
- const nHour = n.getHours()
+ let events = this.config.events.slice();
- let nextT = new Date(t)
- switch (event.repeat) {
- case "yearly":
- nextT = new Date(nYear, tMonth, tDate, tHour, tMinute, tSecond, tMilisecond)
- if (nextT.valueOf() < n.valueOf()) nextT.setFullYear(nYear + 1)
- break
- case "monthly":
- nextT = new Date(nYear, nMonth, tDate, tHour, tMinute, tSecond, tMilisecond)
- if (nextT.valueOf() < n.valueOf()) nextT.setMonth(nMonth + 1)
- if (nextT.getDate() !== tDate) nextT.setDate(0)
- break
- case "weekly":
- nextT = new Date(nYear, nMonth, nDate, tHour, tMinute, tSecond, tMilisecond)
- const dayDiff = t.getDay() - nextT.getDay()
- if (dayDiff === 0 && n.valueOf() > nextT.valueOf()) nextT.setDate(nDate + 7)
- if (dayDiff !== 0) nextT.setDate(nDate + ((dayDiff + 7) % 7))
- break
- case "daily":
- nextT = new Date(nYear, nMonth, nDate, tHour, tMinute, tSecond, tMilisecond)
- if (nextT.valueOf() < n.valueOf()) nextT.setDate(nDate + 1)
- break
- case "hourly":
- nextT = new Date(nYear, nMonth, nDate, nHour, tMinute, tSecond, tMilisecond)
- if (nextT.valueOf() < n.valueOf()) nextT.setHours(nHour + 1)
- break
- default: break
- }
- return nextT
+ if (this.config.orderByTimeLeft) {
+ events.sort((a, b) => {
+ const aTime = this.getTargetTime(this.regularize(a)).valueOf();
+ const bTime = this.getTargetTime(this.regularize(b)).valueOf();
+ if (this.config.orderByTimeLeft === "asc") return aTime - bTime;
+ if (this.config.orderByTimeLeft === "desc") return bTime - aTime;
+ return 0;
+ });
}
- const targetTime = new Date(repeated(event))
- const now = Date.now()
- if (event.ignoreBefore && targetTime.valueOf() - +event.ignoreBefore > now) return false
- if (event.ignoreAfter && targetTime.valueOf() + +event.ignoreAfter < now) return false
+ events.forEach((ev) => {
+ const event = this.regularize(ev);
+ const mmt = this.mmTime(event);
+ if (!mmt) return;
+
+ const eventWrapper = document.createElement("li");
+ if (event.className) eventWrapper.classList.add(event.className);
+ eventWrapper.innerHTML = event.output;
+ eventWrapper.querySelector(".title").innerHTML = event.title;
+ eventWrapper.querySelector(".output").appendChild(mmt);
+ wrapper.appendChild(eventWrapper);
+ });
+ return wrapper;
+ },
+
+ regularize: function (event) {
+ const { events, identifier, ...rest } = this.config;
+ return { ...rest, ...event };
+ },
- const mmt = document.createElement("mm-time")
- mmt.relative = true
- mmt.locale = event?.locale ?? this.config?.locale ?? config?.locale ?? "en"
- mmt.time = targetTime
- mmt.decouple = true
- mmt.dataset.numeric = (event.numericAlways) ? 'always' : 'auto'
- if (event.unit) mmt.relativeUnit = event.unit
- if (event.reverse) mmt.relativeReverse = event.reverse
- if (event.refresh && event.refresh >= 1000) mmt.refresh = event.refresh
- if (event.useQuarter) mmt.relativeQuarter = true
- if (event.numberOnly) mmt.classList.add("number-only")
- if (event.numberSign) mmt.classList.add("number-sign")
- mmt.classList.add((targetTime.valueOf() < now) ? "past" : ((targetTime.valueOf() > now) ? "future" : "now"))
- if (event.className) mmt.classList.add(event.className)
+ mmTime: function (event) {
+ const targetTime = new Date(this.getTargetTime(event));
+ const now = Date.now();
+ if (event.ignoreBefore && targetTime.valueOf() - +event.ignoreBefore > now)
+ return false;
+ if (event.ignoreAfter && targetTime.valueOf() + +event.ignoreAfter < now)
+ return false;
+
+ const mmt = document.createElement("mm-time");
+ mmt.relative = true;
+ mmt.locale = event?.locale ?? this.config?.locale ?? config?.locale ?? "en";
+ mmt.time = targetTime;
+ mmt.decouple = true;
+ mmt.dataset.numeric = event.numericAlways ? "always" : "auto";
+ if (event.unit) mmt.relativeUnit = event.unit;
+ if (event.reverse) mmt.relativeReverse = event.reverse;
+ if (event.refresh && event.refresh >= 1000) mmt.refresh = event.refresh;
+ if (event.useQuarter) mmt.relativeQuarter = true;
+ if (event.numberOnly) mmt.classList.add("number-only");
+ if (event.numberSign) mmt.classList.add("number-sign");
+ mmt.classList.add(
+ targetTime.valueOf() < now
+ ? "past"
+ : targetTime.valueOf() > now
+ ? "future"
+ : "now",
+ );
+ if (event.className) mmt.classList.add(event.className);
mmt.onPassed = (ev) => {
- let ret = null
+ let ret = null;
if (typeof event?.onPassed === "function") {
- ret = event.onPassed(event, mmt)
+ ret = event.onPassed(event, mmt);
}
- if (event.repeat) mmt.time = new Date(repeated(event))
- return ret
- }
+ if (event.repeat) mmt.time = new Date(this.getTargetTime(event));
+ return ret;
+ };
mmt.onUpdated = (ev) => {
- let ret = null
+ let ret = null;
if (typeof event?.onUpdated === "function") {
- ret = event.onUpdated(event, mmt)
+ ret = event.onUpdated(event, mmt);
}
- return ret
- }
+ return ret;
+ };
if (typeof event?.manipulate === "function") {
- return event.manipulate(mmt)
+ return event.manipulate(mmt);
}
- return mmt
+ return mmt;
},
-})
\ No newline at end of file
+});
diff --git a/README.md b/README.md
index fea05ca..ce9d14c 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,24 @@
# MMM-CountEvents
+
Countdown or countup for events
> This module is revamped from the scratch. If you are using the previous version, you may need reinstall again.
## Screenshots
+


## Features
+
- Count up / down to specific date/time
- Various formatting & custom template
- Auto repeat : yearly, monthly, daily, hourly.
- Callback functions for updating or passing the event.
-
## Installation
+
```sh
cd ~/MagicMirror/modules
git clone https://github.com/MMRIZE/MMM-CountEvents
@@ -24,6 +27,7 @@ npm install
```
This module needs `MMM-CustomElementTime` module also. It would be installed by execution of `postinstall.sh`. Usually This script will be run automatically by `npm install` but, when that is not executed properly, do it by manual.
+
```sh
# In some environments, you may need to allow permission
chmod 755 ./postinstall.sh
@@ -34,19 +38,21 @@ sh ./postinstall.sh
```
But when you have still a problem, you can install that module also by manual.
+
```sh
cd ~/MagicMirror/modules
git clone https://github.com/MMRIZE/MMM-CustomElementTime
```
-
## Configuration
+
**IMPORTANT**
You should include `MMM-CustomElementTime` also in the `config.js`
+
```js
{
- module: "MMM-CustomElementTime"
+ module: "MMM-CustomElementTime"
},
{
module: "MMM-CountEvents",
@@ -63,6 +69,7 @@ You should include `MMM-CustomElementTime` also in the `config.js`
```
### Simplest Example
+
```js
{
module: "MMM-CountEvents",
@@ -77,7 +84,9 @@ You should include `MMM-CustomElementTime` also in the `config.js`
}
},
```
+
### Detailed (and default)
+
```js
{
module: "MMM-CountEvents",
@@ -99,7 +108,7 @@ You should include `MMM-CustomElementTime` also in the `config.js`
useQuarter: false,
onPassed: null,
onUpdated: null,
-
+ orderByTimeLeft: null,
events: [
{
title: "To Christmas",
@@ -113,13 +122,14 @@ You should include `MMM-CustomElementTime` also in the `config.js`
```
### Config values for common default properties
+
These properties in `config:[]` will be applied to each event by default. You can reassign specific property in each event again.
|Property | default value | Description |
|:---|:---:|:---|
|`locale`|MM's default `locale` or system locale | `BCP-47` format locale identifier. It needs for displaying format by user's locale.
e.g. "de", "en-CA" |
|`refresh`| 60_000 | (ms) self-refreshing interval of the event. `0` means no refreshing.
|`unit`| "auto" | **Available values**: `"auto"`, `"year"`, `"month"`, `"day"`, `"hour"`, `"minute"`, `"second"` and `"quarter"`
`"years"`, `"months"`, `"days"`, `"hours"`, `"minutes"`, `"seconds"` would be available also.|
-|`repeat`| false | **Available values**: `"yearly"`, `"monthly"`, `"daily"`, `"hourly"` or `false`
When the event is a kind of recurred events.
`"minutely"` and `"secondly"` are not supported. It sounds weird and out-of-sense. |
+|`repeat`| false | **Available values**: `"yearly"`, `"monthly"`, `"daily"`, `"hourly"` or `false`
When the event is a kind of recurred events.
`"minutely"` and `"secondly"` are not supported. It sounds weird and out-of-sense. |
|`ignoreBefore`| false | **Available values**: `false` or `(miliseconds)`
If you set `1000 * 60 * 60 * 24 * 7`, this events will not be displayed before 7days from `targetTime`. |
|`ignoreAfter` | false | **Available values**: `false` or `(miliseconds)`
If you set `1000 * 60 * 60`, this events will not be displayed after 1 hour from `targetTime`.|
|`className`| "default" | You can assign specific CSS class name to the event. |
@@ -131,29 +141,49 @@ These properties in `config:[]` will be applied to each event by default. You ca
|`useQuarter` | false | Add `quarter` unit into the `unit: "auto"` options. |
|`onPassed` | callback function `(event, element)`| When the `targetTime` is passed, this callback function would be called. Usually this will be used for an alarm function. See the [Examples]
- The check for passing moment is depending on the `refresh` interval.
- When if the event is repeated, this function would be called again at a time.|
|`onUpdated` | callback function `(event, element)` | When the event is refreshed by `refresh` option, this callback function would be called. Usually this will be used for modifying the result on real-time. See the [Examples]|
+|`orderByTimeLeft` | null | **Available values**: `null`, `"asc"`, `"desc"`
Orders the events by time left to the target time. `"asc"` for closest first, `"desc"` for farthest first, `null` for default list order.|
|`events` | [] | Array of event objects to display. |
### Event object
-|Property | default value | Description |
-|:---|:---:|:---|
-|`title`| "nonamed" | The title of the event. This value will be injected into the first `class="title"` element in `output` template of the event. |
-|`targetTime` | "2025-01-01" | The target time of the event.
**Available Types**
- *Unix Timestamp*
- *Date-like String*: See the below [[Time format]]
- *Javascript Date Object* |
+
+| Property | default value | Description |
+| :----------- | :-----------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `title` | "nonamed" | The title of the event. This value will be injected into the first `class="title"` element in `output` template of the event. |
+| `targetTime` | "2025-01-01" | The target time of the event.
**Available Types**
- _Unix Timestamp_
- _Date-like String_: See the below [[Time format]]
- _Javascript Date Object_ |
+
> All common default properties could be used and reassigned for the specific evnet object.
## CSS Styling
+
The displaying result of this module woule have the HTML like this. (with default `output` template)
+
```html
-