diff --git a/.eslintrc b/.eslintrc.json similarity index 100% rename from .eslintrc rename to .eslintrc.json diff --git a/.github/PULL_REQUEST_TEMPLATE/bugfix.md b/.github/PULL_REQUEST_TEMPLATE/bugfix.md index 3880470..80dfb37 100644 --- a/.github/PULL_REQUEST_TEMPLATE/bugfix.md +++ b/.github/PULL_REQUEST_TEMPLATE/bugfix.md @@ -5,7 +5,7 @@ Please fill out the following information to help us review your pull request. --- -### Bug Summary +## Bug Summary Explain why this fix is needed and what problem it solves. If it relates to an existing issue, please link it. @@ -15,7 +15,7 @@ See [Linking a pull request to an issue](https://docs.github.com/en/issues/track --- -### Motivation and Context +## Motivation and Context Explain why this bug needs to be fixed and the impact it has on the project or users. @@ -23,20 +23,20 @@ Explain why this bug needs to be fixed and the impact it has on the project or u --- -### List of Changes +## List of Changes [Provide a concise list of the main changes introduced by this pull request to fix the bug.] - ... -### Steps to Reproduce (if not covered in the linked issue) +## Steps to Reproduce (if not covered in the linked issue) If the steps to reproduce the bug are not clearly outlined in the linked issue, please provide them here: 1. ... 2. ... -### Expected Behavior (if not covered in the linked issue) +## Expected Behavior (if not covered in the linked issue) Describe what the expected behavior should have been before the bug occurred. @@ -50,13 +50,13 @@ Describe the actual behavior that occurred due to the bug. --- -### Wiki Updates +## Wiki Updates [If this bug fix requires any updates to the Wiki, please provide details here.] --- -### Agreements +## Agreements Please confirm the following by inserting an `x` between the brackets: diff --git a/.github/PULL_REQUEST_TEMPLATE/feature.md b/.github/PULL_REQUEST_TEMPLATE/feature.md index 47a4cbf..650904a 100644 --- a/.github/PULL_REQUEST_TEMPLATE/feature.md +++ b/.github/PULL_REQUEST_TEMPLATE/feature.md @@ -5,13 +5,13 @@ Please fill out the following information to help us review your pull request. --- -### Feature Summary +## Feature Summary [Briefly describe the feature you are proposing] --- -### Motivation and Context +## Motivation and Context Explain why this feature is needed and what problem it solves. If it relates to an existing issue, please link it. @@ -21,19 +21,19 @@ See [Linking a pull request to an issue](https://docs.github.com/en/issues/track --- -### List of Changes +## List of Changes [Provide a concise list of the main changes introduced by this pull request.] - ... -### Wiki Updates +## Wiki Updates [If this bug feature requires any updates to the Wiki, please provide details here.] --- -### Agreements +## Agreements Please confirm the following by inserting an `x` between the brackets: diff --git a/.github/PULL_REQUEST_TEMPLATE/translation.md b/.github/PULL_REQUEST_TEMPLATE/translation.md index 3acdcac..d17a2cc 100644 --- a/.github/PULL_REQUEST_TEMPLATE/translation.md +++ b/.github/PULL_REQUEST_TEMPLATE/translation.md @@ -5,7 +5,7 @@ Please fill out the following information to help us review your translation cha --- -### Type of Translation Contribution +## Type of Translation Contribution Please select the type of contribution: @@ -15,7 +15,7 @@ Please select the type of contribution: --- -### Target Language +## Target Language Please specify the language you are adding or modifying: @@ -23,7 +23,7 @@ Please specify the language you are adding or modifying: --- -### Motivation and Context +## Motivation and Context Explain why this translation (addition, fix, or update) is needed. - For fixes, please describe the original error. @@ -33,7 +33,7 @@ Explain why this translation (addition, fix, or update) is needed. --- -### Scope of Changes +## Scope of Changes Please describe the scope of your translation changes. Which parts of the project are affected by these translations? @@ -42,7 +42,7 @@ Which parts of the project are affected by these translations? --- -### List of Changes +## List of Changes Provide a concise list of the main changes you've made in this pull request. If it's a large update, you can highlight key areas. @@ -51,7 +51,7 @@ If it's a large update, you can highlight key areas. --- -### Considerations for Reviewers +## Considerations for Reviewers Are there any specific areas you would like reviewers to pay extra attention to? For example, specific terminology, cultural nuances, or consistency with existing translations. @@ -60,7 +60,7 @@ For example, specific terminology, cultural nuances, or consistency with existin --- -### Agreements +## Agreements Please confirm the following: diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..4ecb67b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,26 @@ +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + groups: + dependencies: + patterns: ["*"] + labels: + - "dependencies" + pull-request-branch-name: + separator: "-" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + actions: + patterns: ["*"] + labels: + - "actions" + pull-request-branch-name: + separator: "-" diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 0b7d8d0..1c5d521 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -9,6 +9,6 @@ jobs: runs-on: "ubuntu-latest" steps: - name: HACS Action - uses: "hacs/action@main" + uses: "hacs/action@22.5.0" with: category: "plugin" diff --git a/.github/workflows/webpack.yml b/.github/workflows/webpack.yml index 8da4ee6..06ae377 100644 --- a/.github/workflows/webpack.yml +++ b/.github/workflows/webpack.yml @@ -11,24 +11,29 @@ on: jobs: build: + name: Build Distribution runs-on: ubuntu-latest env: - CI_COMMIT_MESSAGE: Continuous Integration - Build Distribution + CI_COMMIT_MESSAGE: | + Continuous Integration - Build Distribution + + [skip codacy] CI_COMMIT_AUTHOR: Continuous Integration strategy: matrix: - node-version: [ 18.x ] + node-version: [22.x] # Checkout Repository steps: - - uses: actions/checkout@v3 + - name: Checkout Repository + uses: actions/checkout@v4 with: token: ${{ secrets.WORKFLOW_GIT_ACCESS_TOKEN }} # Build steps - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} @@ -44,9 +49,9 @@ jobs: run: | git diff --quiet . || echo "changed=true" >> $GITHUB_OUTPUT - # Commit and push all changed files. + # Commit and push all changed files. # Must only affect files that are listed in "paths-ignore". - - name: GIT Commit Distribution Build + - name: Commit Distribution Build # Only run on a main branch push (e.g., pull request merge). if: github.event_name == 'push' && steps.checkDiff.outputs.changed == 'true' run: | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 81348cd..6346948 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -153,7 +153,7 @@ Enhancement suggestions are tracked as [GitHub issues][issuesUrl]. ### Code Contribution -You can contribute to this project by following the _fork → clone → edit → pull request_ workflow of GitHub. +You can contribute to this project by following the *fork → clone → edit → pull request* workflow of GitHub. #### Prevent changes to the distribution directory diff --git a/README.md b/README.md index 2fce6ff..d3b1856 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Mushroom dashboard strategy -[![release][releaseBadge]][releaseUrl] -[![hacs][hacsBadge]][hacsUrl] +[![Release][releaseBadge]][releaseUrl] +[![HACS][hacsBadge]][hacsUrl] +[![Codacy][codacyBadge]][codacyUrl] ![Preview GIF](./docs/preview.gif) @@ -26,10 +27,10 @@ For easy access, separate views are generated for entities which belong to speci ### Features -- 🛠 Automatically create a dashboard with three lines of YAML. -- 😍 Built-in Views for device-specific controls. -- 🎨 Many options to customize to fit your needs. -- 📈 [Mini graph][miniGraphUrl] cards for sensor entities. +* 🛠 Automatically create a dashboard with three lines of YAML. +* 😍 Built-in Views for device-specific controls. +* 🎨 Many options to customize to fit your needs. +* 📈 [Mini graph][miniGraphUrl] cards for sensor entities. > [!TIP] > If you like this package, please star the [project at GitHub][repositoryUrl]! @@ -59,17 +60,21 @@ Visit the [issues][issuesUrl] page. +[codacyBadge]: https://app.codacy.com/project/badge/Grade/24de1e79aea445499917d9acd5ce9e04 + [hacsBadge]: https://img.shields.io/badge/HACS-Default-blue +[releaseBadge]: https://img.shields.io/github/v/tag/digilive/mushroom-strategy?filter=v2.3.3-alpha.1&label=Release + [sponsorBadge]: https://img.shields.io/badge/Sponsor_him-%E2%9D%A4-%23db61a2.svg?&logo=github&color=%23fe8e86 -[releaseBadge]: https://img.shields.io/github/v/tag/digilive/mushroom-strategy?filter=v2.3.0-alpha.1&label=Release - +[codacyUrl]: https://app.codacy.com/gh/DigiLive/mushroom-strategy/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade + [repositoryUrl]: https://github.com/DigiLive/mushroom-strategy -[releaseUrl]: https://github.com/DigiLive/mushroom-strategy/releases/tag/v2.3.0-alpha.1 +[releaseUrl]: https://github.com/DigiLive/mushroom-strategy/releases/tag/v2.3.3-alpha.1 [issuesUrl]: https://github.com/DigiLive/mushroom-strategy/issues diff --git a/package-lock.json b/package-lock.json index 0a199ab..72bdd5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,22 @@ { "name": "mushroom-strategy", - "version": "2.3.0-alpha.1", + "version": "2.3.3-alpha.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mushroom-strategy", - "version": "2.3.0-alpha.1", + "version": "2.3.2", "license": "MIT", "dependencies": { "deepmerge": "^4" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^8.31.0", - "@typescript-eslint/parser": "^8.31.0", - "eslint": "^9.25.1", - "eslint-config-prettier": "^10.1.2", - "eslint-plugin-prettier": "^5.2.6", + "@typescript-eslint/eslint-plugin": "^8.32.1", + "@typescript-eslint/parser": "^8.32.1", + "eslint": "^9.27.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-prettier": "^5.4.0", "home-assistant-js-websocket": "^9.5.0", "prettier": "^3.5.3", "superstruct": "^2.0.2", @@ -24,7 +24,7 @@ "ts-node": "^10.9.2", "typescript": "^5.8.3", "version-bump-prompt": "^6", - "webpack": "^5.99.7", + "webpack": "^5.99.8", "webpack-cli": "^6.0.1" }, "funding": { @@ -65,9 +65,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", - "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, "license": "MIT", "dependencies": { @@ -143,9 +143,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -204,13 +204,16 @@ } }, "node_modules/@eslint/js": { - "version": "9.25.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz", - "integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { @@ -224,13 +227,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.13.0", + "@eslint/core": "^0.14.0", "levn": "^0.4.1" }, "engines": { @@ -520,21 +523,21 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz", - "integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz", + "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/type-utils": "8.31.0", - "@typescript-eslint/utils": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/type-utils": "8.32.1", + "@typescript-eslint/utils": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "ignore": "^7.0.0", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -549,17 +552,27 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@typescript-eslint/parser": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz", - "integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz", + "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/typescript-estree": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/typescript-estree": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4" }, "engines": { @@ -575,14 +588,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz", - "integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", + "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0" + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -593,16 +606,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz", - "integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz", + "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.31.0", - "@typescript-eslint/utils": "8.31.0", + "@typescript-eslint/typescript-estree": "8.32.1", + "@typescript-eslint/utils": "8.32.1", "debug": "^4.3.4", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -617,9 +630,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz", - "integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", + "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", "dev": true, "license": "MIT", "engines": { @@ -631,20 +644,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz", - "integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", + "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -658,16 +671,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz", - "integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", + "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/typescript-estree": "8.31.0" + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/typescript-estree": "8.32.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -682,13 +695,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz", - "integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", + "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.31.0", + "@typescript-eslint/types": "8.32.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1481,9 +1494,9 @@ } }, "node_modules/eslint": { - "version": "9.25.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz", - "integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", + "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1491,10 +1504,10 @@ "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.13.0", + "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.25.1", - "@eslint/plugin-kit": "^0.2.8", + "@eslint/js": "9.27.0", + "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -1542,22 +1555,25 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz", - "integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==", + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", + "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", "dev": true, "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, "peerDependencies": { "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-prettier": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz", - "integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz", + "integrity": "sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA==", "dev": true, "license": "MIT", "dependencies": { @@ -3612,9 +3628,9 @@ } }, "node_modules/webpack": { - "version": "5.99.7", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.7.tgz", - "integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==", + "version": "5.99.8", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.8.tgz", + "integrity": "sha512-lQ3CPiSTpfOnrEGeXDwoq5hIGzSjmwD72GdfVzF7CQAI7t47rJG9eDWvcEkEn3CUQymAElVvDg3YNTlCYj+qUQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 922573b..151e387 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mushroom-strategy", - "version": "2.3.0-alpha.1", + "version": "2.3.3-alpha.1", "description": "Automatically generate a dashboard of Mushroom cards.", "keywords": [ "dashboard", @@ -30,11 +30,11 @@ "deepmerge": "^4" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^8.31.0", - "@typescript-eslint/parser": "^8.31.0", - "eslint": "^9.25.1", - "eslint-config-prettier": "^10.1.2", - "eslint-plugin-prettier": "^5.2.6", + "@typescript-eslint/eslint-plugin": "^8.32.1", + "@typescript-eslint/parser": "^8.32.1", + "eslint": "^9.27.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-prettier": "^5.4.0", "home-assistant-js-websocket": "^9.5.0", "prettier": "^3.5.3", "superstruct": "^2.0.2", @@ -42,7 +42,7 @@ "ts-node": "^10.9.2", "typescript": "^5.8.3", "version-bump-prompt": "^6", - "webpack": "^5.99.7", + "webpack": "^5.99.8", "webpack-cli": "^6.0.1" }, "scripts": { diff --git a/src/Registry.ts b/src/Registry.ts index 1919d0e..37c49b8 100644 --- a/src/Registry.ts +++ b/src/Registry.ts @@ -187,7 +187,7 @@ class Registry { })); // Process entries of the HASS area registry. - if (Registry.strategyOptions.areas._?.hidden) { + if (Registry.strategyOptions.areas._.hidden) { Registry._areas = []; } else { // Create and add the undisclosed area if not hidden in the strategy options. @@ -201,8 +201,9 @@ class Registry { return { ...area, ...Registry.strategyOptions.areas['_'], ...Registry.strategyOptions.areas?.[area.area_id] }; }); - // Ensure the custom configuration of the undisclosed area doesn't overwrite the area_id. + // Ensure the custom configuration of the undisclosed area doesn't overwrite the required property values. Registry._strategyOptions.areas.undisclosed.area_id = 'undisclosed'; + Registry.strategyOptions.areas.undisclosed.type = 'default'; // Remove hidden areas if configured as so and sort them by name. diff --git a/src/cards/AreaCard.ts b/src/cards/AreaCard.ts index 61dd6a9..11c1ac5 100644 --- a/src/cards/AreaCard.ts +++ b/src/cards/AreaCard.ts @@ -28,8 +28,8 @@ class AreaCard extends AbstractCard { configuration.tap_action.navigation_path = area.area_id; } - // Don't override the default card type if default is set in the strategy options. - if (customConfig && customConfig.type === 'default') { + // Don't override the card type if set differently in the strategy options. + if (customConfig) { customConfig = { ...customConfig, type: configuration.type }; } diff --git a/src/configurationDefaults.ts b/src/configurationDefaults.ts index add6a08..cc773b2 100644 --- a/src/configurationDefaults.ts +++ b/src/configurationDefaults.ts @@ -6,6 +6,9 @@ import { localize } from './utilities/localize'; */ export const ConfigurationDefaults: StrategyDefaults = { areas: { + _: { + type: 'AreaCard', + }, undisclosed: { // TODO: Refactor undisclosed to other. aliases: [], @@ -43,11 +46,14 @@ export const ConfigurationDefaults: StrategyDefaults = { _: { hide_config_entities: undefined, hide_diagnostic_entities: undefined, + showControls: true, + stack_count: 1, }, binary_sensor: { title: `${localize('sensor.binary')} ` + localize('sensor.sensors'), showControls: false, hidden: false, + stack_count: 2, // TODO: Add to wiki. also for other configurations. }, camera: { title: localize('camera.cameras'), @@ -169,6 +175,9 @@ export const ConfigurationDefaults: StrategyDefaults = { extra_views: [], home_view: { hidden: [], + stack_count: { + _: 2, + }, }, views: { camera: { diff --git a/src/mushroom-strategy.ts b/src/mushroom-strategy.ts index 38785fa..6d02d63 100644 --- a/src/mushroom-strategy.ts +++ b/src/mushroom-strategy.ts @@ -124,6 +124,6 @@ async function main() { } } -main().catch((error) => { +main().catch((_) => { throw 'Mushroom Strategy - An error occurred. Check the console (F12) for details.'; }); diff --git a/src/translations/pt-BR.json b/src/translations/pt-BR.json new file mode 100644 index 0000000..7b971fb --- /dev/null +++ b/src/translations/pt-BR.json @@ -0,0 +1,80 @@ +{ + "camera": { + "all_cameras": "Todas as câmeras", + "cameras": "Câmeras" + }, + "climate": { + "all_climates": "Todos os climatizadores", + "climates": "Climatizadores" + }, + "cover": { + "all_covers": "Todas as persianas", + "covers": "Persianas" + }, + "fan": { + "all_fans": "Todos os ventiladores", + "fans": "Ventiladores" + }, + "generic": { + "all": "Todos", + "areas": "Áreas", + "busy": "Ocupado", + "good_afternoon": "Boa tarde", + "good_evening": "Boa noite", + "good_morning": "Bom dia", + "hello": "Olá", + "home": "Início", + "miscellaneous": "Variados", + "numbers": "Números", + "off": "Desligado", + "on": "Ligado", + "open": "Aberto", + "unavailable": "Indisponível", + "unclosed": "Não fechado", + "undisclosed": "Outro", + "unknown": "Desconhecido" + }, + "input_select": { + "input_selects": "Seleção de entrada" + }, + "light": { + "all_lights": "Todas as luzes", + "lights": "Luzes" + }, + "lock": { + "all_locks": "Todas as fechaduras", + "locked": "Travado", + "locks": "Fechaduras", + "unlocked": "Destravado" + }, + "media_player": { + "media_players": "Reprodutores de mídia" + }, + "scene": { + "scenes": "Cenas" + }, + "select": { + "selects": "Seleção" + }, + "sensor": { + "binary": "Binário", + "sensors": "Sensores" + }, + "switch": { + "all_switches": "Todos os interruptores", + "switches": "Interruptores" + }, + "vacuum": { + "all_vacuums": "Todos os aspiradores", + "vacuums": "Aspiradores" + }, + "valve": { + "all_valves": "Todas as válvulas", + "valves": "Válvulas", + "open": "Aberto", + "opening": "Abrindo", + "closed": "Fechado", + "closing": "Fechando", + "stopped": "Parado" + } +} diff --git a/src/types/homeassistant/README.md b/src/types/homeassistant/README.md index 5f3f9e7..7d657e1 100644 --- a/src/types/homeassistant/README.md +++ b/src/types/homeassistant/README.md @@ -13,7 +13,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/src/types/lovelace-mushroom/README.md b/src/types/lovelace-mushroom/README.md index c53b62a..cdb9038 100644 --- a/src/types/lovelace-mushroom/README.md +++ b/src/types/lovelace-mushroom/README.md @@ -13,7 +13,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/src/types/strategy/strategy-generics.ts b/src/types/strategy/strategy-generics.ts index 35e9ea0..7e2784f 100644 --- a/src/types/strategy/strategy-generics.ts +++ b/src/types/strategy/strategy-generics.ts @@ -1,4 +1,3 @@ -import { AreaRegistryEntry } from '../homeassistant/data/area_registry'; import { DeviceRegistryEntry } from '../homeassistant/data/device_registry'; import { EntityRegistryEntry } from '../homeassistant/data/entity_registry'; import { LovelaceCardConfig } from '../homeassistant/data/lovelace/config/card'; @@ -8,6 +7,7 @@ import { HomeAssistant } from '../homeassistant/types'; import { LovelaceChipConfig } from '../lovelace-mushroom/utils/lovelace/chip/types'; import { HeaderCardConfig } from './strategy-cards'; import { ConfigEntry } from '../homeassistant/data/config_entries'; +import { AreaRegistryEntry } from '../homeassistant/data/area_registry'; /** * List of supported domains. @@ -69,6 +69,7 @@ const SUPPORTED_CHIPS = ['light', 'fan', 'cover', 'switch', 'climate', 'weather' * * This constant array defines the sections that are present in the home view. */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars const HOME_VIEW_SECTIONS = ['areas', 'areasTitle', 'chips', 'greeting', 'persons'] as const; export type SupportedDomains = (typeof SUPPORTED_DOMAINS)[number]; @@ -136,14 +137,16 @@ export interface ViewInfo { /** * All-Domains Configuration. * - * @property {boolean | undefined} hide_config_entities - If True, all configuration entities are hidden from the - * dashboard. - * @property {boolean | undefined} hide_diagnostic_entities - If True, all diagnostic entities are hidden from the - * dashboard. + * @property {boolean} [hide_config_entities] - If True, all configuration entities are hidden from the dashboard. + * @property {boolean} [hide_diagnostic_entities] - If True, all diagnostic entities are hidden from the dashboard. + * @property {boolean} [showControls] - False to hide controls. + * @property {number} [stack_count] - Number of cards per row. */ export interface AllDomainsConfig { - hide_config_entities: boolean | undefined; - hide_diagnostic_entities: boolean | undefined; + hide_config_entities?: boolean; + hide_diagnostic_entities?: boolean; + showControls?: boolean; + stack_count?: number; } /** @@ -151,12 +154,55 @@ export interface AllDomainsConfig { * * @property {boolean} hidden - If True, all entities of the domain are hidden from the dashboard. * @property {number} [order] - Ordering position of the domains in a view. + * @property {number} [stack_count] - Number of cards per row. */ export interface SingleDomainConfig extends Partial { hidden: boolean; order?: number; + stack_count?: number; } +/** + * Strategy Configuration. + * + * @property {Object.} areas - The configuration of areas. + * @property {Object.} card_options - Card options for entities. + * @property {ChipConfiguration} chips - The configuration of chips in the Home view. + * @property {boolean} debug - If True, the strategy outputs more verbose debug information in the console. + * @property {Object.} domains - List of domains. + * @property {LovelaceCardConfig[]} extra_cards - List of cards to show below room cards. + * @property {StrategyViewConfig[]} extra_views - List of custom-defined views to add to the dashboard. + * @property {{ Object }} home_view - List of views to add to the dashboard. + * @property {Record} views - The configurations of views. + * @property {LovelaceCardConfig[]} quick_access_cards - List of custom-defined cards to show between the welcome card + * and rooms cards. + */ +export interface StrategyConfig { + areas: { [S: string]: StrategyArea }; + card_options: { [S: string]: CustomCardConfig }; + chips: ChipConfiguration; + debug: boolean; + domains: { [K in SupportedDomains]: K extends '_' ? AllDomainsConfig : SingleDomainConfig }; + extra_cards: LovelaceCardConfig[]; + extra_views: StrategyViewConfig[]; + home_view: { + hidden: HomeViewSections[]; + stack_count: { _: number } & { [K in HomeViewSections]?: K extends 'areas' ? [number, number] : number }; + }; + views: Record; + quick_access_cards: LovelaceCardConfig[]; +} + +/** + * Represents the default configuration for a strategy. + */ +export type StrategyDefaults = Omit & { + areas: { + _: AllAreasConfig; + undisclosed: StrategyArea; + }; +}; + /** * Strategy Area. * @@ -173,6 +219,15 @@ export interface StrategyArea extends AreaRegistryEntry { type?: string; } +/** + * Configuration for all areas. + * + * @property {string} [type] - The type of area card. + */ +export interface AllAreasConfig { + type?: string; +} + /** * A list of chips to show in the Home view. * diff --git a/src/utilities/RegistryFilter.ts b/src/utilities/RegistryFilter.ts index 4e49a43..87a8933 100644 --- a/src/utilities/RegistryFilter.ts +++ b/src/utilities/RegistryFilter.ts @@ -16,7 +16,7 @@ import { logMessage, lvlWarn } from './debug'; class RegistryFilter { private readonly entries: T[]; private filters: (((entry: T) => boolean) | ((entry: T, index: number) => boolean))[] = []; - private readonly entryIdentifier: ('entity_id' | 'floor_id' | 'entry_id' | 'id') & K; + private readonly entryIdentifier: ('entity_id' | 'area_id' | 'id') & K; private invertNext: boolean = false; /** @@ -27,14 +27,8 @@ class RegistryFilter { constructor(entries: T[]) { this.entries = entries; this.entryIdentifier = ( - entries.length === 0 || 'entity_id' in entries[0] - ? 'entity_id' - : 'floor_id' in entries[0] - ? 'floor_id' - : 'entry_id' in entries[0] - ? 'entry_id' - : 'id' - ) as ('entity_id' | 'floor_id' | 'id') & K; + entries.length === 0 || 'entity_id' in entries[0] ? 'entity_id' : 'floor_id' in entries[0] ? 'area_id' : 'id' + ) as ('entity_id' | 'area_id' | 'id') & K; } /** @@ -73,49 +67,36 @@ class RegistryFilter { } /** - * Filters entries **strictly** by their `area_id`. - * - * - Entries with a matching `area_id` are kept. - * - If `expandToDevice` is `true`, the device's `area_id` is evaluated if the entry's area_id doesn't match. - * - If `areaId` is `undefined` (or omitted), entries without an `area_id` property are kept. + * Filters entries by their `area_id`. * * @param {string | undefined} areaId - The area id to match. - * @param {boolean} [expandToDevice=true] - Whether to use the device's `area_id` if the entry's doesn't match. + * @param {boolean} [expandToDevice=true] - Whether to evaluate the device's `area_id` (see remarks). * * @remarks - * For area id `undisclosed`, the `area_id` of the entry's device may be `undisclosed` or `undefined`. + * For entries with area id `undisclosed` or `undefined`, the device's `area_id` must also match if `expandToDevice` + * is `true`. */ whereAreaId(areaId?: string, expandToDevice: boolean = true): this { const predicate = (entry: T) => { - if ('entry_id' in entry) { - return false; - } - + let deviceAreaId: string | null | undefined = undefined; const entryObject = entry as EntityRegistryEntry; - let deviceAreaId: string | null | undefined = undefined; - - // Retrieve the device area ID only if expandToDevice is true if (expandToDevice && entryObject.device_id) { deviceAreaId = Registry.devices.find((device) => device.id === entryObject.device_id)?.area_id; } - // Logic for 'undisclosed' areaId - if (areaId === 'undisclosed') { - return entry.area_id === areaId && (deviceAreaId === areaId || deviceAreaId === undefined); - } - - // Logic for undefined areaId if (areaId === undefined) { - return entry.area_id === undefined && (!expandToDevice || deviceAreaId === undefined); + return entry.area_id === undefined && deviceAreaId === undefined; } - // Logic for any other areaId - return entry.area_id === areaId || (expandToDevice && deviceAreaId === areaId); + if (entry.area_id === 'undisclosed' || !entry.area_id) { + return deviceAreaId === areaId; + } + + return entry.area_id === areaId; }; this.filters.push(this.checkInversion(predicate)); - return this; } @@ -273,9 +254,12 @@ class RegistryFilter { } const id = entry[this.entryIdentifier] as keyof StrategyConfig['card_options']; - const isHiddenByConfig = - Registry.strategyOptions.device_options['_'].hidden || - Registry.strategyOptions.card_options[id]?.hidden === true; + const options = + this.entryIdentifier === 'area_id' + ? { ...Registry.strategyOptions.areas['_'], ...Registry.strategyOptions.areas[id] } + : Registry.strategyOptions.card_options?.[id]; + + const isHiddenByConfig = options?.hidden === true; return !isHiddenByProperty && !isHiddenByConfig; }; @@ -316,7 +300,9 @@ class RegistryFilter { const predicate = (entry: T) => { const category = 'entity_category' in entry ? entry.entity_category : undefined; const hideOption = - typeof category === 'string' ? Registry.strategyOptions.domains['_']?.[`hide_${category}_entities`] : undefined; + typeof category === 'string' + ? Registry.strategyOptions?.domains?.['_']?.[`hide_${category}_entities`] + : undefined; if (hideOption === true) { return false; diff --git a/src/utilities/cardStacking.ts b/src/utilities/cardStacking.ts index 4b0d19b..61b462f 100644 --- a/src/utilities/cardStacking.ts +++ b/src/utilities/cardStacking.ts @@ -1,6 +1,7 @@ import { LovelaceCardConfig } from '../types/homeassistant/data/lovelace/config/card'; import { StackCardConfig } from '../types/homeassistant/panels/lovelace/cards/types'; +// noinspection GrazieInspection /** * Stacks an array of Lovelace card configurations into horizontal stacks based on their type. * @@ -9,6 +10,7 @@ import { StackCardConfig } from '../types/homeassistant/panels/lovelace/cards/ty * It returns a new array of stacked card configurations, preserving the original order of the cards. * * @param cardConfigurations - An array of Lovelace card configurations to be stacked. + * @param defaultCount - The default number of cards to stack if the type or column count is not found in the mapping. * @param [columnCounts] - An object mapping card types to their respective column counts. * If a type is not found in the mapping, it defaults to 2. * @returns An array of stacked card configurations, where each configuration is a horizontal stack @@ -16,17 +18,26 @@ import { StackCardConfig } from '../types/homeassistant/panels/lovelace/cards/ty * * @example * ```typescript - * stackedCards = stackHorizontal(card, {area: 1, "custom:card": 2}); + * stackedCards = stackHorizontal(card, 2, {area: 1, 'custom:card': 2}); * ``` */ export function stackHorizontal( cardConfigurations: LovelaceCardConfig[], + defaultCount: number = 2, columnCounts?: { - [key: string]: number; + [key: string]: number | undefined; }, ): LovelaceCardConfig[] { + if (cardConfigurations.length <= 1) { + return cardConfigurations; + } + // Function to process a sequence of cards const doStack = (cards: LovelaceCardConfig[], columnCount: number) => { + if (cards.length <= 1) { + return cards; + } + const stackedCardConfigurations: StackCardConfig[] = []; for (let i = 0; i < cards.length; i += columnCount) { @@ -44,7 +55,7 @@ export function stackHorizontal( for (let i = 0; i < cardConfigurations.length; ) { const currentCard = cardConfigurations[i]; - const currentType = currentCard.type; // Assuming each card has a 'type' property + const currentType = currentCard.type; // Start a new sequence const sequence: LovelaceCardConfig[] = []; @@ -55,7 +66,7 @@ export function stackHorizontal( i++; // Move to the next card } - const columnCount = Math.max(columnCounts?.[currentType] || 2, 1); + const columnCount = Math.max(columnCounts?.[currentType] || defaultCount, 1); // Process the sequence and add the result to the processedConfigurations array processedConfigurations.push(...doStack(sequence, columnCount)); diff --git a/src/utilities/localize.ts b/src/utilities/localize.ts index 14a88e7..74fdaed 100644 --- a/src/utilities/localize.ts +++ b/src/utilities/localize.ts @@ -2,6 +2,7 @@ import * as de from '../translations/de.json'; import * as en from '../translations/en.json'; import * as es from '../translations/es.json'; import * as nl from '../translations/nl.json'; +import * as pt_br from '../translations/pt-BR.json'; import { HomeAssistant } from '../types/homeassistant/types'; import { logMessage, lvlWarn } from './debug'; @@ -11,6 +12,7 @@ const languages: Record = { en, es, nl, + 'pt-BR': pt_br, }; /** The fallback language if the user-defined language isn't defined */ @@ -50,7 +52,7 @@ let _localize: ((key: string) => string) | undefined = undefined; * It reads the user-defined language with a fall-back to English and returns a function to get strings from * language-files by keyword. * - * If the keyword is undefined, or on error, the keyword itself is returned. + * If the keyword is undefined, or on an error, the keyword itself is returned. * * @param {HomeAssistant} hass The Home Assistant object. */ diff --git a/src/views/AbstractView.ts b/src/views/AbstractView.ts index 943227f..cdcf3e7 100644 --- a/src/views/AbstractView.ts +++ b/src/views/AbstractView.ts @@ -9,6 +9,7 @@ import { ViewConfig, ViewConstructor } from '../types/strategy/strategy-views'; import { sanitizeClassName } from '../utilities/auxiliaries'; import { logMessage, lvlFatal } from '../utilities/debug'; import RegistryFilter from '../utilities/RegistryFilter'; +import { stackHorizontal } from '../utilities/cardStacking'; import { AbstractCardConfig, HeaderCardConfig } from '../types/strategy/strategy-cards'; /** @@ -22,7 +23,7 @@ import { AbstractCardConfig, HeaderCardConfig } from '../types/strategy/strategy */ abstract class AbstractView { /** The base configuration of a view. */ - protected baseConfiguration: LovelaceViewConfig = { + protected baseConfiguration: ViewConfig = { icon: 'mdi:view-dashboard', subview: false, }; @@ -41,7 +42,7 @@ abstract class AbstractView { */ protected constructor() { if (!Registry.initialized) { - logMessage(lvlFatal, 'Registry not initialized!'); + logMessage(lvlFatal, 'Registry is not initialized!'); } } @@ -75,7 +76,7 @@ abstract class AbstractView { // Create card configurations for each area. for (const area of Registry.areas) { - const areaCards: AbstractCardConfig[] = []; + let areaCards: AbstractCardConfig[] = []; // Set the target of the Header card to the current area. let target: HassServiceTarget = { @@ -97,8 +98,14 @@ abstract class AbstractView { ), ); - // Vertically stack the cards of the current area. + // Stack the cards of the current area. if (areaCards.length) { + areaCards = stackHorizontal( + areaCards, + Registry.strategyOptions.domains[this.domain as SupportedDomains].stack_count ?? + Registry.strategyOptions.domains['_'].stack_count, + ); + // Create and insert a Header card. const areaHeaderCardOptions = ( 'headerCardConfiguration' in this.baseConfiguration ? this.baseConfiguration.headerCardConfiguration : {} @@ -132,10 +139,14 @@ abstract class AbstractView { ): void { this.baseConfiguration = { ...this.baseConfiguration, ...viewConfiguration, ...customConfiguration }; + this.baseConfiguration.headerCardConfiguration = { + showControls: + Registry.strategyOptions.domains[this.domain as Exclude]?.showControls ?? + Registry.strategyOptions.domains['_'].showControls, + }; + this.viewHeaderCardConfiguration = new HeaderCard(this.getDomainTargets(), { - ...(('headerCardConfiguration' in this.baseConfiguration - ? this.baseConfiguration.headerCardConfiguration - : {}) as HeaderCardConfig), + ...(this.baseConfiguration.headerCardConfiguration as HeaderCardConfig), ...headerCardConfig, }).createCard(); } diff --git a/src/views/CameraView.ts b/src/views/CameraView.ts index 9d959d8..77d6e65 100644 --- a/src/views/CameraView.ts +++ b/src/views/CameraView.ts @@ -26,7 +26,7 @@ class CameraView extends AbstractView { icon: 'mdi:cctv', subview: false, headerCardConfiguration: { - showControls: domainConfig.showControls, + showControls: domainConfig.showControls, // FIXME: This should be named "show_controls". Also in other files and Wiki. on: domainConfig.on, off: domainConfig.off, }, diff --git a/src/views/HomeView.ts b/src/views/HomeView.ts index 4ee444e..f199c55 100644 --- a/src/views/HomeView.ts +++ b/src/views/HomeView.ts @@ -78,7 +78,7 @@ class HomeView extends AbstractView { } // Create the greeting section. - if (!('greeting' in Registry.strategyOptions.home_view.hidden)) { + if (!Registry.strategyOptions.home_view.hidden.includes('greeting')) { homeViewCards.push({ type: 'custom:mushroom-template-card', primary: `{% set time = now().hour %} @@ -125,7 +125,7 @@ class HomeView extends AbstractView { * If the section is marked as hidden in the strategy option, then the section is not created. */ private async createChipsSection(): Promise { - if ((Registry.strategyOptions.home_view.hidden as string[]).includes('chips')) { + if (Registry.strategyOptions.home_view.hidden.includes('chips')) { // The section is hidden. return; } @@ -193,9 +193,8 @@ class HomeView extends AbstractView { * If the section is marked as hidden in the strategy option, then the section is not created. */ private async createPersonsSection(): Promise { - if ((Registry.strategyOptions.home_view.hidden as string[]).includes('persons')) { + if (Registry.strategyOptions.home_view.hidden.includes('persons')) { // The section is hidden. - return; } @@ -210,7 +209,11 @@ class HomeView extends AbstractView { return { type: 'vertical-stack', - cards: stackHorizontal(cardConfigurations), + cards: stackHorizontal( + cardConfigurations, + Registry.strategyOptions.home_view.stack_count['persons'] ?? + Registry.strategyOptions.home_view.stack_count['_'], + ), }; } @@ -221,24 +224,19 @@ class HomeView extends AbstractView { * If the section is marked as hidden in the strategy option, then the section is not created. */ private async createAreasSection(): Promise { - if ((Registry.strategyOptions.home_view.hidden as string[]).includes('areas')) { + if (Registry.strategyOptions.home_view.hidden.includes('areas')) { // Areas section is hidden. - return; } const cardConfigurations: (TemplateCardConfig | AreaCardConfig)[] = []; - let onlyDefaultCards = true; - for (const area of Registry.areas) { const moduleName = Registry.strategyOptions.areas[area.area_id]?.type ?? Registry.strategyOptions.areas['_']?.type ?? 'default'; let AreaCard; - onlyDefaultCards = onlyDefaultCards && moduleName === 'default'; - try { AreaCard = (await import(`../cards/${moduleName}`)).default; } catch (e) { @@ -250,15 +248,22 @@ class HomeView extends AbstractView { } } - cardConfigurations.push(new AreaCard(area).getCard()); + cardConfigurations.push( + new AreaCard(area, { + ...Registry.strategyOptions.areas['_'], + ...Registry.strategyOptions.areas[area.area_id], + }).getCard(), + ); } + // FIXME: The columns are too narrow when having HASS area cards. return { type: 'vertical-stack', - title: (Registry.strategyOptions.home_view.hidden as HomeViewSections[]).includes('areasTitle') - ? undefined - : localize('generic.areas'), - cards: stackHorizontal(cardConfigurations, { area: 1, 'custom:mushroom-template-card': 2 }), + title: Registry.strategyOptions.home_view.hidden.includes('areasTitle') ? undefined : localize('generic.areas'), + cards: stackHorizontal(cardConfigurations, Registry.strategyOptions.home_view.stack_count['_'], { + 'custom:mushroom-template-card': Registry.strategyOptions.home_view.stack_count.areas?.[0], + area: Registry.strategyOptions.home_view.stack_count.areas?.[1], + }), }; } } diff --git a/src/views/VacuumView.ts b/src/views/VacuumView.ts index 4faeb9b..9aa58e9 100644 --- a/src/views/VacuumView.ts +++ b/src/views/VacuumView.ts @@ -49,8 +49,8 @@ class VacuumView extends AbstractView { return { title: localize('vacuum.all_vacuums'), subtitle: - `${Registry.getCountTemplate(VacuumView.domain, 'in', '[cleaning, returning]')} ${localize('vacuum.vacuums')} ` + - localize('generic.busy'), + Registry.getCountTemplate(VacuumView.domain, 'in', '[cleaning, returning]') + + ` ${localize('vacuum.vacuums')} ${localize('generic.busy')}`, }; } }