mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-06-26 03:52:28 +00:00
Release 2.31.0 (#3758)
Thanks to: @Developer-Incoming, @eltociear, @geraki, @khassel, @KristjanESPERANTO, @MagMar94, @mixasgr, @n8many, @OWL4C, @rejas, @savvadam, @sdetweil. > ⚠️ This release needs nodejs version `v22.14.0 or higher` ### Added - Add CSS support to the digital clock hour/minute/second through the use of the classes `clock-hour-digital`, `clock-minute-digital`, and `clock-second-digital`. - Add Arabic (#3719) and Esperanto translation. - Mark option `secondsColor` as deprecated in clock module. - Add Greek translation to Alerts module. - [newsfeed] Add specific ignoreOlderThan value (override) per feed (#3360) - [weather] Added option Humidity to hourly View - [weather] Added option to hide hourly entries that are Zero, hiding the entire column if empty. - [updatenotification] Added option to iterate over modules directory instead using modules defined in `config.js` (#3739) ### Changed - [core] starting clientonly now checks for needed env var `WAYLAND_DISPLAY` or `DISPLAY` and starts electron with needed parameters (if both are set wayland is used) (#3677) - [core] Optimize systeminformation calls and output (#3689) - [core] Add issue templates for feature requests and bug reports (#3695) - [core] Adapt `start:x11:dev` script - [weather/yr] The Yr weather provider now enforces a minimum `updateInterval` of 600 000 ms (10 minutes) to comply with the terms of service. If a lower value is set, it will be automatically increased to this minimum. - [weather/weatherflow] Fixed icons and added hourly support as well as UV, precipitation, and location name support. - [workflow] Run `sudo apt-get update` before installing packages to avoid install errors - [workflow] Exclude issues with label `ready (coming with next release)` from stale job ### Removed ### Updated - [core] Update requirements and dependencies including electron to v35 and formatting (#3593, #3693, #3717) - [core] Update prettier, ESLint and simplify config - Update Greek translation ### Fixed - [calendar] Fix clipping events being broadcast (#3678) - [tests] Fix Electron tests by running them under new github image ubuntu-24.04, replace xserver with labwc, running under xserver and labwc depending on env variable WAYLAND_DISPLAY is set (#3676) - [calendar] Fix arrayed symbols, #3267, again, add testcase, add testcase for #3678 - [weather] Fix wrong weatherCondition name in openmeteo provider which lead to n/a icon (#3691) - [core] Fix wrong port in log message when starting server only (#3696) - [calendar] Fix NewYork event processed on system in Central timezone shows wrong time #3701 - [weather/yr] The Yr weather provider is now able to recover from bad API responses instead of freezing (#3296) - [compliments] Fix evening events being shown during the day (#3727) - [weather] Fixed minor spacing issues when using UV Index in Hourly - [workflow] Fix command to run spellcheck --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Michael Teeuw <michael@xonaymedia.nl> Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Karsten Hassel <hassel@gmx.de> Co-authored-by: Ross Younger <crazyscot@gmail.com> Co-authored-by: Bugsounet - Cédric <github@bugsounet.fr> Co-authored-by: jkriegshauser <joshuakr@nvidia.com> Co-authored-by: illimarkangur <116028111+illimarkangur@users.noreply.github.com> Co-authored-by: sam detweiler <sdetweil@gmail.com> Co-authored-by: vppencilsharpener <tim.pray@gmail.com> Co-authored-by: Paranoid93 <6515818+Paranoid93@users.noreply.github.com> Co-authored-by: Brian O'Connor <btoconnor@users.noreply.github.com> Co-authored-by: WallysWellies <59727507+WallysWellies@users.noreply.github.com> Co-authored-by: Jason Stieber <jrstieber@gmail.com> Co-authored-by: jargordon <50050429+jargordon@users.noreply.github.com> Co-authored-by: Daniel <32464403+dkallen78@users.noreply.github.com> Co-authored-by: Ryan Williams <65094007+ryan-d-williams@users.noreply.github.com> Co-authored-by: Panagiotis Skias <panagiotis.skias@gmail.com> Co-authored-by: Marc Landis <dirk.rettschlag@gmail.com> Co-authored-by: HeikoGr <20295490+HeikoGr@users.noreply.github.com> Co-authored-by: Pedro Lamas <pedrolamas@gmail.com> Co-authored-by: veeck <gitkraken@veeck.de> Co-authored-by: Magnus <34011212+MagMar94@users.noreply.github.com> Co-authored-by: Ikko Eltociear Ashimine <eltociear@gmail.com> Co-authored-by: DevIncomin <56730075+Developer-Incoming@users.noreply.github.com> Co-authored-by: Nathan <n8nyoung@gmail.com> Co-authored-by: mixasgr <mixasgr@users.noreply.github.com> Co-authored-by: Savvas Adamtziloglou <savvas-gr@greeklug.gr> Co-authored-by: Konstantinos <geraki@gmail.com> Co-authored-by: OWL4C <124401812+OWL4C@users.noreply.github.com>
This commit is contained in:
parent
9c9a5359dd
commit
39a614e0de
18
.github/workflows/automated-tests.yaml
vendored
18
.github/workflows/automated-tests.yaml
vendored
@ -34,12 +34,16 @@ jobs:
|
||||
npm run test:css
|
||||
npm run test:markdown
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.18.1, 20.x, 22.x, 23.x]
|
||||
node-version: [22.14.0, 22.x, 23.x]
|
||||
steps:
|
||||
- name: Install electron dependencies and labwc
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libnss3 libasound2t64 labwc
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v4
|
||||
- name: "Use Node.js ${{ matrix.node-version }}"
|
||||
@ -48,12 +52,16 @@ jobs:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
check-latest: true
|
||||
cache: "npm"
|
||||
- name: "Install dependencies"
|
||||
- name: "Install MagicMirror²"
|
||||
run: |
|
||||
npm run install-mm:dev
|
||||
- name: "Run tests"
|
||||
run: |
|
||||
Xvfb :99 -screen 0 1024x768x16 &
|
||||
export DISPLAY=:99
|
||||
# Fix chrome-sandbox permissions:
|
||||
sudo chown root:root ./node_modules/electron/dist/chrome-sandbox
|
||||
sudo chmod 4755 ./node_modules/electron/dist/chrome-sandbox
|
||||
# Start labwc
|
||||
WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1 WLR_RENDERER=pixman labwc &
|
||||
export WAYLAND_DISPLAY=wayland-0
|
||||
touch css/custom.css
|
||||
npm run test
|
||||
|
6
.github/workflows/electron-rebuild.yaml
vendored
6
.github/workflows/electron-rebuild.yaml
vendored
@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.18.1, 20.x, 22.x, 23.x]
|
||||
node-version: [22.14.0, 22.x, 23.x]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@ -22,7 +22,9 @@ jobs:
|
||||
- name: Install @electron/rebuild
|
||||
run: npm install @electron/rebuild
|
||||
- name: Install node-libgpiod deps
|
||||
run: sudo apt-get install gpiod libgpiod2 libgpiod-dev
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install gpiod libgpiod2 libgpiod-dev
|
||||
- name: Install test library (node-libgpiod) to be rebuilded
|
||||
run: npm install node-libgpiod
|
||||
- name: Run electron-rebuild
|
||||
|
2
.github/workflows/spellcheck.yaml
vendored
2
.github/workflows/spellcheck.yaml
vendored
@ -28,4 +28,4 @@ jobs:
|
||||
run: |
|
||||
npm run install-mm:dev
|
||||
- name: Run Spellcheck
|
||||
run: npm run test:spellcheck
|
||||
run: npm run test:spelling
|
||||
|
2
.github/workflows/stale.yaml
vendored
2
.github/workflows/stale.yaml
vendored
@ -19,4 +19,4 @@ jobs:
|
||||
days-before-issue-close: 7
|
||||
operations-per-run: 100
|
||||
stale-issue-label: "wontfix"
|
||||
exempt-issue-labels: "pinned,security,under investigation,pr welcome"
|
||||
exempt-issue-labels: "pinned,security,under investigation,pr welcome,ready (coming with next release)"
|
||||
|
56
CHANGELOG.md
56
CHANGELOG.md
@ -7,6 +7,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/#donate) With your help we can continue to improve the MagicMirror².
|
||||
|
||||
## [2.31.0] - 2025-04-01
|
||||
|
||||
Thanks to: @Developer-Incoming, @eltociear, @geraki, @khassel, @KristjanESPERANTO, @MagMar94, @mixasgr, @n8many, @OWL4C, @rejas, @savvadam, @sdetweil.
|
||||
|
||||
> ⚠️ This release needs nodejs version `v22.14.0 or higher`
|
||||
|
||||
### Added
|
||||
|
||||
- Add CSS support to the digital clock hour/minute/second through the use of the classes `clock-hour-digital`, `clock-minute-digital`, and `clock-second-digital`.
|
||||
- Add Arabic (#3719) and Esperanto translation.
|
||||
- Mark option `secondsColor` as deprecated in clock module.
|
||||
- Add Greek translation to Alerts module.
|
||||
- [newsfeed] Add specific ignoreOlderThan value (override) per feed (#3360)
|
||||
- [weather] Added option Humidity to hourly View
|
||||
- [weather] Added option to hide hourly entries that are Zero, hiding the entire column if empty.
|
||||
- [updatenotification] Added option to iterate over modules directory instead using modules defined in `config.js` (#3739)
|
||||
|
||||
### Changed
|
||||
|
||||
- [core] starting clientonly now checks for needed env var `WAYLAND_DISPLAY` or `DISPLAY` and starts electron with needed parameters (if both are set wayland is used) (#3677)
|
||||
- [core] Optimize systeminformation calls and output (#3689)
|
||||
- [core] Add issue templates for feature requests and bug reports (#3695)
|
||||
- [core] Adapt `start:x11:dev` script
|
||||
- [weather/yr] The Yr weather provider now enforces a minimum `updateInterval` of 600 000 ms (10 minutes) to comply with the terms of service. If a lower value is set, it will be automatically increased to this minimum.
|
||||
- [weather/weatherflow] Fixed icons and added hourly support as well as UV, precipitation, and location name support.
|
||||
- [workflow] Run `sudo apt-get update` before installing packages to avoid install errors
|
||||
- [workflow] Exclude issues with label `ready (coming with next release)` from stale job
|
||||
|
||||
### Removed
|
||||
|
||||
### Updated
|
||||
|
||||
- [core] Update requirements and dependencies including electron to v35 and formatting (#3593, #3693, #3717)
|
||||
- [core] Update prettier, ESLint and simplify config
|
||||
- Update Greek translation
|
||||
|
||||
### Fixed
|
||||
|
||||
- [calendar] Fix clipping events being broadcast (#3678)
|
||||
- [tests] Fix Electron tests by running them under new github image ubuntu-24.04, replace xserver with labwc, running under xserver and labwc depending on env variable WAYLAND_DISPLAY is set (#3676)
|
||||
- [calendar] Fix arrayed symbols, #3267, again, add testcase, add testcase for #3678
|
||||
- [weather] Fix wrong weatherCondition name in openmeteo provider which lead to n/a icon (#3691)
|
||||
- [core] Fix wrong port in log message when starting server only (#3696)
|
||||
- [calendar] Fix NewYork event processed on system in Central timezone shows wrong time #3701
|
||||
- [weather/yr] The Yr weather provider is now able to recover from bad API responses instead of freezing (#3296)
|
||||
- [compliments] Fix evening events being shown during the day (#3727)
|
||||
- [weather] Fixed minor spacing issues when using UV Index in Hourly
|
||||
- [workflow] Fix command to run spellcheck
|
||||
|
||||
## [2.30.0] - 2025-01-01
|
||||
|
||||
Thanks to: @xsorifc28, @HeikoGr, @bugsounet, @khassel, @KristjanESPERANTO, @rejas, @sdetweil.
|
||||
@ -24,7 +73,7 @@ Thanks to: @xsorifc28, @HeikoGr, @bugsounet, @khassel, @KristjanESPERANTO, @reja
|
||||
- [linter] Re-add `eslint-plugin-import`now that it supports ESLint v9 (#3586)
|
||||
- [linter] Re-activate `eslint-plugin-package-json` to lint `package.json` (#3643)
|
||||
- [linter] Add linting for markdown files (#3646)
|
||||
- [linter] Add some handy ESLint rules.
|
||||
- [linter] Add some handy ESLint rules (#3665)
|
||||
- [calendar] Add ability to display end date for full date events, where end is not same day (showEnd=true) (#3650)
|
||||
- [core] Add text to the config.js.sample file about the locale variable (#3654, #3655)
|
||||
- [core] Add fetch timeout for all node_helpers (thru undici, forces node 20.18.1 minimum) to help on slower systems. (#3660) (3661)
|
||||
@ -103,7 +152,7 @@ Thanks to: @bugsounet, @dkallen78, @jargordon, @khassel, @KristjanESPERANTO, @Ma
|
||||
- [core] Detail optimizations in `config_check.js`
|
||||
- [core] Updated minimal needed node version in `package.json` (currently v20.9.0) (#3559) and except for v21 (no security updates) (#3561)
|
||||
- [linter] Switch to ESLint v9 and flat config and replace `eslint-plugin-unicorn` by `@eslint/js`
|
||||
- [core] fix discovering module positions twice after #3450
|
||||
- [core] Fix discovering module positions twice after #3450
|
||||
|
||||
### Fixed
|
||||
|
||||
@ -167,7 +216,7 @@ For more info, please read the following post: [A New Chapter for MagicMirror: T
|
||||
|
||||
### Updated
|
||||
|
||||
- Update updatenotification (update_helper.js): Recode with pm2 library (#3332)
|
||||
- [updatenotification] Recode update_helper.js with pm2 library (#3332)
|
||||
- Removing lodash dependency by replacing merge by spread operator (#3339)
|
||||
- Use node prefix for build-in modules (#3340)
|
||||
- Rework logging colors (#3350)
|
||||
@ -1671,6 +1720,7 @@ It includes (but is not limited to) the following features:
|
||||
|
||||
This was part of the blogpost: [https://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the](https://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the)
|
||||
|
||||
[2.31.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.30.0...v2.31.0
|
||||
[2.30.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.29.0...v2.30.0
|
||||
[2.29.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.28.0...v2.29.0
|
||||
[2.28.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.27.0...v2.28.0
|
||||
|
@ -30,13 +30,16 @@ Are done by
|
||||
### Deployment steps
|
||||
|
||||
- [ ] pull latest `develop` branch
|
||||
- [ ] update `package.json` and `package-lock.json` to reflect correct version number `2.xx.0`
|
||||
- [ ] test `develop` branch
|
||||
- [ ] update `CHANGELOG.md`
|
||||
- [ ] add all contributor names: `...`
|
||||
- [ ] add min. node version: > ⚠️ This release needs nodejs version `v20` or `v22`, minimum version is `v20.9.0`
|
||||
- [ ] check release link at the bottom of the file
|
||||
- [ ] commit and push all changes
|
||||
- [ ] create `prep-release` branch from `develop`
|
||||
- [ ] update `package.json` and `package-lock.json` to reflect correct version number `2.xx.0`
|
||||
- [ ] test `prep-release` branch
|
||||
- [ ] update `CHANGELOG.md`
|
||||
- [ ] add all contributor names: `...`
|
||||
- [ ] add min. node version: > ⚠️ This release needs nodejs version `v22.14.0` or higher
|
||||
- [ ] check release link at the bottom of the file
|
||||
- [ ] commit and push all changes
|
||||
- [ ] create pull request from `prep-release` to `develop` branch with title `Prepare Release 2.xx.0`
|
||||
- [ ] after successful test run via github actions: merge pull request to `develop`
|
||||
- [ ] after successful test run via github actions: create pull request from `develop` to `master` branch
|
||||
- [ ] add label `mastermerge`
|
||||
- [ ] title of the PR is `Release 2.xx.0`
|
||||
@ -54,6 +57,7 @@ Are done by
|
||||
- [ ] draft new section in `CHANGELOG.md`
|
||||
- [ ] create new release link at the bottom of the file
|
||||
- [ ] commit and publish `develop` branch
|
||||
- [ ] if new release will be in January, update the year in LICENSE.md
|
||||
|
||||
### After release
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# The MIT License (MIT)
|
||||
|
||||
Copyright © 2016-2024 Michael Teeuw
|
||||
Copyright © 2016-2025 Michael Teeuw
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
|
@ -83,6 +83,17 @@
|
||||
if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) === -1) {
|
||||
getServerConfig(`${prefix}${config.address}:${config.port}/config/`)
|
||||
.then(function (configReturn) {
|
||||
// check environment for DISPLAY or WAYLAND_DISPLAY
|
||||
const elecParams = ["js/electron.js"];
|
||||
if (process.env.WAYLAND_DISPLAY) {
|
||||
console.log(`Client: Using WAYLAND_DISPLAY=${process.env.WAYLAND_DISPLAY}`);
|
||||
elecParams.push("--enable-features=UseOzonePlatform");
|
||||
elecParams.push("--ozone-platform=wayland");
|
||||
} else if (process.env.DISPLAY) {
|
||||
console.log(`Client: Using DISPLAY=${process.env.DISPLAY}`);
|
||||
} else {
|
||||
fail("Error: Requires environment variable WAYLAND_DISPLAY or DISPLAY, none is provided.");
|
||||
}
|
||||
// Pass along the server config via an environment variable
|
||||
const env = Object.create(process.env);
|
||||
env.clientonly = true; // set to pass to electron.js
|
||||
@ -94,7 +105,7 @@
|
||||
|
||||
// Spawn electron application
|
||||
const electron = require("electron");
|
||||
const child = require("node:child_process").spawn(electron, ["js/electron.js"], options);
|
||||
const child = require("node:child_process").spawn(electron, elecParams, options);
|
||||
|
||||
// Pipe all child process output to current stdout
|
||||
child.stdout.on("data", function (buf) {
|
||||
|
@ -58,6 +58,7 @@
|
||||
"eddiehung",
|
||||
"Edgardos",
|
||||
"Ekristoffe",
|
||||
"elec",
|
||||
"envcanada",
|
||||
"envsub",
|
||||
"envsubst",
|
||||
@ -85,6 +86,7 @@
|
||||
"GHSA",
|
||||
"ghsas",
|
||||
"grenagit",
|
||||
"Heiko",
|
||||
"Hirschberger",
|
||||
"hourlyweather",
|
||||
"Hwind",
|
||||
@ -117,6 +119,7 @@
|
||||
"krekos",
|
||||
"Kristjan",
|
||||
"krukle",
|
||||
"labwc",
|
||||
"Landis",
|
||||
"larryare",
|
||||
"letsencrypt",
|
||||
@ -132,6 +135,8 @@
|
||||
"martingron",
|
||||
"marvai",
|
||||
"mastermerge",
|
||||
"matchtype",
|
||||
"maxentries",
|
||||
"Meteo",
|
||||
"michaelteeuw",
|
||||
"michmich",
|
||||
@ -195,6 +200,7 @@
|
||||
"sunaction",
|
||||
"suncalc",
|
||||
"suntimes",
|
||||
"symboltest",
|
||||
"systeminformation",
|
||||
"tada",
|
||||
"taglist",
|
||||
@ -228,6 +234,7 @@
|
||||
"xlarge",
|
||||
"xrandr",
|
||||
"xsmall",
|
||||
"xsorifc",
|
||||
"xwindows",
|
||||
"xxxe",
|
||||
"Ybbet",
|
||||
|
@ -1,14 +1,16 @@
|
||||
import eslintPluginImport from "eslint-plugin-import";
|
||||
import eslintPluginJest from "eslint-plugin-jest";
|
||||
import eslintPluginJs from "@eslint/js";
|
||||
import eslintPluginPackageJson from "eslint-plugin-package-json/configs/recommended";
|
||||
import eslintPluginPackageJson from "eslint-plugin-package-json";
|
||||
import eslintPluginStylistic from "@stylistic/eslint-plugin";
|
||||
import globals from "globals";
|
||||
|
||||
const config = [
|
||||
eslintPluginJs.configs.recommended,
|
||||
eslintPluginImport.flatConfigs.recommended,
|
||||
eslintPluginPackageJson,
|
||||
eslintPluginJest.configs["flat/recommended"],
|
||||
eslintPluginJs.configs.recommended,
|
||||
eslintPluginPackageJson.configs.recommended,
|
||||
eslintPluginStylistic.configs.all,
|
||||
{
|
||||
files: ["**/*.js"],
|
||||
languageOptions: {
|
||||
@ -24,13 +26,7 @@ const config = [
|
||||
moment: "readonly"
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
...eslintPluginStylistic.configs["all-flat"].plugins,
|
||||
...eslintPluginJest.configs["flat/recommended"].plugins
|
||||
},
|
||||
rules: {
|
||||
...eslintPluginStylistic.configs["all-flat"].rules,
|
||||
...eslintPluginJest.configs["flat/recommended"].rules,
|
||||
"@stylistic/array-element-newline": ["error", "consistent"],
|
||||
"@stylistic/arrow-parens": ["error", "always"],
|
||||
"@stylistic/brace-style": "off",
|
||||
@ -99,11 +95,7 @@ const config = [
|
||||
},
|
||||
sourceType: "module"
|
||||
},
|
||||
plugins: {
|
||||
...eslintPluginStylistic.configs["all-flat"].plugins
|
||||
},
|
||||
rules: {
|
||||
...eslintPluginStylistic.configs["all-flat"].rules,
|
||||
"@stylistic/array-element-newline": "off",
|
||||
"@stylistic/indent": ["error", "tab"],
|
||||
"@stylistic/padded-blocks": ["error", "never"],
|
||||
|
24
fonts/package-lock.json
generated
24
fonts/package-lock.json
generated
@ -9,19 +9,27 @@
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^5.1.1",
|
||||
"@fontsource/roboto-condensed": "^5.1.1"
|
||||
"@fontsource/roboto": "^5.2.5",
|
||||
"@fontsource/roboto-condensed": "^5.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/roboto": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.1.1.tgz",
|
||||
"integrity": "sha512-XwVVXtERDQIM7HPUIbyDe0FP4SRovpjF7zMI8M7pbqFp3ahLJsJTd18h+E6pkar6UbV3btbwkKjYARr5M+SQow=="
|
||||
"version": "5.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.5.tgz",
|
||||
"integrity": "sha512-70r2UZ0raqLn5W+sPeKhqlf8wGvUXFWlofaDlcbt/S3d06+17gXKr3VNqDODB0I1ASme3dGT5OJj9NABt7OTZQ==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/roboto-condensed": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-5.1.1.tgz",
|
||||
"integrity": "sha512-0SYkGnWPsvyCI3TAqBYAglfVUqVu/fsdgsyl5u396oK8ZgyamWHdQMFHDqCWrb4H4hNiewJT1l2ShDCA/cu6Ug=="
|
||||
"version": "5.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-5.2.5.tgz",
|
||||
"integrity": "sha512-FVubmVJpZ2js2+nCBEA3IOHhAgWmZ2/YKvTae0X25jlxbd85umOOvUIY6FL6OMpUvIgvwOImS9l0GJjzEPk+mg==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^5.1.1",
|
||||
"@fontsource/roboto-condensed": "^5.1.1"
|
||||
"@fontsource/roboto": "^5.2.5",
|
||||
"@fontsource/roboto-condensed": "^5.2.5"
|
||||
}
|
||||
}
|
||||
|
18
js/app.js
18
js/app.js
@ -153,11 +153,23 @@ function App () {
|
||||
*/
|
||||
function checkDeprecatedOptions (userConfig) {
|
||||
const deprecated = require(`${global.root_path}/js/deprecated`);
|
||||
const deprecatedOptions = deprecated.configs;
|
||||
|
||||
// check for deprecated core options
|
||||
const deprecatedOptions = deprecated.configs;
|
||||
const usedDeprecated = deprecatedOptions.filter((option) => userConfig.hasOwnProperty(option));
|
||||
if (usedDeprecated.length > 0) {
|
||||
Log.warn(`WARNING! Your config is using deprecated options: ${usedDeprecated.join(", ")}. Check README and CHANGELOG for more up-to-date ways of getting the same functionality.`);
|
||||
Log.warn(`WARNING! Your config is using deprecated option(s): ${usedDeprecated.join(", ")}. Check README and CHANGELOG for more up-to-date ways of getting the same functionality.`);
|
||||
}
|
||||
|
||||
// check for deprecated module options
|
||||
for (const element of userConfig.modules) {
|
||||
if (deprecated[element.module] !== undefined && element.config !== undefined) {
|
||||
const deprecatedModuleOptions = deprecated[element.module];
|
||||
const usedDeprecatedModuleOptions = deprecatedModuleOptions.filter((option) => element.config.hasOwnProperty(option));
|
||||
if (usedDeprecatedModuleOptions.length > 0) {
|
||||
Log.warn(`WARNING! Your config for module ${element.module} is using deprecated option(s): ${usedDeprecatedModuleOptions.join(", ")}. Check README and CHANGELOG for more up-to-date ways of getting the same functionality.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,7 +189,7 @@ function App () {
|
||||
moduleFolder = defaultModuleFolder;
|
||||
} else {
|
||||
// running in Jest, allow defaultModules placed under moduleDir for testing
|
||||
if (env.modulesDir === "modules") {
|
||||
if (env.modulesDir === "modules" || env.modulesDir === "tests/mocks") {
|
||||
moduleFolder = defaultModuleFolder;
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
module.exports = {
|
||||
configs: ["kioskmode"]
|
||||
configs: ["kioskmode"],
|
||||
clock: ["secondsColor"]
|
||||
};
|
||||
|
13
js/utils.js
13
js/utils.js
@ -19,15 +19,16 @@ module.exports = {
|
||||
let installedNodeVersion = execSync("node -v", { encoding: "utf-8" }).replace("v", "").replace(/(?:\r\n|\r|\n)/g, "");
|
||||
|
||||
const staticData = await si.get({
|
||||
system: "manufacturer, model, raspberry, virtual",
|
||||
system: "manufacturer, model, virtual",
|
||||
osInfo: "platform, distro, release, arch",
|
||||
versions: "kernel, node, npm, pm2"
|
||||
});
|
||||
let systemDataString = "System information:";
|
||||
systemDataString += `\n### SYSTEM: manufacturer: ${staticData.system.manufacturer}; model: ${staticData.system.model}; virtual: ${staticData.system.virtual}`;
|
||||
systemDataString += `\n### OS: platform: ${staticData.osInfo.platform}; distro: ${staticData.osInfo.distro}; release: ${staticData.osInfo.release}; arch: ${staticData.osInfo.arch}; kernel: ${staticData.versions.kernel}`;
|
||||
systemDataString += `\n### VERSIONS: electron: ${process.versions.electron}; used node: ${staticData.versions.node}; installed node: ${installedNodeVersion}; npm: ${staticData.versions.npm}; pm2: ${staticData.versions.pm2}`;
|
||||
systemDataString += `\n### OTHER: timeZone: ${Intl.DateTimeFormat().resolvedOptions().timeZone}; ELECTRON_ENABLE_GPU: ${process.env.ELECTRON_ENABLE_GPU}`;
|
||||
let systemDataString = `System information:
|
||||
### SYSTEM: manufacturer: ${staticData.system.manufacturer}; model: ${staticData.system.model}; virtual: ${staticData.system.virtual}
|
||||
### OS: platform: ${staticData.osInfo.platform}; distro: ${staticData.osInfo.distro}; release: ${staticData.osInfo.release}; arch: ${staticData.osInfo.arch}; kernel: ${staticData.versions.kernel}
|
||||
### VERSIONS: electron: ${process.versions.electron}; used node: ${staticData.versions.node}; installed node: ${installedNodeVersion}; npm: ${staticData.versions.npm}; pm2: ${staticData.versions.pm2}
|
||||
### OTHER: timeZone: ${Intl.DateTimeFormat().resolvedOptions().timeZone}; ELECTRON_ENABLE_GPU: ${process.env.ELECTRON_ENABLE_GPU}`
|
||||
.replace(/\t/g, "");
|
||||
Log.info(systemDataString);
|
||||
|
||||
// Return is currently only for jest
|
||||
|
@ -25,6 +25,7 @@ Module.register("alert", {
|
||||
da: "translations/da.json",
|
||||
de: "translations/de.json",
|
||||
en: "translations/en.json",
|
||||
eo: "translations/eo.json",
|
||||
es: "translations/es.json",
|
||||
fr: "translations/fr.json",
|
||||
hu: "translations/hu.json",
|
||||
|
4
modules/default/alert/translations/el.json
Normal file
4
modules/default/alert/translations/el.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror² Ειδοποίηση",
|
||||
"welcome": "Καλώς ήρθατε, η εκκίνηση ήταν επιτυχής!"
|
||||
}
|
4
modules/default/alert/translations/eo.json
Normal file
4
modules/default/alert/translations/eo.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror²-sciigo",
|
||||
"welcome": "Bonvenon, lanĉo sukcesis!"
|
||||
}
|
@ -689,12 +689,17 @@ Module.register("calendar", {
|
||||
by_url_calevents.push(event);
|
||||
}
|
||||
}
|
||||
by_url_calevents.sort(function (a, b) {
|
||||
return a.startDate - b.startDate;
|
||||
});
|
||||
Log.debug(`pushing ${by_url_calevents.length} events to total with room for ${remainingEntries}`);
|
||||
events = events.concat(by_url_calevents.slice(0, remainingEntries));
|
||||
Log.debug(`events for calendar=${events.length}`);
|
||||
if (limitNumberOfEntries) {
|
||||
// sort entries before clipping
|
||||
by_url_calevents.sort(function (a, b) {
|
||||
return a.startDate - b.startDate;
|
||||
});
|
||||
Log.debug(`pushing ${by_url_calevents.length} events to total with room for ${remainingEntries}`);
|
||||
events = events.concat(by_url_calevents.slice(0, remainingEntries));
|
||||
Log.debug(`events for calendar=${events.length}`);
|
||||
} else {
|
||||
events = events.concat(by_url_calevents);
|
||||
}
|
||||
}
|
||||
Log.info(`sorting events count=${events.length}`);
|
||||
events.sort(function (a, b) {
|
||||
@ -907,7 +912,11 @@ Module.register("calendar", {
|
||||
let p = this.getCalendarProperty(url, property, defaultValue);
|
||||
if (property === "symbol" || property === "recurringSymbol" || property === "fullDaySymbol") {
|
||||
const className = this.getCalendarProperty(url, "symbolClassName", this.config.defaultSymbolClassName);
|
||||
if (p instanceof Array) p.push(className);
|
||||
if (p instanceof Array) {
|
||||
let t = [];
|
||||
p.forEach((n) => { t.push(className + n); });
|
||||
p = t;
|
||||
}
|
||||
else p = className + p;
|
||||
}
|
||||
if (!(p instanceof Array)) p = [p];
|
||||
|
@ -662,9 +662,11 @@ const CalendarFetcherUtils = {
|
||||
Log.debug("signs are the same");
|
||||
if (Math.sign(eventDiff) === -1) {
|
||||
//if west, looking at more west
|
||||
// -350 <-300
|
||||
if (nowDiff < eventDiff) {
|
||||
//-600 -420
|
||||
eventDiff = -(eventDiff - (nowDiff - eventDiff)); //-180
|
||||
//300 -300 -360 +300
|
||||
eventDiff = nowDiff - eventDiff; //-180
|
||||
Log.debug("now looking back east delta diff=", eventDiff);
|
||||
}
|
||||
else {
|
||||
|
@ -23,7 +23,7 @@ Module.register("clock", {
|
||||
analogFace: "simple", // options: 'none', 'simple', 'face-###' (where ### is 001 to 012 inclusive)
|
||||
analogPlacement: "bottom", // options: 'top', 'bottom', 'left', 'right'
|
||||
analogShowDate: "top", // OBSOLETE, can be replaced with analogPlacement and showTime, options: false, 'top', or 'bottom'
|
||||
secondsColor: "#888888",
|
||||
secondsColor: "#888888", // DEPRECATED, use CSS instead. Class "clock-second-digital" for digital clock, "clock-second" for analog clock.
|
||||
|
||||
showSunTimes: false,
|
||||
showMoonTimes: false, // options: false, 'times' (rise/set), 'percent' (lit percent), 'phase' (current phase), or 'both' (percent & phase)
|
||||
@ -105,6 +105,8 @@ Module.register("clock", {
|
||||
*/
|
||||
const dateWrapper = document.createElement("div");
|
||||
const timeWrapper = document.createElement("div");
|
||||
const hoursWrapper = document.createElement("span");
|
||||
const minutesWrapper = document.createElement("span");
|
||||
const secondsWrapper = document.createElement("sup");
|
||||
const periodWrapper = document.createElement("span");
|
||||
const sunWrapper = document.createElement("div");
|
||||
@ -114,39 +116,40 @@ Module.register("clock", {
|
||||
// Style Wrappers
|
||||
dateWrapper.className = "date normal medium";
|
||||
timeWrapper.className = "time bright large light";
|
||||
secondsWrapper.className = "seconds dimmed";
|
||||
hoursWrapper.className = "clock-hour-digital";
|
||||
minutesWrapper.className = "clock-minute-digital";
|
||||
secondsWrapper.className = "clock-second-digital dimmed";
|
||||
sunWrapper.className = "sun dimmed small";
|
||||
moonWrapper.className = "moon dimmed small";
|
||||
weekWrapper.className = "week dimmed medium";
|
||||
|
||||
// Set content of wrappers.
|
||||
// The moment().format("h") method has a bug on the Raspberry Pi.
|
||||
// So we need to generate the timestring manually.
|
||||
// See issue: https://github.com/MagicMirrorOrg/MagicMirror/issues/181
|
||||
let timeString;
|
||||
const now = moment();
|
||||
if (this.config.timezone) {
|
||||
now.tz(this.config.timezone);
|
||||
}
|
||||
|
||||
let hourSymbol = "HH";
|
||||
if (this.config.timeFormat !== 24) {
|
||||
hourSymbol = "h";
|
||||
}
|
||||
|
||||
if (this.config.clockBold) {
|
||||
timeString = now.format(`${hourSymbol}[<span class="bold">]mm[</span>]`);
|
||||
} else {
|
||||
timeString = now.format(`${hourSymbol}:mm`);
|
||||
}
|
||||
|
||||
if (this.config.showDate) {
|
||||
dateWrapper.innerHTML = now.format(this.config.dateFormat);
|
||||
digitalWrapper.appendChild(dateWrapper);
|
||||
}
|
||||
|
||||
if (this.config.displayType !== "analog" && this.config.showTime) {
|
||||
timeWrapper.innerHTML = timeString;
|
||||
let hourSymbol = "HH";
|
||||
if (this.config.timeFormat !== 24) {
|
||||
hourSymbol = "h";
|
||||
}
|
||||
|
||||
hoursWrapper.innerHTML = now.format(hourSymbol);
|
||||
minutesWrapper.innerHTML = now.format("mm");
|
||||
|
||||
timeWrapper.appendChild(hoursWrapper);
|
||||
if (this.config.clockBold) {
|
||||
minutesWrapper.classList.add("bold");
|
||||
} else {
|
||||
timeWrapper.innerHTML += ":";
|
||||
}
|
||||
timeWrapper.appendChild(minutesWrapper);
|
||||
secondsWrapper.innerHTML = now.format("ss");
|
||||
if (this.config.showPeriodUpper) {
|
||||
periodWrapper.innerHTML = now.format("A");
|
||||
@ -181,8 +184,8 @@ Module.register("clock", {
|
||||
const untilNextEventString = `${untilNextEvent.hours()}h ${untilNextEvent.minutes()}m`;
|
||||
sunWrapper.innerHTML
|
||||
= `<span class="${isVisible ? "bright" : ""}"><i class="fas fa-sun" aria-hidden="true"></i> ${untilNextEventString}</span>`
|
||||
+ `<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunrise)}</span>`
|
||||
+ `<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunset)}</span>`;
|
||||
+ `<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunrise)}</span>`
|
||||
+ `<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunset)}</span>`;
|
||||
digitalWrapper.appendChild(sunWrapper);
|
||||
}
|
||||
|
||||
@ -208,8 +211,8 @@ Module.register("clock", {
|
||||
|
||||
moonWrapper.innerHTML
|
||||
= `<span class="${isVisible ? "bright" : ""}">${image} ${showFraction ? illuminatedFractionString : ""}</span>`
|
||||
+ `<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ${moonRise ? formatTime(this.config, moonRise) : "..."}</span>`
|
||||
+ `<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ${moonSet ? formatTime(this.config, moonSet) : "..."}</span>`;
|
||||
+ `<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ${moonRise ? formatTime(this.config, moonRise) : "..."}</span>`
|
||||
+ `<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ${moonSet ? formatTime(this.config, moonSet) : "..."}</span>`;
|
||||
digitalWrapper.appendChild(moonWrapper);
|
||||
}
|
||||
|
||||
@ -267,7 +270,7 @@ Module.register("clock", {
|
||||
clockSecond.id = "clock-second";
|
||||
clockSecond.style.transform = `rotate(${second}deg)`;
|
||||
clockSecond.className = "clock-second";
|
||||
clockSecond.style.backgroundColor = this.config.secondsColor;
|
||||
clockSecond.style.backgroundColor = this.config.secondsColor; /* DEPRECATED, to be removed in a future version , use CSS instead */
|
||||
clockFace.appendChild(clockSecond);
|
||||
}
|
||||
analogWrapper.appendChild(clockFace);
|
||||
|
@ -78,7 +78,12 @@
|
||||
left: 50%;
|
||||
margin: -38% -1px 0 0; /* numbers must match negative length & thickness */
|
||||
padding: 38% 1px 0 0; /* indicator length & thickness */
|
||||
background: var(--color-text);
|
||||
|
||||
/* background: #888888 !important; */
|
||||
|
||||
/* use this instead of secondsColor */
|
||||
|
||||
/* have to use !important, because the code explicitly sets the color currently */
|
||||
transform-origin: 50% 100%;
|
||||
}
|
||||
|
||||
@ -91,3 +96,15 @@
|
||||
.module.clock .moon > * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.module.clock .clock-hour-digital {
|
||||
color: var(--color-text-bright);
|
||||
}
|
||||
|
||||
.module.clock .clock-minute-digital {
|
||||
color: var(--color-text-bright);
|
||||
}
|
||||
|
||||
.module.clock .clock-second-digital {
|
||||
color: var(--color-text-dimmed);
|
||||
}
|
||||
|
@ -139,12 +139,17 @@ Module.register("compliments", {
|
||||
let compliments = [];
|
||||
|
||||
// Add time of day compliments
|
||||
if (hour >= this.config.morningStartTime && hour < this.config.morningEndTime && this.config.compliments.hasOwnProperty("morning")) {
|
||||
compliments = [...this.config.compliments.morning];
|
||||
} else if (hour >= this.config.afternoonStartTime && hour < this.config.afternoonEndTime && this.config.compliments.hasOwnProperty("afternoon")) {
|
||||
compliments = [...this.config.compliments.afternoon];
|
||||
} else if (this.config.compliments.hasOwnProperty("evening")) {
|
||||
compliments = [...this.config.compliments.evening];
|
||||
let timeOfDay;
|
||||
if (hour >= this.config.morningStartTime && hour < this.config.morningEndTime) {
|
||||
timeOfDay = "morning";
|
||||
} else if (hour >= this.config.afternoonStartTime && hour < this.config.afternoonEndTime) {
|
||||
timeOfDay = "afternoon";
|
||||
} else {
|
||||
timeOfDay = "evening";
|
||||
}
|
||||
|
||||
if (timeOfDay && this.config.compliments.hasOwnProperty(timeOfDay)) {
|
||||
compliments = [...this.config.compliments[timeOfDay]];
|
||||
}
|
||||
|
||||
// Add compliments based on weather
|
||||
|
@ -57,7 +57,7 @@ Module.register("newsfeed", {
|
||||
// Define required translations.
|
||||
getTranslations () {
|
||||
// The translations for the default modules are defined in the core translation files.
|
||||
// Therefor we can just return false. Otherwise we should have returned a dictionary.
|
||||
// Therefore we can just return false. Otherwise we should have returned a dictionary.
|
||||
// If you're trying to build your own module including translations, check out the documentation.
|
||||
return false;
|
||||
},
|
||||
@ -177,6 +177,18 @@ Module.register("newsfeed", {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a feed property by name
|
||||
* @param {object} feed A feed object.
|
||||
* @param {string} property The name of the property.
|
||||
*/
|
||||
getFeedProperty (feed, property) {
|
||||
let res = this.config[property];
|
||||
const f = this.config.feeds.find((feedItem) => feedItem.url === feed);
|
||||
if (f && f[property]) res = f[property];
|
||||
return res;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate an ordered list of items for this configured module.
|
||||
* @param {object} feeds An object with feeds returned by the node helper.
|
||||
@ -188,7 +200,7 @@ Module.register("newsfeed", {
|
||||
if (this.subscribedToFeed(feed)) {
|
||||
for (let item of feedItems) {
|
||||
item.sourceTitle = this.titleForFeed(feed);
|
||||
if (!(this.config.ignoreOldItems && Date.now() - new Date(item.pubdate) > this.config.ignoreOlderThan)) {
|
||||
if (!(this.getFeedProperty(feed, "ignoreOldItems") && Date.now() - new Date(item.pubdate) > this.getFeedProperty(feed, "ignoreOlderThan"))) {
|
||||
newsItems.push(item);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const NodeHelper = require("node_helper");
|
||||
const defaultModules = require("../defaultmodules");
|
||||
const GitHelper = require("./git_helper");
|
||||
@ -14,8 +16,23 @@ module.exports = NodeHelper.create({
|
||||
gitHelper: new GitHelper(),
|
||||
updateHelper: null,
|
||||
|
||||
getModules (modules) {
|
||||
if (this.config.useModulesFromConfig) {
|
||||
return modules;
|
||||
} else {
|
||||
// get modules from modules-directory
|
||||
const moduleDir = path.normalize(`${__dirname}/../../`);
|
||||
const getDirectories = (source) => {
|
||||
return fs.readdirSync(source, { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory() && dirent.name !== "default")
|
||||
.map((dirent) => dirent.name);
|
||||
};
|
||||
return getDirectories(moduleDir);
|
||||
}
|
||||
},
|
||||
|
||||
async configureModules (modules) {
|
||||
for (const moduleName of modules) {
|
||||
for (const moduleName of this.getModules(modules)) {
|
||||
if (!this.ignoreUpdateChecking(moduleName)) {
|
||||
await this.gitHelper.add(moduleName);
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ Module.register("updatenotification", {
|
||||
sendUpdatesNotifications: false,
|
||||
updates: [],
|
||||
updateTimeout: 2 * 60 * 1000, // max update duration
|
||||
updateAutorestart: false // autoRestart MM when update done ?
|
||||
updateAutorestart: false, // autoRestart MM when update done ?
|
||||
useModulesFromConfig: true // if `false` iterate over modules directory
|
||||
},
|
||||
|
||||
suspended: false,
|
||||
|
@ -21,15 +21,25 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% if config.showHumidity != "none" %}
|
||||
<td class="align-left bright humidity-hourly">
|
||||
{{ hour.humidity }}
|
||||
<span class="wi wi-humidity humidity-icon"></span>
|
||||
</td>
|
||||
{% endif %}
|
||||
{% if config.showPrecipitationAmount %}
|
||||
<td class="align-right bright precipitation-amount">
|
||||
{{ hour.precipitationAmount | unit("precip", hour.precipitationUnits) }}
|
||||
</td>
|
||||
{% if (not config.hideZeroes or hour.precipitationAmount>0) %}
|
||||
<td class="align-right bright precipitation-amount">
|
||||
{{ hour.precipitationAmount | unit("precip", hour.precipitationUnits) }}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if config.showPrecipitationProbability %}
|
||||
{% if (not config.hideZeroes or hour.precipitationAmount>0) %}
|
||||
<td class="align-right bright precipitation-prob">
|
||||
{{ hour.precipitationProbability | unit('precip', '%') }}
|
||||
{{ hour.precipitationProbability | unit('precip', '%') }}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% set currentStep = currentStep + 1 %}
|
||||
|
@ -470,7 +470,7 @@ WeatherProvider.register("openmeteo", {
|
||||
61: "rain-slight-intensity",
|
||||
63: "rain-moderate-intensity",
|
||||
65: "rain-heavy-intensity",
|
||||
66: "freezing-rain-light-heavy-intensity",
|
||||
66: "freezing-rain-light-intensity",
|
||||
67: "freezing-rain-heavy-intensity",
|
||||
71: "snow-fall-slight-intensity",
|
||||
73: "snow-fall-moderate-intensity",
|
||||
|
@ -25,14 +25,20 @@ WeatherProvider.register("weatherflow", {
|
||||
const currentWeather = new WeatherObject();
|
||||
currentWeather.date = moment();
|
||||
|
||||
// Other available values: air_density, brightness, delta_t, dew_point,
|
||||
// pressure_trend (i.e. rising/falling), sea_level_pressure, wind gust, and more.
|
||||
|
||||
currentWeather.humidity = data.current_conditions.relative_humidity;
|
||||
currentWeather.temperature = data.current_conditions.air_temperature;
|
||||
currentWeather.feelsLikeTemp = data.current_conditions.feels_like;
|
||||
currentWeather.windSpeed = WeatherUtils.convertWindToMs(data.current_conditions.wind_avg);
|
||||
currentWeather.windFromDirection = data.current_conditions.wind_direction;
|
||||
currentWeather.weatherType = data.forecast.daily[0].icon;
|
||||
currentWeather.weatherType = this.convertWeatherType(data.current_conditions.icon);
|
||||
currentWeather.uv_index = data.current_conditions.uv;
|
||||
currentWeather.sunrise = moment.unix(data.forecast.daily[0].sunrise);
|
||||
currentWeather.sunset = moment.unix(data.forecast.daily[0].sunset);
|
||||
this.setCurrentWeather(currentWeather);
|
||||
this.fetchedLocationName = data.location_name;
|
||||
})
|
||||
.catch(function (request) {
|
||||
Log.error("Could not load data ... ", request);
|
||||
@ -52,13 +58,27 @@ WeatherProvider.register("weatherflow", {
|
||||
weather.minTemperature = forecast.air_temp_low;
|
||||
weather.maxTemperature = forecast.air_temp_high;
|
||||
weather.precipitationProbability = forecast.precip_probability;
|
||||
weather.weatherType = forecast.icon;
|
||||
weather.snow = 0;
|
||||
weather.weatherType = this.convertWeatherType(forecast.icon);
|
||||
|
||||
// Must manually build UV and Precipitation from hourly
|
||||
weather.precipitationAmount = 0.0; // This will sum up rain and snow
|
||||
weather.precipitationUnits = "mm";
|
||||
weather.uv_index = 0;
|
||||
|
||||
for (const hour of data.forecast.hourly) {
|
||||
const hour_time = moment.unix(hour.time);
|
||||
if (hour_time.day() === weather.date.day()) { // Iterate though until day is reached
|
||||
// Get data from today
|
||||
weather.uv_index = Math.max(weather.uv_index, hour.uv);
|
||||
weather.precipitationAmount += (hour.precip ?? 0);
|
||||
} else if (hour_time.diff(weather.date) >= 86400) {
|
||||
break; // No more data to be found
|
||||
}
|
||||
}
|
||||
days.push(weather);
|
||||
}
|
||||
|
||||
this.setWeatherForecast(days);
|
||||
this.fetchedLocationName = data.location_name;
|
||||
})
|
||||
.catch(function (request) {
|
||||
Log.error("Could not load data ... ", request);
|
||||
@ -66,6 +86,63 @@ WeatherProvider.register("weatherflow", {
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
fetchWeatherHourly () {
|
||||
this.fetchData(this.getUrl())
|
||||
.then((data) => {
|
||||
const hours = [];
|
||||
for (const hour of data.forecast.hourly) {
|
||||
const weather = new WeatherObject();
|
||||
|
||||
weather.date = moment.unix(hour.time);
|
||||
weather.temperature = hour.air_temperature;
|
||||
weather.feelsLikeTemp = hour.feels_like;
|
||||
weather.humidity = hour.relative_humidity;
|
||||
weather.windSpeed = hour.wind_avg;
|
||||
weather.windFromDirection = hour.wind_direction;
|
||||
weather.weatherType = this.convertWeatherType(hour.icon);
|
||||
weather.precipitationProbability = hour.precip_probability;
|
||||
weather.precipitationAmount = hour.precip; // NOTE: precipitation type is available
|
||||
weather.precipitationUnits = "mm"; // Hardcoded via request, TODO: Add conversion
|
||||
weather.uv_index = hour.uv;
|
||||
|
||||
hours.push(weather);
|
||||
if (hours.length >= 48) break; // 10 days of hours are available, best to trim down.
|
||||
}
|
||||
this.setWeatherHourly(hours);
|
||||
this.fetchedLocationName = data.location_name;
|
||||
})
|
||||
.catch(function (request) {
|
||||
Log.error("Could not load data ... ", request);
|
||||
})
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
convertWeatherType (weatherType) {
|
||||
const weatherTypes = {
|
||||
"clear-day": "day-sunny",
|
||||
"clear-night": "night-clear",
|
||||
cloudy: "cloudy",
|
||||
foggy: "fog",
|
||||
"partly-cloudy-day": "day-cloudy",
|
||||
"partly-cloudy-night": "night-alt-cloudy",
|
||||
"possibly-rainy-day": "day-rain",
|
||||
"possibly-rainy-night": "night-alt-rain",
|
||||
"possibly-sleet-day": "day-sleet",
|
||||
"possibly-sleet-night": "night-alt-sleet",
|
||||
"possibly-snow-day": "day-snow",
|
||||
"possibly-snow-night": "night-alt-snow",
|
||||
"possibly-thunderstorm-day": "day-thunderstorm",
|
||||
"possibly-thunderstorm-night": "night-alt-thunderstorm",
|
||||
rainy: "rain",
|
||||
sleet: "sleet",
|
||||
snow: "snow",
|
||||
thunderstorm: "thunderstorm",
|
||||
windy: "strong-wind"
|
||||
};
|
||||
|
||||
return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
|
||||
},
|
||||
|
||||
// Create a URL from the config and base URL.
|
||||
getUrl () {
|
||||
return `${this.config.apiBase}better_forecast?station_id=${this.config.stationid}&units_temp=c&units_wind=kph&units_pressure=mb&units_precip=mm&units_distance=km&token=${this.config.token}`;
|
||||
|
@ -23,6 +23,10 @@ WeatherProvider.register("yr", {
|
||||
Log.error("The Yr weather provider requires local storage.");
|
||||
throw new Error("Local storage not available");
|
||||
}
|
||||
if (this.config.updateInterval < 600000) {
|
||||
Log.warn("The Yr weather provider requires a minimum update interval of 10 minutes (600 000 ms). The configuration has been adjusted to meet this requirement.");
|
||||
this.delegate.config.updateInterval = 600000;
|
||||
}
|
||||
Log.info(`Weather provider: ${this.providerName} started.`);
|
||||
},
|
||||
|
||||
@ -34,7 +38,7 @@ WeatherProvider.register("yr", {
|
||||
})
|
||||
.catch((error) => {
|
||||
Log.error(error);
|
||||
throw new Error(error);
|
||||
this.updateAvailable();
|
||||
});
|
||||
},
|
||||
|
||||
@ -119,7 +123,12 @@ WeatherProvider.register("yr", {
|
||||
})
|
||||
.catch((err) => {
|
||||
Log.error(err);
|
||||
reject("Unable to get weather data from Yr.");
|
||||
if (weatherData) {
|
||||
Log.warn("Using outdated cached weather data.");
|
||||
resolve(weatherData);
|
||||
} else {
|
||||
reject("Unable to get weather data from Yr.");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
localStorage.removeItem("yrIsFetchingWeatherData");
|
||||
@ -497,7 +506,7 @@ WeatherProvider.register("yr", {
|
||||
})
|
||||
.catch((error) => {
|
||||
Log.error(error);
|
||||
throw new Error(error);
|
||||
this.updateAvailable();
|
||||
});
|
||||
},
|
||||
|
||||
@ -530,7 +539,15 @@ WeatherProvider.register("yr", {
|
||||
getHourlyForecastFrom (weatherData) {
|
||||
const series = [];
|
||||
|
||||
const now = moment({
|
||||
year: moment().year(),
|
||||
month: moment().month(),
|
||||
day: moment().date(),
|
||||
hour: moment().hour()
|
||||
});
|
||||
for (const forecast of weatherData.properties.timeseries) {
|
||||
if (now.isAfter(moment(forecast.time))) continue;
|
||||
|
||||
forecast.symbol = forecast.data.next_1_hours?.summary?.symbol_code;
|
||||
forecast.precipitationAmount = forecast.data.next_1_hours?.details?.precipitation_amount;
|
||||
forecast.precipitationProbability = forecast.data.next_1_hours?.details?.probability_of_precipitation;
|
||||
@ -600,7 +617,7 @@ WeatherProvider.register("yr", {
|
||||
})
|
||||
.catch((error) => {
|
||||
Log.error(error);
|
||||
throw new Error(error);
|
||||
this.updateAvailable();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -31,6 +31,7 @@
|
||||
|
||||
.weather .precipitation-amount,
|
||||
.weather .precipitation-prob,
|
||||
.weather .humidity-hourly,
|
||||
.weather .uv-index {
|
||||
padding-left: 20px;
|
||||
padding-right: 0;
|
||||
|
@ -14,7 +14,8 @@ Module.register("weather", {
|
||||
updateInterval: 10 * 60 * 1000, // every 10 minutes
|
||||
animationSpeed: 1000,
|
||||
showFeelsLike: true,
|
||||
showHumidity: "none", // this is now a string; see current.njk
|
||||
showHumidity: "none", // possible options for "current" weather are "none", "wind", "temp", "feelslike" or "below", for "hourly" weather "none" or "true"
|
||||
hideZeroes: false, // hide zeroes (and empty columns) in hourly, currently only for precipitation
|
||||
showIndoorHumidity: false,
|
||||
showIndoorTemperature: false,
|
||||
allowOverrideNotification: false,
|
||||
|
@ -128,14 +128,14 @@ const WeatherUtils = {
|
||||
} else if (tempInF > 80 && humidity > 40) {
|
||||
feelsLike
|
||||
= -42.379
|
||||
+ 2.04901523 * tempInF
|
||||
+ 10.14333127 * humidity
|
||||
- 0.22475541 * tempInF * humidity
|
||||
- 6.83783 * Math.pow(10, -3) * tempInF * tempInF
|
||||
- 5.481717 * Math.pow(10, -2) * humidity * humidity
|
||||
+ 1.22874 * Math.pow(10, -3) * tempInF * tempInF * humidity
|
||||
+ 8.5282 * Math.pow(10, -4) * tempInF * humidity * humidity
|
||||
- 1.99 * Math.pow(10, -6) * tempInF * tempInF * humidity * humidity;
|
||||
+ 2.04901523 * tempInF
|
||||
+ 10.14333127 * humidity
|
||||
- 0.22475541 * tempInF * humidity
|
||||
- 6.83783 * Math.pow(10, -3) * tempInF * tempInF
|
||||
- 5.481717 * Math.pow(10, -2) * humidity * humidity
|
||||
+ 1.22874 * Math.pow(10, -3) * tempInF * tempInF * humidity
|
||||
+ 8.5282 * Math.pow(10, -4) * tempInF * humidity * humidity
|
||||
- 1.99 * Math.pow(10, -6) * tempInF * tempInF * humidity * humidity;
|
||||
}
|
||||
|
||||
return ((feelsLike - 32) * 5) / 9;
|
||||
|
2173
package-lock.json
generated
2173
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
50
package.json
50
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "magicmirror",
|
||||
"version": "2.30.0",
|
||||
"version": "2.31.0",
|
||||
"description": "The open source modular smart mirror platform.",
|
||||
"keywords": [
|
||||
"magic mirror",
|
||||
@ -30,7 +30,7 @@
|
||||
"install-mm:dev": "npm install --no-audit --no-fund --no-update-notifier",
|
||||
"install-vendor": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error --no-audit --no-fund --no-update-notifier",
|
||||
"lint:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json --fix",
|
||||
"lint:js": "eslint . --fix",
|
||||
"lint:js": "eslint --fix",
|
||||
"lint:markdown": "markdownlint-cli2 . --fix",
|
||||
"lint:prettier": "prettier . --write",
|
||||
"postinstall": "npm run install-vendor && npm run install-fonts && echo \"MagicMirror² installation finished successfully! \n\"",
|
||||
@ -43,14 +43,14 @@
|
||||
"start:windows": ".\\node_modules\\.bin\\electron js\\electron.js",
|
||||
"start:windows:dev": "npm run start:windows -- dev",
|
||||
"start:x11": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js",
|
||||
"start:x11:dev": "npm run start -- dev",
|
||||
"start:x11:dev": "npm run start:x11 -- dev",
|
||||
"test": "NODE_ENV=test jest -i --forceExit",
|
||||
"test:calendar": "node ./modules/default/calendar/debug.js",
|
||||
"test:coverage": "NODE_ENV=test jest --coverage -i --verbose false --forceExit",
|
||||
"test:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json",
|
||||
"test:e2e": "NODE_ENV=test jest --selectProjects e2e -i --forceExit",
|
||||
"test:electron": "NODE_ENV=test jest --selectProjects electron -i --forceExit",
|
||||
"test:js": "eslint .",
|
||||
"test:js": "eslint",
|
||||
"test:markdown": "markdownlint-cli2 .",
|
||||
"test:prettier": "prettier . --check",
|
||||
"test:spelling": "cspell . --gitignore",
|
||||
@ -63,14 +63,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": "^8.17.1",
|
||||
"ansis": "^3.5.2",
|
||||
"ansis": "^3.17.0",
|
||||
"console-stamp": "^3.1.2",
|
||||
"envsub": "^4.1.0",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint": "^9.23.0",
|
||||
"express": "^4.21.2",
|
||||
"express-ipfilter": "^1.3.2",
|
||||
"feedme": "^2.0.2",
|
||||
"helmet": "^8.0.0",
|
||||
"helmet": "^8.1.0",
|
||||
"html-to-text": "^9.0.5",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"module-alias": "^2.2.3",
|
||||
@ -79,34 +79,34 @@
|
||||
"pm2": "^5.4.3",
|
||||
"socket.io": "^4.8.1",
|
||||
"suncalc": "^1.9.0",
|
||||
"systeminformation": "^5.24.3",
|
||||
"undici": "^7.2.0"
|
||||
"systeminformation": "^5.25.11",
|
||||
"undici": "^7.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@stylistic/eslint-plugin": "^2.12.1",
|
||||
"cspell": "^8.17.1",
|
||||
"@stylistic/eslint-plugin": "^4.2.0",
|
||||
"cspell": "^8.18.1",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jest": "^28.10.0",
|
||||
"eslint-plugin-jsdoc": "^50.6.1",
|
||||
"eslint-plugin-package-json": "^0.19.0",
|
||||
"eslint-plugin-jest": "^28.11.0",
|
||||
"eslint-plugin-jsdoc": "^50.6.9",
|
||||
"eslint-plugin-package-json": "^0.29.0",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.7.0",
|
||||
"jsdom": "^25.0.1",
|
||||
"lint-staged": "^15.3.0",
|
||||
"markdownlint-cli2": "^0.17.1",
|
||||
"playwright": "^1.49.1",
|
||||
"prettier": "^3.4.2",
|
||||
"sinon": "^19.0.2",
|
||||
"stylelint": "^16.12.0",
|
||||
"stylelint-config-standard": "^36.0.1",
|
||||
"stylelint-prettier": "^5.0.2"
|
||||
"jsdom": "^26.0.0",
|
||||
"lint-staged": "^15.5.0",
|
||||
"markdownlint-cli2": "^0.17.2",
|
||||
"playwright": "^1.51.1",
|
||||
"prettier": "^3.5.3",
|
||||
"sinon": "^20.0.0",
|
||||
"stylelint": "^16.17.0",
|
||||
"stylelint-config-standard": "^37.0.0",
|
||||
"stylelint-prettier": "^5.0.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"electron": "^32.2.7"
|
||||
"electron": "^35.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.18.1 <21 || >=22"
|
||||
"node": ">=22.14.0"
|
||||
},
|
||||
"_moduleAliases": {
|
||||
"node_helper": "js/node_helper.js",
|
||||
|
@ -4,5 +4,5 @@ const Log = require("../js/logger");
|
||||
app.start().then((config) => {
|
||||
const bindAddress = config.address ? config.address : "localhost";
|
||||
const httpType = config.useHttps ? "https" : "http";
|
||||
Log.info(`\n>>> Ready to go! Please point your browser to: ${httpType}://${bindAddress}:${config.port} <<<`);
|
||||
Log.info(`\n>>> Ready to go! Please point your browser to: ${httpType}://${bindAddress}:${global.mmPort || config.port} <<<`);
|
||||
});
|
||||
|
@ -0,0 +1,33 @@
|
||||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
|
||||
timeFormat: 24,
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
fade: false,
|
||||
urgency: 0,
|
||||
dateFormat: "Do.MMM, HH:mm",
|
||||
fullDayEventDateFormat: "Do.MMM",
|
||||
timeFormat: "absolute",
|
||||
getRelative: 0,
|
||||
maximumNumberOfDays: 28,
|
||||
showEnd: true,
|
||||
calendars: [
|
||||
{
|
||||
maximumEntries: 100,
|
||||
url: "http://localhost:8080/tests/mocks/chicago-nyedge.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
39
tests/configs/modules/calendar/countCalendarEvents.js
Normal file
39
tests/configs/modules/calendar/countCalendarEvents.js
Normal file
@ -0,0 +1,39 @@
|
||||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
foreignModulesDir: "tests/mocks",
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
|
||||
config: {
|
||||
maximumEntries: 1,
|
||||
calendars: [
|
||||
{
|
||||
fetchInterval: 10000, //7 * 24 * 60 * 60 * 1000,
|
||||
symbol: ["calendar-check", "google"],
|
||||
url: "http://localhost:8080/tests/mocks/12_events.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
module: "testNotification",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
debug: true,
|
||||
match: {
|
||||
matchtype: "count",
|
||||
notificationID: "CALENDAR_EVENTS"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
28
tests/configs/modules/calendar/symboltest.js
Normal file
28
tests/configs/modules/calendar/symboltest.js
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
maximumEntries: 1,
|
||||
calendars: [
|
||||
{
|
||||
fetchInterval: 7 * 24 * 60 * 60 * 1000,
|
||||
symbol: ["calendar-check", "google"],
|
||||
url: "https://ics.calendarlabs.com/76/mm3137/US_Holidays.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
22
tests/configs/modules/compliments/compliments_evening.js
Normal file
22
tests/configs/modules/compliments/compliments_evening.js
Normal file
@ -0,0 +1,22 @@
|
||||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
{
|
||||
module: "compliments",
|
||||
position: "middle_center",
|
||||
config: {
|
||||
compliments: {
|
||||
evening: ["Evening here"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
@ -38,6 +38,13 @@ describe("Clock module", () => {
|
||||
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/;
|
||||
await expect(helpers.testMatch(".clock .time", timeRegex)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it("check for discreet elements of clock", async () => {
|
||||
let elemClock = helpers.waitForElement(".clock-hour-digital");
|
||||
await expect(elemClock).not.toBeNull();
|
||||
elemClock = helpers.waitForElement(".clock-minute-digital");
|
||||
await expect(elemClock).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("with showPeriodUpper config enabled", () => {
|
||||
|
@ -19,7 +19,7 @@ describe("Electron app environment", () => {
|
||||
|
||||
describe("Development console tests", () => {
|
||||
beforeEach(async () => {
|
||||
await helpers.startApplication("tests/configs/modules/display.js", null, ["js/electron.js", "dev"]);
|
||||
await helpers.startApplication("tests/configs/modules/display.js", null, ["dev"]);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
@ -3,7 +3,7 @@
|
||||
// https://www.anycodings.com/1questions/958135/can-i-set-the-date-for-playwright-browser
|
||||
const { _electron: electron } = require("playwright");
|
||||
|
||||
exports.startApplication = async (configFilename, systemDate = null, electronParams = ["js/electron.js"], timezone = "GMT") => {
|
||||
exports.startApplication = async (configFilename, systemDate = null, electronParams = [], timezone = "GMT") => {
|
||||
global.electronApp = null;
|
||||
global.page = null;
|
||||
process.env.MM_CONFIG_FILE = configFilename;
|
||||
@ -13,6 +13,13 @@ exports.startApplication = async (configFilename, systemDate = null, electronPar
|
||||
}
|
||||
process.env.mmTestMode = "true";
|
||||
|
||||
// check environment for DISPLAY or WAYLAND_DISPLAY
|
||||
if (process.env.WAYLAND_DISPLAY) {
|
||||
electronParams.unshift("js/electron.js", "--enable-features=UseOzonePlatform", "--ozone-platform=wayland");
|
||||
} else {
|
||||
electronParams.unshift("js/electron.js");
|
||||
}
|
||||
|
||||
global.electronApp = await electron.launch({ args: electronParams });
|
||||
|
||||
await global.electronApp.firstWindow();
|
||||
@ -42,10 +49,10 @@ exports.stopApplication = async () => {
|
||||
process.env.MOCK_DATE = undefined;
|
||||
};
|
||||
|
||||
exports.getElement = async (selector) => {
|
||||
exports.getElement = async (selector, state = "visible") => {
|
||||
expect(global.page).not.toBeNull();
|
||||
let elem = global.page.locator(selector);
|
||||
await elem.waitFor();
|
||||
const elem = global.page.locator(selector);
|
||||
await elem.waitFor({ state: state });
|
||||
expect(elem).not.toBeNull();
|
||||
return elem;
|
||||
};
|
||||
|
@ -13,9 +13,9 @@ describe("Calendar module", () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const doTestCount = async () => {
|
||||
const doTestCount = async (locator = ".calendar .event") => {
|
||||
expect(global.page).not.toBeNull();
|
||||
const loc = await global.page.locator(".calendar .event");
|
||||
const loc = await global.page.locator(locator);
|
||||
const elem = loc.first();
|
||||
await elem.waitFor();
|
||||
expect(elem).not.toBeNull();
|
||||
@ -32,8 +32,8 @@ describe("Calendar module", () => {
|
||||
// uses playwright nth locator syntax
|
||||
const doTestTableContent = async (table_row, table_column, content, row = first) => {
|
||||
const elem = await global.page.locator(table_row);
|
||||
const date = await global.page.locator(table_column).locator(`nth=${row}`);
|
||||
await expect(date.textContent()).resolves.toContain(content);
|
||||
const column = await elem.locator(table_column).locator(`nth=${row}`);
|
||||
await expect(column.textContent()).resolves.toContain(content);
|
||||
return true;
|
||||
};
|
||||
|
||||
@ -81,7 +81,7 @@ describe("Calendar module", () => {
|
||||
*/
|
||||
describe("rrule", () => {
|
||||
it("Issue #3393 recurrence dates past rrule until date", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/rrule_until.js", "07 Mar 2024 10:38:00 GMT-07:00", ["js/electron.js"], "America/Los_Angeles");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/rrule_until.js", "07 Mar 2024 10:38:00 GMT-07:00", [], "America/Los_Angeles");
|
||||
await expect(doTestCount()).resolves.toBe(1);
|
||||
});
|
||||
});
|
||||
@ -98,20 +98,20 @@ describe("Calendar module", () => {
|
||||
*/
|
||||
describe("Exdate: LA crossover DST before midnight GMT", () => {
|
||||
it("LA crossover DST before midnight GMT should have 2 events", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_la_before_midnight.js", "19 Oct 2023 12:30:00 GMT-07:00", ["js/electron.js"], "America/Los_Angeles");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_la_before_midnight.js", "19 Oct 2023 12:30:00 GMT-07:00", [], "America/Los_Angeles");
|
||||
await expect(doTestCount()).resolves.toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Exdate: LA crossover DST at midnight GMT local STD", () => {
|
||||
it("LA crossover DST before midnight GMT should have 2 events", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_la_at_midnight_std.js", "19 Oct 2023 12:30:00 GMT-07:00", ["js/electron.js"], "America/Los_Angeles");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_la_at_midnight_std.js", "19 Oct 2023 12:30:00 GMT-07:00", [], "America/Los_Angeles");
|
||||
await expect(doTestCount()).resolves.toBe(2);
|
||||
});
|
||||
});
|
||||
describe("Exdate: LA crossover DST at midnight GMT local DST", () => {
|
||||
it("LA crossover DST before midnight GMT should have 2 events", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_la_at_midnight_dst.js", "19 Oct 2023 12:30:00 GMT-07:00", ["js/electron.js"], "America/Los_Angeles");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_la_at_midnight_dst.js", "19 Oct 2023 12:30:00 GMT-07:00", [], "America/Los_Angeles");
|
||||
await expect(doTestCount()).resolves.toBe(2);
|
||||
});
|
||||
});
|
||||
@ -128,19 +128,19 @@ describe("Calendar module", () => {
|
||||
*/
|
||||
describe("Exdate: SYD crossover DST before midnight GMT", () => {
|
||||
it("LA crossover DST before midnight GMT should have 2 events", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_syd_before_midnight.js", "14 Sep 2023 12:30:00 GMT+10:00", ["js/electron.js"], "Australia/Sydney");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_syd_before_midnight.js", "14 Sep 2023 12:30:00 GMT+10:00", [], "Australia/Sydney");
|
||||
await expect(doTestCount()).resolves.toBe(2);
|
||||
});
|
||||
});
|
||||
describe("Exdate: SYD crossover DST at midnight GMT local STD", () => {
|
||||
it("LA crossover DST before midnight GMT should have 2 events", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_syd_at_midnight_std.js", "14 Sep 2023 12:30:00 GMT+10:00", ["js/electron.js"], "Australia/Sydney");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_syd_at_midnight_std.js", "14 Sep 2023 12:30:00 GMT+10:00", [], "Australia/Sydney");
|
||||
await expect(doTestCount()).resolves.toBe(2);
|
||||
});
|
||||
});
|
||||
describe("Exdate: SYD crossover DST at midnight GMT local DST", () => {
|
||||
it("SYD crossover DST at midnight GMT local DST should have 2 events", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_syd_at_midnight_dst.js", "14 Sep 2023 12:30:00 GMT+10:00", ["js/electron.js"], "Australia/Sydney");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_syd_at_midnight_dst.js", "14 Sep 2023 12:30:00 GMT+10:00", [], "Australia/Sydney");
|
||||
await expect(doTestCount()).resolves.toBe(2);
|
||||
});
|
||||
});
|
||||
@ -151,7 +151,7 @@ describe("Calendar module", () => {
|
||||
*/
|
||||
describe("sliceMultiDayEvents", () => {
|
||||
it("Issue #3452 split multiday in Europe", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/sliceMultiDayEvents.js", "01 Sept 2024 10:38:00 GMT+02:00", ["js/electron.js"], "Europe/Berlin");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/sliceMultiDayEvents.js", "01 Sept 2024 10:38:00 GMT+02:00", [], "Europe/Berlin");
|
||||
expect(global.page).not.toBeNull();
|
||||
const loc = await global.page.locator(".calendar .event");
|
||||
const elem = loc.first();
|
||||
@ -164,56 +164,56 @@ describe("Calendar module", () => {
|
||||
|
||||
describe("sliceMultiDayEvents direct count", () => {
|
||||
it("Issue #3452 split multiday in Europe", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/sliceMultiDayEvents.js", "01 Sept 2024 10:38:00 GMT+02:00", ["js/electron.js"], "Europe/Berlin");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/sliceMultiDayEvents.js", "01 Sept 2024 10:38:00 GMT+02:00", [], "Europe/Berlin");
|
||||
await expect(doTestCount()).resolves.toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe("germany timezone", () => {
|
||||
it("Issue #unknown fullday timezone East of UTC edge", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/germany_at_end_of_day_repeating.js", "01 Oct 2024 10:38:00 GMT+02:00", ["js/electron.js"], "Europe/Berlin");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/germany_at_end_of_day_repeating.js", "01 Oct 2024 10:38:00 GMT+02:00", [], "Europe/Berlin");
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "Oct 22nd, 23:00", first)).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("germany all day repeating moved (recurrence and exdate)", () => {
|
||||
it("Issue #unknown fullday timezone East of UTC event moved", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/3_move_first_allday_repeating_event.js", "01 Oct 2024 10:38:00 GMT+02:00", ["js/electron.js"], "Europe/Berlin");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/3_move_first_allday_repeating_event.js", "01 Oct 2024 10:38:00 GMT+02:00", [], "Europe/Berlin");
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "12th.Oct")).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("chicago late in timezone", () => {
|
||||
it("Issue #unknown rrule US close to timezone edge", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/chicago_late_in_timezone.js", "01 Sept 2024 10:38:00 GMT-5:00", ["js/electron.js"], "America/Chicago");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/chicago_late_in_timezone.js", "01 Sept 2024 10:38:00 GMT-5:00", [], "America/Chicago");
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "10th.Sep, 20:15")).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("berlin late in day event moved, viewed from berlin", () => {
|
||||
it("Issue #unknown rrule ETC+2 close to timezone edge", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/end_of_day_berlin_moved.js", "08 Oct 2024 12:30:00 GMT+02:00", ["js/electron.js"], "Europe/Berlin");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/end_of_day_berlin_moved.js", "08 Oct 2024 12:30:00 GMT+02:00", [], "Europe/Berlin");
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "24th.Oct, 23:00-00:00", last)).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("berlin late in day event moved, viewed from sydney", () => {
|
||||
it("Issue #unknown rrule ETC+2 close to timezone edge", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/end_of_day_berlin_moved.js", "08 Oct 2024 12:30:00 GMT+02:00", ["js/electron.js"], "Australia/Sydney");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/end_of_day_berlin_moved.js", "08 Oct 2024 12:30:00 GMT+02:00", [], "Australia/Sydney");
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "25th.Oct, 01:00-02:00", last)).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("berlin late in day event moved, viewed from chicago", () => {
|
||||
it("Issue #unknown rrule ETC+2 close to timezone edge", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/end_of_day_berlin_moved.js", "08 Oct 2024 12:30:00 GMT+02:00", ["js/electron.js"], "America/Chicago");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/end_of_day_berlin_moved.js", "08 Oct 2024 12:30:00 GMT+02:00", [], "America/Chicago");
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "24th.Oct, 16:00-17:00", last)).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("berlin multi-events inside offset", () => {
|
||||
it("some events before DST. some after midnight", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/berlin_multi.js", "08 Oct 2024 12:30:00 GMT+02:00", ["js/electron.js"], "Europe/Berlin");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/berlin_multi.js", "08 Oct 2024 12:30:00 GMT+02:00", [], "Europe/Berlin");
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "30th.Oct, 00:00-01:00", last)).resolves.toBe(true);
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "21st.Oct, 00:00-01:00", first)).resolves.toBe(true);
|
||||
});
|
||||
@ -221,7 +221,7 @@ describe("Calendar module", () => {
|
||||
|
||||
describe("berlin whole day repeating, start moved after end", () => {
|
||||
it("some events before DST. some after", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/berlin_whole_day_event_moved_over_dst_change.js", "08 Oct 2024 12:30:00 GMT+02:00", ["js/electron.js"], "Europe/Berlin");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/berlin_whole_day_event_moved_over_dst_change.js", "08 Oct 2024 12:30:00 GMT+02:00", [], "Europe/Berlin");
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "30th.Oct", last)).resolves.toBe(true);
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "27th.Oct", first)).resolves.toBe(true);
|
||||
});
|
||||
@ -229,7 +229,7 @@ describe("Calendar module", () => {
|
||||
|
||||
describe("berlin 11pm-midnight", () => {
|
||||
it("right inside the offset, before midnight", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/berlin_end_of_day_repeating.js", "08 Oct 2024 12:30:00 GMT+02:00", ["js/electron.js"], "Europe/Berlin");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/berlin_end_of_day_repeating.js", "08 Oct 2024 12:30:00 GMT+02:00", [], "Europe/Berlin");
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "24th.Oct, 23:00-00:00", last)).resolves.toBe(true);
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "22nd.Oct, 23:00-00:00", first)).resolves.toBe(true);
|
||||
});
|
||||
@ -237,7 +237,7 @@ describe("Calendar module", () => {
|
||||
|
||||
describe("both moved and delete events in recurring list", () => {
|
||||
it("with moved before and after original", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_and_recurrence_together.js", "08 Oct 2024 12:30:00 GMT-07:00", ["js/electron.js"], "America/Los_Angeles");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/exdate_and_recurrence_together.js", "08 Oct 2024 12:30:00 GMT-07:00", [], "America/Los_Angeles");
|
||||
// moved after end at oct 26
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "27th.Oct, 14:30-15:30", last)).resolves.toBe(true);
|
||||
// moved before start at oct 23
|
||||
@ -249,15 +249,20 @@ describe("Calendar module", () => {
|
||||
|
||||
describe("one event diff tz", () => {
|
||||
it("start/end in diff timezones", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/diff_tz_start_end.js", "08 Oct 2024 12:30:00 GMT-07:00", ["js/electron.js"], "America/Chicago");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/diff_tz_start_end.js", "08 Oct 2024 12:30:00 GMT-07:00", [], "America/Chicago");
|
||||
// just
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "29th.Oct, 05:00-30th.Oct, 18:00", first)).resolves.toBe(true);
|
||||
});
|
||||
it("viewing from further west in diff timezones", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/chicago-looking-at-ny-recurring.js", "22 Jan 2025 14:30:00 GMT-06:00", [], "America/Chicago");
|
||||
// just
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "22nd.Jan, 17:30-19:30", first)).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("one event non repeating", () => {
|
||||
it("fullday non-repeating", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/fullday_event_over_multiple_days_nonrepeating.js", "08 Oct 2024 12:30:00 GMT-07:00", ["js/electron.js"], "America/Chicago");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/fullday_event_over_multiple_days_nonrepeating.js", "08 Oct 2024 12:30:00 GMT-07:00", [], "America/Chicago");
|
||||
// just
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "25th.Oct-30th.Oct", first)).resolves.toBe(true);
|
||||
});
|
||||
@ -265,7 +270,7 @@ describe("Calendar module", () => {
|
||||
|
||||
describe("one event no end display", () => {
|
||||
it("don't display end", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/event_with_time_over_multiple_days_non_repeating_no_display_end.js", "08 Oct 2024 12:30:00 GMT-07:00", ["js/electron.js"], "America/Chicago");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/event_with_time_over_multiple_days_non_repeating_no_display_end.js", "08 Oct 2024 12:30:00 GMT-07:00", [], "America/Chicago");
|
||||
// just
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "25th.Oct, 20:00", first)).resolves.toBe(true);
|
||||
});
|
||||
@ -273,10 +278,27 @@ describe("Calendar module", () => {
|
||||
|
||||
describe("display end display end", () => {
|
||||
it("display end", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/event_with_time_over_multiple_days_non_repeating_display_end.js", "08 Oct 2024 12:30:00 GMT-07:00", ["js/electron.js"], "America/Chicago");
|
||||
await helpers.startApplication("tests/configs/modules/calendar/event_with_time_over_multiple_days_non_repeating_display_end.js", "08 Oct 2024 12:30:00 GMT-07:00", [], "America/Chicago");
|
||||
// just
|
||||
await expect(doTestTableContent(".calendar .event", ".time", "25th.Oct, 20:00-26th.Oct, 06:00", first)).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("count and check symbols", () => {
|
||||
it("in array", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/symboltest.js", "08 Oct 2024 12:30:00 GMT-07:00", [], "America/Chicago");
|
||||
// just
|
||||
await expect(doTestCount(".calendar .event .symbol .fa-fw")).resolves.toBe(2);
|
||||
await expect(doTestCount(".calendar .event .symbol .fa-calendar-check")).resolves.toBe(1);
|
||||
await expect(doTestCount(".calendar .event .symbol .fa-google")).resolves.toBe(1);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("count events broadcast", () => {
|
||||
it("get 12 with maxentries set to 1", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/calendar/countCalendarEvents.js", "01 Jan 2024 12:30:00 GMT-076:00", [], "America/Chicago");
|
||||
await expect(doTestTableContent(".testNotification", ".elementCount", "12", first)).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -7,9 +7,9 @@ describe("Compliments module", () => {
|
||||
* @param {Array} complimentsArray The array of compliments.
|
||||
* @returns {boolean} result
|
||||
*/
|
||||
const doTest = async (complimentsArray) => {
|
||||
await helpers.getElement(".compliments");
|
||||
const elem = await helpers.getElement(".module-content");
|
||||
const doTest = async (complimentsArray, state = "visible") => {
|
||||
await helpers.getElement(".compliments", state);
|
||||
const elem = await helpers.getElement(".module-content", state);
|
||||
expect(elem).not.toBeNull();
|
||||
expect(complimentsArray).toContain(await elem.textContent());
|
||||
return true;
|
||||
@ -34,6 +34,11 @@ describe("Compliments module", () => {
|
||||
await helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js", "01 Oct 2022 20:00:00 GMT");
|
||||
await expect(doTest(["Hello There", "Good Evening", "Evening test"])).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it("doesnt show evening compliments during the day when the other parts of day are not set", async () => {
|
||||
await helpers.startApplication("tests/configs/modules/compliments/compliments_evening.js", "01 Oct 2022 08:00:00 GMT");
|
||||
await expect(doTest([""], "attached")).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Feature date in compliments module", () => {
|
||||
|
164
tests/mocks/12_events.ics
Normal file
164
tests/mocks/12_events.ics
Normal file
@ -0,0 +1,164 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Calendar Labs//Calendar 1.0//EN
|
||||
CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
X-WR-CALNAME:US Holidays
|
||||
X-WR-TIMEZONE:Etc/GMT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Start of Month 1
|
||||
DTSTART:20190101
|
||||
DTEND:20190101
|
||||
RRULE:FREQ=YEARLY;WKST=SU;INTERVAL=1
|
||||
LOCATION:United States
|
||||
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n\n Like us on Facebook: http://fb.com/calendarlabs to get updates
|
||||
UID:5e52949sada28d231582470298@calendarlabs.com
|
||||
DTSTAMP:20200223T150458Z
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Start of Month 2
|
||||
DTSTART:20190201
|
||||
DTEND:20190201
|
||||
RRULE:FREQ=YEARLY;WKST=SU;INTERVAL=1
|
||||
LOCATION:United States
|
||||
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n\n Like us on Facebook: http://fb.com/calendarlabs to get updates
|
||||
UID:5e52949a2wds8d231582470298@calendarlabs.com
|
||||
DTSTAMP:20200223T150458Z
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Start of Month 3
|
||||
DTSTART:20190301
|
||||
DTEND:20190301
|
||||
RRULE:FREQ=YEARLY;WKST=SU;INTERVAL=1
|
||||
LOCATION:United States
|
||||
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n\n Like us on Facebook: http://fb.com/calendarlabs to get updates
|
||||
UID:5e52949a2SDD8d231582470298@calendarlabs.com
|
||||
DTSTAMP:20200223T150458Z
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Start of Month 4
|
||||
DTSTART:20190401
|
||||
DTEND:20190401
|
||||
RRULE:FREQ=YEARLY;WKST=SU;INTERVAL=1
|
||||
LOCATION:United States
|
||||
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n\n Like us on Facebook: http://fb.com/calendarlabs to get updates
|
||||
UID:5e52949a2SDD8d231582FDSFD470298@calendarlabs.com
|
||||
DTSTAMP:20200223T150458Z
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Start of Month 5
|
||||
DTSTART:20190501
|
||||
DTEND:20190501
|
||||
RRULE:FREQ=YEARLY;WKST=SU;INTERVAL=1
|
||||
LOCATION:United States
|
||||
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n\n Like us on Facebook: http://fb.com/calendarlabs to get updates
|
||||
UID:5e52949a2SDD8d2DD315824702598@calendarlabs.com
|
||||
DTSTAMP:20200223T150458Z
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Start of Month 6
|
||||
DTSTART:20190601
|
||||
DTEND:20190601
|
||||
RRULE:FREQ=YEARLY;WKST=SU;INTERVAL=1
|
||||
LOCATION:United States
|
||||
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n\n Like us on Facebook: http://fb.com/calendarlabs to get updates
|
||||
UID:5e52949a2SDD8d2DD31582470298@calendarlabs.com
|
||||
DTSTAMP:20200223T150458Z
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Start of Month 7
|
||||
DTSTART:20190701
|
||||
DTEND:20190701
|
||||
RRULE:FREQ=YEARLY;WKST=SU;INTERVAL=1
|
||||
LOCATION:United States
|
||||
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n\n Like us on Facebook: http://fb.com/calendarlabs to get updates
|
||||
UID:5e52942SDD8d2DD31582470298@calendarlabs.com
|
||||
DTSTAMP:20200223T150458Z
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Start of Month 8
|
||||
DTSTART:20190801
|
||||
DTEND:20190801
|
||||
RRULE:FREQ=YEARLY;WKST=SU;INTERVAL=1
|
||||
LOCATION:United States
|
||||
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n\n Like us on Facebook: http://fb.com/calendarlabs to get updates
|
||||
UID:5e52949a2SDD8d2DDt31582470298@calendarlabs.com
|
||||
DTSTAMP:20200223T150458Z
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Start of Month 9
|
||||
DTSTART:20190901
|
||||
DTEND:20190901
|
||||
RRULE:FREQ=YEARLY;WKST=SU;INTERVAL=1
|
||||
LOCATION:United States
|
||||
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n\n Like us on Facebook: http://fb.com/calendarlabs to get updates
|
||||
UID:5e529449a2SDD8d2DDt315824702798@calendarlabs.com
|
||||
DTSTAMP:20200223T150458Z
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Start of Month 10
|
||||
DTSTART:20191001
|
||||
DTEND:20191001
|
||||
RRULE:FREQ=YEARLY;WKST=SU;INTERVAL=1
|
||||
LOCATION:United States
|
||||
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n\n Like us on Facebook: http://fb.com/calendarlabs to get updates
|
||||
UID:5e529449a2SDD8d2DDt31582470298@calendarlabs.com
|
||||
DTSTAMP:20200223T150458Z
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Start of Month 11
|
||||
DTSTART:20191101
|
||||
DTEND:20191101
|
||||
RRULE:FREQ=YEARLY;WKST=SU;INTERVAL=1
|
||||
LOCATION:United States
|
||||
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n\n Like us on Facebook: http://fb.com/calendarlabs to get updates
|
||||
UID:5e5294449a2SDD8d2DDt31582470298@calendarlabs.com
|
||||
DTSTAMP:20200223T150458Z
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Start of Month 12
|
||||
DTSTART:20191201
|
||||
DTEND:20191201
|
||||
RRULE:FREQ=YEARLY;WKST=SU;INTERVAL=1
|
||||
LOCATION:United States
|
||||
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n\n Like us on Facebook: http://fb.com/calendarlabs to get updates
|
||||
UID:5e5294a2SDD8d2DDt31582470298@calendarlabs.com
|
||||
DTSTAMP:20200223T150458Z
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:0
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
15
tests/mocks/chicago-nyedge.ics
Normal file
15
tests/mocks/chicago-nyedge.ics
Normal file
@ -0,0 +1,15 @@
|
||||
BEGIN:VEVENT
|
||||
DTSTART;TZID=America/New_York:20240918T183000
|
||||
DTEND;TZID=America/New_York:20240918T203000
|
||||
RRULE:FREQ=WEEKLY;BYDAY=WE
|
||||
EXDATE;TZID=America/New_York:20241127T183000
|
||||
EXDATE;TZID=America/New_York:20241225T183000
|
||||
DTSTAMP:20250122T045443Z
|
||||
UID:_@google.com
|
||||
CREATED:20240916T131843Z
|
||||
LAST-MODIFIED:20241222T235014Z
|
||||
SEQUENCE:0
|
||||
STATUS:CONFIRMED
|
||||
SUMMARY:Derby
|
||||
TRANSP:OPAQUE
|
||||
END:VEVENT
|
59
tests/mocks/testNotification/testNotification.js
Normal file
59
tests/mocks/testNotification/testNotification.js
Normal file
@ -0,0 +1,59 @@
|
||||
Module.register("testNotification", {
|
||||
defaults: {
|
||||
debug: false,
|
||||
match: {
|
||||
notificationID: "",
|
||||
matchtype: "count"
|
||||
//or
|
||||
// type: 'contents' // look for item in field of content
|
||||
}
|
||||
},
|
||||
count: 0,
|
||||
table: null,
|
||||
notificationReceived (notification, payload) {
|
||||
if (notification === this.config.match.notificationID) {
|
||||
if (this.config.match.matchtype === "count") {
|
||||
this.count = payload.length;
|
||||
if (this.count) {
|
||||
this.table = document.createElement("table");
|
||||
this.addTableRow(this.table, null, `${this.count}:elementCount`);
|
||||
if (this.config.debug) {
|
||||
payload.forEach((e, i) => {
|
||||
this.addTableRow(this.table, i, e.title);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.updateDom();
|
||||
}
|
||||
}
|
||||
},
|
||||
maketd (row, info) {
|
||||
let td = document.createElement("td");
|
||||
row.appendChild(td);
|
||||
if (info !== null) {
|
||||
let colinfo = info.toString().split(":");
|
||||
if (colinfo.length === 2) td.className = colinfo[1];
|
||||
td.innerText = colinfo[0];
|
||||
}
|
||||
return td;
|
||||
},
|
||||
addTableRow (table, col1 = null, col2 = null, col3 = null) {
|
||||
let tableRow = document.createElement("tr");
|
||||
table.appendChild(tableRow);
|
||||
|
||||
let tablecol1 = this.maketd(tableRow, col1);
|
||||
let tablecol2 = this.maketd(tableRow, col2);
|
||||
let tablecol3 = this.maketd(tableRow, col3);
|
||||
|
||||
return tableRow;
|
||||
},
|
||||
getDom () {
|
||||
let wrapper = document.createElement("div");
|
||||
if (this.table) {
|
||||
wrapper.appendChild(this.table);
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
});
|
48
translations/ar.json
Normal file
48
translations/ar.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"LOADING": "جار التحميل …",
|
||||
|
||||
"YESTERDAY": "أمس",
|
||||
"TODAY": "اليوم",
|
||||
"TOMORROW": "غدًا",
|
||||
"RUNNING": "ينتهي خلال",
|
||||
"EMPTY": "لا توجد أحداث قادمة.",
|
||||
"WEEK": "الأسبوع {weekNumber}",
|
||||
|
||||
"N": "شمال",
|
||||
"NNE": "شمال شمال شرقي",
|
||||
"NE": "شمال شرقي",
|
||||
"ENE": "شرق شمال شرقي",
|
||||
"E": "شرق",
|
||||
"ESE": "شرق جنوب شرقي",
|
||||
"SE": "جنوب شرقي",
|
||||
"SSE": "جنوب جنوب شرقي",
|
||||
"S": "جنوب",
|
||||
"SSW": "جنوب جنوب غربي",
|
||||
"SW": "جنوب غربي",
|
||||
"WSW": "غرب جنوب غربي",
|
||||
"W": "غرب",
|
||||
"WNW": "غرب شمال غربي",
|
||||
"NW": "شمال غربي",
|
||||
"NNW": "شمال شمال غربي",
|
||||
|
||||
"FEELS": "كأنها {DEGREE}",
|
||||
"PRECIP_POP": "احتمالية الهطول",
|
||||
"PRECIP_AMOUNT": "كمية الهطول",
|
||||
|
||||
"MODULE_CONFIG_CHANGED": "تم تغيير خيارات التهيئة لوحدة {MODULE_NAME}.\nيرجى مراجعة الوثائق.",
|
||||
"MODULE_CONFIG_ERROR": "خطأ في وحدة {MODULE_NAME}. {ERROR}",
|
||||
"MODULE_ERROR_MALFORMED_URL": "رابط غير صحيح.",
|
||||
"MODULE_ERROR_NO_CONNECTION": "لا يوجد اتصال بالإنترنت.",
|
||||
"MODULE_ERROR_UNAUTHORIZED": "فشل التصريح.",
|
||||
"MODULE_ERROR_UNSPECIFIED": "تحقق من السجلات لمزيد من التفاصيل.",
|
||||
|
||||
"NEWSFEED_NO_ITEMS": "لا توجد أخبار في الوقت الحالي.",
|
||||
|
||||
"UPDATE_NOTIFICATION": "تحديث MagicMirror² متاح.",
|
||||
"UPDATE_NOTIFICATION_MODULE": "تحديث متاح لوحدة {MODULE_NAME}.",
|
||||
"UPDATE_INFO_SINGLE": "التثبيت الحالي متخلف عن تحديث واحد على فرع {BRANCH_NAME}.",
|
||||
"UPDATE_INFO_MULTIPLE": "التثبيت الحالي متخلف عن {COMMIT_COUNT} تحديثات على فرع {BRANCH_NAME}.",
|
||||
"UPDATE_NOTIFICATION_DONE": "تم التحديث لوحدة {MODULE_NAME}",
|
||||
"UPDATE_NOTIFICATION_ERROR": "حدث خطأ أثناء تحديث وحدة {MODULE_NAME}",
|
||||
"UPDATE_NOTIFICATION_NEED-RESTART": "يتطلب إعادة تشغيل MagicMirror."
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"LOADING": "Φόρτωση …",
|
||||
"LOADING": "Φόρτωση ...",
|
||||
|
||||
"DAYBEFOREYESTERDAY": "Προχθές",
|
||||
"YESTERDAY": "Εχθές",
|
||||
"YESTERDAY": "Χθες",
|
||||
"TODAY": "Σήμερα",
|
||||
"TOMORROW": "Αύριο",
|
||||
"RUNNING": "Λήγει σε",
|
||||
@ -23,5 +23,26 @@
|
||||
"W": "Δ",
|
||||
"WNW": "ΔΒΔ",
|
||||
"NW": "ΒΔ",
|
||||
"NNW": "ΒΒΔ"
|
||||
"NNW": "ΒΒΔ",
|
||||
|
||||
"FEELS": "Αίσθηση {DEGREE}",
|
||||
"PRECIP_POP": "Πιθ. υετού",
|
||||
"PRECIP_AMOUNT": "Ποσότητα υετού",
|
||||
|
||||
"MODULE_CONFIG_CHANGED": "Οι επιλογές διαμόρφωσης για το module {MODULE_NAME} έχουν αλλάξει.\nΕλέγξτε την τεκμηρίωση.",
|
||||
"MODULE_CONFIG_ERROR": "Σφάλμα στο module {MODULE_NAME}. {ERROR}",
|
||||
"MODULE_ERROR_MALFORMED_URL": "Λανθασμένη μορφή url.",
|
||||
"MODULE_ERROR_NO_CONNECTION": "Δεν υπάρχει σύνδεση στο διαδίκτυο.",
|
||||
"MODULE_ERROR_UNAUTHORIZED": "Η εξουσιοδότηση απέτυχε.",
|
||||
"MODULE_ERROR_UNSPECIFIED": "Ελέγξτε τα αρχεία καταγραφής για περισσότερες λεπτομέρειες.",
|
||||
|
||||
"NEWSFEED_NO_ITEMS": "Δεν υπάρχουν ειδήσεις αυτή τη στιγμή.",
|
||||
|
||||
"UPDATE_NOTIFICATION": "Διατίθεται ενημέρωση MagicMirror².",
|
||||
"UPDATE_NOTIFICATION_MODULE": "Διατίθεται ενημέρωση για το module {MODULE_NAME}.",
|
||||
"UPDATE_INFO_SINGLE": "Η τρέχουσα εγκατάσταση είναι {COMMIT_COUNT} commit πίσω στο branch {BRANCH_NAME}.",
|
||||
"UPDATE_INFO_MULTIPLE": "Η τρέχουσα εγκατάσταση είναι {COMMIT_COUNT} commit πίσω στο branch {BRANCH_NAME}",
|
||||
"UPDATE_NOTIFICATION_DONE": "Η ενημέρωση ολοκληρώθηκε για το module {MODULE_NAME}",
|
||||
"UPDATE_NOTIFICATION_ERROR": "Η ενημέρωση απέτυχε για το module {MODULE_NAME}",
|
||||
"UPDATE_NOTIFICATION_NEED-RESTART": "Απαιτείται επανεκκίνηση του MagicMirror."
|
||||
}
|
||||
|
50
translations/eo.json
Normal file
50
translations/eo.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"LOADING": "Ŝarĝas …",
|
||||
|
||||
"DAYBEFOREYESTERDAY": "antaŭhieraŭ",
|
||||
"YESTERDAY": "hieraŭ",
|
||||
"TODAY": "hodiaŭ",
|
||||
"TOMORROW": "morgaŭ",
|
||||
"DAYAFTERTOMORROW": "postmorgaŭ",
|
||||
"RUNNING": "ankoraŭ",
|
||||
"EMPTY": "Neniu evento.",
|
||||
"WEEK": "{weekNumber}a kalendara semajno",
|
||||
|
||||
"N": "N",
|
||||
"NNE": "NNOr",
|
||||
"NE": "NOr",
|
||||
"ENE": "OrNOr",
|
||||
"E": "Or",
|
||||
"ESE": "OrSOr",
|
||||
"SE": "SOr",
|
||||
"SSE": "SSOr",
|
||||
"S": "S",
|
||||
"SSW": "SSOk",
|
||||
"SW": "SOk",
|
||||
"WSW": "OkSOk",
|
||||
"W": "Ok",
|
||||
"WNW": "OkNOk",
|
||||
"NW": "NOk",
|
||||
"NNW": "NNOk",
|
||||
|
||||
"FEELS": "Perceptite kiel {DEGREE}",
|
||||
"PRECIP_POP": "Ŝanco de pluvado",
|
||||
"PRECIP_AMOUNT": "Kvanto de pluvo",
|
||||
|
||||
"MODULE_CONFIG_CHANGED": "La agordaj opcioj por la modulo „{MODULE_NAME}“ ŝanĝiĝis. \nBonvolu kontroli la dokumentadon.",
|
||||
"MODULE_CONFIG_ERROR": "Eraro en la modulo „{MODULE_NAME}“. {ERROR}",
|
||||
"MODULE_ERROR_MALFORMED_URL": "Malĝusta URL.",
|
||||
"MODULE_ERROR_NO_CONNECTION": "Neniu interreta konekto.",
|
||||
"MODULE_ERROR_UNAUTHORIZED": "Aŭtorigo malsukcesis.",
|
||||
"MODULE_ERROR_UNSPECIFIED": "Kontrolu la protokolajn dosierojn por pli da detaloj.",
|
||||
|
||||
"NEWSFEED_NO_ITEMS": "Momente neniu novaĵoj.",
|
||||
|
||||
"UPDATE_NOTIFICATION": "Ĝisdatigo por MagicMirror² disponebla.",
|
||||
"UPDATE_NOTIFICATION_MODULE": "Ĝisdatigo por la modulo „{MODULE_NAME}“ disponebla.",
|
||||
"UPDATE_INFO_SINGLE": "La nuna instalado estas unu komito malantaŭ la {BRANCH_NAME}-branĉo.",
|
||||
"UPDATE_INFO_MULTIPLE": "La nuna instalado estas {COMMIT_COUNT} komitoj malantaŭ la {BRANCH_NAME}-branĉo.",
|
||||
"UPDATE_NOTIFICATION_DONE": "Ĝisdatigo por la modulo {MODULE_NAME} finita.",
|
||||
"UPDATE_NOTIFICATION_ERROR": "Eraro dum la ĝisdatigo de la modulo {MODULE_NAME}.",
|
||||
"UPDATE_NOTIFICATION_NEED-RESTART": "MagicMirror devas esti restartigita."
|
||||
}
|
@ -2,6 +2,7 @@ let translations = {
|
||||
en: "translations/en.json", // English
|
||||
nl: "translations/nl.json", // Dutch
|
||||
de: "translations/de.json", // German
|
||||
eo: "translations/eo.json", // Esperanto
|
||||
fi: "translations/fi.json", // Suomi
|
||||
fr: "translations/fr.json", // French
|
||||
fy: "translations/fy.json", // Frysk
|
||||
|
8
vendor/package-lock.json
generated
vendored
8
vendor/package-lock.json
generated
vendored
@ -13,7 +13,7 @@
|
||||
"animate.css": "^4.1.1",
|
||||
"croner": "^9.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.46",
|
||||
"moment-timezone": "^0.5.48",
|
||||
"nunjucks": "^3.2.4",
|
||||
"suncalc": "^1.9.0",
|
||||
"weathericons": "^2.1.0"
|
||||
@ -74,9 +74,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/moment-timezone": {
|
||||
"version": "0.5.46",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.46.tgz",
|
||||
"integrity": "sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==",
|
||||
"version": "0.5.48",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
|
||||
"integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"moment": "^2.29.4"
|
||||
|
2
vendor/package.json
vendored
2
vendor/package.json
vendored
@ -15,7 +15,7 @@
|
||||
"animate.css": "^4.1.1",
|
||||
"croner": "^9.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.46",
|
||||
"moment-timezone": "^0.5.48",
|
||||
"nunjucks": "^3.2.4",
|
||||
"suncalc": "^1.9.0",
|
||||
"weathericons": "^2.1.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user