diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6337c8dc1d..9fd4d4a921 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -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). -**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. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md index 047a22685c..7cbf0076b4 100644 --- a/.github/ISSUE_TEMPLATE/custom.md +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -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: [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 @@ -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). -**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. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index da43f700a5..ace1e86435 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -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:** > 1. Base your pull requests against the `develop` branch. -> > 2. Include these infos in the description: > > - 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 > style issues are fixed. -> > 4. Don't forget to add an entry about your changes to > the CHANGELOG.md file. diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 38ae723d5e..9e699b7e65 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -5,3 +5,21 @@ updates: schedule: interval: "weekly" 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" diff --git a/.github/workflows/automated-tests.yaml b/.github/workflows/automated-tests.yaml index d2b1b77787..959f43f5cd 100644 --- a/.github/workflows/automated-tests.yaml +++ b/.github/workflows/automated-tests.yaml @@ -18,10 +18,10 @@ jobs: timeout-minutes: 30 strategy: matrix: - node-version: [16.x, 18.x, 20.x] + node-version: [18.x, 20.x] steps: - name: "Checkout code" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Use Node.js ${{ matrix.node-version }}" uses: actions/setup-node@v3 with: diff --git a/.github/workflows/codecov-test-suites.yaml b/.github/workflows/codecov-test-suites.yaml index 7d99843c19..44244bac69 100644 --- a/.github/workflows/codecov-test-suites.yaml +++ b/.github/workflows/codecov-test-suites.yaml @@ -18,7 +18,7 @@ jobs: timeout-minutes: 30 steps: - name: "Checkout code" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Install dependencies" run: | npm ci diff --git a/.github/workflows/depsreview.yaml b/.github/workflows/depsreview.yaml index 308f0017d0..7d8866d89b 100644 --- a/.github/workflows/depsreview.yaml +++ b/.github/workflows/depsreview.yaml @@ -13,6 +13,6 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout code" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Dependency Review" uses: actions/dependency-review-action@v3 diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..386ea9aec8 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +engine-strict=true +audit=false diff --git a/CHANGELOG.md b/CHANGELOG.md index c719848bfa..859bd590fb 100644 --- a/CHANGELOG.md +++ b/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². +## [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 () +- 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 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 envcanada today percentage-of-precipitation (#3106) - 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 diff --git a/clientonly/index.js b/clientonly/index.js index 479d14bb86..e13b0873dd 100644 --- a/clientonly/index.js +++ b/clientonly/index.js @@ -84,6 +84,7 @@ .then(function (configReturn) { // Pass along the server config via an environment variable const env = Object.create(process.env); + env.clientonly = true; // set to pass to electron.js const options = { env: env }; configReturn.address = config.address; configReturn.port = config.port; diff --git a/fonts/package-lock.json b/fonts/package-lock.json index c35712f59c..1324ec673c 100644 --- a/fonts/package-lock.json +++ b/fonts/package-lock.json @@ -7,31 +7,31 @@ "name": "magicmirror-fonts", "license": "MIT", "dependencies": { - "@fontsource/roboto": "^4.5.8", - "@fontsource/roboto-condensed": "^4.5.9" + "@fontsource/roboto": "^5.0.8", + "@fontsource/roboto-condensed": "^5.0.8" } }, "node_modules/@fontsource/roboto": { - "version": "4.5.8", - "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz", - "integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.8.tgz", + "integrity": "sha512-XxPltXs5R31D6UZeLIV1td3wTXU3jzd3f2DLsXI8tytMGBkIsGcc9sIyiupRtA8y73HAhuSCeweOoBqf6DbWCA==" }, "node_modules/@fontsource/roboto-condensed": { - "version": "4.5.9", - "resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.9.tgz", - "integrity": "sha512-ql4sQq+h8puBVildZ5ssjYf8DWDONYDe3PD3Bu/p1ZW9GnRETRNPPcCTs/q62HIl3QimwwkiKWynn6wZhQaetg==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-5.0.8.tgz", + "integrity": "sha512-xAXYY+ys24OZ/eOfXJZILPu2xOB7c0ZruM4cd4TSzX3WGj4dZbXYwCEowLldKbZye6LTqiltpFLP/g/Ne0qGLg==" } }, "dependencies": { "@fontsource/roboto": { - "version": "4.5.8", - "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz", - "integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.8.tgz", + "integrity": "sha512-XxPltXs5R31D6UZeLIV1td3wTXU3jzd3f2DLsXI8tytMGBkIsGcc9sIyiupRtA8y73HAhuSCeweOoBqf6DbWCA==" }, "@fontsource/roboto-condensed": { - "version": "4.5.9", - "resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.9.tgz", - "integrity": "sha512-ql4sQq+h8puBVildZ5ssjYf8DWDONYDe3PD3Bu/p1ZW9GnRETRNPPcCTs/q62HIl3QimwwkiKWynn6wZhQaetg==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-5.0.8.tgz", + "integrity": "sha512-xAXYY+ys24OZ/eOfXJZILPu2xOB7c0ZruM4cd4TSzX3WGj4dZbXYwCEowLldKbZye6LTqiltpFLP/g/Ne0qGLg==" } } } diff --git a/fonts/package.json b/fonts/package.json index 2d09468939..0bb46d5715 100644 --- a/fonts/package.json +++ b/fonts/package.json @@ -10,7 +10,7 @@ "url": "https://github.com/MichMich/MagicMirror/issues" }, "dependencies": { - "@fontsource/roboto": "^4.5.8", - "@fontsource/roboto-condensed": "^4.5.9" + "@fontsource/roboto": "^5.0.8", + "@fontsource/roboto-condensed": "^5.0.8" } } diff --git a/fonts/roboto.css b/fonts/roboto.css index 40c643903c..bb760f8d8e 100644 --- a/fonts/roboto.css +++ b/fonts/roboto.css @@ -1,55 +1,671 @@ +/* roboto-cyrillic-ext-100-normal */ @font-face { font-family: Roboto; font-style: normal; + font-display: var(--fontsource-display, swap); 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-family: "Roboto Condensed"; + font-family: Roboto; + 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; - 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"); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } +/* roboto-cyrillic-300-normal */ @font-face { - font-family: "Roboto Condensed"; + font-family: Roboto; + font-style: normal; + font-display: var(--fontsource-display, swap); + 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-family: Roboto; + 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: local("Roboto Condensed"), local("RobotoCondensed-Regular"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-all-400-normal.woff") format("woff"); + 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 Condensed"; + font-family: Roboto; 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"); + 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: local("Roboto"), local("Roboto-Regular"), url("node_modules/@fontsource/roboto/files/roboto-all-400-normal.woff") format("woff"); + 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; + 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-family: Roboto; + 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: 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-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; + 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-family: Roboto; + font-style: normal; + font-display: var(--fontsource-display, swap); + 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: 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-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: local("Roboto Light"), local("Roboto-Light"), url("node_modules/@fontsource/roboto/files/roboto-all-300-normal.woff") format("woff"); + 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; } diff --git a/index.html b/index.html index 37fc2f6efb..b97124be10 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - + MagicMirror² @@ -13,6 +13,7 @@ + + @@ -52,6 +54,7 @@ + diff --git a/js/animateCSS.js b/js/animateCSS.js new file mode 100644 index 0000000000..cf3f26bcb7 --- /dev/null +++ b/js/animateCSS.js @@ -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"); +} diff --git a/js/defaults.js b/js/defaults.js index b2edb8e4ff..c8f849587b 100644 --- a/js/defaults.js +++ b/js/defaults.js @@ -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 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: [ { module: "updatenotification", diff --git a/js/electron.js b/js/electron.js index 8a7c28148f..43f637acbb 100644 --- a/js/electron.js +++ b/js/electron.js @@ -25,11 +25,20 @@ let mainWindow; * */ 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"]; app.commandLine.appendSwitch(...new Set(electronSwitchesDefaults, config.electronSwitches)); let electronOptionsDefaults = { - width: 800, - height: 600, + width: electronSize.width, + height: electronSize.height, x: 0, y: 0, darkTheme: true, @@ -50,6 +59,7 @@ function createWindow() { electronOptionsDefaults.frame = false; electronOptionsDefaults.transparent = true; electronOptionsDefaults.hasShadow = false; + electronOptionsDefaults.fullscreen = true; } const electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions); @@ -121,7 +131,6 @@ function createWindow() { }); mainWindow.once("ready-to-show", () => { - mainWindow.setFullScreen(true); mainWindow.show(); }); } @@ -168,6 +177,13 @@ app.on("certificate-error", (event, webContents, url, error, certificate, callba 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 // 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)) { diff --git a/js/fetch.js b/js/fetch.js deleted file mode 100644 index b549d9ed62..0000000000 --- a/js/fetch.js +++ /dev/null @@ -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; diff --git a/js/loader.js b/js/loader.js index 517999bb86..a5dc36fcab 100644 --- a/js/loader.js +++ b/js/loader.js @@ -88,6 +88,8 @@ const Loader = (function () { path: `${moduleFolder}/`, file: `${moduleName}.js`, position: moduleData.position, + animateIn: moduleData.animateIn, + animateOut: moduleData.animateOut, hiddenOnStartup: moduleData.hiddenOnStartup, header: moduleData.header, configDeepMerge: typeof moduleData.configDeepMerge === "boolean" ? moduleData.configDeepMerge : false, diff --git a/js/main.js b/js/main.js index 026410c777..c7d4d39f77 100644 --- a/js/main.js +++ b/js/main.js @@ -1,4 +1,4 @@ -/* global Loader, defaults, Translator */ +/* global Loader, defaults, Translator, addAnimateCSS, removeAnimateCSS, AnimateCSSIn, AnimateCSSOut */ /* MagicMirror² * Main System @@ -22,6 +22,10 @@ const MM = (function () { 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 dom = document.createElement("div"); @@ -50,7 +54,12 @@ const MM = (function () { moduleContent.className = "module-content"; 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); domCreationPromise .then(function () { @@ -101,11 +110,30 @@ const MM = (function () { /** * Update the dom for a specific module. * @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. */ - const updateDom = function (module, speed) { + const updateDom = function (module, updateOptions, createAnimatedDom = false) { 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(); let newContentPromise = module.getDom(); @@ -116,7 +144,7 @@ const MM = (function () { newContentPromise .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); }) @@ -130,9 +158,12 @@ const MM = (function () { * @param {number} [speed] The (optional) number of microseconds for the animation. * @param {string} newHeader The new header 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. */ - const updateDomWithContent = function (module, speed, newHeader, newContent) { + const updateDomWithContent = function (module, speed, newHeader, newContent, animateOut, animateIn, createAnimatedDom = false) { return new Promise(function (resolve) { if (module.hidden || !speed) { updateModuleContent(module, newHeader, newContent); @@ -151,13 +182,28 @@ const MM = (function () { return; } - hideModule(module, speed / 2, function () { + if (createAnimatedDom && animateIn !== null) { + Log.debug(`${module.identifier} createAnimatedDom (${animateIn})`); updateModuleContent(module, newHeader, newContent); if (!module.hidden) { - showModule(module, speed / 2); + showModule(module, speed, null, { animate: animateIn }); } 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); if (moduleWrapper !== null) { - moduleWrapper.style.transition = `opacity ${speed / 1000}s`; - moduleWrapper.style.opacity = 0; - moduleWrapper.classList.add("hidden"); - clearTimeout(module.showHideTimer); - 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); + // reset all animations if needed + if (module.hasAnimateOut) { + removeAnimateCSS(module.identifier, module.hasAnimateOut); + Log.debug(`${module.identifier} Force remove animateOut (in hide): ${module.hasAnimateOut}`); + module.hasAnimateOut = false; + } + 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; + + 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; + + updateWrapperStates(); + if (typeof callback === "function") { + callback(); + } + }, 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 { // invoke callback even if no content, issue 1308 if (typeof callback === "function") { @@ -285,6 +372,17 @@ const MM = (function () { } 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; @@ -296,7 +394,18 @@ const MM = (function () { const moduleWrapper = document.getElementById(module.identifier); 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. moduleWrapper.style.position = "static"; moduleWrapper.classList.remove("hidden"); @@ -307,12 +416,27 @@ const MM = (function () { const dummy = moduleWrapper.parentElement.parentElement.offsetHeight; moduleWrapper.style.opacity = 1; - clearTimeout(module.showHideTimer); - module.showHideTimer = setTimeout(function () { - if (typeof callback === "function") { - callback(); - } - }, speed); + if (haveAnimateName) { + // with AnimateCSS + Log.debug(`${module.identifier} Has animateIn: ${haveAnimateName}`); + module.hasAnimateIn = haveAnimateName; + addAnimateCSS(module.identifier, haveAnimateName, speed / 1000); + 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 { // invoke callback if (typeof callback === "function") { @@ -477,12 +601,33 @@ const MM = (function () { */ modulesStarted: function (moduleObjects) { modules = []; + let startUp = ""; + moduleObjects.forEach((module) => modules.push(module)); Log.info("All modules started!"); sendNotification("ALL_MODULES_STARTED"); 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. * @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)) { Log.error("updateDom: Sender should be a module."); return; @@ -528,7 +673,7 @@ const MM = (function () { } // Further implementation is done in the private method. - updateDom(module, speed); + updateDom(module, updateOptions); }, /** diff --git a/js/module.js b/js/module.js index 110ccc5595..4ef3ab1d61 100644 --- a/js/module.js +++ b/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.identifier = data.identifier; this.hidden = false; + this.hasAnimateIn = false; + this.hasAnimateOut = false; this.setConfig(data.config, data.configDeepMerge); }, @@ -327,10 +329,10 @@ const Module = Class.extend({ /** * 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) { - MM.updateDom(this, speed); + updateDom: function (updateOptions) { + MM.updateDom(this, updateOptions); }, /** diff --git a/js/server.js b/js/server.js index 771870f244..0cb1b92286 100644 --- a/js/server.js +++ b/js/server.js @@ -15,7 +15,7 @@ const socketio = require("socket.io"); const Log = require("logger"); const Utils = require("./utils"); -const { cors, getConfig, getHtml, getVersion } = require("./server_functions"); +const { cors, getConfig, getHtml, getVersion, getStartup } = require("./server_functions"); /** * Server @@ -91,6 +91,8 @@ function Server(config) { app.get("/config", (req, res) => getConfig(req, res)); + app.get("/startup", (req, res) => getStartup(req, res)); + app.get("/", (req, res) => getHtml(req, res)); server.on("listening", () => { diff --git a/js/server_functions.js b/js/server_functions.js index 8e9d9aa91d..5693ad41c4 100644 --- a/js/server_functions.js +++ b/js/server_functions.js @@ -1,7 +1,7 @@ const fs = require("fs"); const path = require("path"); const Log = require("logger"); -const fetch = require("./fetch"); +const startUp = new Date(); /** * Gets the config. @@ -12,6 +12,15 @@ function getConfig(req, res) { 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. * @@ -118,4 +127,4 @@ function getVersion(req, res) { res.send(global.version); } -module.exports = { cors, getConfig, getHtml, getVersion }; +module.exports = { cors, getConfig, getHtml, getVersion, getStartup }; diff --git a/module-types.ts b/module-types.ts index be1a2061dd..440b2c4959 100644 --- a/module-types.ts +++ b/module-types.ts @@ -1,16 +1,19 @@ type ModuleProperties = { defaults?: object; + [key: string]: any; start?(): void; + getScripts?(): string[]; + getStyles?(): string[]; + getTranslations?(): object; + getDom?(): HTMLElement; getHeader?(): string; getTemplate?(): string; getTemplateData?(): object; notificationReceived?(notification: string, payload: any, sender: object): void; + nunjucksEnvironment?(): void; socketNotificationReceived?(notification: string, payload: any): void; suspend?(): void; resume?(): void; - getDom?(): HTMLElement; - getStyles?(): string[]; - [key: string]: any; }; export declare const Module: { diff --git a/modules/default/alert/templates/alert.njk b/modules/default/alert/templates/alert.njk index 7349a7ae3d..b7db3a4de4 100644 --- a/modules/default/alert/templates/alert.njk +++ b/modules/default/alert/templates/alert.njk @@ -1,18 +1,20 @@ {% if imageUrl or imageFA %} - {% set imageHeight = imageHeight if imageHeight else "80px" %} - {% if imageUrl %} - - {% else %} - - {% endif %} -
+ {% set imageHeight = imageHeight if imageHeight else "80px" %} + {% if imageUrl %} + + {% else %} + + {% endif %} +
{% endif %} {% if title %} - {{ title if titleType == 'text' else title | safe }} + {{ title if titleType == 'text' else title | safe }} {% endif %} {% if message %} - {% if title %} -
- {% endif %} - {{ message if messageType == 'text' else message | safe }} + {% if title %}
{% endif %} + {{ message if messageType == 'text' else message | safe }} {% endif %} diff --git a/modules/default/alert/templates/notification.njk b/modules/default/alert/templates/notification.njk index 1594ad4866..0de6908b26 100644 --- a/modules/default/alert/templates/notification.njk +++ b/modules/default/alert/templates/notification.njk @@ -1,9 +1,7 @@ {% if title %} - {{ title if titleType == 'text' else title | safe }} + {{ title if titleType == 'text' else title | safe }} {% endif %} {% if message %} - {% if title %} -
- {% endif %} - {{ message if messageType == 'text' else message | safe }} + {% if title %}
{% endif %} + {{ message if messageType == 'text' else message | safe }} {% endif %} diff --git a/modules/default/calendar/calendar.js b/modules/default/calendar/calendar.js index 3343d347ad..681177d514 100644 --- a/modules/default/calendar/calendar.js +++ b/modules/default/calendar/calendar.js @@ -39,9 +39,10 @@ Module.register("calendar", { hidePrivate: false, hideOngoing: false, hideTime: false, + hideDuplicates: true, showTimeToday: 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", calendars: [ { @@ -154,11 +155,14 @@ Module.register("calendar", { // 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. - setTimeout(() => { - setInterval(() => { - this.updateDom(1); - }, ONE_MINUTE); - }, ONE_MINUTE - (new Date() % ONE_MINUTE)); + setTimeout( + () => { + setInterval(() => { + this.updateDom(1); + }, ONE_MINUTE); + }, + ONE_MINUTE - (new Date() % ONE_MINUTE) + ); }, // 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) { 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"); - if (needle.test(event.title)) { + let needle = new RegExp(this.config.customEvents[ev].keyword, "gi"); + 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 if (this.config.coloredText) { eventWrapper.style.cssText = `color:${this.config.customEvents[ev].color}`; @@ -333,6 +337,9 @@ Module.register("calendar", { } 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) { continue; } - if (this.listContainsEvent(events, event)) { + if (this.config.hideDuplicates && this.listContainsEvent(events, event)) { continue; } if (--remainingEntries < 0) { break; } } + event.url = calendarUrl; event.today = event.startDate >= today && 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) { 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; } } diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js index c7b62960d8..51db30d7c7 100644 --- a/modules/default/calendar/calendarfetcher.js +++ b/modules/default/calendar/calendarfetcher.js @@ -6,9 +6,7 @@ */ const https = require("https"); -const digest = require("digest-fetch"); const ical = require("node-ical"); -const fetch = require("fetch"); const Log = require("logger"); const NodeHelper = require("node_helper"); const CalendarFetcherUtils = require("./calendarfetcherutils"); @@ -39,7 +37,6 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn clearTimeout(reloadTimer); reloadTimer = null; const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); - let fetcher = null; let httpsAgent = null; let headers = { "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.method === "bearer") { headers.Authorization = `Bearer ${auth.pass}`; - } else if (auth.method === "digest") { - fetcher = new digest(auth.user, auth.pass).fetch(url, { headers: headers, agent: httpsAgent }); } else { 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((response) => response.text()) .then((responseData) => { @@ -115,7 +107,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn * Broadcast the existing events. */ this.broadcastEvents = function () { - Log.info(`Calendar-Fetcher: Broadcasting ${events.length} events.`); + Log.info(`Calendar-Fetcher: Broadcasting ${events.length} events from ${url}.`); eventsReceivedCallback(this); }; diff --git a/modules/default/calendar/calendarfetcherutils.js b/modules/default/calendar/calendarfetcherutils.js index d425f0a478..34cc2578c6 100644 --- a/modules/default/calendar/calendarfetcherutils.js +++ b/modules/default/calendar/calendarfetcherutils.js @@ -313,6 +313,9 @@ const CalendarFetcherUtils = { let curEvent = event; 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 // This will be the correction, we need to apply. let nowOffset = new Date().getTimezoneOffset(); diff --git a/modules/default/helloworld/helloworld.njk b/modules/default/helloworld/helloworld.njk index 005ca28e3f..05e5edd37f 100644 --- a/modules/default/helloworld/helloworld.njk +++ b/modules/default/helloworld/helloworld.njk @@ -2,4 +2,4 @@ Use ` | safe` to allow html tages within the text string. https://mozilla.github.io/nunjucks/templating.html#autoescaping --> -
{{text | safe}}
+
{{ text | safe }}
diff --git a/modules/default/newsfeed/fullarticle.njk b/modules/default/newsfeed/fullarticle.njk index 6570396e5e..0be4b04256 100644 --- a/modules/default/newsfeed/fullarticle.njk +++ b/modules/default/newsfeed/fullarticle.njk @@ -1,3 +1,3 @@
-
\ No newline at end of file + diff --git a/modules/default/newsfeed/newsfeed.njk b/modules/default/newsfeed/newsfeed.njk index 9e7e9d78bb..5bdfe2cd49 100644 --- a/modules/default/newsfeed/newsfeed.njk +++ b/modules/default/newsfeed/newsfeed.njk @@ -1,27 +1,31 @@ {% macro escapeText(text, dangerouslyDisableAutoEscaping=false) %} - {% if dangerouslyDisableAutoEscaping %} - {{ text | safe}} - {% else %} - {{ text }} - {% endif %} + {% if dangerouslyDisableAutoEscaping -%} + {{ text | safe }} + {%- else -%} + {{ text }} + {%- endif %} {% endmacro %} - {% macro escapeTitle(title, url, dangerouslyDisableAutoEscaping=false, showTitleAsUrl=false) %} - {% if dangerouslyDisableAutoEscaping %} - {% if showTitleAsUrl %} - {{ title | safe }} + {% if dangerouslyDisableAutoEscaping %} + {% if showTitleAsUrl %} + {{ title | safe }} + {% else %} + {{ title | safe }} + {% endif %} {% else %} - {{ title | safe}} + {% if showTitleAsUrl %} + {{ title }} + {% else %} + {{ title }} + {% endif %} {% endif %} - {% else %} - {% if showTitleAsUrl %} - {{ title }} - {% else %} - {{ title }} - {% endif %} - {% endif %} {% endmacro %} - {% if loaded %} {% if config.showAsList %}