Merge branch 'add-cards-per-row' into add-device-view

# Conflicts:
#	README.md
#	dist/mushroom-strategy.js
#	package-lock.json
#	package.json
#	src/Registry.ts
#	src/cards/HeaderCard.ts
#	src/configurationDefaults.ts
#	src/mushroom-strategy.ts
#	src/types/strategy/strategy-cards.ts
#	src/types/strategy/strategy-generics.ts
#	src/utilities/RegistryFilter.ts
#	src/views/AbstractView.ts
#	src/views/CameraView.ts
#	src/views/HomeView.ts
#	src/views/VacuumView.ts
This commit is contained in:
DigiLive
2025-05-18 12:24:15 +02:00
26 changed files with 421 additions and 209 deletions

View File

@@ -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. Explain why this fix is needed and what problem it solves.
If it relates to an existing issue, please link it. 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. 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.] [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: If the steps to reproduce the bug are not clearly outlined in the linked issue, please provide them here:
1. ... 1. ...
2. ... 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. 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.] [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: Please confirm the following by inserting an `x` between the brackets:

View File

@@ -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] [Briefly describe the feature you are proposing]
--- ---
### Motivation and Context ## Motivation and Context
Explain why this feature is needed and what problem it solves. Explain why this feature is needed and what problem it solves.
If it relates to an existing issue, please link it. 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.] [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.] [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: Please confirm the following by inserting an `x` between the brackets:

View File

@@ -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: 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: 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. Explain why this translation (addition, fix, or update) is needed.
- For fixes, please describe the original error. - 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. Please describe the scope of your translation changes.
Which parts of the project are affected by these translations? 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. 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. 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? 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. 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: Please confirm the following:

26
.github/dependabot.yml vendored Normal file
View File

@@ -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: "-"

View File

@@ -9,6 +9,6 @@ jobs:
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
steps: steps:
- name: HACS Action - name: HACS Action
uses: "hacs/action@main" uses: "hacs/action@22.5.0"
with: with:
category: "plugin" category: "plugin"

View File

@@ -11,24 +11,29 @@ on:
jobs: jobs:
build: build:
name: Build Distribution
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
CI_COMMIT_MESSAGE: Continuous Integration - Build Distribution CI_COMMIT_MESSAGE: |
Continuous Integration - Build Distribution
[skip codacy]
CI_COMMIT_AUTHOR: Continuous Integration CI_COMMIT_AUTHOR: Continuous Integration
strategy: strategy:
matrix: matrix:
node-version: [ 18.x ] node-version: [22.x]
# Checkout Repository # Checkout Repository
steps: steps:
- uses: actions/checkout@v3 - name: Checkout Repository
uses: actions/checkout@v4
with: with:
token: ${{ secrets.WORKFLOW_GIT_ACCESS_TOKEN }} token: ${{ secrets.WORKFLOW_GIT_ACCESS_TOKEN }}
# Build steps # Build steps
- name: Use Node.js ${{ matrix.node-version }} - name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
@@ -44,9 +49,9 @@ jobs:
run: | run: |
git diff --quiet . || echo "changed=true" >> $GITHUB_OUTPUT 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". # 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). # Only run on a main branch push (e.g., pull request merge).
if: github.event_name == 'push' && steps.checkDiff.outputs.changed == 'true' if: github.event_name == 'push' && steps.checkDiff.outputs.changed == 'true'
run: | run: |

View File

@@ -153,7 +153,7 @@ Enhancement suggestions are tracked as [GitHub issues][issuesUrl].
### Code Contribution ### 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 #### Prevent changes to the distribution directory

View File

@@ -1,7 +1,8 @@
# Mushroom dashboard strategy # Mushroom dashboard strategy
[![release][releaseBadge]][releaseUrl] [![Release][releaseBadge]][releaseUrl]
[![hacs][hacsBadge]][hacsUrl] [![HACS][hacsBadge]][hacsUrl]
[![Codacy][codacyBadge]][codacyUrl]
![Preview GIF](./docs/preview.gif) ![Preview GIF](./docs/preview.gif)
@@ -26,10 +27,10 @@ For easy access, separate views are generated for entities which belong to speci
### Features ### Features
- 🛠 Automatically create a dashboard with three lines of YAML. * 🛠 Automatically create a dashboard with three lines of YAML.
- 😍 Built-in Views for device-specific controls. * 😍 Built-in Views for device-specific controls.
- 🎨 Many options to customize to fit your needs. * 🎨 Many options to customize to fit your needs.
- 📈 [Mini graph][miniGraphUrl] cards for sensor entities. * 📈 [Mini graph][miniGraphUrl] cards for sensor entities.
> [!TIP] > [!TIP]
> If you like this package, please star the [project at GitHub][repositoryUrl]! > If you like this package, please star the [project at GitHub][repositoryUrl]!
@@ -59,17 +60,21 @@ Visit the [issues][issuesUrl] page.
<!-- Badge References --> <!-- Badge References -->
[codacyBadge]: https://app.codacy.com/project/badge/Grade/24de1e79aea445499917d9acd5ce9e04
[hacsBadge]: https://img.shields.io/badge/HACS-Default-blue [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 [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
<!-- Repository References --> <!-- Repository References -->
[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 [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 [issuesUrl]: https://github.com/DigiLive/mushroom-strategy/issues

182
package-lock.json generated
View File

@@ -1,22 +1,22 @@
{ {
"name": "mushroom-strategy", "name": "mushroom-strategy",
"version": "2.3.0-alpha.1", "version": "2.3.3-alpha.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mushroom-strategy", "name": "mushroom-strategy",
"version": "2.3.0-alpha.1", "version": "2.3.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"deepmerge": "^4" "deepmerge": "^4"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.31.0", "@typescript-eslint/eslint-plugin": "^8.32.1",
"@typescript-eslint/parser": "^8.31.0", "@typescript-eslint/parser": "^8.32.1",
"eslint": "^9.25.1", "eslint": "^9.27.0",
"eslint-config-prettier": "^10.1.2", "eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.2.6", "eslint-plugin-prettier": "^5.4.0",
"home-assistant-js-websocket": "^9.5.0", "home-assistant-js-websocket": "^9.5.0",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"superstruct": "^2.0.2", "superstruct": "^2.0.2",
@@ -24,7 +24,7 @@
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"version-bump-prompt": "^6", "version-bump-prompt": "^6",
"webpack": "^5.99.7", "webpack": "^5.99.8",
"webpack-cli": "^6.0.1" "webpack-cli": "^6.0.1"
}, },
"funding": { "funding": {
@@ -65,9 +65,9 @@
} }
}, },
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.6.1", "version": "4.7.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
"integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -143,9 +143,9 @@
} }
}, },
"node_modules/@eslint/core": { "node_modules/@eslint/core": {
"version": "0.13.0", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz",
"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -204,13 +204,16 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.25.1", "version": "9.27.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz",
"integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==", "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://eslint.org/donate"
} }
}, },
"node_modules/@eslint/object-schema": { "node_modules/@eslint/object-schema": {
@@ -224,13 +227,13 @@
} }
}, },
"node_modules/@eslint/plugin-kit": { "node_modules/@eslint/plugin-kit": {
"version": "0.2.8", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz",
"integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/core": "^0.13.0", "@eslint/core": "^0.14.0",
"levn": "^0.4.1" "levn": "^0.4.1"
}, },
"engines": { "engines": {
@@ -520,21 +523,21 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.31.0", "version": "8.32.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz",
"integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==", "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.32.1",
"@typescript-eslint/type-utils": "8.31.0", "@typescript-eslint/type-utils": "8.32.1",
"@typescript-eslint/utils": "8.31.0", "@typescript-eslint/utils": "8.32.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.32.1",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^7.0.0",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^2.1.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -549,17 +552,27 @@
"typescript": ">=4.8.4 <5.9.0" "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": { "node_modules/@typescript-eslint/parser": {
"version": "8.31.0", "version": "8.32.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz",
"integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==", "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.32.1",
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.32.1",
"@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/typescript-estree": "8.32.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.32.1",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -575,14 +588,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.31.0", "version": "8.32.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz",
"integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==", "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.32.1",
"@typescript-eslint/visitor-keys": "8.31.0" "@typescript-eslint/visitor-keys": "8.32.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -593,16 +606,16 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.31.0", "version": "8.32.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz",
"integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==", "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/typescript-estree": "8.32.1",
"@typescript-eslint/utils": "8.31.0", "@typescript-eslint/utils": "8.32.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^2.1.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -617,9 +630,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.31.0", "version": "8.32.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz",
"integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==", "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -631,20 +644,20 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.31.0", "version": "8.32.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz",
"integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==", "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.32.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.32.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
"minimatch": "^9.0.4", "minimatch": "^9.0.4",
"semver": "^7.6.0", "semver": "^7.6.0",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^2.1.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -658,16 +671,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.31.0", "version": "8.32.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz",
"integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==", "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.32.1",
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.32.1",
"@typescript-eslint/typescript-estree": "8.31.0" "@typescript-eslint/typescript-estree": "8.32.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -682,13 +695,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.31.0", "version": "8.32.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz",
"integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==", "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.32.1",
"eslint-visitor-keys": "^4.2.0" "eslint-visitor-keys": "^4.2.0"
}, },
"engines": { "engines": {
@@ -1481,9 +1494,9 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.25.1", "version": "9.27.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz",
"integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==", "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1491,10 +1504,10 @@
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.20.0", "@eslint/config-array": "^0.20.0",
"@eslint/config-helpers": "^0.2.1", "@eslint/config-helpers": "^0.2.1",
"@eslint/core": "^0.13.0", "@eslint/core": "^0.14.0",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.25.1", "@eslint/js": "9.27.0",
"@eslint/plugin-kit": "^0.2.8", "@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2", "@humanwhocodes/retry": "^0.4.2",
@@ -1542,22 +1555,25 @@
} }
}, },
"node_modules/eslint-config-prettier": { "node_modules/eslint-config-prettier": {
"version": "10.1.2", "version": "10.1.5",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz",
"integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==", "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"eslint-config-prettier": "bin/cli.js" "eslint-config-prettier": "bin/cli.js"
}, },
"funding": {
"url": "https://opencollective.com/eslint-config-prettier"
},
"peerDependencies": { "peerDependencies": {
"eslint": ">=7.0.0" "eslint": ">=7.0.0"
} }
}, },
"node_modules/eslint-plugin-prettier": { "node_modules/eslint-plugin-prettier": {
"version": "5.2.6", "version": "5.4.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz",
"integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==", "integrity": "sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3612,9 +3628,9 @@
} }
}, },
"node_modules/webpack": { "node_modules/webpack": {
"version": "5.99.7", "version": "5.99.8",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.7.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.8.tgz",
"integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==", "integrity": "sha512-lQ3CPiSTpfOnrEGeXDwoq5hIGzSjmwD72GdfVzF7CQAI7t47rJG9eDWvcEkEn3CUQymAElVvDg3YNTlCYj+qUQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "mushroom-strategy", "name": "mushroom-strategy",
"version": "2.3.0-alpha.1", "version": "2.3.3-alpha.1",
"description": "Automatically generate a dashboard of Mushroom cards.", "description": "Automatically generate a dashboard of Mushroom cards.",
"keywords": [ "keywords": [
"dashboard", "dashboard",
@@ -30,11 +30,11 @@
"deepmerge": "^4" "deepmerge": "^4"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.31.0", "@typescript-eslint/eslint-plugin": "^8.32.1",
"@typescript-eslint/parser": "^8.31.0", "@typescript-eslint/parser": "^8.32.1",
"eslint": "^9.25.1", "eslint": "^9.27.0",
"eslint-config-prettier": "^10.1.2", "eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.2.6", "eslint-plugin-prettier": "^5.4.0",
"home-assistant-js-websocket": "^9.5.0", "home-assistant-js-websocket": "^9.5.0",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"superstruct": "^2.0.2", "superstruct": "^2.0.2",
@@ -42,7 +42,7 @@
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"version-bump-prompt": "^6", "version-bump-prompt": "^6",
"webpack": "^5.99.7", "webpack": "^5.99.8",
"webpack-cli": "^6.0.1" "webpack-cli": "^6.0.1"
}, },
"scripts": { "scripts": {

View File

@@ -187,7 +187,7 @@ class Registry {
})); }));
// Process entries of the HASS area registry. // Process entries of the HASS area registry.
if (Registry.strategyOptions.areas._?.hidden) { if (Registry.strategyOptions.areas._.hidden) {
Registry._areas = []; Registry._areas = [];
} else { } else {
// Create and add the undisclosed area if not hidden in the strategy options. // 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] }; 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.area_id = 'undisclosed';
Registry.strategyOptions.areas.undisclosed.type = 'default';
// Remove hidden areas if configured as so and sort them by name. // Remove hidden areas if configured as so and sort them by name.

View File

@@ -28,8 +28,8 @@ class AreaCard extends AbstractCard {
configuration.tap_action.navigation_path = area.area_id; configuration.tap_action.navigation_path = area.area_id;
} }
// Don't override the default card type if default is set in the strategy options. // Don't override the card type if set differently in the strategy options.
if (customConfig && customConfig.type === 'default') { if (customConfig) {
customConfig = { ...customConfig, type: configuration.type }; customConfig = { ...customConfig, type: configuration.type };
} }

View File

@@ -6,6 +6,9 @@ import { localize } from './utilities/localize';
*/ */
export const ConfigurationDefaults: StrategyDefaults = { export const ConfigurationDefaults: StrategyDefaults = {
areas: { areas: {
_: {
type: 'AreaCard',
},
undisclosed: { undisclosed: {
// TODO: Refactor undisclosed to other. // TODO: Refactor undisclosed to other.
aliases: [], aliases: [],
@@ -43,11 +46,14 @@ export const ConfigurationDefaults: StrategyDefaults = {
_: { _: {
hide_config_entities: undefined, hide_config_entities: undefined,
hide_diagnostic_entities: undefined, hide_diagnostic_entities: undefined,
showControls: true,
stack_count: 1,
}, },
binary_sensor: { binary_sensor: {
title: `${localize('sensor.binary')} ` + localize('sensor.sensors'), title: `${localize('sensor.binary')} ` + localize('sensor.sensors'),
showControls: false, showControls: false,
hidden: false, hidden: false,
stack_count: 2, // TODO: Add to wiki. also for other configurations.
}, },
camera: { camera: {
title: localize('camera.cameras'), title: localize('camera.cameras'),
@@ -169,6 +175,9 @@ export const ConfigurationDefaults: StrategyDefaults = {
extra_views: [], extra_views: [],
home_view: { home_view: {
hidden: [], hidden: [],
stack_count: {
_: 2,
},
}, },
views: { views: {
camera: { camera: {

View File

@@ -124,6 +124,6 @@ async function main() {
} }
} }
main().catch((error) => { main().catch((_) => {
throw 'Mushroom Strategy - An error occurred. Check the console (F12) for details.'; throw 'Mushroom Strategy - An error occurred. Check the console (F12) for details.';
}); });

View File

@@ -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"
}
}

View File

@@ -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 not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 <http://www.apache.org/licenses/LICENSE-2.0>
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,

View File

@@ -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 not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 <http://www.apache.org/licenses/LICENSE-2.0>
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,

View File

@@ -1,4 +1,3 @@
import { AreaRegistryEntry } from '../homeassistant/data/area_registry';
import { DeviceRegistryEntry } from '../homeassistant/data/device_registry'; import { DeviceRegistryEntry } from '../homeassistant/data/device_registry';
import { EntityRegistryEntry } from '../homeassistant/data/entity_registry'; import { EntityRegistryEntry } from '../homeassistant/data/entity_registry';
import { LovelaceCardConfig } from '../homeassistant/data/lovelace/config/card'; 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 { LovelaceChipConfig } from '../lovelace-mushroom/utils/lovelace/chip/types';
import { HeaderCardConfig } from './strategy-cards'; import { HeaderCardConfig } from './strategy-cards';
import { ConfigEntry } from '../homeassistant/data/config_entries'; import { ConfigEntry } from '../homeassistant/data/config_entries';
import { AreaRegistryEntry } from '../homeassistant/data/area_registry';
/** /**
* List of supported domains. * 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. * 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; const HOME_VIEW_SECTIONS = ['areas', 'areasTitle', 'chips', 'greeting', 'persons'] as const;
export type SupportedDomains = (typeof SUPPORTED_DOMAINS)[number]; export type SupportedDomains = (typeof SUPPORTED_DOMAINS)[number];
@@ -136,14 +137,16 @@ export interface ViewInfo {
/** /**
* All-Domains Configuration. * All-Domains Configuration.
* *
* @property {boolean | undefined} hide_config_entities - If True, all configuration entities are hidden from the * @property {boolean} [hide_config_entities] - If True, all configuration entities are hidden from the dashboard.
* dashboard. * @property {boolean} [hide_diagnostic_entities] - If True, all diagnostic entities are hidden from the dashboard.
* @property {boolean | undefined} hide_diagnostic_entities - If True, all diagnostic entities are hidden from the * @property {boolean} [showControls] - False to hide controls.
* dashboard. * @property {number} [stack_count] - Number of cards per row.
*/ */
export interface AllDomainsConfig { export interface AllDomainsConfig {
hide_config_entities: boolean | undefined; hide_config_entities?: boolean;
hide_diagnostic_entities: boolean | undefined; 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 {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} [order] - Ordering position of the domains in a view.
* @property {number} [stack_count] - Number of cards per row.
*/ */
export interface SingleDomainConfig extends Partial<HeaderCardConfig> { export interface SingleDomainConfig extends Partial<HeaderCardConfig> {
hidden: boolean; hidden: boolean;
order?: number; order?: number;
stack_count?: number;
} }
/**
* Strategy Configuration.
*
* @property {Object.<string, StrategyArea>} areas - The configuration of areas.
* @property {Object.<string, CustomCardConfig>} 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.<string, AllDomainsConfig | SingleDomainConfig>} 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<SupportedViews, StrategyViewConfig>} 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<SupportedViews, StrategyViewConfig>;
quick_access_cards: LovelaceCardConfig[];
}
/**
* Represents the default configuration for a strategy.
*/
export type StrategyDefaults = Omit<StrategyConfig, 'areas'> & {
areas: {
_: AllAreasConfig;
undisclosed: StrategyArea;
};
};
/** /**
* Strategy Area. * Strategy Area.
* *
@@ -173,6 +219,15 @@ export interface StrategyArea extends AreaRegistryEntry {
type?: string; 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. * A list of chips to show in the Home view.
* *

View File

@@ -16,7 +16,7 @@ import { logMessage, lvlWarn } from './debug';
class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> { class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> {
private readonly entries: T[]; private readonly entries: T[];
private filters: (((entry: T) => boolean) | ((entry: T, index: number) => boolean))[] = []; 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; private invertNext: boolean = false;
/** /**
@@ -27,14 +27,8 @@ class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> {
constructor(entries: T[]) { constructor(entries: T[]) {
this.entries = entries; this.entries = entries;
this.entryIdentifier = ( this.entryIdentifier = (
entries.length === 0 || 'entity_id' in entries[0] entries.length === 0 || 'entity_id' in entries[0] ? 'entity_id' : 'floor_id' in entries[0] ? 'area_id' : 'id'
? 'entity_id' ) as ('entity_id' | 'area_id' | 'id') & K;
: 'floor_id' in entries[0]
? 'floor_id'
: 'entry_id' in entries[0]
? 'entry_id'
: 'id'
) as ('entity_id' | 'floor_id' | 'id') & K;
} }
/** /**
@@ -73,49 +67,36 @@ class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> {
} }
/** /**
* Filters entries **strictly** by their `area_id`. * Filters entries 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.
* *
* @param {string | undefined} areaId - The area id to match. * @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 * @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 { whereAreaId(areaId?: string, expandToDevice: boolean = true): this {
const predicate = (entry: T) => { const predicate = (entry: T) => {
if ('entry_id' in entry) { let deviceAreaId: string | null | undefined = undefined;
return false;
}
const entryObject = entry as EntityRegistryEntry; 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) { if (expandToDevice && entryObject.device_id) {
deviceAreaId = Registry.devices.find((device) => device.id === entryObject.device_id)?.area_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) { if (areaId === undefined) {
return entry.area_id === undefined && (!expandToDevice || deviceAreaId === undefined); return entry.area_id === undefined && deviceAreaId === undefined;
} }
// Logic for any other areaId if (entry.area_id === 'undisclosed' || !entry.area_id) {
return entry.area_id === areaId || (expandToDevice && deviceAreaId === areaId); return deviceAreaId === areaId;
}
return entry.area_id === areaId;
}; };
this.filters.push(this.checkInversion(predicate)); this.filters.push(this.checkInversion(predicate));
return this; return this;
} }
@@ -273,9 +254,12 @@ class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> {
} }
const id = entry[this.entryIdentifier] as keyof StrategyConfig['card_options']; const id = entry[this.entryIdentifier] as keyof StrategyConfig['card_options'];
const isHiddenByConfig = const options =
Registry.strategyOptions.device_options['_'].hidden || this.entryIdentifier === 'area_id'
Registry.strategyOptions.card_options[id]?.hidden === true; ? { ...Registry.strategyOptions.areas['_'], ...Registry.strategyOptions.areas[id] }
: Registry.strategyOptions.card_options?.[id];
const isHiddenByConfig = options?.hidden === true;
return !isHiddenByProperty && !isHiddenByConfig; return !isHiddenByProperty && !isHiddenByConfig;
}; };
@@ -316,7 +300,9 @@ class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> {
const predicate = (entry: T) => { const predicate = (entry: T) => {
const category = 'entity_category' in entry ? entry.entity_category : undefined; const category = 'entity_category' in entry ? entry.entity_category : undefined;
const hideOption = 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) { if (hideOption === true) {
return false; return false;

View File

@@ -1,6 +1,7 @@
import { LovelaceCardConfig } from '../types/homeassistant/data/lovelace/config/card'; import { LovelaceCardConfig } from '../types/homeassistant/data/lovelace/config/card';
import { StackCardConfig } from '../types/homeassistant/panels/lovelace/cards/types'; 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. * 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. * 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 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. * @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. * 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 * @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 * @example
* ```typescript * ```typescript
* stackedCards = stackHorizontal(card, {area: 1, "custom:card": 2}); * stackedCards = stackHorizontal(card, 2, {area: 1, 'custom:card': 2});
* ``` * ```
*/ */
export function stackHorizontal( export function stackHorizontal(
cardConfigurations: LovelaceCardConfig[], cardConfigurations: LovelaceCardConfig[],
defaultCount: number = 2,
columnCounts?: { columnCounts?: {
[key: string]: number; [key: string]: number | undefined;
}, },
): LovelaceCardConfig[] { ): LovelaceCardConfig[] {
if (cardConfigurations.length <= 1) {
return cardConfigurations;
}
// Function to process a sequence of cards // Function to process a sequence of cards
const doStack = (cards: LovelaceCardConfig[], columnCount: number) => { const doStack = (cards: LovelaceCardConfig[], columnCount: number) => {
if (cards.length <= 1) {
return cards;
}
const stackedCardConfigurations: StackCardConfig[] = []; const stackedCardConfigurations: StackCardConfig[] = [];
for (let i = 0; i < cards.length; i += columnCount) { for (let i = 0; i < cards.length; i += columnCount) {
@@ -44,7 +55,7 @@ export function stackHorizontal(
for (let i = 0; i < cardConfigurations.length; ) { for (let i = 0; i < cardConfigurations.length; ) {
const currentCard = cardConfigurations[i]; const currentCard = cardConfigurations[i];
const currentType = currentCard.type; // Assuming each card has a 'type' property const currentType = currentCard.type;
// Start a new sequence // Start a new sequence
const sequence: LovelaceCardConfig[] = []; const sequence: LovelaceCardConfig[] = [];
@@ -55,7 +66,7 @@ export function stackHorizontal(
i++; // Move to the next card 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 // Process the sequence and add the result to the processedConfigurations array
processedConfigurations.push(...doStack(sequence, columnCount)); processedConfigurations.push(...doStack(sequence, columnCount));

View File

@@ -2,6 +2,7 @@ import * as de from '../translations/de.json';
import * as en from '../translations/en.json'; import * as en from '../translations/en.json';
import * as es from '../translations/es.json'; import * as es from '../translations/es.json';
import * as nl from '../translations/nl.json'; import * as nl from '../translations/nl.json';
import * as pt_br from '../translations/pt-BR.json';
import { HomeAssistant } from '../types/homeassistant/types'; import { HomeAssistant } from '../types/homeassistant/types';
import { logMessage, lvlWarn } from './debug'; import { logMessage, lvlWarn } from './debug';
@@ -11,6 +12,7 @@ const languages: Record<string, unknown> = {
en, en,
es, es,
nl, nl,
'pt-BR': pt_br,
}; };
/** The fallback language if the user-defined language isn't defined */ /** 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 * It reads the user-defined language with a fall-back to English and returns a function to get strings from
* language-files by keyword. * 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. * @param {HomeAssistant} hass The Home Assistant object.
*/ */

View File

@@ -9,6 +9,7 @@ import { ViewConfig, ViewConstructor } from '../types/strategy/strategy-views';
import { sanitizeClassName } from '../utilities/auxiliaries'; import { sanitizeClassName } from '../utilities/auxiliaries';
import { logMessage, lvlFatal } from '../utilities/debug'; import { logMessage, lvlFatal } from '../utilities/debug';
import RegistryFilter from '../utilities/RegistryFilter'; import RegistryFilter from '../utilities/RegistryFilter';
import { stackHorizontal } from '../utilities/cardStacking';
import { AbstractCardConfig, HeaderCardConfig } from '../types/strategy/strategy-cards'; import { AbstractCardConfig, HeaderCardConfig } from '../types/strategy/strategy-cards';
/** /**
@@ -22,7 +23,7 @@ import { AbstractCardConfig, HeaderCardConfig } from '../types/strategy/strategy
*/ */
abstract class AbstractView { abstract class AbstractView {
/** The base configuration of a view. */ /** The base configuration of a view. */
protected baseConfiguration: LovelaceViewConfig = { protected baseConfiguration: ViewConfig = {
icon: 'mdi:view-dashboard', icon: 'mdi:view-dashboard',
subview: false, subview: false,
}; };
@@ -41,7 +42,7 @@ abstract class AbstractView {
*/ */
protected constructor() { protected constructor() {
if (!Registry.initialized) { 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. // Create card configurations for each area.
for (const area of Registry.areas) { for (const area of Registry.areas) {
const areaCards: AbstractCardConfig[] = []; let areaCards: AbstractCardConfig[] = [];
// Set the target of the Header card to the current area. // Set the target of the Header card to the current area.
let target: HassServiceTarget = { 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) { 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. // Create and insert a Header card.
const areaHeaderCardOptions = ( const areaHeaderCardOptions = (
'headerCardConfiguration' in this.baseConfiguration ? this.baseConfiguration.headerCardConfiguration : {} 'headerCardConfiguration' in this.baseConfiguration ? this.baseConfiguration.headerCardConfiguration : {}
@@ -132,10 +139,14 @@ abstract class AbstractView {
): void { ): void {
this.baseConfiguration = { ...this.baseConfiguration, ...viewConfiguration, ...customConfiguration }; this.baseConfiguration = { ...this.baseConfiguration, ...viewConfiguration, ...customConfiguration };
this.baseConfiguration.headerCardConfiguration = {
showControls:
Registry.strategyOptions.domains[this.domain as Exclude<SupportedDomains, 'home'>]?.showControls ??
Registry.strategyOptions.domains['_'].showControls,
};
this.viewHeaderCardConfiguration = new HeaderCard(this.getDomainTargets(), { this.viewHeaderCardConfiguration = new HeaderCard(this.getDomainTargets(), {
...(('headerCardConfiguration' in this.baseConfiguration ...(this.baseConfiguration.headerCardConfiguration as HeaderCardConfig),
? this.baseConfiguration.headerCardConfiguration
: {}) as HeaderCardConfig),
...headerCardConfig, ...headerCardConfig,
}).createCard(); }).createCard();
} }

View File

@@ -26,7 +26,7 @@ class CameraView extends AbstractView {
icon: 'mdi:cctv', icon: 'mdi:cctv',
subview: false, subview: false,
headerCardConfiguration: { headerCardConfiguration: {
showControls: domainConfig.showControls, showControls: domainConfig.showControls, // FIXME: This should be named "show_controls". Also in other files and Wiki.
on: domainConfig.on, on: domainConfig.on,
off: domainConfig.off, off: domainConfig.off,
}, },

View File

@@ -78,7 +78,7 @@ class HomeView extends AbstractView {
} }
// Create the greeting section. // Create the greeting section.
if (!('greeting' in Registry.strategyOptions.home_view.hidden)) { if (!Registry.strategyOptions.home_view.hidden.includes('greeting')) {
homeViewCards.push({ homeViewCards.push({
type: 'custom:mushroom-template-card', type: 'custom:mushroom-template-card',
primary: `{% set time = now().hour %} 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. * If the section is marked as hidden in the strategy option, then the section is not created.
*/ */
private async createChipsSection(): Promise<ChipsCardConfig | undefined> { private async createChipsSection(): Promise<ChipsCardConfig | undefined> {
if ((Registry.strategyOptions.home_view.hidden as string[]).includes('chips')) { if (Registry.strategyOptions.home_view.hidden.includes('chips')) {
// The section is hidden. // The section is hidden.
return; 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. * If the section is marked as hidden in the strategy option, then the section is not created.
*/ */
private async createPersonsSection(): Promise<StackCardConfig | undefined> { private async createPersonsSection(): Promise<StackCardConfig | undefined> {
if ((Registry.strategyOptions.home_view.hidden as string[]).includes('persons')) { if (Registry.strategyOptions.home_view.hidden.includes('persons')) {
// The section is hidden. // The section is hidden.
return; return;
} }
@@ -210,7 +209,11 @@ class HomeView extends AbstractView {
return { return {
type: 'vertical-stack', 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. * If the section is marked as hidden in the strategy option, then the section is not created.
*/ */
private async createAreasSection(): Promise<StackCardConfig | undefined> { private async createAreasSection(): Promise<StackCardConfig | undefined> {
if ((Registry.strategyOptions.home_view.hidden as string[]).includes('areas')) { if (Registry.strategyOptions.home_view.hidden.includes('areas')) {
// Areas section is hidden. // Areas section is hidden.
return; return;
} }
const cardConfigurations: (TemplateCardConfig | AreaCardConfig)[] = []; const cardConfigurations: (TemplateCardConfig | AreaCardConfig)[] = [];
let onlyDefaultCards = true;
for (const area of Registry.areas) { for (const area of Registry.areas) {
const moduleName = const moduleName =
Registry.strategyOptions.areas[area.area_id]?.type ?? Registry.strategyOptions.areas['_']?.type ?? 'default'; Registry.strategyOptions.areas[area.area_id]?.type ?? Registry.strategyOptions.areas['_']?.type ?? 'default';
let AreaCard; let AreaCard;
onlyDefaultCards = onlyDefaultCards && moduleName === 'default';
try { try {
AreaCard = (await import(`../cards/${moduleName}`)).default; AreaCard = (await import(`../cards/${moduleName}`)).default;
} catch (e) { } 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 { return {
type: 'vertical-stack', type: 'vertical-stack',
title: (Registry.strategyOptions.home_view.hidden as HomeViewSections[]).includes('areasTitle') title: Registry.strategyOptions.home_view.hidden.includes('areasTitle') ? undefined : localize('generic.areas'),
? undefined cards: stackHorizontal(cardConfigurations, Registry.strategyOptions.home_view.stack_count['_'], {
: localize('generic.areas'), 'custom:mushroom-template-card': Registry.strategyOptions.home_view.stack_count.areas?.[0],
cards: stackHorizontal(cardConfigurations, { area: 1, 'custom:mushroom-template-card': 2 }), area: Registry.strategyOptions.home_view.stack_count.areas?.[1],
}),
}; };
} }
} }

View File

@@ -49,8 +49,8 @@ class VacuumView extends AbstractView {
return { return {
title: localize('vacuum.all_vacuums'), title: localize('vacuum.all_vacuums'),
subtitle: subtitle:
`${Registry.getCountTemplate(VacuumView.domain, 'in', '[cleaning, returning]')} ${localize('vacuum.vacuums')} ` + Registry.getCountTemplate(VacuumView.domain, 'in', '[cleaning, returning]') +
localize('generic.busy'), ` ${localize('vacuum.vacuums')} ${localize('generic.busy')}`,
}; };
} }
} }