mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-04-25 06:58:30 -04:00
Release v2.25.0 (#3214)
## [2.25.0] - 2023-10-01 Thanks to: @bugsounet, @dgoth, @dependabot, @kenzal, @Knapoc, @KristjanESPERANTO, @martingron, @NolanKingdon, @Paranoid93, @TeddyStarinvest and @Ybbet. Special thanks to @khassel, @rejas and @sdetweil for taking over most (if not all) of the work on this release as project collaborators. This version would not be there without their effort. Thank you guys! You are awesome! > ⚠️ This release needs nodejs version >= `v18`, older releases have reached end of life and will not work! ### Added - Added UV Index support to OpenWeatherMap - Added 'hideDuplicates' flag to the calendar module - Added `allowOverrideNotification` to weather module to enable sending current weather objects with the `CURRENT_WEATHER_OVERRIDE` notification to supplement/replace the current weather displayed - Added optional AnimateCSS animate for `hide()`, `show()`, `updateDom()` - Added AnimateIn and animateOut in module config definition - Apply AnimateIn rules on the first start - Added automatic client page reload when server was restarted by setting `reloadAfterServerRestart: true` in `config.js`, per default `false` (#3105) - Added eventClass option for customEvents on the default calendar - Added AnimateCSS integration in tests suite (#3206) - Added npm dependabot [Reserved to developer] (#3210) - Added improved logging for calendar (#3110) ### Removed - **Breaking Change**: Removed `digest` authentication method from calendar module (which was already broken since release `2.15.0`) ### Updated - Update roboto fonts to version v5 - Update issue template - Update dev/dependencies incl. electron to v26 - Replace pretty-quick by lint-staged (<https://github.com/azz/pretty-quick/issues/164>) - Update engine node >=18. v16 reached it's end of life. (#3170) - Update typescript definition for modules - Cleaned up nunjuck templates - Replace `node-fetch` with internal fetch (#2649) and remove `digest-fetch` - Update the French translation according to the English file. - Update dependabot incl. vendor/fonts (monthly check) - Renew `package-lock.json` for release ### Fixed - Fix engine check on npm install (#3135) - Fix undefined formatTime method in clock module (#3143) - Fix clientonly startup fails after async added (#3151) - Fix electron width/heigth when using xrandr under bullseye - Fix time issue with certain recurring events in calendar module - Fix ipWhiteList test (#3179) - Fix newsfeed: Convert HTML entities, codes and tag in description (#3191) - Respect width/height (no fullscreen) if set in electronOptions (together with `fullscreen: false`) in `config.js` (#3174) - Fix: AnimateCSS merge hide() and show() animated css class when we do multiple call - Fix `Uncaught SyntaxError: Identifier 'getCorsUrl' has already been declared (at utils.js:1:1)` when using `clock` and `weather` module (#3204) - Fix overriding `config.js` when running tests (#3201) - Fix issue in weathergov provider with probability of precipitation not showing up on hourly or daily forecast --------- Signed-off-by: naveen <172697+naveensrinivasan@users.noreply.github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Karsten Hassel <hassel@gmx.de> Co-authored-by: Malte Hallström <46646495+SkySails@users.noreply.github.com> Co-authored-by: Veeck <github@veeck.de> Co-authored-by: veeck <michael@veeck.de> Co-authored-by: dWoolridge <dwoolridge@charter.net> Co-authored-by: Johan <jojjepersson@yahoo.se> Co-authored-by: Dario Mratovich <dario_mratovich@hotmail.com> Co-authored-by: Dario Mratovich <dario.mratovich@outlook.com> Co-authored-by: Magnus <34011212+MagMar94@users.noreply.github.com> Co-authored-by: Naveen <172697+naveensrinivasan@users.noreply.github.com> Co-authored-by: buxxi <buxxi@omfilm.net> Co-authored-by: Thomas Hirschberger <47733292+Tom-Hirschberger@users.noreply.github.com> Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Co-authored-by: Andrés Vanegas Jiménez <142350+angeldeejay@users.noreply.github.com> Co-authored-by: Dave Child <dave@addedbytes.com> Co-authored-by: grenagit <46225780+grenagit@users.noreply.github.com> Co-authored-by: Grena <grena@grenabox.fr> Co-authored-by: Magnus Marthinsen <magmar@online.no> Co-authored-by: Patrick <psieg@users.noreply.github.com> Co-authored-by: Piotr Rajnisz <56397164+rajniszp@users.noreply.github.com> Co-authored-by: Suthep Yonphimai <tomzt@users.noreply.github.com> Co-authored-by: CarJem Generations (Carter Wallace) <cwallacecs@gmail.com> Co-authored-by: Nicholas Fogal <nfogal.misc@gmail.com> Co-authored-by: JakeBinney <126349119+JakeBinney@users.noreply.github.com> Co-authored-by: OWL4C <124401812+OWL4C@users.noreply.github.com> Co-authored-by: Oscar Björkman <17575446+oscarb@users.noreply.github.com> Co-authored-by: Ismar Slomic <ismar@slomic.no> Co-authored-by: Jørgen Veum-Wahlberg <jorgen.wahlberg@amedia.no> Co-authored-by: Eddie Hung <6740044+eddiehung@users.noreply.github.com> Co-authored-by: Bugsounet - Cédric <github@bugsounet.fr> Co-authored-by: bugsounet <bugsounet@bugsounet.fr> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Knapoc <Knapoc@users.noreply.github.com> Co-authored-by: sam detweiler <sdetweil@gmail.com> Co-authored-by: veeck <michael.veeck@nebenan.de> Co-authored-by: Paranoid93 <6515818+Paranoid93@users.noreply.github.com> Co-authored-by: NolanKingdon <27908974+NolanKingdon@users.noreply.github.com> Co-authored-by: J. Kenzal Hunter <kenzal.hunter@gmail.com> Co-authored-by: Teddy <teddy.payet@gmail.com> Co-authored-by: TeddyStarinvest <teddy.payet@starinvest.com> Co-authored-by: martingron <61826403+martingron@users.noreply.github.com> Co-authored-by: dgoth <132394363+dgoth@users.noreply.github.com>
This commit is contained in:
parent
e87f50e64a
commit
343e7de7bd
80 changed files with 4643 additions and 8570 deletions
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
|
@ -43,7 +43,7 @@ When submitting a new issue, please supply the following information:
|
||||||
|
|
||||||
**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX).
|
**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX).
|
||||||
|
|
||||||
**Node Version**: Make sure it's version 16 or later (recommended is 18).
|
**Node Version**: Make sure it's version 18 or later (recommended is 20).
|
||||||
|
|
||||||
**MagicMirror² Version**: Please let us know which version of MagicMirror² you are running. It can be found in the `package.json` file.
|
**MagicMirror² Version**: Please let us know which version of MagicMirror² you are running. It can be found in the `package.json` file.
|
||||||
|
|
||||||
|
|
6
.github/ISSUE_TEMPLATE/custom.md
vendored
6
.github/ISSUE_TEMPLATE/custom.md
vendored
|
@ -22,6 +22,10 @@ If you are facing an issue or found a bug while trying to install MagicMirror²
|
||||||
If you are facing an issue or found a bug while running MagicMirror² inside a Docker container please create an issue in the corresponding repository:
|
If you are facing an issue or found a bug while running MagicMirror² inside a Docker container please create an issue in the corresponding repository:
|
||||||
[https://gitlab.com/khassel/magicmirror](https://gitlab.com/khassel/magicmirror)
|
[https://gitlab.com/khassel/magicmirror](https://gitlab.com/khassel/magicmirror)
|
||||||
|
|
||||||
|
## I'm having troubles installing or configuring foreign modules
|
||||||
|
|
||||||
|
Please open an issue in the module repository or ask for help in the [forum](https://forum.magicmirror.builders/)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## I found a bug in MagicMirror
|
## I found a bug in MagicMirror
|
||||||
|
@ -31,7 +35,7 @@ When submitting a new issue, please supply the following information:
|
||||||
|
|
||||||
**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX).
|
**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX).
|
||||||
|
|
||||||
**Node Version**: Make sure it's version 16 or later (recommended is 18).
|
**Node Version**: Make sure it's version 18 or later (recommended is 20).
|
||||||
|
|
||||||
**MagicMirror² Version**: Please let us know which version of MagicMirror² you are running. It can be found in the `package.json` file.
|
**MagicMirror² Version**: Please let us know which version of MagicMirror² you are running. It can be found in the `package.json` file.
|
||||||
|
|
||||||
|
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -3,7 +3,6 @@ Hello and thank you for wanting to contribute to the MagicMirror² project
|
||||||
**Please make sure that you have followed these 4 rules before submitting your Pull Request:**
|
**Please make sure that you have followed these 4 rules before submitting your Pull Request:**
|
||||||
|
|
||||||
> 1. Base your pull requests against the `develop` branch.
|
> 1. Base your pull requests against the `develop` branch.
|
||||||
>
|
|
||||||
> 2. Include these infos in the description:
|
> 2. Include these infos in the description:
|
||||||
>
|
>
|
||||||
> - Does the pull request solve a **related** issue?
|
> - Does the pull request solve a **related** issue?
|
||||||
|
@ -13,7 +12,6 @@ Hello and thank you for wanting to contribute to the MagicMirror² project
|
||||||
>
|
>
|
||||||
> 3. Please run `npm run lint:prettier` before submitting so that
|
> 3. Please run `npm run lint:prettier` before submitting so that
|
||||||
> style issues are fixed.
|
> style issues are fixed.
|
||||||
>
|
|
||||||
> 4. Don't forget to add an entry about your changes to
|
> 4. Don't forget to add an entry about your changes to
|
||||||
> the CHANGELOG.md file.
|
> the CHANGELOG.md file.
|
||||||
|
|
||||||
|
|
18
.github/dependabot.yaml
vendored
18
.github/dependabot.yaml
vendored
|
@ -5,3 +5,21 @@ updates:
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
target-branch: "develop"
|
target-branch: "develop"
|
||||||
|
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
target-branch: "develop"
|
||||||
|
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/vendor"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
target-branch: "develop"
|
||||||
|
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/fonts"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
target-branch: "develop"
|
||||||
|
|
4
.github/workflows/automated-tests.yaml
vendored
4
.github/workflows/automated-tests.yaml
vendored
|
@ -18,10 +18,10 @@ jobs:
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [16.x, 18.x, 20.x]
|
node-version: [18.x, 20.x]
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: "Use Node.js ${{ matrix.node-version }}"
|
- name: "Use Node.js ${{ matrix.node-version }}"
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
|
|
2
.github/workflows/codecov-test-suites.yaml
vendored
2
.github/workflows/codecov-test-suites.yaml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: "Install dependencies"
|
- name: "Install dependencies"
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
|
|
2
.github/workflows/depsreview.yaml
vendored
2
.github/workflows/depsreview.yaml
vendored
|
@ -13,6 +13,6 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: "Dependency Review"
|
- name: "Dependency Review"
|
||||||
uses: actions/dependency-review-action@v3
|
uses: actions/dependency-review-action@v3
|
||||||
|
|
2
.npmrc
Normal file
2
.npmrc
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
engine-strict=true
|
||||||
|
audit=false
|
56
CHANGELOG.md
56
CHANGELOG.md
|
@ -5,6 +5,61 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror².
|
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror².
|
||||||
|
|
||||||
|
## [2.25.0] - 2023-10-01
|
||||||
|
|
||||||
|
Thanks to: @bugsounet, @dgoth, @dependabot, @kenzal, @Knapoc, @KristjanESPERANTO, @martingron, @NolanKingdon, @Paranoid93, @TeddyStarinvest and @Ybbet.
|
||||||
|
|
||||||
|
Special thanks to @khassel, @rejas and @sdetweil for taking over most (if not all) of the work on this release as project collaborators. This version would not be there without their effort. Thank you guys! You are awesome!
|
||||||
|
|
||||||
|
> ⚠️ This release needs nodejs version >= `v18`, older releases have reached end of life and will not work!
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added UV Index support to OpenWeatherMap
|
||||||
|
- Added 'hideDuplicates' flag to the calendar module
|
||||||
|
- Added `allowOverrideNotification` to weather module to enable sending current weather objects with the `CURRENT_WEATHER_OVERRIDE` notification to supplement/replace the current weather displayed
|
||||||
|
- Added optional AnimateCSS animate for `hide()`, `show()`, `updateDom()`
|
||||||
|
- Added AnimateIn and animateOut in module config definition
|
||||||
|
- Apply AnimateIn rules on the first start
|
||||||
|
- Added automatic client page reload when server was restarted by setting `reloadAfterServerRestart: true` in `config.js`, per default `false` (#3105)
|
||||||
|
- Added eventClass option for customEvents on the default calendar
|
||||||
|
- Added AnimateCSS integration in tests suite (#3206)
|
||||||
|
- Added npm dependabot [Reserved to developer] (#3210)
|
||||||
|
- Added improved logging for calendar (#3110)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- **Breaking Change**: Removed `digest` authentication method from calendar module (which was already broken since release `2.15.0`)
|
||||||
|
|
||||||
|
### Updated
|
||||||
|
|
||||||
|
- Update roboto fonts to version v5
|
||||||
|
- Update issue template
|
||||||
|
- Update dev/dependencies incl. electron to v26
|
||||||
|
- Replace pretty-quick by lint-staged (<https://github.com/azz/pretty-quick/issues/164>)
|
||||||
|
- Update engine node >=18. v16 reached it's end of life. (#3170)
|
||||||
|
- Update typescript definition for modules
|
||||||
|
- Cleaned up nunjuck templates
|
||||||
|
- Replace `node-fetch` with internal fetch (#2649) and remove `digest-fetch`
|
||||||
|
- Update the French translation according to the English file.
|
||||||
|
- Update dependabot incl. vendor/fonts (monthly check)
|
||||||
|
- Renew `package-lock.json` for release
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix engine check on npm install (#3135)
|
||||||
|
- Fix undefined formatTime method in clock module (#3143)
|
||||||
|
- Fix clientonly startup fails after async added (#3151)
|
||||||
|
- Fix electron width/heigth when using xrandr under bullseye
|
||||||
|
- Fix time issue with certain recurring events in calendar module
|
||||||
|
- Fix ipWhiteList test (#3179)
|
||||||
|
- Fix newsfeed: Convert HTML entities, codes and tag in description (#3191)
|
||||||
|
- Respect width/height (no fullscreen) if set in electronOptions (together with `fullscreen: false`) in `config.js` (#3174)
|
||||||
|
- Fix: AnimateCSS merge hide() and show() animated css class when we do multiple call
|
||||||
|
- Fix `Uncaught SyntaxError: Identifier 'getCorsUrl' has already been declared (at utils.js:1:1)` when using `clock` and `weather` module (#3204)
|
||||||
|
- Fix overriding `config.js` when running tests (#3201)
|
||||||
|
- Fix issue in weathergov provider with probability of precipitation not showing up on hourly or daily forecast
|
||||||
|
|
||||||
## [2.24.0] - 2023-07-01
|
## [2.24.0] - 2023-07-01
|
||||||
|
|
||||||
Thanks to: @angeldeejay, @bugsounet, @buxxi, @CarJem, @dariom, @DaveChild, @dWoolridge, @eddiehung, @grenagit, @Hirschberger, @ismarslomic, @JakeBinney, @KristjanESPERANTO, @MagMar94, @naveensrinivasan, @nfogal, @oscarb, @OWL4C, @psieg, @rajniszp, @retroflex, @SkySails and @tomzt
|
Thanks to: @angeldeejay, @bugsounet, @buxxi, @CarJem, @dariom, @DaveChild, @dWoolridge, @eddiehung, @grenagit, @Hirschberger, @ismarslomic, @JakeBinney, @KristjanESPERANTO, @MagMar94, @naveensrinivasan, @nfogal, @oscarb, @OWL4C, @psieg, @rajniszp, @retroflex, @SkySails and @tomzt
|
||||||
|
@ -46,6 +101,7 @@ Special thanks to @khassel, @rejas and @sdetweil for taking over most (if not al
|
||||||
- Fix date not shown when clock in analog mode (#3100)
|
- Fix date not shown when clock in analog mode (#3100)
|
||||||
- Fix envcanada today percentage-of-precipitation (#3106)
|
- Fix envcanada today percentage-of-precipitation (#3106)
|
||||||
- Fix updatenotification where no branch is checked out but e.g. a version tag (#3130)
|
- Fix updatenotification where no branch is checked out but e.g. a version tag (#3130)
|
||||||
|
- Fix yr weather provider after changes in yr API (#3189)
|
||||||
|
|
||||||
## [2.23.0] - 2023-04-04
|
## [2.23.0] - 2023-04-04
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
.then(function (configReturn) {
|
.then(function (configReturn) {
|
||||||
// Pass along the server config via an environment variable
|
// Pass along the server config via an environment variable
|
||||||
const env = Object.create(process.env);
|
const env = Object.create(process.env);
|
||||||
|
env.clientonly = true; // set to pass to electron.js
|
||||||
const options = { env: env };
|
const options = { env: env };
|
||||||
configReturn.address = config.address;
|
configReturn.address = config.address;
|
||||||
configReturn.port = config.port;
|
configReturn.port = config.port;
|
||||||
|
|
28
fonts/package-lock.json
generated
28
fonts/package-lock.json
generated
|
@ -7,31 +7,31 @@
|
||||||
"name": "magicmirror-fonts",
|
"name": "magicmirror-fonts",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/roboto": "^4.5.8",
|
"@fontsource/roboto": "^5.0.8",
|
||||||
"@fontsource/roboto-condensed": "^4.5.9"
|
"@fontsource/roboto-condensed": "^5.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fontsource/roboto": {
|
"node_modules/@fontsource/roboto": {
|
||||||
"version": "4.5.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.8.tgz",
|
||||||
"integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA=="
|
"integrity": "sha512-XxPltXs5R31D6UZeLIV1td3wTXU3jzd3f2DLsXI8tytMGBkIsGcc9sIyiupRtA8y73HAhuSCeweOoBqf6DbWCA=="
|
||||||
},
|
},
|
||||||
"node_modules/@fontsource/roboto-condensed": {
|
"node_modules/@fontsource/roboto-condensed": {
|
||||||
"version": "4.5.9",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-5.0.8.tgz",
|
||||||
"integrity": "sha512-ql4sQq+h8puBVildZ5ssjYf8DWDONYDe3PD3Bu/p1ZW9GnRETRNPPcCTs/q62HIl3QimwwkiKWynn6wZhQaetg=="
|
"integrity": "sha512-xAXYY+ys24OZ/eOfXJZILPu2xOB7c0ZruM4cd4TSzX3WGj4dZbXYwCEowLldKbZye6LTqiltpFLP/g/Ne0qGLg=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/roboto": {
|
"@fontsource/roboto": {
|
||||||
"version": "4.5.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.8.tgz",
|
||||||
"integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA=="
|
"integrity": "sha512-XxPltXs5R31D6UZeLIV1td3wTXU3jzd3f2DLsXI8tytMGBkIsGcc9sIyiupRtA8y73HAhuSCeweOoBqf6DbWCA=="
|
||||||
},
|
},
|
||||||
"@fontsource/roboto-condensed": {
|
"@fontsource/roboto-condensed": {
|
||||||
"version": "4.5.9",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-5.0.8.tgz",
|
||||||
"integrity": "sha512-ql4sQq+h8puBVildZ5ssjYf8DWDONYDe3PD3Bu/p1ZW9GnRETRNPPcCTs/q62HIl3QimwwkiKWynn6wZhQaetg=="
|
"integrity": "sha512-xAXYY+ys24OZ/eOfXJZILPu2xOB7c0ZruM4cd4TSzX3WGj4dZbXYwCEowLldKbZye6LTqiltpFLP/g/Ne0qGLg=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"url": "https://github.com/MichMich/MagicMirror/issues"
|
"url": "https://github.com/MichMich/MagicMirror/issues"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/roboto": "^4.5.8",
|
"@fontsource/roboto": "^5.0.8",
|
||||||
"@fontsource/roboto-condensed": "^4.5.9"
|
"@fontsource/roboto-condensed": "^5.0.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
662
fonts/roboto.css
662
fonts/roboto.css
|
@ -1,55 +1,671 @@
|
||||||
|
/* roboto-cyrillic-ext-100-normal */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Roboto;
|
font-family: Roboto;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
src: local("Roboto Thin"), local("Roboto-Thin"), url("node_modules/@fontsource/roboto/files/roboto-all-100-normal.woff") format("woff");
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-ext-100-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-ext-100-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* roboto-cyrillic-100-normal */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Roboto Condensed";
|
font-family: Roboto;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 100;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-100-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-100-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-greek-ext-100-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 100;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-ext-100-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-ext-100-normal.woff") format("woff");
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-greek-100-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 100;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-100-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-100-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0370-03FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-vietnamese-100-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 100;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-vietnamese-100-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-vietnamese-100-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-latin-ext-100-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 100;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-ext-100-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-ext-100-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0100-02AF, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-latin-100-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 100;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-100-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-100-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-cyrillic-ext-300-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local("Roboto Condensed Light"), local("RobotoCondensed-Light"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-all-300-normal.woff") format("woff");
|
src:
|
||||||
}
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-ext-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-ext-300-normal.woff") format("woff");
|
||||||
@font-face {
|
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
font-family: "Roboto Condensed";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: local("Roboto Condensed"), local("RobotoCondensed-Regular"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-all-400-normal.woff") format("woff");
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Roboto Condensed";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
src: local("Roboto Condensed Bold"), local("RobotoCondensed-Bold"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-all-700-normal.woff") format("woff");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* roboto-cyrillic-300-normal */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Roboto;
|
font-family: Roboto;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-display: var(--fontsource-display, swap);
|
||||||
src: local("Roboto"), local("Roboto-Regular"), url("node_modules/@fontsource/roboto/files/roboto-all-400-normal.woff") format("woff");
|
font-weight: 300;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-300-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* roboto-greek-ext-300-normal */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Roboto;
|
font-family: Roboto;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 300;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-ext-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-ext-300-normal.woff") format("woff");
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-greek-300-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 300;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-300-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0370-03FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-vietnamese-300-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 300;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-vietnamese-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-vietnamese-300-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-latin-ext-300-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 300;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-ext-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-ext-300-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0100-02AF, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-latin-300-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 300;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-300-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-cyrillic-ext-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-ext-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-ext-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-cyrillic-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-greek-ext-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-ext-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-ext-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-greek-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0370-03FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-vietnamese-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-vietnamese-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-vietnamese-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-latin-ext-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-ext-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-ext-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0100-02AF, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-latin-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-cyrillic-ext-500-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
src: local("Roboto Medium"), local("Roboto-Medium"), url("node_modules/@fontsource/roboto/files/roboto-all-500-normal.woff") format("woff");
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-ext-500-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-ext-500-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* roboto-cyrillic-500-normal */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Roboto;
|
font-family: Roboto;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 500;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-500-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-500-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-greek-ext-500-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 500;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-ext-500-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-ext-500-normal.woff") format("woff");
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-greek-500-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 500;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-500-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-500-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0370-03FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-vietnamese-500-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 500;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-vietnamese-500-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-vietnamese-500-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-latin-ext-500-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 500;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-ext-500-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-ext-500-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0100-02AF, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-latin-500-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 500;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-500-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-500-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-cyrillic-ext-700-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
src: local("Roboto Bold"), local("Roboto-Bold"), url("node_modules/@fontsource/roboto/files/roboto-all-700-normal.woff") format("woff");
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-ext-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-ext-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* roboto-cyrillic-700-normal */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Roboto;
|
font-family: Roboto;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-display: var(--fontsource-display, swap);
|
||||||
src: local("Roboto Light"), local("Roboto-Light"), url("node_modules/@fontsource/roboto/files/roboto-all-300-normal.woff") format("woff");
|
font-weight: 700;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-cyrillic-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-greek-ext-700-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 700;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-ext-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-ext-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-greek-700-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 700;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-greek-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0370-03FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-vietnamese-700-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 700;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-vietnamese-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-vietnamese-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-latin-ext-700-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 700;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-ext-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-ext-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0100-02AF, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-latin-700-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 700;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto/files/roboto-latin-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-cyrillic-ext-300-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 300;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-cyrillic-ext-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-cyrillic-ext-300-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-cyrillic-300-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 300;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-cyrillic-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-cyrillic-300-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-greek-ext-300-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 300;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-greek-ext-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-greek-ext-300-normal.woff") format("woff");
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-greek-300-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 300;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-greek-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-greek-300-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0370-03FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-vietnamese-300-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 300;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-vietnamese-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-vietnamese-300-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-latin-ext-300-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 300;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-ext-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-ext-300-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0100-02AF, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-latin-300-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 300;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-300-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-300-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-cyrillic-ext-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-cyrillic-ext-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-cyrillic-ext-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-cyrillic-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-cyrillic-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-cyrillic-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-greek-ext-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-greek-ext-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-greek-ext-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-greek-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-greek-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-greek-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0370-03FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-vietnamese-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-vietnamese-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-vietnamese-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-latin-ext-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-ext-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-ext-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0100-02AF, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-latin-400-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-400-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-400-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-cyrillic-ext-700-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 700;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-cyrillic-ext-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-cyrillic-ext-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-cyrillic-700-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 700;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-cyrillic-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-cyrillic-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-greek-ext-700-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 700;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-greek-ext-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-greek-ext-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-greek-700-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 700;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-greek-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-greek-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0370-03FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-vietnamese-700-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 700;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-vietnamese-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-vietnamese-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-latin-ext-700-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 700;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-ext-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-ext-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0100-02AF, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-condensed-latin-700-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Roboto Condensed";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: var(--fontsource-display, swap);
|
||||||
|
font-weight: 700;
|
||||||
|
src:
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-700-normal.woff2") format("woff2"),
|
||||||
|
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-700-normal.woff") format("woff");
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>MagicMirror²</title>
|
<title>MagicMirror²</title>
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
|
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
|
||||||
<link rel="stylesheet" type="text/css" href="css/main.css" />
|
<link rel="stylesheet" type="text/css" href="css/main.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="fonts/roboto.css" />
|
<link rel="stylesheet" type="text/css" href="fonts/roboto.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="vendor/node_modules/animate.css/animate.min.css" />
|
||||||
<!-- custom.css is loaded by the loader.js to make sure it's loaded after the module css files. -->
|
<!-- custom.css is loaded by the loader.js to make sure it's loaded after the module css files. -->
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
@ -45,6 +46,7 @@
|
||||||
<script type="text/javascript" src="#CONFIG_FILE#"></script>
|
<script type="text/javascript" src="#CONFIG_FILE#"></script>
|
||||||
<script type="text/javascript" src="vendor/vendor.js"></script>
|
<script type="text/javascript" src="vendor/vendor.js"></script>
|
||||||
<script type="text/javascript" src="modules/default/defaultmodules.js"></script>
|
<script type="text/javascript" src="modules/default/defaultmodules.js"></script>
|
||||||
|
<script type="text/javascript" src="modules/default/utils.js"></script>
|
||||||
<script type="text/javascript" src="js/logger.js"></script>
|
<script type="text/javascript" src="js/logger.js"></script>
|
||||||
<script type="text/javascript" src="translations/translations.js"></script>
|
<script type="text/javascript" src="translations/translations.js"></script>
|
||||||
<script type="text/javascript" src="js/translator.js"></script>
|
<script type="text/javascript" src="js/translator.js"></script>
|
||||||
|
@ -52,6 +54,7 @@
|
||||||
<script type="text/javascript" src="js/module.js"></script>
|
<script type="text/javascript" src="js/module.js"></script>
|
||||||
<script type="text/javascript" src="js/loader.js"></script>
|
<script type="text/javascript" src="js/loader.js"></script>
|
||||||
<script type="text/javascript" src="js/socketclient.js"></script>
|
<script type="text/javascript" src="js/socketclient.js"></script>
|
||||||
|
<script type="text/javascript" src="js/animateCSS.js"></script>
|
||||||
<script type="text/javascript" src="js/main.js"></script>
|
<script type="text/javascript" src="js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
164
js/animateCSS.js
Normal file
164
js/animateCSS.js
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
/* MagicMirror²
|
||||||
|
* AnimateCSS System from https://animate.style/
|
||||||
|
* by @bugsounet
|
||||||
|
* for Michael Teeuw https://michaelteeuw.nl
|
||||||
|
* MIT Licensed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* enumeration of animations in Array **/
|
||||||
|
const AnimateCSSIn = [
|
||||||
|
// Attention seekers
|
||||||
|
"bounce",
|
||||||
|
"flash",
|
||||||
|
"pulse",
|
||||||
|
"rubberBand",
|
||||||
|
"shakeX",
|
||||||
|
"shakeY",
|
||||||
|
"headShake",
|
||||||
|
"swing",
|
||||||
|
"tada",
|
||||||
|
"wobble",
|
||||||
|
"jello",
|
||||||
|
"heartBeat",
|
||||||
|
// Back entrances
|
||||||
|
"backInDown",
|
||||||
|
"backInLeft",
|
||||||
|
"backInRight",
|
||||||
|
"backInUp",
|
||||||
|
// Bouncing entrances
|
||||||
|
"bounceIn",
|
||||||
|
"bounceInDown",
|
||||||
|
"bounceInLeft",
|
||||||
|
"bounceInRight",
|
||||||
|
"bounceInUp",
|
||||||
|
// Fading entrances
|
||||||
|
"fadeIn",
|
||||||
|
"fadeInDown",
|
||||||
|
"fadeInDownBig",
|
||||||
|
"fadeInLeft",
|
||||||
|
"fadeInLeftBig",
|
||||||
|
"fadeInRight",
|
||||||
|
"fadeInRightBig",
|
||||||
|
"fadeInUp",
|
||||||
|
"fadeInUpBig",
|
||||||
|
"fadeInTopLeft",
|
||||||
|
"fadeInTopRight",
|
||||||
|
"fadeInBottomLeft",
|
||||||
|
"fadeInBottomRight",
|
||||||
|
// Flippers
|
||||||
|
"flip",
|
||||||
|
"flipInX",
|
||||||
|
"flipInY",
|
||||||
|
// Lightspeed
|
||||||
|
"lightSpeedInRight",
|
||||||
|
"lightSpeedInLeft",
|
||||||
|
// Rotating entrances
|
||||||
|
"rotateIn",
|
||||||
|
"rotateInDownLeft",
|
||||||
|
"rotateInDownRight",
|
||||||
|
"rotateInUpLeft",
|
||||||
|
"rotateInUpRight",
|
||||||
|
// Specials
|
||||||
|
"jackInTheBox",
|
||||||
|
"rollIn",
|
||||||
|
// Zooming entrances
|
||||||
|
"zoomIn",
|
||||||
|
"zoomInDown",
|
||||||
|
"zoomInLeft",
|
||||||
|
"zoomInRight",
|
||||||
|
"zoomInUp",
|
||||||
|
// Sliding entrances
|
||||||
|
"slideInDown",
|
||||||
|
"slideInLeft",
|
||||||
|
"slideInRight",
|
||||||
|
"slideInUp"
|
||||||
|
];
|
||||||
|
|
||||||
|
const AnimateCSSOut = [
|
||||||
|
// Back exits
|
||||||
|
"backOutDown",
|
||||||
|
"backOutLeft",
|
||||||
|
"backOutRight",
|
||||||
|
"backOutUp",
|
||||||
|
// Bouncing exits
|
||||||
|
"bounceOut",
|
||||||
|
"bounceOutDown",
|
||||||
|
"bounceOutLeft",
|
||||||
|
"bounceOutRight",
|
||||||
|
"bounceOutUp",
|
||||||
|
// Fading exits
|
||||||
|
"fadeOut",
|
||||||
|
"fadeOutDown",
|
||||||
|
"fadeOutDownBig",
|
||||||
|
"fadeOutLeft",
|
||||||
|
"fadeOutLeftBig",
|
||||||
|
"fadeOutRight",
|
||||||
|
"fadeOutRightBig",
|
||||||
|
"fadeOutUp",
|
||||||
|
"fadeOutUpBig",
|
||||||
|
"fadeOutTopLeft",
|
||||||
|
"fadeOutTopRight",
|
||||||
|
"fadeOutBottomRight",
|
||||||
|
"fadeOutBottomLeft",
|
||||||
|
// Flippers
|
||||||
|
"flipOutX",
|
||||||
|
"flipOutY",
|
||||||
|
// Lightspeed
|
||||||
|
"lightSpeedOutRight",
|
||||||
|
"lightSpeedOutLeft",
|
||||||
|
// Rotating exits
|
||||||
|
"rotateOut",
|
||||||
|
"rotateOutDownLeft",
|
||||||
|
"rotateOutDownRight",
|
||||||
|
"rotateOutUpLeft",
|
||||||
|
"rotateOutUpRight",
|
||||||
|
// Specials
|
||||||
|
"hinge",
|
||||||
|
"rollOut",
|
||||||
|
// Zooming exits
|
||||||
|
"zoomOut",
|
||||||
|
"zoomOutDown",
|
||||||
|
"zoomOutLeft",
|
||||||
|
"zoomOutRight",
|
||||||
|
"zoomOutUp",
|
||||||
|
// Sliding exits
|
||||||
|
"slideOutDown",
|
||||||
|
"slideOutLeft",
|
||||||
|
"slideOutRight",
|
||||||
|
"slideOutUp"
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an animation with Animate CSS
|
||||||
|
* @param {string} [element] div element to animate.
|
||||||
|
* @param {string} [animation] animation name.
|
||||||
|
* @param {number} [animationTime] animation duration.
|
||||||
|
*/
|
||||||
|
function addAnimateCSS(element, animation, animationTime) {
|
||||||
|
const animationName = `animate__${animation}`;
|
||||||
|
const node = document.getElementById(element);
|
||||||
|
if (!node) {
|
||||||
|
// don't execute animate: we don't find div
|
||||||
|
Log.warn(`addAnimateCSS: node not found for`, element);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.style.setProperty("--animate-duration", `${animationTime}s`);
|
||||||
|
node.classList.add("animate__animated", animationName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an animation with Animate CSS
|
||||||
|
* @param {string} [element] div element to animate.
|
||||||
|
* @param {string} [animation] animation name.
|
||||||
|
*/
|
||||||
|
function removeAnimateCSS(element, animation) {
|
||||||
|
const animationName = `animate__${animation}`;
|
||||||
|
const node = document.getElementById(element);
|
||||||
|
if (!node) {
|
||||||
|
// don't execute animate: we don't find div
|
||||||
|
Log.warn(`removeAnimateCSS: node not found for`, element);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.classList.remove("animate__animated", animationName);
|
||||||
|
node.style.removeProperty("--animate-duration");
|
||||||
|
}
|
|
@ -29,6 +29,11 @@ const defaults = {
|
||||||
// e.g. you need to add `frameguard: false` for embedding MagicMirror in another website, see https://github.com/MichMich/MagicMirror/issues/2847
|
// e.g. you need to add `frameguard: false` for embedding MagicMirror in another website, see https://github.com/MichMich/MagicMirror/issues/2847
|
||||||
httpHeaders: { contentSecurityPolicy: false, crossOriginOpenerPolicy: false, crossOriginEmbedderPolicy: false, crossOriginResourcePolicy: false, originAgentCluster: false },
|
httpHeaders: { contentSecurityPolicy: false, crossOriginOpenerPolicy: false, crossOriginEmbedderPolicy: false, crossOriginResourcePolicy: false, originAgentCluster: false },
|
||||||
|
|
||||||
|
// properties for checking if server is alive and has same startup-timestamp, the check is per default enabled
|
||||||
|
// (interval 30 seconds). If startup-timestamp has changed the client reloads the magicmirror webpage.
|
||||||
|
checkServerInterval: 30 * 1000,
|
||||||
|
reloadAfterServerRestart: false,
|
||||||
|
|
||||||
modules: [
|
modules: [
|
||||||
{
|
{
|
||||||
module: "updatenotification",
|
module: "updatenotification",
|
||||||
|
|
|
@ -25,11 +25,20 @@ let mainWindow;
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
|
// see https://www.electronjs.org/docs/latest/api/screen
|
||||||
|
// Create a window that fills the screen's available work area.
|
||||||
|
let electronSize = (800, 600);
|
||||||
|
try {
|
||||||
|
electronSize = electron.screen.getPrimaryDisplay().workAreaSize;
|
||||||
|
} catch {
|
||||||
|
Log.warn("Could not get display size, using defaults ...");
|
||||||
|
}
|
||||||
|
|
||||||
let electronSwitchesDefaults = ["autoplay-policy", "no-user-gesture-required"];
|
let electronSwitchesDefaults = ["autoplay-policy", "no-user-gesture-required"];
|
||||||
app.commandLine.appendSwitch(...new Set(electronSwitchesDefaults, config.electronSwitches));
|
app.commandLine.appendSwitch(...new Set(electronSwitchesDefaults, config.electronSwitches));
|
||||||
let electronOptionsDefaults = {
|
let electronOptionsDefaults = {
|
||||||
width: 800,
|
width: electronSize.width,
|
||||||
height: 600,
|
height: electronSize.height,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
darkTheme: true,
|
darkTheme: true,
|
||||||
|
@ -50,6 +59,7 @@ function createWindow() {
|
||||||
electronOptionsDefaults.frame = false;
|
electronOptionsDefaults.frame = false;
|
||||||
electronOptionsDefaults.transparent = true;
|
electronOptionsDefaults.transparent = true;
|
||||||
electronOptionsDefaults.hasShadow = false;
|
electronOptionsDefaults.hasShadow = false;
|
||||||
|
electronOptionsDefaults.fullscreen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions);
|
const electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions);
|
||||||
|
@ -121,7 +131,6 @@ function createWindow() {
|
||||||
});
|
});
|
||||||
|
|
||||||
mainWindow.once("ready-to-show", () => {
|
mainWindow.once("ready-to-show", () => {
|
||||||
mainWindow.setFullScreen(true);
|
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -168,6 +177,13 @@ app.on("certificate-error", (event, webContents, url, error, certificate, callba
|
||||||
callback(true);
|
callback(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (process.env.clientonly) {
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
Log.log("Launching client viewer application.");
|
||||||
|
createWindow();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Start the core application if server is run on localhost
|
// Start the core application if server is run on localhost
|
||||||
// This starts all node helpers and starts the webserver.
|
// This starts all node helpers and starts the webserver.
|
||||||
if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].includes(config.address)) {
|
if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].includes(config.address)) {
|
||||||
|
|
27
js/fetch.js
27
js/fetch.js
|
@ -1,27 +0,0 @@
|
||||||
/**
|
|
||||||
* Helper class to provide either third party fetch library or (if node >= 18)
|
|
||||||
* return internal node fetch implementation.
|
|
||||||
*
|
|
||||||
* Attention: After some discussion we always return the third party
|
|
||||||
* implementation until the node implementation is stable and more tested
|
|
||||||
* @see https://github.com/MichMich/MagicMirror/pull/2952
|
|
||||||
* @see https://github.com/MichMich/MagicMirror/issues/2649
|
|
||||||
* @param {string} url to be fetched
|
|
||||||
* @param {object} options object e.g. for headers
|
|
||||||
* @class
|
|
||||||
*/
|
|
||||||
async function fetch(url, options = {}) {
|
|
||||||
// const nodeVersion = process.version.match(/^v(\d+)\.*/)[1];
|
|
||||||
// if (nodeVersion >= 18) {
|
|
||||||
// // node version >= 18
|
|
||||||
// return global.fetch(url, options);
|
|
||||||
// } else {
|
|
||||||
// // node version < 18
|
|
||||||
// const nodefetch = require("node-fetch");
|
|
||||||
// return nodefetch(url, options);
|
|
||||||
// }
|
|
||||||
const nodefetch = require("node-fetch");
|
|
||||||
return nodefetch(url, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = fetch;
|
|
|
@ -88,6 +88,8 @@ const Loader = (function () {
|
||||||
path: `${moduleFolder}/`,
|
path: `${moduleFolder}/`,
|
||||||
file: `${moduleName}.js`,
|
file: `${moduleName}.js`,
|
||||||
position: moduleData.position,
|
position: moduleData.position,
|
||||||
|
animateIn: moduleData.animateIn,
|
||||||
|
animateOut: moduleData.animateOut,
|
||||||
hiddenOnStartup: moduleData.hiddenOnStartup,
|
hiddenOnStartup: moduleData.hiddenOnStartup,
|
||||||
header: moduleData.header,
|
header: moduleData.header,
|
||||||
configDeepMerge: typeof moduleData.configDeepMerge === "boolean" ? moduleData.configDeepMerge : false,
|
configDeepMerge: typeof moduleData.configDeepMerge === "boolean" ? moduleData.configDeepMerge : false,
|
||||||
|
|
213
js/main.js
213
js/main.js
|
@ -1,4 +1,4 @@
|
||||||
/* global Loader, defaults, Translator */
|
/* global Loader, defaults, Translator, addAnimateCSS, removeAnimateCSS, AnimateCSSIn, AnimateCSSOut */
|
||||||
|
|
||||||
/* MagicMirror²
|
/* MagicMirror²
|
||||||
* Main System
|
* Main System
|
||||||
|
@ -22,6 +22,10 @@ const MM = (function () {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let haveAnimateIn = null;
|
||||||
|
// check if have valid animateIn in module definition (module.data.animateIn)
|
||||||
|
if (module.data.animateIn && AnimateCSSIn.indexOf(module.data.animateIn) !== -1) haveAnimateIn = module.data.animateIn;
|
||||||
|
|
||||||
const wrapper = selectWrapper(module.data.position);
|
const wrapper = selectWrapper(module.data.position);
|
||||||
|
|
||||||
const dom = document.createElement("div");
|
const dom = document.createElement("div");
|
||||||
|
@ -50,7 +54,12 @@ const MM = (function () {
|
||||||
moduleContent.className = "module-content";
|
moduleContent.className = "module-content";
|
||||||
dom.appendChild(moduleContent);
|
dom.appendChild(moduleContent);
|
||||||
|
|
||||||
const domCreationPromise = updateDom(module, 0);
|
// create the domCreationPromise with AnimateCSS (with animateIn of module definition)
|
||||||
|
// or just display it
|
||||||
|
var domCreationPromise;
|
||||||
|
if (haveAnimateIn) domCreationPromise = updateDom(module, { options: { speed: 1000, animate: { in: haveAnimateIn } } }, true);
|
||||||
|
else domCreationPromise = updateDom(module, 0);
|
||||||
|
|
||||||
domCreationPromises.push(domCreationPromise);
|
domCreationPromises.push(domCreationPromise);
|
||||||
domCreationPromise
|
domCreationPromise
|
||||||
.then(function () {
|
.then(function () {
|
||||||
|
@ -101,11 +110,30 @@ const MM = (function () {
|
||||||
/**
|
/**
|
||||||
* Update the dom for a specific module.
|
* Update the dom for a specific module.
|
||||||
* @param {Module} module The module that needs an update.
|
* @param {Module} module The module that needs an update.
|
||||||
* @param {number} [speed] The (optional) number of microseconds for the animation.
|
* @param {object|number} [updateOptions] The (optional) number of microseconds for the animation or object with updateOptions (speed/animates)
|
||||||
|
* @param {boolean} [createAnimatedDom] for displaying only animateIn (used on first start of MagicMirror)
|
||||||
* @returns {Promise} Resolved when the dom is fully updated.
|
* @returns {Promise} Resolved when the dom is fully updated.
|
||||||
*/
|
*/
|
||||||
const updateDom = function (module, speed) {
|
const updateDom = function (module, updateOptions, createAnimatedDom = false) {
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
|
let speed = updateOptions;
|
||||||
|
let animateOut = null;
|
||||||
|
let animateIn = null;
|
||||||
|
if (typeof updateOptions === "object") {
|
||||||
|
if (typeof updateOptions.options === "object" && updateOptions.options.speed !== undefined) {
|
||||||
|
speed = updateOptions.options.speed;
|
||||||
|
Log.debug(`updateDom: ${module.identifier} Has speed in object: ${speed}`);
|
||||||
|
if (typeof updateOptions.options.animate === "object") {
|
||||||
|
animateOut = updateOptions.options.animate.out;
|
||||||
|
animateIn = updateOptions.options.animate.in;
|
||||||
|
Log.debug(`updateDom: ${module.identifier} Has animate in object: out->${animateOut}, in->${animateIn}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.debug(`updateDom: ${module.identifier} Has no speed in object`);
|
||||||
|
speed = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newHeader = module.getHeader();
|
const newHeader = module.getHeader();
|
||||||
let newContentPromise = module.getDom();
|
let newContentPromise = module.getDom();
|
||||||
|
|
||||||
|
@ -116,7 +144,7 @@ const MM = (function () {
|
||||||
|
|
||||||
newContentPromise
|
newContentPromise
|
||||||
.then(function (newContent) {
|
.then(function (newContent) {
|
||||||
const updatePromise = updateDomWithContent(module, speed, newHeader, newContent);
|
const updatePromise = updateDomWithContent(module, speed, newHeader, newContent, animateOut, animateIn, createAnimatedDom);
|
||||||
|
|
||||||
updatePromise.then(resolve).catch(Log.error);
|
updatePromise.then(resolve).catch(Log.error);
|
||||||
})
|
})
|
||||||
|
@ -130,9 +158,12 @@ const MM = (function () {
|
||||||
* @param {number} [speed] The (optional) number of microseconds for the animation.
|
* @param {number} [speed] The (optional) number of microseconds for the animation.
|
||||||
* @param {string} newHeader The new header that is generated.
|
* @param {string} newHeader The new header that is generated.
|
||||||
* @param {HTMLElement} newContent The new content that is generated.
|
* @param {HTMLElement} newContent The new content that is generated.
|
||||||
|
* @param {string} [animateOut] AnimateCss animation name before hidden
|
||||||
|
* @param {string} [animateIn] AnimateCss animation name on show
|
||||||
|
* @param {boolean} [createAnimatedDom] for displaying only animateIn (used on first start)
|
||||||
* @returns {Promise} Resolved when the module dom has been updated.
|
* @returns {Promise} Resolved when the module dom has been updated.
|
||||||
*/
|
*/
|
||||||
const updateDomWithContent = function (module, speed, newHeader, newContent) {
|
const updateDomWithContent = function (module, speed, newHeader, newContent, animateOut, animateIn, createAnimatedDom = false) {
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
if (module.hidden || !speed) {
|
if (module.hidden || !speed) {
|
||||||
updateModuleContent(module, newHeader, newContent);
|
updateModuleContent(module, newHeader, newContent);
|
||||||
|
@ -151,13 +182,28 @@ const MM = (function () {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
hideModule(module, speed / 2, function () {
|
if (createAnimatedDom && animateIn !== null) {
|
||||||
|
Log.debug(`${module.identifier} createAnimatedDom (${animateIn})`);
|
||||||
updateModuleContent(module, newHeader, newContent);
|
updateModuleContent(module, newHeader, newContent);
|
||||||
if (!module.hidden) {
|
if (!module.hidden) {
|
||||||
showModule(module, speed / 2);
|
showModule(module, speed, null, { animate: animateIn });
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideModule(
|
||||||
|
module,
|
||||||
|
speed / 2,
|
||||||
|
function () {
|
||||||
|
updateModuleContent(module, newHeader, newContent);
|
||||||
|
if (!module.hidden) {
|
||||||
|
showModule(module, speed / 2, null, { animate: animateIn });
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
{ animate: animateOut }
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -234,24 +280,65 @@ const MM = (function () {
|
||||||
|
|
||||||
const moduleWrapper = document.getElementById(module.identifier);
|
const moduleWrapper = document.getElementById(module.identifier);
|
||||||
if (moduleWrapper !== null) {
|
if (moduleWrapper !== null) {
|
||||||
moduleWrapper.style.transition = `opacity ${speed / 1000}s`;
|
|
||||||
moduleWrapper.style.opacity = 0;
|
|
||||||
moduleWrapper.classList.add("hidden");
|
|
||||||
|
|
||||||
clearTimeout(module.showHideTimer);
|
clearTimeout(module.showHideTimer);
|
||||||
module.showHideTimer = setTimeout(function () {
|
// reset all animations if needed
|
||||||
// To not take up any space, we just make the position absolute.
|
if (module.hasAnimateOut) {
|
||||||
// since it's fade out anyway, we can see it lay above or
|
removeAnimateCSS(module.identifier, module.hasAnimateOut);
|
||||||
// below other modules. This works way better than adjusting
|
Log.debug(`${module.identifier} Force remove animateOut (in hide): ${module.hasAnimateOut}`);
|
||||||
// the .display property.
|
module.hasAnimateOut = false;
|
||||||
moduleWrapper.style.position = "fixed";
|
}
|
||||||
|
if (module.hasAnimateIn) {
|
||||||
|
removeAnimateCSS(module.identifier, module.hasAnimateIn);
|
||||||
|
Log.debug(`${module.identifier} Force remove animateIn (in hide): ${module.hasAnimateIn}`);
|
||||||
|
module.hasAnimateIn = false;
|
||||||
|
}
|
||||||
|
// haveAnimateName for verify if we are using AninateCSS library
|
||||||
|
// we check AnimateCSSOut Array for validate it
|
||||||
|
// and finaly return the animate name or `null` (for default MM² animation)
|
||||||
|
let haveAnimateName = null;
|
||||||
|
// check if have valid animateOut in module definition (module.data.animateOut)
|
||||||
|
if (module.data.animateOut && AnimateCSSOut.indexOf(module.data.animateOut) !== -1) haveAnimateName = module.data.animateOut;
|
||||||
|
// can't be override with options.animate
|
||||||
|
else if (options.animate && AnimateCSSOut.indexOf(options.animate) !== -1) haveAnimateName = options.animate;
|
||||||
|
|
||||||
updateWrapperStates();
|
if (haveAnimateName) {
|
||||||
|
// with AnimateCSS
|
||||||
|
Log.debug(`${module.identifier} Has animateOut: ${haveAnimateName}`);
|
||||||
|
module.hasAnimateOut = haveAnimateName;
|
||||||
|
addAnimateCSS(module.identifier, haveAnimateName, speed / 1000);
|
||||||
|
module.showHideTimer = setTimeout(function () {
|
||||||
|
removeAnimateCSS(module.identifier, haveAnimateName);
|
||||||
|
Log.debug(`${module.identifier} Remove animateOut: ${module.hasAnimateOut}`);
|
||||||
|
// AnimateCSS is now done
|
||||||
|
moduleWrapper.style.opacity = 0;
|
||||||
|
moduleWrapper.classList.add("hidden");
|
||||||
|
moduleWrapper.style.position = "fixed";
|
||||||
|
module.hasAnimateOut = false;
|
||||||
|
|
||||||
if (typeof callback === "function") {
|
updateWrapperStates();
|
||||||
callback();
|
if (typeof callback === "function") {
|
||||||
}
|
callback();
|
||||||
}, speed);
|
}
|
||||||
|
}, speed);
|
||||||
|
} else {
|
||||||
|
// default MM² Animate
|
||||||
|
moduleWrapper.style.transition = `opacity ${speed / 1000}s`;
|
||||||
|
moduleWrapper.style.opacity = 0;
|
||||||
|
moduleWrapper.classList.add("hidden");
|
||||||
|
module.showHideTimer = setTimeout(function () {
|
||||||
|
// To not take up any space, we just make the position absolute.
|
||||||
|
// since it's fade out anyway, we can see it lay above or
|
||||||
|
// below other modules. This works way better than adjusting
|
||||||
|
// the .display property.
|
||||||
|
moduleWrapper.style.position = "fixed";
|
||||||
|
|
||||||
|
updateWrapperStates();
|
||||||
|
|
||||||
|
if (typeof callback === "function") {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}, speed);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// invoke callback even if no content, issue 1308
|
// invoke callback even if no content, issue 1308
|
||||||
if (typeof callback === "function") {
|
if (typeof callback === "function") {
|
||||||
|
@ -285,6 +372,17 @@ const MM = (function () {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// reset all animations if needed
|
||||||
|
if (module.hasAnimateOut) {
|
||||||
|
removeAnimateCSS(module.identifier, module.hasAnimateOut);
|
||||||
|
Log.debug(`${module.identifier} Force remove animateOut (in show): ${module.hasAnimateOut}`);
|
||||||
|
module.hasAnimateOut = false;
|
||||||
|
}
|
||||||
|
if (module.hasAnimateIn) {
|
||||||
|
removeAnimateCSS(module.identifier, module.hasAnimateIn);
|
||||||
|
Log.debug(`${module.identifier} Force remove animateIn (in show): ${module.hasAnimateIn}`);
|
||||||
|
module.hasAnimateIn = false;
|
||||||
|
}
|
||||||
|
|
||||||
module.hidden = false;
|
module.hidden = false;
|
||||||
|
|
||||||
|
@ -296,7 +394,18 @@ const MM = (function () {
|
||||||
|
|
||||||
const moduleWrapper = document.getElementById(module.identifier);
|
const moduleWrapper = document.getElementById(module.identifier);
|
||||||
if (moduleWrapper !== null) {
|
if (moduleWrapper !== null) {
|
||||||
moduleWrapper.style.transition = `opacity ${speed / 1000}s`;
|
clearTimeout(module.showHideTimer);
|
||||||
|
|
||||||
|
// haveAnimateName for verify if we are using AninateCSS library
|
||||||
|
// we check AnimateCSSIn Array for validate it
|
||||||
|
// and finaly return the animate name or `null` (for default MM² animation)
|
||||||
|
let haveAnimateName = null;
|
||||||
|
// check if have valid animateOut in module definition (module.data.animateIn)
|
||||||
|
if (module.data.animateIn && AnimateCSSIn.indexOf(module.data.animateIn) !== -1) haveAnimateName = module.data.animateIn;
|
||||||
|
// can't be override with options.animate
|
||||||
|
else if (options.animate && AnimateCSSIn.indexOf(options.animate) !== -1) haveAnimateName = options.animate;
|
||||||
|
|
||||||
|
if (!haveAnimateName) moduleWrapper.style.transition = `opacity ${speed / 1000}s`;
|
||||||
// Restore the position. See hideModule() for more info.
|
// Restore the position. See hideModule() for more info.
|
||||||
moduleWrapper.style.position = "static";
|
moduleWrapper.style.position = "static";
|
||||||
moduleWrapper.classList.remove("hidden");
|
moduleWrapper.classList.remove("hidden");
|
||||||
|
@ -307,12 +416,27 @@ const MM = (function () {
|
||||||
const dummy = moduleWrapper.parentElement.parentElement.offsetHeight;
|
const dummy = moduleWrapper.parentElement.parentElement.offsetHeight;
|
||||||
moduleWrapper.style.opacity = 1;
|
moduleWrapper.style.opacity = 1;
|
||||||
|
|
||||||
clearTimeout(module.showHideTimer);
|
if (haveAnimateName) {
|
||||||
module.showHideTimer = setTimeout(function () {
|
// with AnimateCSS
|
||||||
if (typeof callback === "function") {
|
Log.debug(`${module.identifier} Has animateIn: ${haveAnimateName}`);
|
||||||
callback();
|
module.hasAnimateIn = haveAnimateName;
|
||||||
}
|
addAnimateCSS(module.identifier, haveAnimateName, speed / 1000);
|
||||||
}, speed);
|
module.showHideTimer = setTimeout(function () {
|
||||||
|
removeAnimateCSS(module.identifier, haveAnimateName);
|
||||||
|
Log.debug(`${module.identifier} Remove animateIn: ${haveAnimateName}`);
|
||||||
|
module.hasAnimateIn = false;
|
||||||
|
if (typeof callback === "function") {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}, speed);
|
||||||
|
} else {
|
||||||
|
// default MM² Animate
|
||||||
|
module.showHideTimer = setTimeout(function () {
|
||||||
|
if (typeof callback === "function") {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}, speed);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// invoke callback
|
// invoke callback
|
||||||
if (typeof callback === "function") {
|
if (typeof callback === "function") {
|
||||||
|
@ -477,12 +601,33 @@ const MM = (function () {
|
||||||
*/
|
*/
|
||||||
modulesStarted: function (moduleObjects) {
|
modulesStarted: function (moduleObjects) {
|
||||||
modules = [];
|
modules = [];
|
||||||
|
let startUp = "";
|
||||||
|
|
||||||
moduleObjects.forEach((module) => modules.push(module));
|
moduleObjects.forEach((module) => modules.push(module));
|
||||||
|
|
||||||
Log.info("All modules started!");
|
Log.info("All modules started!");
|
||||||
sendNotification("ALL_MODULES_STARTED");
|
sendNotification("ALL_MODULES_STARTED");
|
||||||
|
|
||||||
createDomObjects();
|
createDomObjects();
|
||||||
|
|
||||||
|
if (config.reloadAfterServerRestart) {
|
||||||
|
setInterval(async () => {
|
||||||
|
// if server startup time has changed (which means server was restarted)
|
||||||
|
// the client reloads the mm page
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${location.protocol}//${location.host}/startup`);
|
||||||
|
const curr = await res.text();
|
||||||
|
if (startUp === "") startUp = curr;
|
||||||
|
if (startUp !== curr) {
|
||||||
|
startUp = "";
|
||||||
|
window.location.reload(true);
|
||||||
|
console.warn("Refreshing Website because server was restarted");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Log.error(`MagicMirror not reachable: ${err}`);
|
||||||
|
}
|
||||||
|
}, config.checkServerInterval);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -514,9 +659,9 @@ const MM = (function () {
|
||||||
/**
|
/**
|
||||||
* Update the dom for a specific module.
|
* Update the dom for a specific module.
|
||||||
* @param {Module} module The module that needs an update.
|
* @param {Module} module The module that needs an update.
|
||||||
* @param {number} [speed] The number of microseconds for the animation.
|
* @param {object|number} [updateOptions] The (optional) number of microseconds for the animation or object with updateOptions (speed/animates)
|
||||||
*/
|
*/
|
||||||
updateDom: function (module, speed) {
|
updateDom: function (module, updateOptions) {
|
||||||
if (!(module instanceof Module)) {
|
if (!(module instanceof Module)) {
|
||||||
Log.error("updateDom: Sender should be a module.");
|
Log.error("updateDom: Sender should be a module.");
|
||||||
return;
|
return;
|
||||||
|
@ -528,7 +673,7 @@ const MM = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Further implementation is done in the private method.
|
// Further implementation is done in the private method.
|
||||||
updateDom(module, speed);
|
updateDom(module, updateOptions);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
10
js/module.js
10
js/module.js
|
@ -193,7 +193,7 @@ const Module = Class.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
/*********************************************
|
/*********************************************
|
||||||
* The methods below don"t need subclassing. *
|
* The methods below don't need subclassing. *
|
||||||
*********************************************/
|
*********************************************/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -205,6 +205,8 @@ const Module = Class.extend({
|
||||||
this.name = data.name;
|
this.name = data.name;
|
||||||
this.identifier = data.identifier;
|
this.identifier = data.identifier;
|
||||||
this.hidden = false;
|
this.hidden = false;
|
||||||
|
this.hasAnimateIn = false;
|
||||||
|
this.hasAnimateOut = false;
|
||||||
|
|
||||||
this.setConfig(data.config, data.configDeepMerge);
|
this.setConfig(data.config, data.configDeepMerge);
|
||||||
},
|
},
|
||||||
|
@ -327,10 +329,10 @@ const Module = Class.extend({
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request an (animated) update of the module.
|
* Request an (animated) update of the module.
|
||||||
* @param {number} [speed] The speed of the animation.
|
* @param {number|object} [updateOptions] The speed of the animation or object with for updateOptions (speed/animates)
|
||||||
*/
|
*/
|
||||||
updateDom: function (speed) {
|
updateDom: function (updateOptions) {
|
||||||
MM.updateDom(this, speed);
|
MM.updateDom(this, updateOptions);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,7 +15,7 @@ const socketio = require("socket.io");
|
||||||
|
|
||||||
const Log = require("logger");
|
const Log = require("logger");
|
||||||
const Utils = require("./utils");
|
const Utils = require("./utils");
|
||||||
const { cors, getConfig, getHtml, getVersion } = require("./server_functions");
|
const { cors, getConfig, getHtml, getVersion, getStartup } = require("./server_functions");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server
|
* Server
|
||||||
|
@ -91,6 +91,8 @@ function Server(config) {
|
||||||
|
|
||||||
app.get("/config", (req, res) => getConfig(req, res));
|
app.get("/config", (req, res) => getConfig(req, res));
|
||||||
|
|
||||||
|
app.get("/startup", (req, res) => getStartup(req, res));
|
||||||
|
|
||||||
app.get("/", (req, res) => getHtml(req, res));
|
app.get("/", (req, res) => getHtml(req, res));
|
||||||
|
|
||||||
server.on("listening", () => {
|
server.on("listening", () => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const Log = require("logger");
|
const Log = require("logger");
|
||||||
const fetch = require("./fetch");
|
const startUp = new Date();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the config.
|
* Gets the config.
|
||||||
|
@ -12,6 +12,15 @@ function getConfig(req, res) {
|
||||||
res.send(config);
|
res.send(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the startup time.
|
||||||
|
* @param {Request} req - the request
|
||||||
|
* @param {Response} res - the result
|
||||||
|
*/
|
||||||
|
function getStartup(req, res) {
|
||||||
|
res.send(startUp);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A method that forwards HTTP Get-methods to the internet to avoid CORS-errors.
|
* A method that forwards HTTP Get-methods to the internet to avoid CORS-errors.
|
||||||
*
|
*
|
||||||
|
@ -118,4 +127,4 @@ function getVersion(req, res) {
|
||||||
res.send(global.version);
|
res.send(global.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { cors, getConfig, getHtml, getVersion };
|
module.exports = { cors, getConfig, getHtml, getVersion, getStartup };
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
type ModuleProperties = {
|
type ModuleProperties = {
|
||||||
defaults?: object;
|
defaults?: object;
|
||||||
|
[key: string]: any;
|
||||||
start?(): void;
|
start?(): void;
|
||||||
|
getScripts?(): string[];
|
||||||
|
getStyles?(): string[];
|
||||||
|
getTranslations?(): object;
|
||||||
|
getDom?(): HTMLElement;
|
||||||
getHeader?(): string;
|
getHeader?(): string;
|
||||||
getTemplate?(): string;
|
getTemplate?(): string;
|
||||||
getTemplateData?(): object;
|
getTemplateData?(): object;
|
||||||
notificationReceived?(notification: string, payload: any, sender: object): void;
|
notificationReceived?(notification: string, payload: any, sender: object): void;
|
||||||
|
nunjucksEnvironment?(): void;
|
||||||
socketNotificationReceived?(notification: string, payload: any): void;
|
socketNotificationReceived?(notification: string, payload: any): void;
|
||||||
suspend?(): void;
|
suspend?(): void;
|
||||||
resume?(): void;
|
resume?(): void;
|
||||||
getDom?(): HTMLElement;
|
|
||||||
getStyles?(): string[];
|
|
||||||
[key: string]: any;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export declare const Module: {
|
export declare const Module: {
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
{% if imageUrl or imageFA %}
|
{% if imageUrl or imageFA %}
|
||||||
{% set imageHeight = imageHeight if imageHeight else "80px" %}
|
{% set imageHeight = imageHeight if imageHeight else "80px" %}
|
||||||
{% if imageUrl %}
|
{% if imageUrl %}
|
||||||
<img src="{{ imageUrl }}" height="{{ imageHeight }}" style="margin-bottom: 10px;"/>
|
<img src="{{ imageUrl }}"
|
||||||
{% else %}
|
height="{{ imageHeight }}"
|
||||||
<span class="bright fas fa-{{ imageFA }}" style='margin-bottom: 10px; font-size: {{ imageHeight }};'/></span>
|
style="margin-bottom: 10px" />
|
||||||
{% endif %}
|
{% else %}
|
||||||
<br/>
|
<span class="bright fas fa-{{ imageFA }}"
|
||||||
|
style="margin-bottom: 10px;
|
||||||
|
font-size: {{ imageHeight }}"></span>
|
||||||
|
{% endif %}
|
||||||
|
<br />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if title %}
|
{% if title %}
|
||||||
<span class="thin dimmed medium">{{ title if titleType == 'text' else title | safe }}</span>
|
<span class="thin dimmed medium">{{ title if titleType == 'text' else title | safe }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if message %}
|
{% if message %}
|
||||||
{% if title %}
|
{% if title %}<br />{% endif %}
|
||||||
<br/>
|
<span class="light bright small">{{ message if messageType == 'text' else message | safe }}</span>
|
||||||
{% endif %}
|
|
||||||
<span class="light bright small">{{ message if messageType == 'text' else message | safe }}</span>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
{% if title %}
|
{% if title %}
|
||||||
<span class="thin dimmed medium">{{ title if titleType == 'text' else title | safe }}</span>
|
<span class="thin dimmed medium">{{ title if titleType == 'text' else title | safe }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if message %}
|
{% if message %}
|
||||||
{% if title %}
|
{% if title %}<br />{% endif %}
|
||||||
<br/>
|
<span class="light bright small">{{ message if messageType == 'text' else message | safe }}</span>
|
||||||
{% endif %}
|
|
||||||
<span class="light bright small">{{ message if messageType == 'text' else message | safe }}</span>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -39,9 +39,10 @@ Module.register("calendar", {
|
||||||
hidePrivate: false,
|
hidePrivate: false,
|
||||||
hideOngoing: false,
|
hideOngoing: false,
|
||||||
hideTime: false,
|
hideTime: false,
|
||||||
|
hideDuplicates: true,
|
||||||
showTimeToday: false,
|
showTimeToday: false,
|
||||||
colored: false,
|
colored: false,
|
||||||
customEvents: [], // Array of {keyword: "", symbol: "", color: ""} where Keyword is a regexp and symbol/color are to be applied for matched
|
customEvents: [], // Array of {keyword: "", symbol: "", color: "", eventClass: ""} where Keyword is a regexp and symbol/color/eventClass are to be applied for matched
|
||||||
tableClass: "small",
|
tableClass: "small",
|
||||||
calendars: [
|
calendars: [
|
||||||
{
|
{
|
||||||
|
@ -154,11 +155,14 @@ Module.register("calendar", {
|
||||||
|
|
||||||
// Refresh the DOM every minute if needed: When using relative date format for events that start
|
// Refresh the DOM every minute if needed: When using relative date format for events that start
|
||||||
// or end in less than an hour, the date shows minute granularity and we want to keep that accurate.
|
// or end in less than an hour, the date shows minute granularity and we want to keep that accurate.
|
||||||
setTimeout(() => {
|
setTimeout(
|
||||||
setInterval(() => {
|
() => {
|
||||||
this.updateDom(1);
|
setInterval(() => {
|
||||||
}, ONE_MINUTE);
|
this.updateDom(1);
|
||||||
}, ONE_MINUTE - (new Date() % ONE_MINUTE));
|
}, ONE_MINUTE);
|
||||||
|
},
|
||||||
|
ONE_MINUTE - (new Date() % ONE_MINUTE)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Override socket notification handler.
|
// Override socket notification handler.
|
||||||
|
@ -317,12 +321,12 @@ Module.register("calendar", {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Color events if custom color is specified
|
// Color events if custom color or eventClass are specified
|
||||||
if (this.config.customEvents.length > 0) {
|
if (this.config.customEvents.length > 0) {
|
||||||
for (let ev in this.config.customEvents) {
|
for (let ev in this.config.customEvents) {
|
||||||
if (typeof this.config.customEvents[ev].color !== "undefined" && this.config.customEvents[ev].color !== "") {
|
let needle = new RegExp(this.config.customEvents[ev].keyword, "gi");
|
||||||
let needle = new RegExp(this.config.customEvents[ev].keyword, "gi");
|
if (needle.test(event.title)) {
|
||||||
if (needle.test(event.title)) {
|
if (typeof this.config.customEvents[ev].color !== "undefined" && this.config.customEvents[ev].color !== "") {
|
||||||
// Respect parameter ColoredSymbolOnly also for custom events
|
// Respect parameter ColoredSymbolOnly also for custom events
|
||||||
if (this.config.coloredText) {
|
if (this.config.coloredText) {
|
||||||
eventWrapper.style.cssText = `color:${this.config.customEvents[ev].color}`;
|
eventWrapper.style.cssText = `color:${this.config.customEvents[ev].color}`;
|
||||||
|
@ -333,6 +337,9 @@ Module.register("calendar", {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (typeof this.config.customEvents[ev].eventClass !== "undefined" && this.config.customEvents[ev].eventClass !== "") {
|
||||||
|
eventWrapper.className += ` ${this.config.customEvents[ev].eventClass}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -571,13 +578,14 @@ Module.register("calendar", {
|
||||||
if (this.config.hideOngoing && event.startDate < now) {
|
if (this.config.hideOngoing && event.startDate < now) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (this.listContainsEvent(events, event)) {
|
if (this.config.hideDuplicates && this.listContainsEvent(events, event)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (--remainingEntries < 0) {
|
if (--remainingEntries < 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event.url = calendarUrl;
|
event.url = calendarUrl;
|
||||||
event.today = event.startDate >= today && event.startDate < today + ONE_DAY;
|
event.today = event.startDate >= today && event.startDate < today + ONE_DAY;
|
||||||
event.dayBeforeYesterday = event.startDate >= today - ONE_DAY * 2 && event.startDate < today - ONE_DAY;
|
event.dayBeforeYesterday = event.startDate >= today - ONE_DAY * 2 && event.startDate < today - ONE_DAY;
|
||||||
|
@ -662,7 +670,7 @@ Module.register("calendar", {
|
||||||
|
|
||||||
listContainsEvent: function (eventList, event) {
|
listContainsEvent: function (eventList, event) {
|
||||||
for (const evt of eventList) {
|
for (const evt of eventList) {
|
||||||
if (evt.title === event.title && parseInt(evt.startDate) === parseInt(event.startDate)) {
|
if (evt.title === event.title && parseInt(evt.startDate) === parseInt(event.startDate) && parseInt(evt.endDate) === parseInt(event.endDate)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const https = require("https");
|
const https = require("https");
|
||||||
const digest = require("digest-fetch");
|
|
||||||
const ical = require("node-ical");
|
const ical = require("node-ical");
|
||||||
const fetch = require("fetch");
|
|
||||||
const Log = require("logger");
|
const Log = require("logger");
|
||||||
const NodeHelper = require("node_helper");
|
const NodeHelper = require("node_helper");
|
||||||
const CalendarFetcherUtils = require("./calendarfetcherutils");
|
const CalendarFetcherUtils = require("./calendarfetcherutils");
|
||||||
|
@ -39,7 +37,6 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
|
||||||
clearTimeout(reloadTimer);
|
clearTimeout(reloadTimer);
|
||||||
reloadTimer = null;
|
reloadTimer = null;
|
||||||
const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
|
const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
|
||||||
let fetcher = null;
|
|
||||||
let httpsAgent = null;
|
let httpsAgent = null;
|
||||||
let headers = {
|
let headers = {
|
||||||
"User-Agent": `Mozilla/5.0 (Node.js ${nodeVersion}) MagicMirror/${global.version}`
|
"User-Agent": `Mozilla/5.0 (Node.js ${nodeVersion}) MagicMirror/${global.version}`
|
||||||
|
@ -53,17 +50,12 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
|
||||||
if (auth) {
|
if (auth) {
|
||||||
if (auth.method === "bearer") {
|
if (auth.method === "bearer") {
|
||||||
headers.Authorization = `Bearer ${auth.pass}`;
|
headers.Authorization = `Bearer ${auth.pass}`;
|
||||||
} else if (auth.method === "digest") {
|
|
||||||
fetcher = new digest(auth.user, auth.pass).fetch(url, { headers: headers, agent: httpsAgent });
|
|
||||||
} else {
|
} else {
|
||||||
headers.Authorization = `Basic ${Buffer.from(`${auth.user}:${auth.pass}`).toString("base64")}`;
|
headers.Authorization = `Basic ${Buffer.from(`${auth.user}:${auth.pass}`).toString("base64")}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fetcher === null) {
|
|
||||||
fetcher = fetch(url, { headers: headers, agent: httpsAgent });
|
|
||||||
}
|
|
||||||
|
|
||||||
fetcher
|
fetch(url, { headers: headers, agent: httpsAgent })
|
||||||
.then(NodeHelper.checkFetchStatus)
|
.then(NodeHelper.checkFetchStatus)
|
||||||
.then((response) => response.text())
|
.then((response) => response.text())
|
||||||
.then((responseData) => {
|
.then((responseData) => {
|
||||||
|
@ -115,7 +107,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
|
||||||
* Broadcast the existing events.
|
* Broadcast the existing events.
|
||||||
*/
|
*/
|
||||||
this.broadcastEvents = function () {
|
this.broadcastEvents = function () {
|
||||||
Log.info(`Calendar-Fetcher: Broadcasting ${events.length} events.`);
|
Log.info(`Calendar-Fetcher: Broadcasting ${events.length} events from ${url}.`);
|
||||||
eventsReceivedCallback(this);
|
eventsReceivedCallback(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -313,6 +313,9 @@ const CalendarFetcherUtils = {
|
||||||
let curEvent = event;
|
let curEvent = event;
|
||||||
let showRecurrence = true;
|
let showRecurrence = true;
|
||||||
|
|
||||||
|
// set the time information in the date to equal the time information in the event
|
||||||
|
date.setUTCHours(curEvent.start.getUTCHours(), curEvent.start.getUTCMinutes(), curEvent.start.getUTCSeconds(), curEvent.start.getUTCMilliseconds());
|
||||||
|
|
||||||
// Get the offset of today where we are processing
|
// Get the offset of today where we are processing
|
||||||
// This will be the correction, we need to apply.
|
// This will be the correction, we need to apply.
|
||||||
let nowOffset = new Date().getTimezoneOffset();
|
let nowOffset = new Date().getTimezoneOffset();
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
Use ` | safe` to allow html tages within the text string.
|
Use ` | safe` to allow html tages within the text string.
|
||||||
https://mozilla.github.io/nunjucks/templating.html#autoescaping
|
https://mozilla.github.io/nunjucks/templating.html#autoescaping
|
||||||
-->
|
-->
|
||||||
<div>{{text | safe}}</div>
|
<div>{{ text | safe }}</div>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<div>
|
<div>
|
||||||
<iframe class="newsfeed-fullarticle" src="{{ url }}"></iframe>
|
<iframe class="newsfeed-fullarticle" src="{{ url }}"></iframe>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,27 +1,31 @@
|
||||||
{% macro escapeText(text, dangerouslyDisableAutoEscaping=false) %}
|
{% macro escapeText(text, dangerouslyDisableAutoEscaping=false) %}
|
||||||
{% if dangerouslyDisableAutoEscaping %}
|
{% if dangerouslyDisableAutoEscaping -%}
|
||||||
{{ text | safe}}
|
{{ text | safe }}
|
||||||
{% else %}
|
{%- else -%}
|
||||||
{{ text }}
|
{{ text }}
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro escapeTitle(title, url, dangerouslyDisableAutoEscaping=false, showTitleAsUrl=false) %}
|
{% macro escapeTitle(title, url, dangerouslyDisableAutoEscaping=false, showTitleAsUrl=false) %}
|
||||||
{% if dangerouslyDisableAutoEscaping %}
|
{% if dangerouslyDisableAutoEscaping %}
|
||||||
{% if showTitleAsUrl %}
|
{% if showTitleAsUrl %}
|
||||||
<a href="{{ url }}" style="text-decoration:none;color:#ffffff" target="_blank">{{ title | safe }}</a>
|
<a href="{{ url }}"
|
||||||
|
style="text-decoration:none;
|
||||||
|
color:#ffffff"
|
||||||
|
target="_blank">{{ title | safe }}</a>
|
||||||
|
{% else %}
|
||||||
|
{{ title | safe }}
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ title | safe}}
|
{% if showTitleAsUrl %}
|
||||||
|
<a href="{{ url }}"
|
||||||
|
style="text-decoration:none;
|
||||||
|
color:#ffffff"
|
||||||
|
target="_blank">{{ title }}</a>
|
||||||
|
{% else %}
|
||||||
|
{{ title }}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
|
||||||
{% if showTitleAsUrl %}
|
|
||||||
<a href="{{ url }}" style="text-decoration:none;color:#ffffff" target="_blank">{{ title }}</a>
|
|
||||||
{% else %}
|
|
||||||
{{ title }}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% if loaded %}
|
{% if loaded %}
|
||||||
{% if config.showAsList %}
|
{% if config.showAsList %}
|
||||||
<ul class="newsfeed-list">
|
<ul class="newsfeed-list">
|
||||||
|
@ -30,11 +34,9 @@
|
||||||
{% if (config.showSourceTitle and item.sourceTitle) or config.showPublishDate %}
|
{% if (config.showSourceTitle and item.sourceTitle) or config.showPublishDate %}
|
||||||
<div class="newsfeed-source light small dimmed">
|
<div class="newsfeed-source light small dimmed">
|
||||||
{% if item.sourceTitle and config.showSourceTitle %}
|
{% if item.sourceTitle and config.showSourceTitle %}
|
||||||
{{ item.sourceTitle }}{% if config.showPublishDate %}, {% else %}: {% endif %}
|
{{ item.sourceTitle }}{% if config.showPublishDate %}, {% else %}:{% endif %}
|
||||||
{% endif %}
|
|
||||||
{% if config.showPublishDate %}
|
|
||||||
{{ item.publishDate }}:
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if config.showPublishDate %}{{ item.publishDate }}:{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">
|
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">
|
||||||
|
@ -43,7 +45,7 @@
|
||||||
{% if config.showDescription %}
|
{% if config.showDescription %}
|
||||||
<div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}">
|
<div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}">
|
||||||
{% if config.truncDescription %}
|
{% if config.truncDescription %}
|
||||||
{{ escapeText(item.description | truncate(config.lengthDescription), config.dangerouslyDisableAutoEscaping) }}
|
{{ escapeText(item.description | truncate(config.lengthDescription) , config.dangerouslyDisableAutoEscaping) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ escapeText(item.description, config.dangerouslyDisableAutoEscaping) }}
|
{{ escapeText(item.description, config.dangerouslyDisableAutoEscaping) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -57,11 +59,9 @@
|
||||||
{% if (config.showSourceTitle and sourceTitle) or config.showPublishDate %}
|
{% if (config.showSourceTitle and sourceTitle) or config.showPublishDate %}
|
||||||
<div class="newsfeed-source light small dimmed">
|
<div class="newsfeed-source light small dimmed">
|
||||||
{% if sourceTitle and config.showSourceTitle %}
|
{% if sourceTitle and config.showSourceTitle %}
|
||||||
{{ escapeText(sourceTitle, config.dangerouslyDisableAutoEscaping) }}{% if config.showPublishDate %}, {% else %}: {% endif %}
|
{{ escapeText(sourceTitle, config.dangerouslyDisableAutoEscaping) }}{% if config.showPublishDate %}, {% else %}:{% endif %}
|
||||||
{% endif %}
|
|
||||||
{% if config.showPublishDate %}
|
|
||||||
{{ publishDate }}:
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if config.showPublishDate %}{{ publishDate }}:{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">
|
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
{% if config.showDescription %}
|
{% if config.showDescription %}
|
||||||
<div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}">
|
<div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}">
|
||||||
{% if config.truncDescription %}
|
{% if config.truncDescription %}
|
||||||
{{ escapeText(description | truncate(config.lengthDescription), config.dangerouslyDisableAutoEscaping) }}
|
{{ escapeText(description | truncate(config.lengthDescription) , config.dangerouslyDisableAutoEscaping) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ escapeText(description, config.dangerouslyDisableAutoEscaping) }}
|
{{ escapeText(description, config.dangerouslyDisableAutoEscaping) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -79,15 +79,11 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elseif empty %}
|
{% elseif empty %}
|
||||||
<div class="small dimmed">
|
<div class="small dimmed">{{ "NEWSFEED_NO_ITEMS" | translate | safe }}</div>
|
||||||
{{ "NEWSFEED_NO_ITEMS" | translate | safe }}
|
|
||||||
</div>
|
|
||||||
{% elseif error %}
|
{% elseif error %}
|
||||||
<div class="small dimmed">
|
<div class="small dimmed">
|
||||||
{{ "MODULE_CONFIG_ERROR" | translate({MODULE_NAME: "Newsfeed", ERROR: error}) | safe }}
|
{{ "MODULE_CONFIG_ERROR" | translate({MODULE_NAME: "Newsfeed", ERROR: error}) | safe }}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="small dimmed">
|
<div class="small dimmed">{{ "LOADING" | translate | safe }}</div>
|
||||||
{{ "LOADING" | translate | safe }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
const stream = require("stream");
|
const stream = require("stream");
|
||||||
const FeedMe = require("feedme");
|
const FeedMe = require("feedme");
|
||||||
const iconv = require("iconv-lite");
|
const iconv = require("iconv-lite");
|
||||||
const fetch = require("fetch");
|
const { htmlToText } = require("html-to-text");
|
||||||
const Log = require("logger");
|
const Log = require("logger");
|
||||||
const NodeHelper = require("node_helper");
|
const NodeHelper = require("node_helper");
|
||||||
|
|
||||||
|
@ -54,6 +54,8 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
|
||||||
if (title && pubdate) {
|
if (title && pubdate) {
|
||||||
const regex = /(<([^>]+)>)/gi;
|
const regex = /(<([^>]+)>)/gi;
|
||||||
description = description.toString().replace(regex, "");
|
description = description.toString().replace(regex, "");
|
||||||
|
// Convert HTML entities, codes and tag
|
||||||
|
description = htmlToText(description, { wordwrap: false });
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
title: title,
|
title: title,
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<div class="small bright">
|
<div class="small bright">
|
||||||
{{ "MODULE_CONFIG_CHANGED" | translate({MODULE_NAME: "Newsfeed"}) | safe }}
|
{{ "MODULE_CONFIG_CHANGED" | translate({MODULE_NAME: "Newsfeed"}) | safe }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -65,9 +65,12 @@ module.exports = NodeHelper.create({
|
||||||
scheduleNextFetch(delay) {
|
scheduleNextFetch(delay) {
|
||||||
clearTimeout(this.updateTimer);
|
clearTimeout(this.updateTimer);
|
||||||
|
|
||||||
this.updateTimer = setTimeout(() => {
|
this.updateTimer = setTimeout(
|
||||||
this.performFetch();
|
() => {
|
||||||
}, Math.max(delay, ONE_MINUTE));
|
this.performFetch();
|
||||||
|
},
|
||||||
|
Math.max(delay, ONE_MINUTE)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
ignoreUpdateChecking(moduleName) {
|
ignoreUpdateChecking(moduleName) {
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
{% if not suspended %}
|
{% if not suspended %}
|
||||||
{% for name, status in moduleList %}
|
{% for name, status in moduleList %}
|
||||||
<div class="small bright">
|
<div class="small bright">
|
||||||
<i class="fas fa-exclamation-circle"></i>
|
<i class="fas fa-exclamation-circle"></i>
|
||||||
<span>
|
<span>
|
||||||
{% set mainTextLabel = "UPDATE_NOTIFICATION" if name === "MagicMirror" else "UPDATE_NOTIFICATION_MODULE" %}
|
{% set mainTextLabel = "UPDATE_NOTIFICATION" if name === "MagicMirror" else "UPDATE_NOTIFICATION_MODULE" %}
|
||||||
{{ mainTextLabel | translate({MODULE_NAME: name}) }}
|
{{ mainTextLabel | translate({MODULE_NAME: name}) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="xsmall dimmed">
|
<div class="xsmall dimmed">
|
||||||
{% set subTextLabel = "UPDATE_INFO_SINGLE" if status.behind === 1 else "UPDATE_INFO_MULTIPLE" %}
|
{% set subTextLabel = "UPDATE_INFO_SINGLE" if status.behind === 1 else "UPDATE_INFO_MULTIPLE" %}
|
||||||
{{ subTextLabel | translate({COMMIT_COUNT: status.behind, BRANCH_NAME: status.current}) | diffLink(status) | safe }}
|
{{ subTextLabel | translate({COMMIT_COUNT: status.behind, BRANCH_NAME: status.current}) | diffLink(status) | safe }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
{% if config.showWindDirection %}
|
{% if config.showWindDirection %}
|
||||||
<sup>
|
<sup>
|
||||||
{% if config.showWindDirectionAsArrow %}
|
{% if config.showWindDirectionAsArrow %}
|
||||||
<i class="fas fa-long-arrow-alt-down" style="transform:rotate({{ current.windFromDirection }}deg);"></i>
|
<i class="fas fa-long-arrow-alt-down"
|
||||||
|
style="transform:rotate({{ current.windFromDirection }}deg)"></i>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ current.cardinalWindDirection() | translate }}
|
{{ current.cardinalWindDirection() | translate }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -29,34 +30,28 @@
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if config.showUVIndex %}
|
{% if config.showUVIndex %}
|
||||||
<td class="align-right bright uv-index">
|
<td class="align-right bright uv-index">
|
||||||
<div class="wi dimmed wi-hot"></div>
|
<div class="wi dimmed wi-hot"></div>
|
||||||
{{ current.uv_index }}
|
{{ current.uv_index }}
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="large light">
|
<div class="large light">
|
||||||
<span class="wi weathericon wi-{{current.weatherType}}"></span>
|
<span class="wi weathericon wi-{{ current.weatherType }}"></span>
|
||||||
<span class="bright">
|
<span class="bright">{{ current.temperature | roundValue | unit("temperature") | decimalSymbol }}</span>
|
||||||
{{ current.temperature | roundValue | unit("temperature") | decimalSymbol }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="normal light indoor">
|
<div class="normal light indoor">
|
||||||
{% if config.showIndoorTemperature and indoor.temperature %}
|
{% if config.showIndoorTemperature and indoor.temperature %}
|
||||||
<div>
|
<div>
|
||||||
<span class="fas fa-home"></span>
|
<span class="fas fa-home"></span>
|
||||||
<span class="bright">
|
<span class="bright">{{ indoor.temperature | roundValue | unit("temperature") | decimalSymbol }}</span>
|
||||||
{{ indoor.temperature | roundValue | unit("temperature") | decimalSymbol }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if config.showIndoorHumidity and indoor.humidity %}
|
{% if config.showIndoorHumidity and indoor.humidity %}
|
||||||
<div>
|
<div>
|
||||||
<span class="fas fa-tint"></span>
|
<span class="fas fa-tint"></span>
|
||||||
<span class="bright">
|
<span class="bright">{{ indoor.humidity | roundValue | unit("humidity") | decimalSymbol }}</span>
|
||||||
{{ indoor.humidity | roundValue | unit("humidity") | decimalSymbol }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,14 +60,16 @@
|
||||||
{% if config.showFeelsLike %}
|
{% if config.showFeelsLike %}
|
||||||
<span class="dimmed">
|
<span class="dimmed">
|
||||||
{{ "FEELS" | translate({DEGREE: current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }) }}
|
{{ "FEELS" | translate({DEGREE: current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }) }}
|
||||||
</span><br/>
|
</span>
|
||||||
|
<br />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if config.showPrecipitationAmount and current.precipitationAmount %}
|
{% if config.showPrecipitationAmount and current.precipitationAmount %}
|
||||||
<span class="dimmed">
|
<span class="dimmed">
|
||||||
<span class="precipitationLeadText">{{ "PRECIP_AMOUNT" | translate }}</span> {{ current.precipitationAmount | unit("precip", current.precipitationUnits) }}
|
<span class="precipitationLeadText">{{ "PRECIP_AMOUNT" | translate }}</span> {{ current.precipitationAmount | unit("precip", current.precipitationUnits) }}
|
||||||
</span><br/>
|
</span>
|
||||||
|
<br />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if config.showPrecipitationProbability and current.precipitationProbability %}
|
{% if config.showPrecipitationProbability and current.precipitationProbability %}
|
||||||
<span class="dimmed">
|
<span class="dimmed">
|
||||||
<span class="precipitationLeadText">{{ "PRECIP_POP" | translate }}</span> {{ current.precipitationProbability }}%
|
<span class="precipitationLeadText">{{ "PRECIP_POP" | translate }}</span> {{ current.precipitationProbability }}%
|
||||||
</span>
|
</span>
|
||||||
|
@ -80,10 +77,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="dimmed light small">
|
<div class="dimmed light small">{{ "LOADING" | translate }}</div>
|
||||||
{{ "LOADING" | translate }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Uncomment the line below to see the contents of the `current` object. -->
|
<!-- Uncomment the line below to see the contents of the `current` object. -->
|
||||||
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{current | dump}}</div> -->
|
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{current | dump}}</div> -->
|
||||||
|
|
|
@ -7,15 +7,18 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% set forecast = forecast.slice(0, numSteps) %}
|
{% set forecast = forecast.slice(0, numSteps) %}
|
||||||
{% for f in forecast %}
|
{% for f in forecast %}
|
||||||
<tr {% if config.colored %}class="colored"{% endif %} {% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}>
|
<tr {% if config.colored %}class="colored"{% endif %}
|
||||||
|
{% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}>
|
||||||
{% if (currentStep == 0) and config.ignoreToday == false and config.absoluteDates == false %}
|
{% if (currentStep == 0) and config.ignoreToday == false and config.absoluteDates == false %}
|
||||||
<td class="day">{{ "TODAY" | translate }}</td>
|
<td class="day">{{ "TODAY" | translate }}</td>
|
||||||
{% elif (currentStep == 1) and config.ignoreToday == false and config.absoluteDates == false %}
|
{% elif (currentStep == 1) and config.ignoreToday == false and config.absoluteDates == false %}
|
||||||
<td class="day">{{ "TOMORROW" | translate }}</td>
|
<td class="day">{{ "TOMORROW" | translate }}</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td class="day">{{ f.date.format('ddd') }}</td>
|
<td class="day">{{ f.date.format("ddd") }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="bright weather-icon"><span class="wi weathericon wi-{{ f.weatherType }}"></span></td>
|
<td class="bright weather-icon">
|
||||||
|
<span class="wi weathericon wi-{{ f.weatherType }}"></span>
|
||||||
|
</td>
|
||||||
<td class="align-right bright max-temp">
|
<td class="align-right bright max-temp">
|
||||||
{{ f.maxTemperature | roundValue | unit("temperature") | decimalSymbol }}
|
{{ f.maxTemperature | roundValue | unit("temperature") | decimalSymbol }}
|
||||||
</td>
|
</td>
|
||||||
|
@ -29,7 +32,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if config.showPrecipitationProbability %}
|
{% if config.showPrecipitationProbability %}
|
||||||
<td class="align-right bright precipitation-prob">
|
<td class="align-right bright precipitation-prob">
|
||||||
{{ f.precipitationProbability | unit("precip", "%") }}
|
{{ f.precipitationProbability | unit('precip', '%') }}
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if config.showUVIndex %}
|
{% if config.showUVIndex %}
|
||||||
|
@ -43,10 +46,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="dimmed light small">
|
<div class="dimmed light small">{{ "LOADING" | translate }}</div>
|
||||||
{{ "LOADING" | translate }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Uncomment the line below to see the contents of the `forecast` object. -->
|
<!-- Uncomment the line below to see the contents of the `forecast` object. -->
|
||||||
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{forecast | dump}}</div> -->
|
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{forecast | dump}}</div> -->
|
||||||
|
|
|
@ -4,9 +4,12 @@
|
||||||
<table class="{{ config.tableClass }}">
|
<table class="{{ config.tableClass }}">
|
||||||
{% set hours = hourly.slice(0, numSteps) %}
|
{% set hours = hourly.slice(0, numSteps) %}
|
||||||
{% for hour in hours %}
|
{% for hour in hours %}
|
||||||
<tr {% if config.colored %}class="colored"{% endif %} {% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}>
|
<tr {% if config.colored %}class="colored"{% endif %}
|
||||||
|
{% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}>
|
||||||
<td class="day">{{ hour.date | formatTime }}</td>
|
<td class="day">{{ hour.date | formatTime }}</td>
|
||||||
<td class="bright weather-icon"><span class="wi weathericon wi-{{ hour.weatherType }}"></span></td>
|
<td class="bright weather-icon">
|
||||||
|
<span class="wi weathericon wi-{{ hour.weatherType }}"></span>
|
||||||
|
</td>
|
||||||
<td class="align-right bright">
|
<td class="align-right bright">
|
||||||
{{ hour.temperature | roundValue | unit("temperature") }}
|
{{ hour.temperature | roundValue | unit("temperature") }}
|
||||||
</td>
|
</td>
|
||||||
|
@ -25,7 +28,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if config.showPrecipitationProbability %}
|
{% if config.showPrecipitationProbability %}
|
||||||
<td class="align-right bright precipitation-prob">
|
<td class="align-right bright precipitation-prob">
|
||||||
{{ hour.precipitationProbability | unit("precip", "%") }}
|
{{ hour.precipitationProbability | unit('precip', '%') }}
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -33,10 +36,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="dimmed light small">
|
<div class="dimmed light small">{{ "LOADING" | translate }}</div>
|
||||||
{{ "LOADING" | translate }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Uncomment the line below to see the contents of the `hourly` object. -->
|
<!-- Uncomment the line below to see the contents of the `hourly` object. -->
|
||||||
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{hourly | dump}}</div> -->
|
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{hourly | dump}}</div> -->
|
||||||
|
|
|
@ -295,6 +295,7 @@ WeatherProvider.register("openweathermap", {
|
||||||
current.temperature = data.current.temp;
|
current.temperature = data.current.temp;
|
||||||
current.weatherType = this.convertWeatherType(data.current.weather[0].icon);
|
current.weatherType = this.convertWeatherType(data.current.weather[0].icon);
|
||||||
current.humidity = data.current.humidity;
|
current.humidity = data.current.humidity;
|
||||||
|
current.uv_index = data.current.uvi;
|
||||||
if (data.current.hasOwnProperty("rain") && !isNaN(data.current["rain"]["1h"])) {
|
if (data.current.hasOwnProperty("rain") && !isNaN(data.current["rain"]["1h"])) {
|
||||||
current.rain = data.current["rain"]["1h"];
|
current.rain = data.current["rain"]["1h"];
|
||||||
precip = true;
|
precip = true;
|
||||||
|
@ -323,6 +324,7 @@ WeatherProvider.register("openweathermap", {
|
||||||
weather.windFromDirection = hour.wind_deg;
|
weather.windFromDirection = hour.wind_deg;
|
||||||
weather.weatherType = this.convertWeatherType(hour.weather[0].icon);
|
weather.weatherType = this.convertWeatherType(hour.weather[0].icon);
|
||||||
weather.precipitationProbability = hour.pop ? hour.pop * 100 : undefined;
|
weather.precipitationProbability = hour.pop ? hour.pop * 100 : undefined;
|
||||||
|
weather.uv_index = hour.uvi;
|
||||||
precip = false;
|
precip = false;
|
||||||
if (hour.hasOwnProperty("rain") && !isNaN(hour.rain["1h"])) {
|
if (hour.hasOwnProperty("rain") && !isNaN(hour.rain["1h"])) {
|
||||||
weather.rain = hour.rain["1h"];
|
weather.rain = hour.rain["1h"];
|
||||||
|
@ -355,6 +357,7 @@ WeatherProvider.register("openweathermap", {
|
||||||
weather.windFromDirection = day.wind_deg;
|
weather.windFromDirection = day.wind_deg;
|
||||||
weather.weatherType = this.convertWeatherType(day.weather[0].icon);
|
weather.weatherType = this.convertWeatherType(day.weather[0].icon);
|
||||||
weather.precipitationProbability = day.pop ? day.pop * 100 : undefined;
|
weather.precipitationProbability = day.pop ? day.pop * 100 : undefined;
|
||||||
|
weather.uv_index = day.uvi;
|
||||||
precip = false;
|
precip = false;
|
||||||
if (!isNaN(day.rain)) {
|
if (!isNaN(day.rain)) {
|
||||||
weather.rain = day.rain;
|
weather.rain = day.rain;
|
||||||
|
|
112
modules/default/weather/providers/overrideWrapper.js
Normal file
112
modules/default/weather/providers/overrideWrapper.js
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
/* global Class, WeatherObject */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wrapper class to enable overrides of currentOverrideWeatherObject.
|
||||||
|
*
|
||||||
|
* Sits between the weather.js module and the provider implementations to allow us to
|
||||||
|
* combine the incoming data from the CURRENT_WEATHER_OVERRIDE notification with the
|
||||||
|
* existing data received from the current api provider. If no notifications have
|
||||||
|
* been received then the api provider's data is used.
|
||||||
|
*
|
||||||
|
* The intent is to allow partial WeatherObjects from local sensors to augment or
|
||||||
|
* replace the WeatherObjects from the api providers.
|
||||||
|
*
|
||||||
|
* This class shares the signature of WeatherProvider, and passes any methods not
|
||||||
|
* concerning the current weather directly to the api provider implementation that
|
||||||
|
* is currently in use.
|
||||||
|
*/
|
||||||
|
const OverrideWrapper = Class.extend({
|
||||||
|
baseProvider: null,
|
||||||
|
providerName: "localWrapper",
|
||||||
|
notificationWeatherObject: null,
|
||||||
|
currentOverrideWeatherObject: null,
|
||||||
|
|
||||||
|
init(baseProvider) {
|
||||||
|
this.baseProvider = baseProvider;
|
||||||
|
|
||||||
|
// Binding the scope of current weather functions so any fetchData calls with
|
||||||
|
// setCurrentWeather nested in them call this classes implementation instead
|
||||||
|
// of the provider's default
|
||||||
|
this.baseProvider.setCurrentWeather = this.setCurrentWeather.bind(this);
|
||||||
|
this.baseProvider.currentWeather = this.currentWeather.bind(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Unchanged Api Provider Methods */
|
||||||
|
|
||||||
|
setConfig(config) {
|
||||||
|
this.baseProvider.setConfig(config);
|
||||||
|
},
|
||||||
|
start() {
|
||||||
|
this.baseProvider.start();
|
||||||
|
},
|
||||||
|
fetchCurrentWeather() {
|
||||||
|
this.baseProvider.fetchCurrentWeather();
|
||||||
|
},
|
||||||
|
fetchWeatherForecast() {
|
||||||
|
this.baseProvider.fetchWeatherForecast();
|
||||||
|
},
|
||||||
|
fetchWeatherHourly() {
|
||||||
|
this.baseProvider.fetchEatherHourly();
|
||||||
|
},
|
||||||
|
weatherForecast() {
|
||||||
|
this.baseProvider.weatherForecast();
|
||||||
|
},
|
||||||
|
weatherHourly() {
|
||||||
|
this.baseProvider.weatherHourly();
|
||||||
|
},
|
||||||
|
fetchedLocation() {
|
||||||
|
this.baseProvider.fetchedLocation();
|
||||||
|
},
|
||||||
|
setWeatherForecast(weatherForecastArray) {
|
||||||
|
this.baseProvider.setWeatherForecast(weatherForecastArray);
|
||||||
|
},
|
||||||
|
setWeatherHourly(weatherHourlyArray) {
|
||||||
|
this.baseProvider.setWeatherHourly(weatherHourlyArray);
|
||||||
|
},
|
||||||
|
setFetchedLocation(name) {
|
||||||
|
this.baseProvider.setFetchedLocation(name);
|
||||||
|
},
|
||||||
|
updateAvailable() {
|
||||||
|
this.baseProvider.updateAvailable();
|
||||||
|
},
|
||||||
|
async fetchData(url, type = "json", requestHeaders = undefined, expectedResponseHeaders = undefined) {
|
||||||
|
this.baseProvider.fetchData(url, type, requestHeaders, expectedResponseHeaders);
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Override Methods */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to return this scope's
|
||||||
|
* @returns {WeatherObject} The current weather object. May or may not contain overridden data.
|
||||||
|
*/
|
||||||
|
currentWeather() {
|
||||||
|
return this.currentOverrideWeatherObject;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to combine the overrideWeatherObejct provided in the
|
||||||
|
* notificationReceived method with the currentOverrideWeatherObject provided by the
|
||||||
|
* api provider fetchData implementation.
|
||||||
|
* @param {WeatherObject} currentWeatherObject - the api provider weather object
|
||||||
|
*/
|
||||||
|
setCurrentWeather(currentWeatherObject) {
|
||||||
|
this.currentOverrideWeatherObject = Object.assign(currentWeatherObject, this.notificationWeatherObject);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the overrideWeatherObject, calls setCurrentWeather to combine it with
|
||||||
|
* the existing current weather object provided by the base provider, and signals
|
||||||
|
* that an update is ready.
|
||||||
|
* @param {WeatherObject} payload - the weather object received from the CURRENT_WEATHER_OVERRIDE
|
||||||
|
* notification. Represents information to augment the
|
||||||
|
* existing currentOverrideWeatherObject with.
|
||||||
|
*/
|
||||||
|
notificationReceived(payload) {
|
||||||
|
this.notificationWeatherObject = payload;
|
||||||
|
|
||||||
|
// setCurrentWeather combines the newly received notification weather with
|
||||||
|
// the existing weather object we return for current weather
|
||||||
|
this.setCurrentWeather(this.currentOverrideWeatherObject);
|
||||||
|
this.updateAvailable();
|
||||||
|
}
|
||||||
|
});
|
|
@ -182,6 +182,12 @@ WeatherProvider.register("weathergov", {
|
||||||
weather.windSpeed = WeatherUtils.convertWindToMs(weather.windSpeed);
|
weather.windSpeed = WeatherUtils.convertWindToMs(weather.windSpeed);
|
||||||
weather.windFromDirection = forecast.windDirection;
|
weather.windFromDirection = forecast.windDirection;
|
||||||
weather.temperature = forecast.temperature;
|
weather.temperature = forecast.temperature;
|
||||||
|
//assign probability of precipitation
|
||||||
|
if (forecast.probabilityOfPrecipitation.value === null) {
|
||||||
|
weather.precipitationProbability = 0;
|
||||||
|
} else {
|
||||||
|
weather.precipitationProbability = forecast.probabilityOfPrecipitation.value;
|
||||||
|
}
|
||||||
// use the forecast isDayTime attribute to help build the weatherType label
|
// use the forecast isDayTime attribute to help build the weatherType label
|
||||||
weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime);
|
weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime);
|
||||||
|
|
||||||
|
@ -238,8 +244,6 @@ WeatherProvider.register("weathergov", {
|
||||||
* fetch forecast information for daily forecast.
|
* fetch forecast information for daily forecast.
|
||||||
*/
|
*/
|
||||||
fetchForecastDaily(forecasts) {
|
fetchForecastDaily(forecasts) {
|
||||||
const precipitationProbabilityRegEx = "Chance of precipitation is ([0-9]+?)%";
|
|
||||||
|
|
||||||
// initial variable declaration
|
// initial variable declaration
|
||||||
const days = [];
|
const days = [];
|
||||||
// variables for temperature range and rain
|
// variables for temperature range and rain
|
||||||
|
@ -262,8 +266,12 @@ WeatherProvider.register("weathergov", {
|
||||||
|
|
||||||
minTemp = [];
|
minTemp = [];
|
||||||
maxTemp = [];
|
maxTemp = [];
|
||||||
const precipitation = new RegExp(precipitationProbabilityRegEx, "g").exec(forecast.detailedForecast);
|
//assign probability of precipitation
|
||||||
if (precipitation) weather.precipitationProbability = precipitation[1];
|
if (forecast.probabilityOfPrecipitation.value === null) {
|
||||||
|
weather.precipitationProbability = 0;
|
||||||
|
} else {
|
||||||
|
weather.precipitationProbability = forecast.probabilityOfPrecipitation.value;
|
||||||
|
}
|
||||||
|
|
||||||
// set new date
|
// set new date
|
||||||
date = moment(forecast.startTime).format("YYYY-MM-DD");
|
date = moment(forecast.startTime).format("YYYY-MM-DD");
|
||||||
|
|
|
@ -352,8 +352,7 @@ WeatherProvider.register("yr", {
|
||||||
if (hours.length < 2) {
|
if (hours.length < 2) {
|
||||||
hours = `0${hours}`;
|
hours = `0${hours}`;
|
||||||
}
|
}
|
||||||
|
return `${this.config.apiBase}/sunrise/2.3/sun?lat=${lat}&lon=${lon}&date=${date}&offset=${utcOffsetPrefix}${hours}%3A${minutes}`;
|
||||||
return `${this.config.apiBase}/sunrise/2.0/.json?date=${date}&days=${days}&height=${altitude}&lat=${lat}&lon=${lon}&offset=${utcOffsetPrefix}${hours}%3A${minutes}`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cacheStellarData(data) {
|
cacheStellarData(data) {
|
||||||
|
@ -362,8 +361,6 @@ WeatherProvider.register("yr", {
|
||||||
|
|
||||||
getWeatherDataFrom(forecast, stellarData, units) {
|
getWeatherDataFrom(forecast, stellarData, units) {
|
||||||
const weather = new WeatherObject();
|
const weather = new WeatherObject();
|
||||||
const stellarTimesToday = stellarData?.today ? this.getStellarTimesFrom(stellarData.today, moment().format("YYYY-MM-DD")) : undefined;
|
|
||||||
const stellarTimesTomorrow = stellarData?.tomorrow ? this.getStellarTimesFrom(stellarData.tomorrow, moment().add(1, "days").format("YYYY-MM-DD")) : undefined;
|
|
||||||
|
|
||||||
weather.date = moment(forecast.time);
|
weather.date = moment(forecast.time);
|
||||||
weather.windSpeed = forecast.data.instant.details.wind_speed;
|
weather.windSpeed = forecast.data.instant.details.wind_speed;
|
||||||
|
@ -377,10 +374,8 @@ WeatherProvider.register("yr", {
|
||||||
weather.precipitationProbability = forecast.precipitationProbability;
|
weather.precipitationProbability = forecast.precipitationProbability;
|
||||||
weather.precipitationUnits = units.precipitation_amount;
|
weather.precipitationUnits = units.precipitation_amount;
|
||||||
|
|
||||||
if (stellarTimesToday) {
|
weather.sunrise = stellarData?.today?.properties?.sunrise?.time;
|
||||||
weather.sunset = moment(stellarTimesToday.sunset.time);
|
weather.sunset = stellarData?.today?.properties?.sunset?.time;
|
||||||
weather.sunrise = weather.sunset < moment() && stellarTimesTomorrow ? moment(stellarTimesTomorrow.sunrise.time) : moment(stellarTimesToday.sunrise.time);
|
|
||||||
}
|
|
||||||
|
|
||||||
return weather;
|
return weather;
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,6 +23,7 @@ Module.register("weather", {
|
||||||
showHumidity: false,
|
showHumidity: false,
|
||||||
showIndoorHumidity: false,
|
showIndoorHumidity: false,
|
||||||
showIndoorTemperature: false,
|
showIndoorTemperature: false,
|
||||||
|
allowOverrideNotification: false,
|
||||||
showPeriod: true,
|
showPeriod: true,
|
||||||
showPeriodUpper: false,
|
showPeriodUpper: false,
|
||||||
showPrecipitationAmount: false,
|
showPrecipitationAmount: false,
|
||||||
|
@ -61,7 +62,7 @@ Module.register("weather", {
|
||||||
|
|
||||||
// Return the scripts that are necessary for the weather module.
|
// Return the scripts that are necessary for the weather module.
|
||||||
getScripts: function () {
|
getScripts: function () {
|
||||||
return ["moment.js", this.file("../utils.js"), "weatherutils.js", "weatherprovider.js", "weatherobject.js", "suncalc.js", this.file(`providers/${this.config.weatherProvider.toLowerCase()}.js`)];
|
return ["moment.js", "weatherutils.js", "weatherobject.js", this.file("providers/overrideWrapper.js"), "weatherprovider.js", "suncalc.js", this.file(`providers/${this.config.weatherProvider.toLowerCase()}.js`)];
|
||||||
},
|
},
|
||||||
|
|
||||||
// Override getHeader method.
|
// Override getHeader method.
|
||||||
|
@ -119,6 +120,8 @@ Module.register("weather", {
|
||||||
} else if (notification === "INDOOR_HUMIDITY") {
|
} else if (notification === "INDOOR_HUMIDITY") {
|
||||||
this.indoorHumidity = this.roundValue(payload);
|
this.indoorHumidity = this.roundValue(payload);
|
||||||
this.updateDom(300);
|
this.updateDom(300);
|
||||||
|
} else if (notification === "CURRENT_WEATHER_OVERRIDE" && this.config.allowOverrideNotification) {
|
||||||
|
this.weatherProvider.notificationReceived(payload);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global Class, performWebRequest */
|
/* global Class, performWebRequest, OverrideWrapper */
|
||||||
|
|
||||||
/* MagicMirror²
|
/* MagicMirror²
|
||||||
* Module: Weather
|
* Module: Weather
|
||||||
|
@ -164,5 +164,9 @@ WeatherProvider.initialize = function (providerIdentifier, delegate) {
|
||||||
provider.providerName = pi;
|
provider.providerName = pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.allowOverrideNotification) {
|
||||||
|
return new OverrideWrapper(provider);
|
||||||
|
}
|
||||||
|
|
||||||
return provider;
|
return provider;
|
||||||
};
|
};
|
||||||
|
|
10742
package-lock.json
generated
10742
package-lock.json
generated
File diff suppressed because it is too large
Load diff
51
package.json
51
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "magicmirror",
|
"name": "magicmirror",
|
||||||
"version": "2.24.0",
|
"version": "2.25.0",
|
||||||
"description": "The open source modular smart mirror platform.",
|
"description": "The open source modular smart mirror platform.",
|
||||||
"main": "js/electron.js",
|
"main": "js/electron.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
"lint:prettier": "prettier . --write",
|
"lint:prettier": "prettier . --write",
|
||||||
"lint:js": "eslint 'js/**/*.js' 'modules/default/**/*.js' 'clientonly/*.js' 'serveronly/*.js' 'translations/*.js' 'vendor/*.js' 'tests/**/*.js' 'config/*' --config .eslintrc.json --fix",
|
"lint:js": "eslint 'js/**/*.js' 'modules/default/**/*.js' 'clientonly/*.js' 'serveronly/*.js' 'translations/*.js' 'vendor/*.js' 'tests/**/*.js' 'config/*' --config .eslintrc.json --fix",
|
||||||
"lint:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json --fix",
|
"lint:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json --fix",
|
||||||
"lint:staged": "pretty-quick --staged",
|
"lint:staged": "lint-staged",
|
||||||
"prepare": "[ -f node_modules/.bin/husky ] && husky install || echo no husky installed."
|
"prepare": "[ -f node_modules/.bin/husky ] && husky install || echo no husky installed."
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -49,52 +49,55 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://magicmirror.builders",
|
"homepage": "https://magicmirror.builders",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-import": "^2.28.1",
|
||||||
"eslint-plugin-jest": "^27.2.2",
|
"eslint-plugin-jest": "^27.4.2",
|
||||||
"eslint-plugin-jsdoc": "^46.4.2",
|
"eslint-plugin-jsdoc": "^46.8.2",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.7.0",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
|
"lint-staged": "^14.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"playwright": "^1.35.1",
|
"playwright": "^1.38.1",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^3.0.3",
|
||||||
"pretty-quick": "^3.1.3",
|
"sinon": "^16.0.0",
|
||||||
"sinon": "^15.2.0",
|
"stylelint": "^15.10.3",
|
||||||
"stylelint": "^15.9.0",
|
"stylelint-config-standard": "^34.0.0",
|
||||||
"stylelint-config-standard": "^33.0.0",
|
"stylelint-prettier": "^4.0.2",
|
||||||
"stylelint-prettier": "^3.0.0",
|
|
||||||
"suncalc": "^1.9.0"
|
"suncalc": "^1.9.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"electron": "^25.2.0"
|
"electron": "^26.2.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"console-stamp": "^3.1.1",
|
"console-stamp": "^3.1.2",
|
||||||
"digest-fetch": "^2.0.3",
|
|
||||||
"envsub": "^4.1.0",
|
"envsub": "^4.1.0",
|
||||||
"eslint": "^8.43.0",
|
"eslint": "^8.50.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-ipfilter": "^1.3.1",
|
"express-ipfilter": "^1.3.1",
|
||||||
"feedme": "^2.0.2",
|
"feedme": "^2.0.2",
|
||||||
"helmet": "^7.0.0",
|
"helmet": "^7.0.0",
|
||||||
|
"html-to-text": "^9.0.5",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
"luxon": "^1.28.1",
|
"luxon": "^1.28.1",
|
||||||
"module-alias": "^2.2.3",
|
"module-alias": "^2.2.3",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"node-fetch": "^2.6.12",
|
|
||||||
"node-ical": "^0.16.1",
|
"node-ical": "^0.16.1",
|
||||||
"socket.io": "^4.7.1"
|
"socket.io": "^4.7.2"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*": "prettier --write",
|
||||||
|
"*.js": "eslint",
|
||||||
|
"*.css": "stylelint"
|
||||||
},
|
},
|
||||||
"_moduleAliases": {
|
"_moduleAliases": {
|
||||||
"node_helper": "js/node_helper.js",
|
"node_helper": "js/node_helper.js",
|
||||||
"logger": "js/logger.js",
|
"logger": "js/logger.js"
|
||||||
"fetch": "js/fetch.js"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
* MIT Licensed.
|
* MIT Licensed.
|
||||||
*/
|
*/
|
||||||
let config = require(`${process.cwd()}/tests/configs/default.js`).configFactory({
|
let config = require(`${process.cwd()}/tests/configs/default.js`).configFactory({
|
||||||
ipWhitelist: []
|
ipWhitelist: [],
|
||||||
|
port: 8282
|
||||||
});
|
});
|
||||||
|
|
||||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||||
|
|
|
@ -11,7 +11,7 @@ let config = {
|
||||||
module: "calendar",
|
module: "calendar",
|
||||||
position: "bottom_bar",
|
position: "bottom_bar",
|
||||||
config: {
|
config: {
|
||||||
customEvents: [{ keyword: "CustomEvent", symbol: "dice" }],
|
customEvents: [{ keyword: "CustomEvent", symbol: "dice", eventClass: "undo" }],
|
||||||
calendars: [
|
calendars: [
|
||||||
{
|
{
|
||||||
maximumEntries: 5,
|
maximumEntries: 5,
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* MagicMirror² Test config for multiple calendar events having the same name and start date/time
|
||||||
|
*
|
||||||
|
* By Paranoid93 https://github.com/Paranoid93/
|
||||||
|
* MIT Licensed.
|
||||||
|
*/
|
||||||
|
let config = {
|
||||||
|
timeFormat: 12,
|
||||||
|
|
||||||
|
modules: [
|
||||||
|
{
|
||||||
|
module: "calendar",
|
||||||
|
position: "bottom_bar",
|
||||||
|
config: {
|
||||||
|
maximumEntries: 30,
|
||||||
|
hideDuplicates: false,
|
||||||
|
calendars: [
|
||||||
|
{
|
||||||
|
maximumEntries: 15,
|
||||||
|
maximumNumberOfDays: 10000,
|
||||||
|
url: "http://localhost:8080/tests/mocks/calendar_test.ics" // contains 11 events
|
||||||
|
},
|
||||||
|
{
|
||||||
|
maximumEntries: 15,
|
||||||
|
maximumNumberOfDays: 10000,
|
||||||
|
url: "http://localhost:8080/tests/mocks/calendar_test_clone.ics" // clone of upper calendar
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||||
|
if (typeof module !== "undefined") {
|
||||||
|
module.exports = config;
|
||||||
|
}
|
24
tests/configs/modules/clock/clock_showSunMoon.js
Normal file
24
tests/configs/modules/clock/clock_showSunMoon.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/* MagicMirror² Test config for default clock module
|
||||||
|
*
|
||||||
|
* By Johan Hammar
|
||||||
|
* MIT Licensed.
|
||||||
|
*/
|
||||||
|
let config = {
|
||||||
|
timeFormat: 12,
|
||||||
|
|
||||||
|
modules: [
|
||||||
|
{
|
||||||
|
module: "clock",
|
||||||
|
position: "middle_center",
|
||||||
|
config: {
|
||||||
|
showSunTimes: true,
|
||||||
|
showMoonTimes: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||||
|
if (typeof module !== "undefined") {
|
||||||
|
module.exports = config;
|
||||||
|
}
|
28
tests/configs/modules/compliments/compliments_animateCSS.js
Normal file
28
tests/configs/modules/compliments/compliments_animateCSS.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/* MagicMirror² Test config sample for AnimateCSS integration with compliments module
|
||||||
|
*
|
||||||
|
* By bugsounet https://github.com/bugsounet
|
||||||
|
* 09/2023
|
||||||
|
* MIT Licensed.
|
||||||
|
*/
|
||||||
|
let config = {
|
||||||
|
modules: [
|
||||||
|
{
|
||||||
|
module: "compliments",
|
||||||
|
position: "lower_third",
|
||||||
|
animateIn: "flipInX",
|
||||||
|
animateOut: "flipOutX",
|
||||||
|
config: {
|
||||||
|
compliments: {
|
||||||
|
anytime: ["AnimateCSS Testing..."]
|
||||||
|
},
|
||||||
|
updateInterval: 2000,
|
||||||
|
fadeSpeed: 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||||
|
if (typeof module !== "undefined") {
|
||||||
|
module.exports = config;
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/* MagicMirror² Test config sample for AnimateCSS integration with compliments module
|
||||||
|
* --> if animation name is not an AnimateCSS animation
|
||||||
|
* --> must fallback to default (no animation)
|
||||||
|
* By bugsounet https://github.com/bugsounet
|
||||||
|
* 09/2023
|
||||||
|
* MIT Licensed.
|
||||||
|
*/
|
||||||
|
let config = {
|
||||||
|
modules: [
|
||||||
|
{
|
||||||
|
module: "compliments",
|
||||||
|
position: "lower_third",
|
||||||
|
animateIn: "foo",
|
||||||
|
animateOut: "bar",
|
||||||
|
config: {
|
||||||
|
compliments: {
|
||||||
|
anytime: ["AnimateCSS Testing..."]
|
||||||
|
},
|
||||||
|
updateInterval: 2000,
|
||||||
|
fadeSpeed: 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||||
|
if (typeof module !== "undefined") {
|
||||||
|
module.exports = config;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* MagicMirror² Test config sample for AnimateCSS integration with compliments module
|
||||||
|
* --> inversed name animation : in for out and vice versa (must return no animation)
|
||||||
|
* By bugsounet https://github.com/bugsounet
|
||||||
|
* 09/2023
|
||||||
|
* MIT Licensed.
|
||||||
|
*/
|
||||||
|
let config = {
|
||||||
|
modules: [
|
||||||
|
{
|
||||||
|
module: "compliments",
|
||||||
|
position: "lower_third",
|
||||||
|
animateIn: "flipOutX",
|
||||||
|
animateOut: "flipInX",
|
||||||
|
config: {
|
||||||
|
compliments: {
|
||||||
|
anytime: ["AnimateCSS Testing..."]
|
||||||
|
},
|
||||||
|
updateInterval: 2000,
|
||||||
|
fadeSpeed: 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||||
|
if (typeof module !== "undefined") {
|
||||||
|
module.exports = config;
|
||||||
|
}
|
|
@ -4,7 +4,8 @@
|
||||||
* MIT Licensed.
|
* MIT Licensed.
|
||||||
*/
|
*/
|
||||||
let config = require(`${process.cwd()}/tests/configs/default.js`).configFactory({
|
let config = require(`${process.cwd()}/tests/configs/default.js`).configFactory({
|
||||||
ipWhitelist: ["x.x.x.x"]
|
ipWhitelist: ["x.x.x.x"],
|
||||||
|
port: 8181
|
||||||
});
|
});
|
||||||
|
|
||||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||||
|
|
78
tests/e2e/animateCSS_spec.js
Normal file
78
tests/e2e/animateCSS_spec.js
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/* AnimateCSS integration Test with compliments module
|
||||||
|
*
|
||||||
|
* By bugsounet https://github.com/bugsounet
|
||||||
|
* and helped by khassel
|
||||||
|
* 09/2023
|
||||||
|
* MIT Licensed.
|
||||||
|
*/
|
||||||
|
const helpers = require("./helpers/global-setup.js");
|
||||||
|
|
||||||
|
describe("AnimateCSS integration Test", () => {
|
||||||
|
// define config file for testing
|
||||||
|
let testConfigFile = "tests/configs/modules/compliments/compliments_animateCSS.js";
|
||||||
|
// define config file to fallback to default: wrong animation name (must return no animation)
|
||||||
|
let testConfigFileFallbackToDefault = "tests/configs/modules/compliments/compliments_animateCSS_fallbackToDefault.js";
|
||||||
|
// define config file with an inversed name animation : in for out and vice versa (must return no animation)
|
||||||
|
let testConfigFileInvertedAnimationName = "tests/configs/modules/compliments/compliments_animateCSS_invertedAnimationName.js";
|
||||||
|
// define config file with no animation defined
|
||||||
|
let testConfigByDefault = "tests/configs/modules/compliments/compliments_anytime.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* move similar tests in function doTest
|
||||||
|
* @param {string} [animationIn] animation in name of AnimateCSS to test.
|
||||||
|
* @param {string} [animationOut] animation out name of AnimateCSS to test.
|
||||||
|
*/
|
||||||
|
const doTest = async (animationIn, animationOut) => {
|
||||||
|
await helpers.getDocument();
|
||||||
|
let elem = await helpers.waitForElement(`.compliments`);
|
||||||
|
expect(elem).not.toBe(null);
|
||||||
|
let styles = window.getComputedStyle(elem);
|
||||||
|
|
||||||
|
if (animationIn && animationIn !== "") {
|
||||||
|
expect(styles._values["animation-name"]).toBe(animationIn);
|
||||||
|
} else {
|
||||||
|
expect(styles._values["animation-name"]).toBe(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animationOut && animationOut !== "") {
|
||||||
|
elem = await helpers.waitForElement(`.compliments.animate__animated.animate__${animationOut}`);
|
||||||
|
expect(elem).not.toBe(null);
|
||||||
|
styles = window.getComputedStyle(elem);
|
||||||
|
expect(styles._values["animation-name"]).toBe(animationOut);
|
||||||
|
} else {
|
||||||
|
expect(styles._values["animation-name"]).toBe(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await helpers.stopApplication();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("animateIn and animateOut Test", () => {
|
||||||
|
it("with flipInX and flipOutX animation", async () => {
|
||||||
|
await helpers.startApplication(testConfigFile);
|
||||||
|
await doTest("flipInX", "flipOutX");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("use animateOut name for animateIn (vice versa) Test", () => {
|
||||||
|
it("without animation", async () => {
|
||||||
|
await helpers.startApplication(testConfigFileInvertedAnimationName);
|
||||||
|
await doTest();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("false Animation name test", () => {
|
||||||
|
it("without animation", async () => {
|
||||||
|
await helpers.startApplication(testConfigFileFallbackToDefault);
|
||||||
|
await doTest();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("no Animation defined test", () => {
|
||||||
|
it("without animation", async () => {
|
||||||
|
await helpers.startApplication(testConfigByDefault);
|
||||||
|
await doTest();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -10,12 +10,12 @@ describe("App environment", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("get request from http://localhost:8080 should return 200", async () => {
|
it("get request from http://localhost:8080 should return 200", async () => {
|
||||||
const res = await helpers.fetch("http://localhost:8080");
|
const res = await fetch("http://localhost:8080");
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("get request from http://localhost:8080/nothing should return 404", async () => {
|
it("get request from http://localhost:8080/nothing should return 404", async () => {
|
||||||
const res = await helpers.fetch("http://localhost:8080/nothing");
|
const res = await fetch("http://localhost:8080/nothing");
|
||||||
expect(res.status).toBe(404);
|
expect(res.status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ describe("All font files from roboto.css should be downloadable", () => {
|
||||||
|
|
||||||
test.each(fontFiles)("should return 200 HTTP code for file '%s'", async (fontFile) => {
|
test.each(fontFiles)("should return 200 HTTP code for file '%s'", async (fontFile) => {
|
||||||
const fontUrl = `http://localhost:8080/fonts/${fontFile}`;
|
const fontUrl = `http://localhost:8080/fonts/${fontFile}`;
|
||||||
const res = await helpers.fetch(fontUrl);
|
const res = await fetch(fontUrl);
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
const jsdom = require("jsdom");
|
const jsdom = require("jsdom");
|
||||||
const corefetch = require("fetch");
|
|
||||||
|
|
||||||
exports.startApplication = async (configFilename, exec) => {
|
exports.startApplication = async (configFilename, exec) => {
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
|
@ -31,7 +30,8 @@ exports.getDocument = () => {
|
||||||
const url = `http://${config.address || "localhost"}:${config.port || "8080"}`;
|
const url = `http://${config.address || "localhost"}:${config.port || "8080"}`;
|
||||||
jsdom.JSDOM.fromURL(url, { resources: "usable", runScripts: "dangerously" }).then((dom) => {
|
jsdom.JSDOM.fromURL(url, { resources: "usable", runScripts: "dangerously" }).then((dom) => {
|
||||||
dom.window.name = "jsdom";
|
dom.window.name = "jsdom";
|
||||||
dom.window.fetch = corefetch;
|
global.window = dom.window;
|
||||||
|
dom.window.fetch = fetch;
|
||||||
dom.window.onload = () => {
|
dom.window.onload = () => {
|
||||||
global.document = dom.window.document;
|
global.document = dom.window.document;
|
||||||
resolve();
|
resolve();
|
||||||
|
@ -80,14 +80,6 @@ exports.waitForAllElements = (selector) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.fetch = (url) => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
corefetch(url).then((res) => {
|
|
||||||
resolve(res);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.testMatch = async (element, regex) => {
|
exports.testMatch = async (element, regex) => {
|
||||||
const elem = await this.waitForElement(element);
|
const elem = await this.waitForElement(element);
|
||||||
expect(elem).not.toBe(null);
|
expect(elem).not.toBe(null);
|
||||||
|
|
|
@ -13,7 +13,6 @@ exports.getText = async (element, result) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.startApp = async (configFileName, additionalMockData) => {
|
exports.startApp = async (configFileName, additionalMockData) => {
|
||||||
injectMockData(configFileName, additionalMockData);
|
await helpers.startApplication(injectMockData(configFileName, additionalMockData));
|
||||||
await helpers.startApplication("");
|
|
||||||
await helpers.getDocument();
|
await helpers.getDocument();
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,7 @@ describe("ipWhitelist directive configuration", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 403", async () => {
|
it("should return 403", async () => {
|
||||||
const res = await helpers.fetch("http://localhost:8080");
|
const res = await fetch("http://localhost:8181");
|
||||||
expect(res.status).toBe(403);
|
expect(res.status).toBe(403);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -24,7 +24,7 @@ describe("ipWhitelist directive configuration", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 200", async () => {
|
it("should return 200", async () => {
|
||||||
const res = await helpers.fetch("http://localhost:8080");
|
const res = await fetch("http://localhost:8282");
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -60,6 +60,10 @@ describe("Calendar module", () => {
|
||||||
await testElementLength(".calendar .event .fa-dice", 1);
|
await testElementLength(".calendar .event .fa-dice", 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should show a customEvent calendar eventClass in one event", async () => {
|
||||||
|
await testElementLength(".calendar .event.undo", 1);
|
||||||
|
});
|
||||||
|
|
||||||
it("should show two custom icons for repeating events", async () => {
|
it("should show two custom icons for repeating events", async () => {
|
||||||
await testElementLength(".calendar .event .fa-undo", 2);
|
await testElementLength(".calendar .event .fa-undo", 2);
|
||||||
});
|
});
|
||||||
|
@ -80,6 +84,17 @@ describe("Calendar module", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Events from multiple calendars", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await helpers.startApplication("tests/configs/modules/calendar/show-duplicates-in-calendar.js");
|
||||||
|
await helpers.getDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show multiple events with the same title and start time from different calendars", async () => {
|
||||||
|
await testElementLength(".calendar .event", 22);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
process.setMaxListeners(0);
|
process.setMaxListeners(0);
|
||||||
for (let i = -12; i < 12; i++) {
|
for (let i = -12; i < 12; i++) {
|
||||||
describe("Recurring event per timezone", () => {
|
describe("Recurring event per timezone", () => {
|
||||||
|
|
|
@ -71,11 +71,28 @@ describe("Clock module", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not show the time when digital clock is shown", async () => {
|
it("should not show the time when digital clock is shown", async () => {
|
||||||
const elem = await document.querySelector(".clock .digital .time");
|
const elem = document.querySelector(".clock .digital .time");
|
||||||
expect(elem).toBe(null);
|
expect(elem).toBe(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("with showSun/MoonTime enabled", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await helpers.startApplication("tests/configs/modules/clock/clock_showSunMoon.js");
|
||||||
|
await helpers.getDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show the sun times", async () => {
|
||||||
|
const elem = await helpers.waitForElement(".clock .digital .sun");
|
||||||
|
expect(elem).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show the moon times", async () => {
|
||||||
|
const elem = await helpers.waitForElement(".clock .digital .moon");
|
||||||
|
expect(elem).not.toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("with showWeek config enabled", () => {
|
describe("with showWeek config enabled", () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await helpers.startApplication("tests/configs/modules/clock/clock_showWeek.js");
|
await helpers.startApplication("tests/configs/modules/clock/clock_showWeek.js");
|
||||||
|
|
|
@ -25,8 +25,8 @@ describe("Newsfeed module", () => {
|
||||||
|
|
||||||
it("should NOT show the newsfeed description", async () => {
|
it("should NOT show the newsfeed description", async () => {
|
||||||
await helpers.waitForElement(".newsfeed");
|
await helpers.waitForElement(".newsfeed");
|
||||||
const element = document.querySelector(".newsfeed .newsfeed-desc");
|
const elem = document.querySelector(".newsfeed .newsfeed-desc");
|
||||||
expect(element).toBe(null);
|
expect(elem).toBe(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
const helpers = require("../helpers/global-setup");
|
const helpers = require("../helpers/global-setup");
|
||||||
const weatherFunc = require("../helpers/weather-functions");
|
const weatherFunc = require("../helpers/weather-functions");
|
||||||
|
const { cleanupMockData } = require("../../utils/weather_mocker");
|
||||||
|
|
||||||
describe("Weather module", () => {
|
describe("Weather module", () => {
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await helpers.stopApplication();
|
await helpers.stopApplication();
|
||||||
|
await cleanupMockData();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Current weather", () => {
|
describe("Current weather", () => {
|
||||||
|
@ -48,7 +50,7 @@ describe("Weather module", () => {
|
||||||
it("should render windDirection with an arrow", async () => {
|
it("should render windDirection with an arrow", async () => {
|
||||||
const elem = await helpers.waitForElement(".weather .normal.medium sup i.fa-long-arrow-alt-down");
|
const elem = await helpers.waitForElement(".weather .normal.medium sup i.fa-long-arrow-alt-down");
|
||||||
expect(elem).not.toBe(null);
|
expect(elem).not.toBe(null);
|
||||||
expect(elem.outerHTML).toContain("transform:rotate(250deg);");
|
expect(elem.outerHTML).toContain("transform:rotate(250deg)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render humidity", async () => {
|
it("should render humidity", async () => {
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
const helpers = require("../helpers/global-setup");
|
const helpers = require("../helpers/global-setup");
|
||||||
const weatherFunc = require("../helpers/weather-functions");
|
const weatherFunc = require("../helpers/weather-functions");
|
||||||
|
const { cleanupMockData } = require("../../utils/weather_mocker");
|
||||||
|
|
||||||
describe("Weather module: Weather Forecast", () => {
|
describe("Weather module: Weather Forecast", () => {
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await helpers.stopApplication();
|
await helpers.stopApplication();
|
||||||
|
await cleanupMockData();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Default configuration", () => {
|
describe("Default configuration", () => {
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
const helpers = require("../helpers/global-setup");
|
const helpers = require("../helpers/global-setup");
|
||||||
const weatherFunc = require("../helpers/weather-functions");
|
const weatherFunc = require("../helpers/weather-functions");
|
||||||
|
const { cleanupMockData } = require("../../utils/weather_mocker");
|
||||||
|
|
||||||
describe("Weather module: Weather Hourly Forecast", () => {
|
describe("Weather module: Weather Hourly Forecast", () => {
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await helpers.stopApplication();
|
await helpers.stopApplication();
|
||||||
|
await cleanupMockData();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Default configuration", () => {
|
describe("Default configuration", () => {
|
||||||
|
|
|
@ -10,7 +10,7 @@ describe("port directive configuration", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 200", async () => {
|
it("should return 200", async () => {
|
||||||
const res = await helpers.fetch("http://localhost:8090");
|
const res = await fetch("http://localhost:8090");
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -24,7 +24,7 @@ describe("port directive configuration", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 200", async () => {
|
it("should return 200", async () => {
|
||||||
const res = await helpers.fetch("http://localhost:8100");
|
const res = await fetch("http://localhost:8100");
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,12 +17,12 @@ describe("App environment", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("get request from http://localhost:8080 should return 200", async () => {
|
it("get request from http://localhost:8080 should return 200", async () => {
|
||||||
const res = await helpers.fetch("http://localhost:8080");
|
const res = await fetch("http://localhost:8080");
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("get request from http://localhost:8080/nothing should return 404", async () => {
|
it("get request from http://localhost:8080/nothing should return 404", async () => {
|
||||||
const res = await helpers.fetch("http://localhost:8080/nothing");
|
const res = await fetch("http://localhost:8080/nothing");
|
||||||
expect(res.status).toBe(404);
|
expect(res.status).toBe(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
const fs = require("fs");
|
||||||
const helpers = require("./helpers/global-setup");
|
const helpers = require("./helpers/global-setup");
|
||||||
|
|
||||||
describe("templated config with port variable", () => {
|
describe("templated config with port variable", () => {
|
||||||
|
@ -6,10 +7,15 @@ describe("templated config with port variable", () => {
|
||||||
});
|
});
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await helpers.stopApplication();
|
await helpers.stopApplication();
|
||||||
|
try {
|
||||||
|
fs.unlinkSync("tests/configs/port_variable.js");
|
||||||
|
} catch (err) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 200", async () => {
|
it("should return 200", async () => {
|
||||||
const res = await helpers.fetch("http://localhost:8090");
|
const res = await fetch("http://localhost:8090");
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,7 +14,7 @@ describe("Vendors", () => {
|
||||||
Object.keys(vendors).forEach((vendor) => {
|
Object.keys(vendors).forEach((vendor) => {
|
||||||
it(`should return 200 HTTP code for vendor "${vendor}"`, async () => {
|
it(`should return 200 HTTP code for vendor "${vendor}"`, async () => {
|
||||||
const urlVendor = `http://localhost:8080/vendor/${vendors[vendor]}`;
|
const urlVendor = `http://localhost:8080/vendor/${vendors[vendor]}`;
|
||||||
const res = await helpers.fetch(urlVendor);
|
const res = await fetch(urlVendor);
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -22,7 +22,7 @@ describe("Vendors", () => {
|
||||||
Object.keys(vendors).forEach((vendor) => {
|
Object.keys(vendors).forEach((vendor) => {
|
||||||
it(`should return 404 HTTP code for vendor https://localhost/"${vendor}"`, async () => {
|
it(`should return 404 HTTP code for vendor https://localhost/"${vendor}"`, async () => {
|
||||||
const urlVendor = `http://localhost:8080/${vendors[vendor]}`;
|
const urlVendor = `http://localhost:8080/${vendors[vendor]}`;
|
||||||
const res = await helpers.fetch(urlVendor);
|
const res = await fetch(urlVendor);
|
||||||
expect(res.status).toBe(404);
|
expect(res.status).toBe(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,7 +13,6 @@ exports.getText = async (element, result) => {
|
||||||
).toBe(result);
|
).toBe(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.startApp = async (configFileNameName, systemDate) => {
|
exports.startApp = async (configFileName, systemDate) => {
|
||||||
injectMockData(configFileNameName);
|
await helpers.startApplication(injectMockData(configFileName), systemDate);
|
||||||
await helpers.startApplication("", systemDate);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
const helpers = require("../helpers/global-setup");
|
const helpers = require("../helpers/global-setup");
|
||||||
const weatherHelper = require("../helpers/weather-setup");
|
const weatherHelper = require("../helpers/weather-setup");
|
||||||
|
const { cleanupMockData } = require("../../utils/weather_mocker");
|
||||||
|
|
||||||
describe("Weather module", () => {
|
describe("Weather module", () => {
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await helpers.stopApplication();
|
await helpers.stopApplication();
|
||||||
|
await cleanupMockData();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Current weather with sunrise", () => {
|
describe("Current weather with sunrise", () => {
|
||||||
|
|
190
tests/mocks/calendar_test_clone.ics
Normal file
190
tests/mocks/calendar_test_clone.ics
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Google Inc//Google Calendar 70.9054//EN
|
||||||
|
VERSION:2.0
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-WR-CALNAME:MagicMirrorTest
|
||||||
|
X-WR-TIMEZONE:America/Santiago
|
||||||
|
X-WR-CALDESC:Testing propose MagicMirror
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:America/Santiago
|
||||||
|
X-LIC-LOCATION:America/Santiago
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:-0300
|
||||||
|
TZOFFSETTO:-0400
|
||||||
|
TZNAME:-04
|
||||||
|
DTSTART:19700510T000000
|
||||||
|
RDATE:19700510T030000
|
||||||
|
RDATE:19710509T030000
|
||||||
|
RDATE:19720514T030000
|
||||||
|
RDATE:19730513T030000
|
||||||
|
RDATE:19740512T030000
|
||||||
|
RDATE:19750511T030000
|
||||||
|
RDATE:19760509T030000
|
||||||
|
RDATE:19770515T030000
|
||||||
|
RDATE:19780514T030000
|
||||||
|
RDATE:19790513T030000
|
||||||
|
RDATE:19800511T030000
|
||||||
|
RDATE:19810510T030000
|
||||||
|
RDATE:19820509T030000
|
||||||
|
RDATE:19830515T030000
|
||||||
|
RDATE:19840513T030000
|
||||||
|
RDATE:19850512T030000
|
||||||
|
RDATE:19860511T030000
|
||||||
|
RDATE:19870510T030000
|
||||||
|
RDATE:19880515T030000
|
||||||
|
RDATE:19890514T030000
|
||||||
|
RDATE:19900513T030000
|
||||||
|
RDATE:19910512T030000
|
||||||
|
RDATE:19920510T030000
|
||||||
|
RDATE:19930509T030000
|
||||||
|
RDATE:19940515T030000
|
||||||
|
RDATE:19950514T030000
|
||||||
|
RDATE:19960512T030000
|
||||||
|
RDATE:19970511T030000
|
||||||
|
RDATE:19980510T030000
|
||||||
|
RDATE:19990509T030000
|
||||||
|
RDATE:20000514T030000
|
||||||
|
RDATE:20010513T030000
|
||||||
|
RDATE:20020512T030000
|
||||||
|
RDATE:20030511T030000
|
||||||
|
RDATE:20040509T030000
|
||||||
|
RDATE:20050515T030000
|
||||||
|
RDATE:20060514T030000
|
||||||
|
RDATE:20070513T030000
|
||||||
|
RDATE:20080511T030000
|
||||||
|
RDATE:20090510T030000
|
||||||
|
RDATE:20100509T030000
|
||||||
|
RDATE:20110515T030000
|
||||||
|
RDATE:20120513T030000
|
||||||
|
RDATE:20130512T030000
|
||||||
|
RDATE:20140511T030000
|
||||||
|
RDATE:20150510T030000
|
||||||
|
RDATE:20160515T030000
|
||||||
|
RDATE:20170514T030000
|
||||||
|
RDATE:20180513T030000
|
||||||
|
RDATE:20190512T030000
|
||||||
|
RDATE:20200510T030000
|
||||||
|
RDATE:20210509T030000
|
||||||
|
RDATE:20220515T030000
|
||||||
|
RDATE:20230514T030000
|
||||||
|
RDATE:20240512T030000
|
||||||
|
RDATE:20250511T030000
|
||||||
|
RDATE:20260510T030000
|
||||||
|
RDATE:20270509T030000
|
||||||
|
RDATE:20280514T030000
|
||||||
|
RDATE:20290513T030000
|
||||||
|
RDATE:20300512T030000
|
||||||
|
RDATE:20310511T030000
|
||||||
|
RDATE:20320509T030000
|
||||||
|
RDATE:20330515T030000
|
||||||
|
RDATE:20340514T030000
|
||||||
|
RDATE:20350513T030000
|
||||||
|
RDATE:20360511T030000
|
||||||
|
RDATE:20370510T030000
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:-0300
|
||||||
|
TZOFFSETTO:-0400
|
||||||
|
TZNAME:-04
|
||||||
|
DTSTART:20380509T000000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=5;BYDAY=2SU
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:-0400
|
||||||
|
TZOFFSETTO:-0300
|
||||||
|
TZNAME:-03
|
||||||
|
DTSTART:19700809T000000
|
||||||
|
RDATE:19700809T040000
|
||||||
|
RDATE:19710815T040000
|
||||||
|
RDATE:19720813T040000
|
||||||
|
RDATE:19730812T040000
|
||||||
|
RDATE:19740811T040000
|
||||||
|
RDATE:19750810T040000
|
||||||
|
RDATE:19760815T040000
|
||||||
|
RDATE:19770814T040000
|
||||||
|
RDATE:19780813T040000
|
||||||
|
RDATE:19790812T040000
|
||||||
|
RDATE:19800810T040000
|
||||||
|
RDATE:19810809T040000
|
||||||
|
RDATE:19820815T040000
|
||||||
|
RDATE:19830814T040000
|
||||||
|
RDATE:19840812T040000
|
||||||
|
RDATE:19850811T040000
|
||||||
|
RDATE:19860810T040000
|
||||||
|
RDATE:19870809T040000
|
||||||
|
RDATE:19880814T040000
|
||||||
|
RDATE:19890813T040000
|
||||||
|
RDATE:19900812T040000
|
||||||
|
RDATE:19910811T040000
|
||||||
|
RDATE:19920809T040000
|
||||||
|
RDATE:19930815T040000
|
||||||
|
RDATE:19940814T040000
|
||||||
|
RDATE:19950813T040000
|
||||||
|
RDATE:19960811T040000
|
||||||
|
RDATE:19970810T040000
|
||||||
|
RDATE:19980809T040000
|
||||||
|
RDATE:19990815T040000
|
||||||
|
RDATE:20000813T040000
|
||||||
|
RDATE:20010812T040000
|
||||||
|
RDATE:20020811T040000
|
||||||
|
RDATE:20030810T040000
|
||||||
|
RDATE:20040815T040000
|
||||||
|
RDATE:20050814T040000
|
||||||
|
RDATE:20060813T040000
|
||||||
|
RDATE:20070812T040000
|
||||||
|
RDATE:20080810T040000
|
||||||
|
RDATE:20090809T040000
|
||||||
|
RDATE:20100815T040000
|
||||||
|
RDATE:20110814T040000
|
||||||
|
RDATE:20120812T040000
|
||||||
|
RDATE:20130811T040000
|
||||||
|
RDATE:20140810T040000
|
||||||
|
RDATE:20150809T040000
|
||||||
|
RDATE:20160814T040000
|
||||||
|
RDATE:20170813T040000
|
||||||
|
RDATE:20180812T040000
|
||||||
|
RDATE:20190811T040000
|
||||||
|
RDATE:20200809T040000
|
||||||
|
RDATE:20210815T040000
|
||||||
|
RDATE:20220814T040000
|
||||||
|
RDATE:20230813T040000
|
||||||
|
RDATE:20240811T040000
|
||||||
|
RDATE:20250810T040000
|
||||||
|
RDATE:20260809T040000
|
||||||
|
RDATE:20270815T040000
|
||||||
|
RDATE:20280813T040000
|
||||||
|
RDATE:20290812T040000
|
||||||
|
RDATE:20300811T040000
|
||||||
|
RDATE:20310810T040000
|
||||||
|
RDATE:20320815T040000
|
||||||
|
RDATE:20330814T040000
|
||||||
|
RDATE:20340813T040000
|
||||||
|
RDATE:20350812T040000
|
||||||
|
RDATE:20360810T040000
|
||||||
|
RDATE:20370809T040000
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:-0400
|
||||||
|
TZOFFSETTO:-0300
|
||||||
|
TZNAME:-03
|
||||||
|
DTSTART:20380815T000000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=8;BYDAY=2SU
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART;TZID=America/Santiago:20170309T100000
|
||||||
|
DTEND;TZID=America/Santiago:20170309T110000
|
||||||
|
RRULE:FREQ=MONTHLY;INTERVAL=30;BYMONTHDAY=9
|
||||||
|
DTSTAMP:20170310T172720Z
|
||||||
|
UID:80rl9kuu5bq49gme99eklov27k@google.com
|
||||||
|
CREATED:20170310T172400Z
|
||||||
|
DESCRIPTION:
|
||||||
|
LAST-MODIFIED:20170310T172400Z
|
||||||
|
LOCATION:
|
||||||
|
SEQUENCE:0
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
SUMMARY:TestEvent
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -8,13 +8,9 @@ describe("server_functions tests", () => {
|
||||||
let corsResponse;
|
let corsResponse;
|
||||||
let request;
|
let request;
|
||||||
|
|
||||||
jest.mock("node-fetch");
|
|
||||||
let nodefetch = require("node-fetch");
|
|
||||||
let fetchMock;
|
let fetchMock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
nodefetch.mockReset();
|
|
||||||
|
|
||||||
fetchResponseHeadersGet = jest.fn(() => {});
|
fetchResponseHeadersGet = jest.fn(() => {});
|
||||||
fetchResponseHeadersText = jest.fn(() => {});
|
fetchResponseHeadersText = jest.fn(() => {});
|
||||||
fetchResponse = {
|
fetchResponse = {
|
||||||
|
@ -23,10 +19,11 @@ describe("server_functions tests", () => {
|
||||||
},
|
},
|
||||||
text: fetchResponseHeadersText
|
text: fetchResponseHeadersText
|
||||||
};
|
};
|
||||||
jest.mock("node-fetch", () => jest.fn());
|
// eslint-disable-next-line
|
||||||
nodefetch.mockImplementation(() => fetchResponse);
|
fetch = jest.fn();
|
||||||
|
fetch.mockImplementation(() => fetchResponse);
|
||||||
|
|
||||||
fetchMock = nodefetch;
|
fetchMock = fetch;
|
||||||
|
|
||||||
corsResponse = {
|
corsResponse = {
|
||||||
set: jest.fn(() => {}),
|
set: jest.fn(() => {}),
|
||||||
|
|
|
@ -1,113 +1,107 @@
|
||||||
global.moment = require("moment-timezone");
|
global.moment = require("moment-timezone");
|
||||||
const { performWebRequest, formatTime } = require("../../../../modules/default/utils");
|
const { performWebRequest, formatTime } = require("../../../../modules/default/utils");
|
||||||
|
|
||||||
const nodeVersion = process.version.match(/^v(\d+)\.*/)[1];
|
|
||||||
|
|
||||||
describe("Default modules utils tests", () => {
|
describe("Default modules utils tests", () => {
|
||||||
describe("performWebRequest", () => {
|
describe("performWebRequest", () => {
|
||||||
if (nodeVersion > 18) {
|
const locationHost = "localhost:8080";
|
||||||
const locationHost = "localhost:8080";
|
const locationProtocol = "http";
|
||||||
const locationProtocol = "http";
|
|
||||||
|
|
||||||
let fetchResponse;
|
let fetchResponse;
|
||||||
let fetchMock;
|
let fetchMock;
|
||||||
let urlToCall;
|
let urlToCall;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fetchResponse = new Response();
|
fetchResponse = new Response();
|
||||||
global.fetch = jest.fn(() => Promise.resolve(fetchResponse));
|
global.fetch = jest.fn(() => Promise.resolve(fetchResponse));
|
||||||
fetchMock = global.fetch;
|
fetchMock = global.fetch;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("When using cors proxy", () => {
|
||||||
|
Object.defineProperty(global, "location", {
|
||||||
|
value: {
|
||||||
|
host: locationHost,
|
||||||
|
protocol: locationProtocol
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("When using cors proxy", () => {
|
test("Calls correct URL once", async () => {
|
||||||
Object.defineProperty(global, "location", {
|
urlToCall = "http://www.test.com/path?param1=value1";
|
||||||
value: {
|
|
||||||
host: locationHost,
|
|
||||||
protocol: locationProtocol
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Calls correct URL once", async () => {
|
await performWebRequest(urlToCall, "json", true);
|
||||||
urlToCall = "http://www.test.com/path?param1=value1";
|
|
||||||
|
|
||||||
await performWebRequest(urlToCall, "json", true);
|
expect(fetchMock.mock.calls.length).toBe(1);
|
||||||
|
expect(fetchMock.mock.calls[0][0]).toBe(`${locationProtocol}//${locationHost}/cors?url=${urlToCall}`);
|
||||||
expect(fetchMock.mock.calls.length).toBe(1);
|
|
||||||
expect(fetchMock.mock.calls[0][0]).toBe(`${locationProtocol}//${locationHost}/cors?url=${urlToCall}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Sends correct headers", async () => {
|
|
||||||
urlToCall = "http://www.test.com/path?param1=value1";
|
|
||||||
|
|
||||||
const headers = [
|
|
||||||
{ name: "header1", value: "value1" },
|
|
||||||
{ name: "header2", value: "value2" }
|
|
||||||
];
|
|
||||||
|
|
||||||
await performWebRequest(urlToCall, "json", true, headers);
|
|
||||||
|
|
||||||
expect(fetchMock.mock.calls.length).toBe(1);
|
|
||||||
expect(fetchMock.mock.calls[0][0]).toBe(`${locationProtocol}//${locationHost}/cors?sendheaders=header1:value1,header2:value2&url=${urlToCall}`);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("When not using cors proxy", () => {
|
test("Sends correct headers", async () => {
|
||||||
test("Calls correct URL once", async () => {
|
urlToCall = "http://www.test.com/path?param1=value1";
|
||||||
urlToCall = "http://www.test.com/path?param1=value1";
|
|
||||||
|
|
||||||
await performWebRequest(urlToCall);
|
const headers = [
|
||||||
|
{ name: "header1", value: "value1" },
|
||||||
|
{ name: "header2", value: "value2" }
|
||||||
|
];
|
||||||
|
|
||||||
expect(fetchMock.mock.calls.length).toBe(1);
|
await performWebRequest(urlToCall, "json", true, headers);
|
||||||
expect(fetchMock.mock.calls[0][0]).toBe(urlToCall);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Sends correct headers", async () => {
|
expect(fetchMock.mock.calls.length).toBe(1);
|
||||||
urlToCall = "http://www.test.com/path?param1=value1";
|
expect(fetchMock.mock.calls[0][0]).toBe(`${locationProtocol}//${locationHost}/cors?sendheaders=header1:value1,header2:value2&url=${urlToCall}`);
|
||||||
const headers = [
|
});
|
||||||
{ name: "header1", value: "value1" },
|
});
|
||||||
{ name: "header2", value: "value2" }
|
|
||||||
];
|
|
||||||
|
|
||||||
await performWebRequest(urlToCall, "json", false, headers);
|
describe("When not using cors proxy", () => {
|
||||||
|
test("Calls correct URL once", async () => {
|
||||||
|
urlToCall = "http://www.test.com/path?param1=value1";
|
||||||
|
|
||||||
const expectedHeaders = { headers: { header1: "value1", header2: "value2" } };
|
await performWebRequest(urlToCall);
|
||||||
expect(fetchMock.mock.calls.length).toBe(1);
|
|
||||||
expect(fetchMock.mock.calls[0][1]).toStrictEqual(expectedHeaders);
|
expect(fetchMock.mock.calls.length).toBe(1);
|
||||||
});
|
expect(fetchMock.mock.calls[0][0]).toBe(urlToCall);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("When receiving json format", () => {
|
test("Sends correct headers", async () => {
|
||||||
test("Returns undefined when no data is received", async () => {
|
urlToCall = "http://www.test.com/path?param1=value1";
|
||||||
urlToCall = "www.test.com";
|
const headers = [
|
||||||
|
{ name: "header1", value: "value1" },
|
||||||
|
{ name: "header2", value: "value2" }
|
||||||
|
];
|
||||||
|
|
||||||
const response = await performWebRequest(urlToCall);
|
await performWebRequest(urlToCall, "json", false, headers);
|
||||||
|
|
||||||
expect(response).toBe(undefined);
|
const expectedHeaders = { headers: { header1: "value1", header2: "value2" } };
|
||||||
});
|
expect(fetchMock.mock.calls.length).toBe(1);
|
||||||
|
expect(fetchMock.mock.calls[0][1]).toStrictEqual(expectedHeaders);
|
||||||
test("Returns object when data is received", async () => {
|
|
||||||
urlToCall = "www.test.com";
|
|
||||||
fetchResponse = new Response('{"body": "some content"}');
|
|
||||||
|
|
||||||
const response = await performWebRequest(urlToCall);
|
|
||||||
|
|
||||||
expect(response.body).toBe("some content");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Returns expected headers when data is received", async () => {
|
|
||||||
urlToCall = "www.test.com";
|
|
||||||
fetchResponse = new Response('{"body": "some content"}', { headers: { header1: "value1", header2: "value2" } });
|
|
||||||
|
|
||||||
const response = await performWebRequest(urlToCall, "json", false, undefined, ["header1"]);
|
|
||||||
|
|
||||||
expect(response.headers.length).toBe(1);
|
|
||||||
expect(response.headers[0].name).toBe("header1");
|
|
||||||
expect(response.headers[0].value).toBe("value1");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else {
|
});
|
||||||
test("Always ok, need one test", () => {});
|
|
||||||
}
|
describe("When receiving json format", () => {
|
||||||
|
test("Returns undefined when no data is received", async () => {
|
||||||
|
urlToCall = "www.test.com";
|
||||||
|
|
||||||
|
const response = await performWebRequest(urlToCall);
|
||||||
|
|
||||||
|
expect(response).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Returns object when data is received", async () => {
|
||||||
|
urlToCall = "www.test.com";
|
||||||
|
fetchResponse = new Response('{"body": "some content"}');
|
||||||
|
|
||||||
|
const response = await performWebRequest(urlToCall);
|
||||||
|
|
||||||
|
expect(response.body).toBe("some content");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Returns expected headers when data is received", async () => {
|
||||||
|
urlToCall = "www.test.com";
|
||||||
|
fetchResponse = new Response('{"body": "some content"}', { headers: { header1: "value1", header2: "value2" } });
|
||||||
|
|
||||||
|
const response = await performWebRequest(urlToCall, "json", false, undefined, ["header1"]);
|
||||||
|
|
||||||
|
expect(response.headers.length).toBe(1);
|
||||||
|
expect(response.headers[0].name).toBe("header1");
|
||||||
|
expect(response.headers[0].value).toBe("value1");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("formatTime", () => {
|
describe("formatTime", () => {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const util = require("util");
|
||||||
|
const exec = util.promisify(require("child_process").exec);
|
||||||
const _ = require("lodash");
|
const _ = require("lodash");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,9 +37,16 @@ const injectMockData = (configFileName, extendedData = {}) => {
|
||||||
} else {
|
} else {
|
||||||
mockWeather = readMockData("current", extendedData);
|
mockWeather = readMockData("current", extendedData);
|
||||||
}
|
}
|
||||||
let content = fs.readFileSync(path.resolve(`${__dirname}../../../${configFileName}`)).toString();
|
let content = fs.readFileSync(configFileName).toString();
|
||||||
content = content.replace("#####WEATHERDATA#####", mockWeather);
|
content = content.replace("#####WEATHERDATA#####", mockWeather);
|
||||||
fs.writeFileSync(path.resolve(`${__dirname}../../../config/config.js`), content);
|
const tempFile = configFileName.replace(".js", "_temp.js");
|
||||||
|
fs.writeFileSync(tempFile, content);
|
||||||
|
return tempFile;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { injectMockData };
|
const cleanupMockData = async () => {
|
||||||
|
const tempDir = path.resolve(`${__dirname}/../configs`).toString();
|
||||||
|
await exec(`find ${tempDir} -type f -name *_temp.js -delete`);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { injectMockData, cleanupMockData };
|
||||||
|
|
|
@ -29,9 +29,16 @@
|
||||||
|
|
||||||
"FEELS": "Ressenti {DEGREE}",
|
"FEELS": "Ressenti {DEGREE}",
|
||||||
"PRECIP_POP": "Probabilité de précipitations",
|
"PRECIP_POP": "Probabilité de précipitations",
|
||||||
|
"PRECIP_AMOUNT": "Quantité des précipitations",
|
||||||
|
|
||||||
"MODULE_CONFIG_CHANGED": "Les options de configuration du module {MODULE_NAME} ont changé.\nVeuillez consulter la documentation.",
|
"MODULE_CONFIG_CHANGED": "Les options de configuration du module {MODULE_NAME} ont changé.\nVeuillez consulter la documentation.",
|
||||||
"MODULE_CONFIG_ERROR": "Erreur dans le module {MODULE_NAME}. {ERROR}",
|
"MODULE_CONFIG_ERROR": "Erreur dans le module {MODULE_NAME}. {ERROR}",
|
||||||
|
"MODULE_ERROR_MALFORMED_URL": "URL mal formée.",
|
||||||
|
"MODULE_ERROR_NO_CONNECTION": "Pas de connexion Internet.",
|
||||||
|
"MODULE_ERROR_UNAUTHORIZED": "L'autorisation à échouée.",
|
||||||
|
"MODULE_ERROR_UNSPECIFIED": "Consultez les journaux pour plus de détails.",
|
||||||
|
|
||||||
|
"NEWSFEED_NO_ITEMS": "Aucune nouvelle pour le moment.",
|
||||||
|
|
||||||
"UPDATE_NOTIFICATION": "Une mise à jour de MagicMirror² est disponible",
|
"UPDATE_NOTIFICATION": "Une mise à jour de MagicMirror² est disponible",
|
||||||
"UPDATE_NOTIFICATION_MODULE": "Une mise à jour est disponible pour le module {MODULE_NAME}.",
|
"UPDATE_NOTIFICATION_MODULE": "Une mise à jour est disponible pour le module {MODULE_NAME}.",
|
||||||
|
|
25
vendor/package-lock.json
generated
vendored
25
vendor/package-lock.json
generated
vendored
|
@ -7,7 +7,8 @@
|
||||||
"name": "magicmirror-vendors",
|
"name": "magicmirror-vendors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||||
|
"animate.css": "^4.1.1",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"moment-timezone": "^0.5.43",
|
"moment-timezone": "^0.5.43",
|
||||||
"nunjucks": "^3.2.4",
|
"nunjucks": "^3.2.4",
|
||||||
|
@ -16,9 +17,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fortawesome/fontawesome-free": {
|
"node_modules/@fortawesome/fontawesome-free": {
|
||||||
"version": "6.4.0",
|
"version": "6.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz",
|
||||||
"integrity": "sha512-0NyytTlPJwB/BF5LtRV8rrABDbe3TdTXqNB3PdZ+UUUZAEIrdOJdmABqKjt4AXwIoJNaRVVZEXxpNrqvE1GAYQ==",
|
"integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
|
@ -29,6 +30,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
|
||||||
"integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA=="
|
"integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/animate.css": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ=="
|
||||||
|
},
|
||||||
"node_modules/asap": {
|
"node_modules/asap": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||||
|
@ -98,15 +104,20 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": {
|
"@fortawesome/fontawesome-free": {
|
||||||
"version": "6.4.0",
|
"version": "6.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz",
|
||||||
"integrity": "sha512-0NyytTlPJwB/BF5LtRV8rrABDbe3TdTXqNB3PdZ+UUUZAEIrdOJdmABqKjt4AXwIoJNaRVVZEXxpNrqvE1GAYQ=="
|
"integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg=="
|
||||||
},
|
},
|
||||||
"a-sync-waterfall": {
|
"a-sync-waterfall": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
|
||||||
"integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA=="
|
"integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA=="
|
||||||
},
|
},
|
||||||
|
"animate.css": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ=="
|
||||||
|
},
|
||||||
"asap": {
|
"asap": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||||
|
|
3
vendor/package.json
vendored
3
vendor/package.json
vendored
|
@ -10,7 +10,8 @@
|
||||||
"url": "https://github.com/MichMich/MagicMirror/issues"
|
"url": "https://github.com/MichMich/MagicMirror/issues"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||||
|
"animate.css": "^4.1.1",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"moment-timezone": "^0.5.43",
|
"moment-timezone": "^0.5.43",
|
||||||
"nunjucks": "^3.2.4",
|
"nunjucks": "^3.2.4",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue