Refactor JavaScript to TypeScript (#81)

Other changes include:

* Add Vacuum Card and View.

* Add hiding a view if no device is configured for it. Closes #24.

* Add icon to HomeView configuration.

* Add version output to console.

* Add version bumper.

* Add .editorconfig settings.

* Refactor README, because the information moved to the repository's
   Wiki. Closes #87.
This commit is contained in:
Ferry Cools
2023-11-22 21:02:56 +01:00
committed by GitHub
parent c8a9cd5618
commit 82a91c3bd4
120 changed files with 6349 additions and 4399 deletions

View File

@ -1,19 +1,19 @@
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 4
trim_trailing_whitespace = true
ij_continuation_indent_size = 8
ij_continuation_indent_size = 2
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = true
ij_smart_tabs = false
ij_visual_guides =
ij_wrap_on_typing = false
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 2
trim_trailing_whitespace = true
[.editorconfig]
ij_editorconfig_align_group_field_declarations = false
@ -43,7 +43,6 @@ ij_xml_space_inside_empty_tag = false
ij_xml_text_wrap = normal
[{*.ats,*.cts,*.mts,*.ts}]
ij_continuation_indent_size = 4
ij_typescript_align_imports = false
ij_typescript_align_multiline_array_initializer_expression = false
ij_typescript_align_multiline_binary_operation = false
@ -55,7 +54,7 @@ ij_typescript_align_multiline_parameters_in_calls = false
ij_typescript_align_multiline_ternary_operation = false
ij_typescript_align_object_properties = 0
ij_typescript_align_union_types = false
ij_typescript_align_var_statements = 0
ij_typescript_align_var_statements = 1
ij_typescript_array_initializer_new_line_after_left_brace = false
ij_typescript_array_initializer_right_brace_on_new_line = false
ij_typescript_array_initializer_wrap = off
@ -64,7 +63,7 @@ ij_typescript_binary_operation_sign_on_next_line = false
ij_typescript_binary_operation_wrap = off
ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/**
ij_typescript_blank_lines_after_imports = 1
ij_typescript_blank_lines_around_class = 1
ij_typescript_blank_lines_around_class = 0
ij_typescript_blank_lines_around_field = 0
ij_typescript_blank_lines_around_field_in_interface = 0
ij_typescript_blank_lines_around_function = 1
@ -112,7 +111,7 @@ ij_typescript_keep_blank_lines_in_code = 2
ij_typescript_keep_first_column_comment = true
ij_typescript_keep_indents_on_empty_lines = false
ij_typescript_keep_line_breaks = true
ij_typescript_keep_simple_blocks_in_one_line = false
ij_typescript_keep_simple_blocks_in_one_line = true
ij_typescript_keep_simple_methods_in_one_line = false
ij_typescript_line_comment_add_space = true
ij_typescript_line_comment_at_first_column = false
@ -132,7 +131,7 @@ ij_typescript_prefer_explicit_types_function_returns = false
ij_typescript_prefer_explicit_types_vars_fields = false
ij_typescript_prefer_parameters_wrap = false
ij_typescript_property_prefix =
ij_typescript_reformat_c_style_comments = false
ij_typescript_reformat_c_style_comments = true
ij_typescript_space_after_colon = true
ij_typescript_space_after_comma = true
ij_typescript_space_after_dots_in_rest_parameter = false
@ -207,6 +206,7 @@ ij_typescript_union_types_wrap = on_every_item
ij_typescript_use_chained_calls_group_indents = false
ij_typescript_use_double_quotes = true
ij_typescript_use_explicit_js_extension = auto
ij_typescript_use_import_type = auto
ij_typescript_use_path_mapping = always
ij_typescript_use_public_modifier = false
ij_typescript_use_semicolon_after_statement = true
@ -215,20 +215,7 @@ ij_typescript_while_brace_force = never
ij_typescript_while_on_new_line = false
ij_typescript_wrap_comments = false
[{*.bash,*.sh,*.zsh}]
indent_size = 2
tab_width = 2
ij_shell_binary_ops_start_line = false
ij_shell_keep_column_alignment_padding = false
ij_shell_minify_program = false
ij_shell_redirect_followed_by_space = false
ij_shell_switch_cases_indented = false
ij_shell_use_unix_line_separator = true
[{*.cjs,*.js}]
indent_size = 2
tab_width = 2
ij_continuation_indent_size = 2
ij_javascript_align_imports = false
ij_javascript_align_multiline_array_initializer_expression = false
ij_javascript_align_multiline_binary_operation = false
@ -397,7 +384,6 @@ ij_javascript_while_on_new_line = false
ij_javascript_wrap_comments = false
[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.remarkrc,.stylelintrc,bowerrc,composer.lock,jest.config}]
indent_size = 2
ij_json_array_wrapping = split_into_lines
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
@ -460,7 +446,6 @@ ij_markdown_wrap_text_if_long = true
ij_markdown_wrap_text_inside_blockquotes = true
[{*.yaml,*.yml}]
indent_size = 2
ij_yaml_align_values_properties = do_not_align
ij_yaml_autoinsert_sequence_marker = true
ij_yaml_block_mapping_on_new_line = false

0
.eslintrc Normal file
View File

View File

@ -7,27 +7,28 @@ assignees: ''
---
**Describe the bug**
**Describe the bug**
A clear and concise description of what the bug is.
**Versions**
**Versions**
* Mushroom-Strategy:
* HACS:
* Mushroom:
* Home Assistant:
**To Reproduce**
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
1. Go to '…'
2. Click on '…'
3. Scroll down to '…'
4. See error
**Expected behavior**
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
**Additional context**
Add any other context about the problem here.

View File

@ -4,7 +4,7 @@ on:
push:
branches: [ "main" ]
# Ignore changes in folders that are affected by the auto commit. (Node.js project)
paths-ignore:
paths-ignore:
- 'dist/**'
# pull_request:
# branches: [ "main" ]
@ -24,7 +24,7 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
token: ${{ secrets.WORKFLOW_GIT_ACCESS_TOKEN }}
token: ${{ secrets.WORKFLOW_GIT_ACCESS_TOKEN }}
# Build steps
- name: Use Node.js ${{ matrix.node-version }}
@ -34,7 +34,7 @@ jobs:
- name: Node Install
run: npm ci
- name: Build Distribution
run: |
npm run build
@ -44,10 +44,10 @@ 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
# Only run on main branch push (e.g. pull request merge).
# Only run on main branch push (e.g., pull request merge).
if: github.event_name == 'push' && steps.checkDiff.outputs.changed == 'true'
run: |
git config --global user.name "${{ env.CI_COMMIT_AUTHOR }}"

View File

@ -3,16 +3,17 @@
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make
participation in our project and our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education,
socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
participation in our project and our community a harassment-free experience for everyone.
This is regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socioeconomic status, nationality, personal appearance, race, religion, or
sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
* Demonstrating empathy and kindness toward other people.
* Being respectful of differing opinions, viewpoints and experiences.
* Being respectful of differing opinions, viewpoints, and experiences.
* Giving and gracefully accepting constructive feedback.
* Accepting responsibility and apologizing to those affected by our mistakes and learning from the experience.
* Focusing on what is best not just for us as individuals, but for the overall community.
@ -28,18 +29,18 @@ Examples of unacceptable behavior include:
## Our Responsibilities
Project maintainers are responsible for clarifying and enforcing our standards of acceptable behavior and will take
appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive
appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits,
issues and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation
issues, and other contributions that are not aligned to this Code of Conduct and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing
the community in public spaces. Examples of representing our community include using an official e-mail address, posting
via an official social media account or acting as an appointed representative at an online or offline event.
via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
@ -80,8 +81,8 @@ enforcing the Code of Conduct, is allowed during this period. Violating these te
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate
behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Community Impact**: Demonstrating violations of community standards, including sustained inappropriate
behavior, harassment of an individual, or aggression toward or disparagement of their class.
**Consequence**: A permanent ban from any sort of public interaction within the community.

View File

@ -11,7 +11,7 @@ It will make it a lot easier for us maintainers and smooth out the experience fo
The community looks forward to your contributions.
> And if you like the project, but just don't have time to contribute, that's fine.
> There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
> There are other easy ways to support the project and show your appreciation, which we would also be thrilled about:
> - Star the project
> - Tweet about it
> - Refer this project in your project's readme
@ -58,14 +58,14 @@ We will then take care of the issue as soon as possible.
> ### Legal Notice
>
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the
> necessary rights to the content and that the content you contribute may be provided under the project license.
> necessary rights to the content, and that the content you contribute may be provided under the project license.
### Reporting Bugs
#### Before Submitting a Bug Report
A good bug report shouldn't leave others needing to chase you up for more information.
Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report.
Therefore, we ask you to investigate carefully, collect information, and describe the issue in detail in your report.
Please complete the following steps in advance to help us fix any potential bug as fast as possible.
- Make sure that you are using the latest version.
@ -87,7 +87,7 @@ Please complete the following steps in advance to help us fix any potential bug
#### How Do I Submit a Good Bug Report?
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue
> You must never report security related issues, vulnerabilities, or bugs including sensitive information to the issue
> tracker, or elsewhere in public.
> Instead, sensitive bugs must be sent by email to <aaliankhan5@gmail.com>.

571
README.md
View File

@ -1,24 +1,28 @@
# Mushroom dashboard strategy
[![hacs][hacsBadge]][hacsUrl]
[![release][releaseBadge]][releaseUrl]
[![hacs][hacsBadge]][hacsUrl]
![Preview GIF](./docs/preview.gif)
<details>
<summary>More images...</summary>
![Automatic](./docs/auto.png)
![Views](./docs/views.png)
![customizable](./docs/customizable.png)
</details>
## What is Mushroom dashboard strategy?
## What is the Mushroom Dashboard Strategy?
Mushroom dashboard strategy provides a strategy for Home assistant to automatically create a dashboard using Mushroom
cards, the area configuration and entity configuration.
Mushroom Dashboard Strategy provides a strategy for Home Assistant to automatically generate a dashboard using Mushroom
cards.
My goal is to propose a way to create powerful dashboards without the need of spending hours manually creating them.
**Note:** This is my first javascript code and GitHub repository. Any recommendations are always welcome.
It generates cards for your Home Assistants entities and areas, divided over several views.
Besides a Home view from where you can enter a subview of your areas, a separate view for lights, fans and other domains
are generated for easy access to your entities.
### Features
@ -26,556 +30,53 @@ My goal is to propose a way to create powerful dashboards without the need of sp
- 😍 Built-in Views for device-specific controls.
- 🎨 Many options to customize to fit your needs.
## Installation
> [!TIP]
> If you like this package, please star the [project at GitHub](https://github.com/AalianKhan/mushroom-strategy)! 🌟
### Prerequisites
## Getting started
You need to install these cards before using this strategy:
The strategy is easily installable from [HACS][hacsUrl] (Home Assistant Community Store).
Please visit [Installation Guide](https://github.com/AalianKhan/mushroom-strategy/wiki/#installation) at our Wiki.
- [Mushroom cards][mushroomUrl]
- [Mini graph card][mini-graphUrl]
## Need some help?
### HACS
Visit the [discussions](https://github.com/AalianKhan/mushroom-strategy/discussions) page.
Mushroom dashboard strategy is available in [HACS][hacsUrl] (Home Assistant Community Store).
## Have an idea or want to report a bug?
1. Install HACS if you don't have it already.
2. Open HACS in Home Assistant.
3. Go to the "Frontend" section.
4. Click the button with the "+" icon
5. Search for "Mushroom dashboard" and install.
Make sure your idea or bug isn't discussed already in our Discussions or Issues!
Visit the [issues](https://github.com/AalianKhan/mushroom-strategy/issues/new/choose) page.
### Manual
1. Download `mushroom-strategy.js` file from
the [`dist`](https://github.com/AalianKhan/mushroom-strategy/tree/main/dist) directory.
2. Put `mushroom-strategy.js` file into your `config/www` folder.
3. Add a reference to `mushroom-strategy.js` in Dashboard.
There are two ways to do that:
- **Using UI:** _Settings__Dashboards__More Options icon__Resources__Add Resource_ → Set _Url_
as `/local/mushroom-strategy.js` → Set _Resource type_ as `JavaScript Module`.
**Note:** If you do not see the Resources menu, you will need to enable _Advanced Mode_ in your _User Profile_
- **Using YAML:** Add the following code to the `lovelace` section.
```yaml
resources:
- url: /local/mushroom-strategy.js
type: module
```
## Usage
All the rounded cards can be configured using the Dashboard UI editor.
1. In the UI of the dashboard, click the three dots in the top right corner.
2. Click _Edit Dashboard_.
3. Click 3 dots again
4. Click `Raw configuration editor`
5. Add the following lines:
```yaml
strategy:
type: custom:mushroom-strategy
views: []
```
### Hidding specific entities
When creating this dashboard for the first time, you probably have many entities that you don't want to see.
You can hide these entities by following the steps below:
1. Click and hold the entity
2. Click the `cog icon` in the top right corner of the popup.
3. Set `Visible` to `off`.
The view should update when the page is refreshed.
If you don't want to hide the entity from all dashboards, you can use [Card Options](#card-options) to hide specific
entities and devices.
![Views](./docs/Hidden.png)
### Adding devices to areas
You can add devices to an area by going to `Settings` found at the bottom of the sidebar.
1. Click `Devices and integration`
2. Select the integration of your device
3. Click the device you wish to add
4. Click the `pencil icon` found in the top right corner
5. Enter an area in area field.
You can also set an entity of that device to a different area by going to the advanced settings of that entity.
If you created an entity in your `configuration.yaml` you may need to enter a `unique_id` first before you set an area
to it.
See [docs](https://www.home-assistant.io/faq/unique_id/)
## Strategy options
You can set strategy options to further customize the dashboard.
By default, all views are enabled which include lights, fans, covers, switches, climates and cameras. All chips are also
enabled which count the number of devices on for the platforms light, fan, cover and climate. It also auto-selects a
weather entity for the weather chip.
The options available are:
| Name | Type | Default | Description |
|:---------------------|:--------------------------|:--------------------------------------------------------|:-----------------------------------------------------------------------|
| `areas` | object (optional) | unset | One or more areas in a list, see [areas object](#area-object). |
| `card_options` | object (optional) | unset | Card options for cards, see [Card Options](#card-options). |
| `views` | object (optional) | All default views | See available [Pre-built views](#pre-built-views). |
| `chips` | object | All count chips enabled with auto selected weather card | See [chips](#chips). |
| `quick_access_cards` | array of cards (optional) | unset | List of cards to show between welcome card and rooms cards. |
| `extra_cards` | array of cards (optional | unset | List of cards to show below room cards. |
| `extra_views` | array of views (optional) | unset | List of views to add to the dashboard. |
| `domains` | object (optional) | All supported domains | See [Supported domains](#supported-domains). |
| `homeView` | object (optional) | unset | Options for the home view, see [Home View Options](#home-view-options) |
#### Example
```yaml
strategy:
type: custom:mushroom-strategy
options:
areas:
family_room_id:
name: Family Room
icon: mdi:sofa
icon_color: green
views: []
```
### Area Object
The area object includes all options from the template mushroom card and `extra_cards` which is a list of cards to show
at the top of the area subview.
| Name | Type | Default | Description |
|:----------------------|:------------------|:---------------|:------------------------------------------------------------------------------------------------------------------------------------|
| `name` | string | N.A. | The name of the area. |
| `icon` | string (optional) | unset or empty | Icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). |
| `icon_color` | string (optional) | unset or empty | Icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). |
| `primary` | string (optional) | unset or empty | Primary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). |
| `secondary` | string (optional) | unset or empty | Secondary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). |
| `badge_icon` | string (optional) | unset or empty | Badge icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). |
| `badge_color` | string (optional) | unset or empty | Badge icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). |
| `picture` | string (optional) | unset or empty | Picture to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). |
| `multiline_secondary` | boolean | `false` | Enables support for multiline text for the secondary info. |
| `layout` | string (optional) | unset or empty | Layout of the card. Vertical, horizontal and default layout are supported. |
| `fill_container` | boolean | `false` | Fill container or not. Useful when card is in a grid, vertical or horizontal layout. |
| `tap_action` | action* | `none` | Home assistant action to perform on tap. |
| `hold_action` | action* | `none` | Home assistant action to perform on hold. |
| `entity_id` | `string` `array` | unset or empty | Only reacts to the state changes of these entities. This can be used if the automatic analysis fails to find all relevant entities. |
| `double_tap_action` | action* | `more-info` | Home assistant action to perform on double_tap. |
| `hidden` | boolean | false | Set to `true` to exclude the area from the dashboard and views. |
| `order` | number | Infinity | Ordering position of the area in the list of available areas. |
| `extra_cards` | array of cards | unset or empty | A list of cards to show on the top of the area subview. |
| `type` | string | `default` | Set to a type of area card. (Currently supported: `default` & `HaAreaCard` |
*) `more-info` `toggle` `call-service` `navigate` `url` `none`
#### Example
```yaml
strategy:
type: custom:mushroom-strategy
options:
areas:
family_room_id:
name: Family Room
icon: mdi:television
icon_color: green
order: 1
extra_cards:
- type: custom:mushroom-chips-card
chips:
- type: entity
entity: sensor.family_room_temperature
icon: mdi:thermometer
icon_color: pink
alignment: center
kitchen_id:
name: Kitchen
icon: mdi:silverware-fork-knife
icon_color: red
order: 2
garage_id:
hidden: true
hallway_id:
type: HaAreaCard
views: []
```
#### Undisclosed Area
The strategy has a special area, named `undisclosed`.
This area is enabled by default and includes the entities that aren't linked to any Home Assistant area.
The area can be configured like any other area as described above.
To exclude this area from the dashboard and views, set its property `hidden` to `true`.
#### Setting options for all areas
Use `_` as an identifier to set the options for all areas.
The following example sets the type of all area-cards to Home Assistant's area card:
```yaml
strategy:
type: custom:mushroom-strategy
options:
areas:
_:
type: HaAreaCard
views: []
```
### Card Options
The `card_options` entry enables you to specify a card type for an entity or to hide the card from the dashboard.
You can also provide a device ID and hide all entities linked to that device.
See [Instructions on to find a device ID](https://community.home-assistant.io/t/device-id-entity-id-where-to-find/289230/4?u=aaliankhan).
#### Example
```yaml
strategy:
type: custom:mushroom-strategy
options:
card_options:
fan.master_bedroom_fan:
type: custom:mushroom-fan-card
remote.harmony_hub_wk:
hidden: true
077ba0492c9bb3b31ffac34f1f3a626a:
hidden: true
views: []
```
### Pre-built views
![Light Views](./docs/light_view.png)
Mushroom strategy includes pre-built views to control/view specific domains.
All devices that are in an area where `hidden` is set to false/undefined are shown*.
By default, all pre-built views below are shown:
| Available views | type | Description |
|:----------------|:--------|:-----------------------------------------------------------------------------|
| `home` | object* | View to show the home screen. |
| `light` | object* | View to control all lights and lights of each area. |
| `fan` | object* | View to control all fans and fans of each area. |
| `cover` | object* | View to control all covers and covers of each area. |
| `switch` | object* | View to control all switches and switches of each area. |
| `climate` | object* | View to control climate devices such as thermostats. Seperated by each area. |
| `camera` | object* | View to show all cameras using WebRTC cards. Seperated by each area. |
* See [View Options](#view-options).
#### View Options
For each of the pre-built views, the following options are available:
| name | type | description |
|:---------|:--------|:----------------------------------------------------------------------------------------------|
| `title` | string | Title of the view in the navigation bar. (Shown when no icon is defined or hovering above it. |
| `icon` | string | Icon of the view in the navigation bar. |
| `order` | string | Ordering position of the view in the navigation bar. |
| `hidden` | boolean | Set to `true` to exclude the view from the dashboard |
#### Example
```yaml
strategy:
type: custom:mushroom-strategy
options:
views:
light:
order: 0
title: illumination
switch:
order: 1
hidden: true
icon: mdi:toggle-switch
views: []
```
### Supported domains
The following domains are supported and enabled by default:
* light
* fan
* cover
* switch
* camera
* lock
* climate
* media_player
* sensor
* binary_sensor
* number
* default (Miscellaneous)
For these domains, the following options are supported:
| Option | type | Description |
|:---------------|:--------|:--------------------------------------------------------------------------|
| `title` | string | Title of the domain in a view. |
| `showControls` | boolean | Weather to show controls in a view, to switch all entities of the domain. |
| `hidden` | boolean | Set to `true` to exclude the domain from the dashboard. |
| `order` | number | Ordering position of the domain entities in a view. |
#### Example
```YAML
strategy:
type: custom:mushroom-strategy
options:
domains:
lights:
title: "My cool lights"
order: 1
switch:
showControls: false
default:
hidden: true
views: []
```
### Home View Options
Home View options will let you configure the Home View.
| Option | type | Description |
|:---------|:------|:----------------------------------------------|
| `hidden` | array | Array of elements to hide from the home view. |
#### hidden
The following elements are supported:
* chips
* persons
* greeting
* areasTitle
* areas
#### Example
```YAML
strategy:
type: custom:mushroom-strategy
options:
homeView:
hidden:
- greeting
- areasTitle
views: []
```
### Chips
![Chips](./docs/chips.png)
Mushroom strategy has chips that indicate the number of devices which are active for a specific domain.
All devices that are in an area where `hidden` is set to false/undefined are counted.
By default, all chips are enabled.
You can manually configure a weather entity-id to use, and there's also an option to add
more [Mushroom Chips][mushroom-chipsUrl] using `extra_chips`.
**Note: To hide the weather chip, you should hide or disable the entity itself.**
| Available chips | type | Description |
|:-----------------|:------------------|:---------------------------------------------------------------------------------------------------------------|
| `light_count` | boolean | Chip to display the number of lights on, tapping turns off all lights, holding navigates to lights view. |
| `fan_count` | boolean | Chip to display the number of fans on, tapping turns off all fans, holding navigates to fans view. |
| `cover_count` | boolean | Chip to display the number of covers not closed, tapping navigates to covers view. |
| `switch_count` | boolean | Chip to display the number of switches on, tapping turns off all switches, holding navigates to switches view. |
| `climate_count` | boolean | Chip to display the number of climate not off, tapping navigates to climates view. |
| `weather_entity` | string (optional) | Entity ID for the weather chip to use, accepts `weather.` only. |
| `extra_chips` | array (optional) | List of extra chips to display, see [Mushroom Chips][mushroom-chipsUrl]. |
#### Example
```yaml
strategy:
type: custom:mushroom-strategy
options:
chips:
climate_count: false
cover_count: false
weather_entity: weather.forecast_home
extra_chips:
- type: conditional
conditions:
- entity: lock.front_door
state: unlocked
chip:
type: entity
entity: lock.front_door
icon_color: red
content_info: none
tap_action:
action: toggle
```
## Full Example
```yaml
strategy:
type: custom:mushroom-strategy
options:
views:
light:
title: illumination
switches:
hidden: true
icon: mdi:toggle-switch
homeView:
hidden:
- Greeting
- AreaTitle
chips:
weather_entity: weather.forecast_home
climate_count: false
cover_count: false
extra_chips:
- type: conditional
conditions:
- entity: lock.front_door
state: unlocked
chip:
type: entity
entity: lock.front_door
icon_color: red
content_info: none
icon: ''
use_entity_picture: false
tap_action:
action: toggle
- type: conditional
conditions:
- entity: cover.garage_door
state_not: closed
chip:
type: entity
entity: cover.garage_door
icon_color: red
content_info: none
tap_action:
action: toggle
areas:
family_room_id:
name: Family Room
icon: mdi:television
icon_color: green
extra_cards:
- type: custom:mushroom-chips-card
chips:
- type: entity
entity: sensor.family_room_temperature
icon: mdi:thermometer
icon_color: pink
alignment: center
kitchen_id:
name: Kitchen
icon: mdi:silverware-fork-knife
icon_color: red
master_bedroom_id:
name: Master Bedroom
icon: mdi:bed-king
icon_color: blue
abias_bedroom_id:
name: Abia's Bedroom
icon: mdi:flower-tulip
icon_color: green
aalians_bedroom_id:
name: Aalian's Bedroom
icon: mdi:rocket-launch
icon_color: yellow
rohaans_bedroom_id:
name: Rohaan's Bedroom
icon: mdi:controller
icon_color: red
hallway_id:
name: Hallway
living_room_id:
name: Living Room
icon: mdi:sofa
front_door_id:
name: Front Door
icon: mdi:door-closed
card_options:
fan.master_bedroom_fan:
type: custom:mushroom-fan-card
remote.harmony_hub_wk:
hidden: true
quick_access_cards:
- type: custom:mushroom-title-card
title: Security
- type: custom:mushroom-cover-card
entity: cover.garage_door
show_buttons_control: true
- type: horizontal-stack
cards:
- type: custom:mushroom-lock-card
entity: lock.front_door
- type: custom:mushroom-entity-card
entity: sensor.front_door_lock_battery
name: Battery
extra_cards:
- type: custom:xiaomi-vacuum-map-card
map_source:
camera: camera.xiaomi_cloud_map_extractor
calibration_source:
camera: true
entity: vacuum.robot_vacuum
vacuum_platform: default
extra_views:
- theme: Backend-selected
title: cool view
path: cool-view
icon: mdi:emoticon-cool
badges: []
cards:
- type: markdown
content: I am cool
views: []
```
## Credits
* The cards used are from [Mushroom][mushroomUrl], [Mini graph card][mini-graphUrl] and [WebRTC][webRtcUrl]
* Took inspiration from [Balloob battery strategy][balloobBatteryUrl]
## Contributors
* [DigiLive](https://github.com/DigiLive)
[![Sponsor DigiLive][sponsorBadge]](https://github.com/sponsors/DigiLive)
* [Johan Frick](https://github.com/johanfrick)
<!-- Badges References -->
## Credits
[hacsBadge]: https://img.shields.io/badge/HACS-Default-41BDF5.svg
* The cards used are from [Mushroom][mushroomUrl] and [Mini graph card][miniGraphUrl].
* Took inspiration from [Balloob battery strategy][balloobBatteryUrl].
[releaseBadge]: https://img.shields.io/github/v/release/AalianKhan/mushroom-strategy?include_prereleases
<!-- Badge References -->
[hacsBadge]: https://img.shields.io/badge/HACS-Default-blue
[sponsorBadge]: https://img.shields.io/badge/Sponsor_him-%E2%9D%A4-%23db61a2.svg?&logo=github&color=%23fe8e86
[releaseBadge]: https://img.shields.io/badge/Release-v2.0.0-blue
<!-- Other References -->
[hacsUrl]: https://hacs.xyz
[releaseUrl]: https://github.com/AalianKhan/mushroom-strategy/releases
[releaseUrl]: https://github.com/AalianKhan/mushroom-strategy/releases/tag/v2.0.0
[mushroomUrl]: https://github.com/piitaya/lovelace-mushroom
[mushroom-chipsUrl]: https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/chips.md
[mini-graphUrl]: https://github.com/kalkih/mini-graph-card
[webRtcUrl]: https://github.com/AlexxIT/WebRTC
[miniGraphUrl]: https://github.com/kalkih/mini-graph-card
[balloobBatteryUrl]: https://gist.github.com/balloob/4a70c83287ddba4e9085cb578ffb161f

File diff suppressed because one or more lines are too long

3755
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +1,42 @@
{
"name": "mushroom-strategy",
"version": "v1.0.0",
"description": "Automatically create a dashboard using Mushroom cards",
"keywords": [
"strategy",
"mushroom"
],
"homepage": "https://github.com/AalianKhan/mushroom-strategy",
"bugs": "https://github.com/AalianKhan/mushroom-strategy/issues",
"license": "MIT",
"author": {
"name": "Aalian Khan"
},
"contributors": [
{
"name": "Ferry Cools"
}
],
"repository": {
"type": "git",
"url": "https://github.com/AalianKhan/mushroom-strategy"
},
"devDependencies": {
"webpack": "^5",
"webpack-cli": "^5.1.4"
},
"scripts": {
"build": "webpack",
"build-dev": "webpack --config webpack.dev.config.js"
"name": "mushroom-strategy",
"version": "2.0.0",
"description": "Automatically create a dashboard using Mushroom cards",
"keywords": [
"strategy",
"mushroom"
],
"homepage": "https://github.com/AalianKhan/mushroom-strategy",
"bugs": "https://github.com/AalianKhan/mushroom-strategy/issues",
"license": "MIT",
"author": {
"name": "Aalian Khan"
},
"contributors": [
{
"name": "Ferry Cools"
}
],
"repository": {
"type": "git",
"url": "https://github.com/AalianKhan/mushroom-strategy"
},
"dependencies": {
"deepmerge": "^4"
},
"devDependencies": {
"home-assistant-js-websocket": "^9",
"superstruct": "^1",
"ts-node": "^10",
"ts-loader": "^9",
"typescript": "^5",
"version-bump-prompt": "^6",
"webpack": "^5",
"webpack-cli": "^5"
},
"scripts": {
"build-dev": "webpack --config webpack.dev.config.ts",
"build": "webpack",
"bump": "bump -t --preid alpha package.json package-lock.json README.md ./src/mushroom-strategy.ts"
}
}

View File

@ -1,4 +1,11 @@
import {optionDefaults} from "./optionDefaults";
import {configurationDefaults} from "./configurationDefaults";
import {HassEntities, HassEntity} from "home-assistant-js-websocket";
import deepmerge from "deepmerge";
import {EntityRegistryEntry} from "./types/homeassistant/data/entity_registry";
import {DeviceRegistryEntry} from "./types/homeassistant/data/device_registry";
import {AreaRegistryEntry} from "./types/homeassistant/data/area_registry";
import {generic} from "./types/strategy/generic";
import StrategyArea = generic.StrategyArea;
/**
* Helper Class
@ -9,31 +16,34 @@ class Helper {
/**
* An array of entities from Home Assistant's entity registry.
*
* @type {hassEntity[]}
* @type {EntityRegistryEntry[]}
* @private
*/
static #entities;
static #entities: EntityRegistryEntry[];
/**
* An array of entities from Home Assistant's device registry.
*
* @type {deviceEntity[]}
* @type {DeviceRegistryEntry[]}
* @private
*/
static #devices;
static #devices: DeviceRegistryEntry[];
/**
* An array of entities from Home Assistant's area registry.
*
* @type {areaEntity[]}
* @type {StrategyArea[]}
* @private
*/
static #areas = [];
static #areas: StrategyArea[] = [];
/**
* An array of state entities from Home Assistant's Hass object.
*
* @type {hassObject["states"]}
* @type {HassEntities}
* @private
*/
static #hassStates;
static #hassStates: HassEntities;
/**
* Indicates whether this module is initialized.
@ -41,27 +51,30 @@ class Helper {
* @type {boolean} True if initialized.
* @private
*/
static #initialized = false;
static #initialized: boolean = false;
/**
* The Custom strategy configuration.
*
* @type {customStrategyOptions | {}}
* @type {generic.StrategyConfig}
* @private
*/
static #strategyOptions = {};
static #strategyOptions: generic.StrategyConfig;
/**
* Set to true for more verbose information in the console.
*
* @type {boolean}
* @private
*/
static debug = optionDefaults.debug;
static #debug: boolean;
/**
* Class constructor.
*
* This class shouldn't be instantiated directly. Instead, it should be initialized with method initialize().
* This class shouldn't be instantiated directly.
* Instead, it should be initialized with method initialize().
*
* @throws {Error} If trying to instantiate this class.
*/
constructor() {
@ -71,127 +84,115 @@ class Helper {
/**
* Custom strategy configuration.
*
* @returns {customStrategyOptions|{}}
* @returns {generic.StrategyConfig}
* @static
*/
static get strategyOptions() {
static get strategyOptions(): generic.StrategyConfig {
return this.#strategyOptions;
}
/**
* @returns {areaEntity[]}
* Get the entities from Home Assistant's area registry.
*
* @returns {StrategyArea[]}
* @static
*/
static get areas() {
static get areas(): StrategyArea[] {
return this.#areas;
}
/**
* @returns {deviceEntity[]}
* Get the devices from Home Assistant's device registry.
*
* @returns {DeviceRegistryEntry[]}
* @static
*/
static get devices() {
static get devices(): DeviceRegistryEntry[] {
return this.#devices;
}
/**
* @returns {hassEntity[]}
* Get the entities from Home Assistant's entity registry.
*
* @returns {EntityRegistryEntry[]}
* @static
*/
static get entities() {
static get entities(): EntityRegistryEntry[] {
return this.#entities;
}
/**
* Get the current debug mode of the mushroom strategy.
*
* @returns {boolean}
* @static
*/
static get debug() {
return this.debug;
static get debug(): boolean {
return this.#debug;
}
/**
* Initialize this module.
*
* @param {dashBoardInfo | viewInfo} info Strategy information object.
* @param {generic.DashBoardInfo} info Strategy information object.
* @returns {Promise<void>}
* @static
*/
static async initialize(info) {
static async initialize(info: generic.DashBoardInfo): Promise<void> {
// Initialize properties.
this.#hassStates = info.hass.states;
this.#strategyOptions = deepmerge(configurationDefaults, info.config?.strategy?.options ?? {});
this.#debug = this.#strategyOptions.debug;
try {
// Query the registries of Home Assistant.
[this.#entities, this.#devices, this.#areas] = await Promise.all([
info.hass.callWS({type: "config/entity_registry/list"}),
info.hass.callWS({type: "config/device_registry/list"}),
info.hass.callWS({type: "config/area_registry/list"}),
// noinspection ES6MissingAwait False positive? https://youtrack.jetbrains.com/issue/WEB-63746
[Helper.#entities, Helper.#devices, Helper.#areas] = await Promise.all([
info.hass.callWS({type: "config/entity_registry/list"}) as Promise<EntityRegistryEntry[]>,
info.hass.callWS({type: "config/device_registry/list"}) as Promise<DeviceRegistryEntry[]>,
info.hass.callWS({type: "config/area_registry/list"}) as Promise<AreaRegistryEntry[]>,
]);
} catch (e) {
console.error(Helper.debug ? e : "An error occurred while querying Home assistant's registries!");
Helper.logError("An error occurred while querying Home assistant's registries!", e);
throw 'Check the console for details';
}
// Cloning is required for the purpose of the required undisclosed area.
this.#strategyOptions = structuredClone(info.config.strategy.options || {});
this.debug = this.#strategyOptions.debug;
// Setup required configuration entries.
// TODO: Refactor to something smarter than repeating code for areas, views and domains.
this.#strategyOptions.areas = this.#strategyOptions.areas ?? {};
this.#strategyOptions.views = this.#strategyOptions.views ?? {};
this.#strategyOptions.domains = this.#strategyOptions.domains ?? {};
// Setup 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.
if (!this.#strategyOptions.areas.undisclosed?.hidden) {
this.#strategyOptions.areas.undisclosed = {
...optionDefaults.areas.undisclosed,
...configurationDefaults.areas.undisclosed,
...this.#strategyOptions.areas.undisclosed,
};
// Make sure the area_id of the custom undisclosed area remains null.
this.#strategyOptions.areas.undisclosed.area_id = null;
// Make sure the custom configuration of the undisclosed area doesn't overwrite the area_id.
this.#strategyOptions.areas.undisclosed.area_id = "undisclosed";
this.#areas.push(this.#strategyOptions.areas.undisclosed);
}
// Merge custom areas of the strategy options into hass areas.
// Merge custom areas of the strategy options into strategy areas.
this.#areas = Helper.areas.map(area => {
return {...area, ...this.#strategyOptions.areas[area.area_id ?? "undisclosed"]};
return {...area, ...this.#strategyOptions.areas?.[area.area_id]};
});
// Sort hass areas by order first and then by name.
// Sort strategy areas by order first and then by name.
this.#areas.sort((a, b) => {
return (a.order ?? Infinity) - (b.order ?? Infinity) || a.name.localeCompare(b.name);
});
// Merge the views of the strategy options and the default views.
for (const view of Object.keys(optionDefaults.views)) {
this.#strategyOptions.views[view] = {
...optionDefaults.views[view],
...(this.#strategyOptions.views[view]),
};
}
// Sort views of the strategy options by order first and then by title.
// Sort custom and default views of the strategy options by order first and then by title.
this.#strategyOptions.views = Object.fromEntries(
Object.entries(this.#strategyOptions.views).sort(([, a], [, b]) => {
return (a.order ?? Infinity) - (b.order ?? Infinity) || a.title?.localeCompare(b.title);
}),
Object.entries(this.#strategyOptions.views).sort(([, a], [, b]) => {
return (a.order ?? Infinity) - (b.order ?? Infinity) || (a.title ?? "undefined").localeCompare(b.title ?? "undefined");
}),
);
// Merge the domains of the strategy options and the default domains.
for (const domain of Object.keys(optionDefaults.domains)) {
this.#strategyOptions.domains[domain] = {
...optionDefaults.domains[domain],
...(this.#strategyOptions.domains[domain]),
};
}
// Sort domains of the strategy options by order first and then by title.
// Sort custom and default domains of the strategy options by order first and then by title.
this.#strategyOptions.domains = Object.fromEntries(
Object.entries(this.#strategyOptions.domains).sort(([, a], [, b]) => {
return (a.order ?? Infinity) - (b.order ?? Infinity) || a.title?.localeCompare(b.title);
}),
Object.entries(this.#strategyOptions.domains).sort(([, a], [, b]) => {
return (a.order ?? Infinity) - (b.order ?? Infinity) || (a.title ?? "undefined").localeCompare(b.title ?? "undefined");
}),
);
this.#initialized = true;
@ -203,7 +204,7 @@ class Helper {
* @returns {boolean} True if this module is initialized.
* @static
*/
static isInitialized() {
static isInitialized(): boolean {
return this.#initialized;
}
@ -219,7 +220,7 @@ class Helper {
* @return {string} The template string.
* @static
*/
static getCountTemplate(domain, operator, value) {
static getCountTemplate(domain: string, operator: string, value: string): string {
// noinspection JSMismatchedCollectionQueryUpdate (False positive per 17-04-2023)
/**
* Array of entity state-entries, filtered by domain.
@ -232,7 +233,7 @@ class Helper {
*
* @type {string[]}
*/
const states = [];
const states: string[] = [];
if (!this.isInitialized()) {
console.warn("Helper class should be initialized before calling this method!");
@ -240,20 +241,20 @@ class Helper {
// Get the ID of the devices which are linked to the given area.
for (const area of this.#areas) {
const areaDeviceIds = this.#devices.filter(device => {
const areaDeviceIds = this.#devices.filter((device) => {
return device.area_id === area.area_id;
}).map(device => {
}).map((device) => {
return device.id;
});
// Get the entities of which all conditions of the callback function are met. @see areaFilterCallback.
const newStates = this.#entities.filter(
this.#areaFilterCallback, {
area: area,
domain: domain,
areaDeviceIds: areaDeviceIds,
})
.map(entity => `states['${entity.entity_id}']`);
this.#areaFilterCallback, {
area: area,
domain: domain,
areaDeviceIds: areaDeviceIds,
})
.map((entity) => `states['${entity.entity_id}']`);
states.push(...newStates);
}
@ -261,35 +262,6 @@ class Helper {
return `{% set entities = [${states}] %} {{ entities | selectattr('state','${operator}','${value}') | list | count }}`;
}
/**
* Callback function for filtering entities.
*
* Entities of which all the conditions below are met are kept:
* 1. Or/Neither the entity's linked device (if any) or/nor the entity itself is lined to the given area.
* (See variable areaMatch)
* 2. The entity's domain matches the given domain.
* 3. The entity is not hidden and is not disabled.
*
* @param {hassEntity} entity The current hass entity to evaluate.
* @this {areaFilterContext}
*
* @return {boolean} True to keep the entity.
* @static
*/
static #areaFilterCallback(entity) {
const areaMatch = this.area.area_id
// Area is a hass entity; The entity's linked device or the entity itself is linked to the given area.
? this.areaDeviceIds.includes(entity.device_id) || entity.area_id === this.area.area_id
// Undisclosed area; Neither the entity's linked device (if any), nor the entity itself is linked to any area.
: (this.areaDeviceIds.includes(entity.device_id) || !entity.device_id) && !entity.area_id;
return (
areaMatch
&& entity.entity_id.startsWith(`${this.domain}.`)
&& entity.hidden_by == null && entity.disabled_by == null
);
}
/**
* Get device entities from the entity registry, filtered by area and domain.
*
@ -298,36 +270,35 @@ class Helper {
*
* The result excludes hidden and disabled entities.
*
* @param {areaEntity} area Area entity.
* @param {AreaRegistryEntry} area Area entity.
* @param {string} domain The domain of the entity-id.
*
* @return {hassEntity[]} Array of device entities.
* @return {EntityRegistryEntry[]} Array of device entities.
* @static
*/
static getDeviceEntities(area, domain) {
static getDeviceEntities(area: AreaRegistryEntry, domain: string): EntityRegistryEntry[] {
if (!this.isInitialized()) {
console.warn("Helper class should be initialized before calling this method!");
}
// Get the ID of the devices which are linked to the given area.
const areaDeviceIds = this.#devices.filter(device => {
return device.area_id === area.area_id;
}).map(device => {
const areaDeviceIds = this.#devices.filter((device) => {
return (device.area_id ?? "undisclosed") === area.area_id;
}).map((device: DeviceRegistryEntry) => {
return device.id;
});
// Return the entities of which all conditions of the callback function are met. @see areaFilterCallback.
return this.#entities.filter(
this.#areaFilterCallback, {
area: area,
domain: domain,
areaDeviceIds: areaDeviceIds,
})
.sort((a, b) => {
/** @type hassEntity */
return a.original_name?.localeCompare(b.original_name);
});
this.#areaFilterCallback, {
area: area,
domain: domain,
areaDeviceIds: areaDeviceIds,
})
.sort((a, b) => {
return (a.original_name ?? "undefined").localeCompare(b.original_name ?? "undefined");
});
}
/**
@ -335,39 +306,41 @@ class Helper {
*
* The result excludes hidden and disabled entities.
*
* @param {areaEntity} area Area entity.
* @param {AreaRegistryEntry} area Area entity.
* @param {string} domain Domain of the entity-id.
*
* @return {stateObject[]} Array of state entities.
* @return {HassEntity[]} Array of state entities.
*/
static getStateEntities(area, domain) {
static getStateEntities(area: AreaRegistryEntry, domain: string): HassEntity[] {
if (!this.isInitialized()) {
console.warn("Helper class should be initialized before calling this method!");
}
const states = [];
const states: HassEntity[] = [];
// Create a map for the hassEntities and devices {id: object} to improve lookup speed.
/** @type {Object<string, hassEntity>} */
const entityMap = Object.fromEntries(this.#entities.map(entity => [entity.entity_id, entity]));
/** @type {Object<string, deviceEntity>} */
const deviceMap = Object.fromEntries(this.#devices.map(device => [device.id, device]));
const entityMap: {
[s: string]: EntityRegistryEntry;
} = Object.fromEntries(this.#entities.map((entity) => [entity.entity_id, entity]));
const deviceMap: {
[s: string]: DeviceRegistryEntry;
} = Object.fromEntries(this.#devices.map((device) => [device.id, device]));
// Get states whose entity-id starts with the given string.
const stateEntities = Object.values(this.#hassStates).filter(
state => state.entity_id.startsWith(`${domain}.`),
(state) => state.entity_id.startsWith(`${domain}.`),
);
for (const state of stateEntities) {
const hassEntity = entityMap[state.entity_id];
const device = deviceMap[hassEntity?.device_id];
const device = deviceMap[hassEntity?.device_id ?? ""];
// Collect states of which any (whichever comes first) of the conditions below are met:
// 1. The linked entity is linked to the given area.
// 2. The entity is linked to a device, and the linked device is linked to the given area.
if (
(hassEntity?.area_id === area.area_id)
|| (device && device.area_id === area.area_id)
(hassEntity?.area_id === area.area_id)
|| (device && device.area_id === area.area_id)
) {
states.push(state);
}
@ -385,44 +358,22 @@ class Helper {
* @param {string} className Name of the class to sanitize.
* @returns {string} The sanitized classname.
*/
static sanitizeClassName(className) {
static sanitizeClassName(className: string): string {
className = className.charAt(0).toUpperCase() + className.slice(1);
return className.replace(/([-_][a-z])/g, group =>
group
.toUpperCase()
.replace("-", "")
.replace("_", ""),
return className.replace(/([-_][a-z])/g, (group) => group
.toUpperCase()
.replace("-", "")
.replace("_", ""),
);
}
/**
* Get the keys of nested objects by its property value.
*
* @param {Object<Object>} object An object of objects.
* @param {string|number} property The name of the property to evaluate.
* @param {*} value The value which the property should match.
*
* @return {string[]|number[]} An array with keys.
*/
static #getObjectKeysByPropertyValue(object, property, value) {
const keys = [];
for (const key of Object.keys(object)) {
if (object[key][property] === value) {
keys.push(key);
}
}
return keys;
}
/**
* Get the ids of the views which aren't set to hidden in the strategy options.
*
* @return {string[]} An array of view ids.
*/
static getExposedViewIds() {
static getExposedViewIds(): string[] {
if (!this.isInitialized()) {
console.warn("Helper class should be initialized before calling this method!");
}
@ -435,13 +386,90 @@ class Helper {
*
* @return {string[]} An array of domain ids.
*/
static getExposedDomainIds() {
static getExposedDomainIds(): string[] {
if (!this.isInitialized()) {
console.warn("Helper class should be initialized before calling this method!");
}
return this.#getObjectKeysByPropertyValue(this.#strategyOptions.domains, "hidden", false);
}
/**
* Callback function for filtering entities.
*
* Entities of which all the conditions below are met are kept:
* 1. The entity is not hidden and is not disabled.
* 2. The entity's domain matches the given domain.
* 3. Or/Neither the entity's linked device (if any) or/nor the entity itself is linked to the given area.
* (See variable areaMatch)
*
* @param {EntityRegistryEntry} entity The current hass entity to evaluate.
* @this {AreaFilterContext}
*
* @return {boolean} True to keep the entity.
* @static
*/
static #areaFilterCallback(
this: {
area: AreaRegistryEntry,
areaDeviceIds: string[],
domain: string,
},
entity: EntityRegistryEntry): boolean {
const entityUnhidden = entity.hidden_by === null && entity.disabled_by === null;
const domainMatches = entity.entity_id.startsWith(`${this.domain}.`);
const entityLinked = this.area.area_id === "undisclosed"
// Undisclosed area;
// nor the entity itself, neither the entity's linked device (if any) is linked to any area.
? !entity.area_id && (this.areaDeviceIds.includes(entity.device_id ?? "") || !entity.device_id)
// Area is a hass entity;
// The entity's linked device or the entity itself is linked to the given area.
: this.areaDeviceIds.includes(entity.device_id ?? "") || entity.area_id === this.area.area_id;
return (entityUnhidden && domainMatches && entityLinked);
}
/**
* Get the keys of nested objects by its property value.
*
* @param {Object<string, any>} object An object of objects.
* @param {string|number} property The name of the property to evaluate.
* @param {*} value The value which the property should match.
*
* @return {string[]} An array with keys.
*/
static #getObjectKeysByPropertyValue(
object: { [k: string]: any },
property: string, value: any
): string[] {
const keys: string[] = [];
for (const key of Object.keys(object)) {
if (object[key][property] === value) {
keys.push(key);
}
}
return keys;
}
/**
* Logs an error message to the console.
*
* @param {string} userMessage - The error message to display.
* @param {unknown} [e] - (Optional) The error object or additional information.
*
* @return {void}
*/
static logError(userMessage: string, e?: unknown): void {
if (Helper.debug) {
console.error(userMessage, e);
return;
}
console.error(userMessage);
}
}
export {Helper};

View File

@ -1,81 +0,0 @@
import {Helper} from "../Helper";
/**
* Abstract Card Class
*
* To create a new card, extend the new class with this one.
*
* @class
* @abstract
*/
class AbstractCard {
/**
* Entity to create the card for.
*
* @type {hassEntity | areaEntity}
*/
entity;
/**
* Options for creating a card.
*
* @type {abstractOptions}
*/
options = {
type: "custom:mushroom-entity-card",
icon: "mdi:help-circle",
double_tap_action: {
action: null,
},
};
/**
* Class constructor.
*
* @param {hassEntity | areaEntity} entity The hass entity to create a card for.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity) {
if (this.constructor === AbstractCard) {
throw new Error("Abstract classes can't be instantiated.");
}
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.entity = entity;
}
/**
* Merge the default options of this class and the custom options into the options of the parent class.
*
* @param {Object} [defaultOptions={}] Default options for the card.
* @param {Object} [customOptions={}] Custom Options for the card.
*/
mergeOptions(defaultOptions, customOptions) {
this.options = {
...this.options,
...defaultOptions,
...customOptions,
};
try {
this.options.double_tap_action.target.entity_id = this.entity.entity_id;
} catch { }
}
/**
* Get a card for an entity.
*
* @return {abstractOptions & Object} A card object.
*/
getCard() {
return {
entity: this.entity.entity_id,
...this.options,
};
}
}
export {AbstractCard};

59
src/cards/AbstractCard.ts Normal file
View File

@ -0,0 +1,59 @@
import {Helper} from "../Helper";
import {cards} from "../types/strategy/cards";
import {generic} from "../types/strategy/generic";
import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config";
/**
* Abstract Card Class
*
* To create a new card, extend the new class with this one.
*
* @class
* @abstract
*/
abstract class AbstractCard {
/**
* Entity to create the card for.
*
* @type {generic.RegistryEntry}
*/
entity: generic.RegistryEntry;
/**
* Configuration of the card.
*
* @type {EntityCardConfig}
*/
config: EntityCardConfig = {
type: "custom:mushroom-entity-card",
icon: "mdi:help-circle",
};
/**
* Class constructor.
*
* @param {generic.RegistryEntry} entity The hass entity to create a card for.
* @throws {Error} If the Helper module isn't initialized.
*/
protected constructor(entity: generic.RegistryEntry) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.entity = entity;
}
/**
* Get a card.
*
* @return {cards.AbstractCardConfig} A card object.
*/
getCard(): cards.AbstractCardConfig {
return {
...this.config,
entity: "entity_id" in this.entity ? this.entity.entity_id : undefined,
};
}
}
export {AbstractCard};

View File

@ -1,62 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Area Card Class
*
* Used to create a card for an entity of the area domain.
*
* @class
* @extends AbstractCard
*/
class AreaCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {areaCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-template-card",
primary: undefined,
icon: "mdi:texture-box",
icon_color: "blue",
tap_action: {
action: "navigate",
navigation_path: undefined,
},
hold_action: {
action: "none",
},
};
/**
* Class constructor.
*
* @param {areaEntity} area The area entity to create a card for.
* @param {areaCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(area, options = {}) {
super(area);
this.#defaultOptions.primary = area.name;
this.#defaultOptions.tap_action.navigation_path = area.area_id ?? area.name;
// Set card type to default if a type "default" is given in strategy options.
if (options.type === "default") {
options.type = this.#defaultOptions.type;
}
this.mergeOptions(
this.#defaultOptions,
options,
);
// Override the area's name with a custom name, unless a custom primary text is set.
if (!options.primary && options.name) {
this.options.primary = options.name;
}
}
}
export {AreaCard};

62
src/cards/AreaCard.ts Normal file
View File

@ -0,0 +1,62 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {AreaRegistryEntry} from "../types/homeassistant/data/area_registry";
import {TemplateCardConfig} from "../types/lovelace-mushroom/cards/template-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Area Card Class
*
* Used to create a card for an entity of the area domain.
*
* @class
* @extends AbstractCard
*/
class AreaCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {TemplateCardConfig}
* @private
*/
#defaultConfig: TemplateCardConfig = {
type: "custom:mushroom-template-card",
primary: undefined,
icon: "mdi:texture-box",
icon_color: "blue",
tap_action: {
action: "navigate",
navigation_path: "",
},
hold_action: {
action: "none",
},
};
/**
* Class constructor.
*
* @param {AreaRegistryEntry} area The area entity to create a card for.
* @param {cards.TemplateCardOptions} [options={}] Options for the card.
*
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(area: AreaRegistryEntry, options: cards.TemplateCardOptions = {}) {
super(area);
// Don't override the default card type if default is set in the strategy options.
if (options.type === "default") {
delete options.type;
}
// Initialize the default configuration.
this.#defaultConfig.primary = area.name;
if (this.#defaultConfig.tap_action && ("navigation_path" in this.#defaultConfig.tap_action)) {
this.#defaultConfig.tap_action.navigation_path = area.area_id;
}
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {AreaCard};

View File

@ -1,41 +0,0 @@
import {SensorCard} from "./SensorCard";
/**
* Sensor Card Class
*
* Used to create a card for controlling an entity of the binary_sensor domain.
*
* @class
* @extends SensorCard
*/
class BinarySensorCard extends SensorCard {
/**
* Default options of the card.
*
* @type {sensorCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-entity-card",
icon: "mdi:power-cycle",
icon_color: "green",
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {sensorCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {BinarySensorCard};

View File

@ -0,0 +1,42 @@
import {SensorCard} from "./SensorCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Sensor Card Class
*
* Used to create a card for controlling an entity of the binary_sensor domain.
*
* @class
* @extends SensorCard
*/
class BinarySensorCard extends SensorCard {
/**
* Default configuration of the card.
*
* @type {EntityCardConfig}
* @private
*/
#defaultConfig: EntityCardConfig = {
type: "custom:mushroom-entity-card",
icon: "mdi:power-cycle",
icon_color: "green",
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.EntityCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.EntityCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {BinarySensorCard};

View File

@ -1,41 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Camera Card Class
*
* Used to create a card for controlling an entity of the camera domain.
*
* @class
* @extends AbstractCard
*/
class CameraCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {cameraCardOptions}
* @private
*/
#defaultOptions = {
type: "picture-entity",
show_name: false,
show_state: false,
camera_view: "live",
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {cameraCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {CameraCard};

44
src/cards/CameraCard.ts Normal file
View File

@ -0,0 +1,44 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {PictureEntityCardConfig} from "../types/homeassistant/panels/lovelave/cards/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Camera Card Class
*
* Used to create a card for controlling an entity of the camera domain.
*
* @class
* @extends AbstractCard
*/
class CameraCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {PictureEntityCardConfig}
* @private
*/
#defaultConfig: PictureEntityCardConfig = {
entity: "",
type: "picture-entity",
show_name: false,
show_state: false,
camera_view: "live",
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.PictureEntityCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.PictureEntityCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {CameraCard};

View File

@ -1,46 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Climate Card Class
*
* Used to create a card for controlling an entity of the climate domain.
*
* @class
* @extends AbstractCard
*/
class ClimateCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {climateCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-climate-card",
icon: undefined,
hvac_modes: [
"off",
"cool",
"heat",
"fan_only",
],
show_temperature_control: true,
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {climateCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {ClimateCard};

48
src/cards/ClimateCard.ts Normal file
View File

@ -0,0 +1,48 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {ClimateCardConfig} from "../types/lovelace-mushroom/cards/climate-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Climate Card Class
*
* Used to create a card for controlling an entity of the climate domain.
*
* @class
* @extends AbstractCard
*/
class ClimateCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {ClimateCardConfig}
* @private
*/
#defaultConfig: ClimateCardConfig = {
type: "custom:mushroom-climate-card",
icon: undefined,
hvac_modes: [
"off",
"cool",
"heat",
"fan_only",
],
show_temperature_control: true,
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.ClimateCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.ClimateCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {ClimateCard};

102
src/cards/ControllerCard.ts Normal file
View File

@ -0,0 +1,102 @@
import {cards} from "../types/strategy/cards";
import {StackCardConfig} from "../types/homeassistant/lovelace/cards/types";
import {LovelaceCardConfig} from "../types/homeassistant/data/lovelace";
import {HassServiceTarget} from "home-assistant-js-websocket";
/**
* Controller Card class.
*
* Used for creating a Title Card with controls.
*
* @class
*/
class ControllerCard {
/**
* @type {HassServiceTarget} The target to control the entities of.
* @private
*/
readonly #target: HassServiceTarget;
/**
* Default configuration of the card.
*
* @type {cards.ControllerCardConfig}
* @private
*/
readonly #defaultConfig: cards.ControllerCardConfig = {
type: "mushroom-title-card",
showControls: true,
iconOn: "mdi:power-on",
iconOff: "mdi:power-off",
onService: "none",
offService: "none",
};
/**
* Class constructor.
*
* @param {HassServiceTarget} target The target to control the entities of.
* @param {cards.ControllerCardOptions} options Controller Card options.
*/
constructor(target: HassServiceTarget, options: cards.ControllerCardOptions = {}) {
this.#target = target;
this.#defaultConfig = {
...this.#defaultConfig,
...options,
};
}
/**
* Create a Controller card.
*
* @return {StackCardConfig} A Controller card.
*/
createCard(): StackCardConfig {
const cards: LovelaceCardConfig[] = [
{
type: "custom:mushroom-title-card",
title: this.#defaultConfig.title,
subtitle: this.#defaultConfig.subtitle,
},
];
if (this.#defaultConfig.showControls) {
cards.push({
type: "horizontal-stack",
cards: [
{
type: "custom:mushroom-template-card",
icon: this.#defaultConfig.iconOff,
layout: "vertical",
icon_color: "red",
tap_action: {
action: "call-service",
service: this.#defaultConfig.offService,
target: this.#target,
data: {},
},
},
{
type: "custom:mushroom-template-card",
icon: this.#defaultConfig.iconOn,
layout: "vertical",
icon_color: "amber",
tap_action: {
action: "call-service",
service: this.#defaultConfig.onService,
target: this.#target,
data: {},
},
},
],
});
}
return {
type: "horizontal-stack",
cards: cards,
};
}
}
export {ControllerCard};

View File

@ -1,42 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Cover Card Class
*
* Used to create a card for controlling an entity of the cover domain.
*
* @class
* @extends AbstractCard
*/
class CoverCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {coverCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-cover-card",
icon: undefined,
show_buttons_control: true,
show_position_control: true,
show_tilt_position_control: true,
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {coverCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {CoverCard};

44
src/cards/CoverCard.ts Normal file
View File

@ -0,0 +1,44 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {CoverCardConfig} from "../types/lovelace-mushroom/cards/cover-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Cover Card Class
*
* Used to create a card for controlling an entity of the cover domain.
*
* @class
* @extends AbstractCard
*/
class CoverCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {CoverCardConfig}
* @private
*/
#defaultConfig: CoverCardConfig = {
type: "custom:mushroom-cover-card",
icon: undefined,
show_buttons_control: true,
show_position_control: true,
show_tilt_position_control: true,
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.CoverCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.CoverCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {CoverCard};

View File

@ -1,42 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Fan Card Class
*
* Used to create a card for controlling an entity of the fan domain.
*
* @class
* @extends AbstractCard
*/
class FanCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {fanCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-fan-card",
icon: undefined,
show_percentage_control: true,
show_oscillate_control: true,
icon_animation: true,
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {fanCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {FanCard};

44
src/cards/FanCard.ts Normal file
View File

@ -0,0 +1,44 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {FanCardConfig} from "../types/lovelace-mushroom/cards/fan-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Fan Card Class
*
* Used to create a card for controlling an entity of the fan domain.
*
* @class
* @extends AbstractCard
*/
class FanCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {FanCardConfig}
* @private
*/
#defaultConfig: FanCardConfig = {
type: "custom:mushroom-fan-card",
icon: undefined,
show_percentage_control: true,
show_oscillate_control: true,
icon_animation: true,
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.FanCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.FanCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {FanCard};

View File

@ -1,47 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* HA Area Card Class
*
* Used to create a card for an entity of the area domain using the built in type 'area'.
*
* @class
* @extends AbstractCard
*/
class AreaCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {HaAreaCardOptions}
* @private
*/
#defaultOptions = {
type: "area",
area: undefined,
navigation_path: undefined,
};
/**
* Class constructor.
*
* @param {areaEntity} area The area entity to create a card for.
* @param {HaAreaCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(area, options = {}) {
super(area);
this.#defaultOptions.area = area.area_id ?? area.name;
this.#defaultOptions.navigation_path = area.area_id ?? area.name;
// Enforce the card type.
options.type = this.#defaultOptions.type;
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {AreaCard};

49
src/cards/HaAreaCard.ts Normal file
View File

@ -0,0 +1,49 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {AreaRegistryEntry} from "../types/homeassistant/data/area_registry";
import {AreaCardConfig} from "../types/homeassistant/lovelace/cards/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* HA Area Card Class
*
* Used to create a card for an entity of the area domain using the built-in type 'area'.
*
* @class
* @extends AbstractCard
*/
class AreaCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {AreaCardConfig}
* @private
*/
#defaultConfig: AreaCardConfig = {
type: "area",
area: "",
};
/**
* Class constructor.
*
* @param {AreaRegistryEntry} area The area entity to create a card for.
* @param {cards.AreaCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(area: AreaRegistryEntry, options: cards.AreaCardOptions = {}) {
super(area);
// Initialize the default configuration.
this.#defaultConfig.area = area.area_id;
this.#defaultConfig.navigation_path = this.#defaultConfig.area;
// Enforce the card type.
delete options.type;
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {AreaCard};

View File

@ -1,53 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Light Card Class
*
* Used to create a card for controlling an entity of the light domain.
*
* @class
* @extends AbstractCard
*/
class LightCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {lightCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-light-card",
icon: undefined,
show_brightness_control: true,
show_color_control: true,
use_light_color: true,
show_color_temp_control: true,
double_tap_action: {
target: {
entity_id: undefined,
},
action: "call-service",
service: "light.turn_on",
data: {
rgb_color: [255, 255, 255],
},
},
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {lightCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {LightCard};

67
src/cards/LightCard.ts Normal file
View File

@ -0,0 +1,67 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {LightCardConfig} from "../types/lovelace-mushroom/cards/light-card-config";
import {generic} from "../types/strategy/generic";
import isCallServiceActionConfig = generic.isCallServiceActionConfig;
import isCallServiceActionTarget = generic.isCallServiceActionTarget;
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Light Card Class
*
* Used to create a card for controlling an entity of the light domain.
*
* @class
* @extends AbstractCard
*/
class LightCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {LightCardConfig}
* @private
*/
#defaultConfig: LightCardConfig = {
type: "custom:mushroom-light-card",
icon: undefined,
show_brightness_control: true,
show_color_control: true,
show_color_temp_control: true,
use_light_color: true,
double_tap_action: {
action: "call-service",
service: "light.turn_on",
target: {
entity_id: undefined,
},
data: {
rgb_color: [255, 255, 255],
},
},
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.LightCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.LightCardOptions = {}) {
super(entity);
// Set the target for double-tap action.
if (
isCallServiceActionConfig(this.#defaultConfig.double_tap_action)
&& isCallServiceActionTarget(this.#defaultConfig.double_tap_action.target)
) {
this.#defaultConfig.double_tap_action.target.entity_id = entity.entity_id;
}
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {LightCard};

View File

@ -1,39 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Lock Card Class
*
* Used to create a card for controlling an entity of the lock domain.
*
* @class
* @extends AbstractCard
*/
class LockCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {lockCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-lock-card",
icon: undefined,
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {lockCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {LockCard};

41
src/cards/LockCard.ts Normal file
View File

@ -0,0 +1,41 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {LockCardConfig} from "../types/lovelace-mushroom/cards/lock-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Lock Card Class
*
* Used to create a card for controlling an entity of the lock domain.
*
* @class
* @extends AbstractCard
*/
class LockCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {LockCardConfig}
* @private
*/
#defaultConfig: LockCardConfig = {
type: "custom:mushroom-lock-card",
icon: undefined,
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.LockCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.LockCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {LockCard};

View File

@ -1,50 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Mediaplayer Card Class
*
* Used to create a card for controlling an entity of the media_player domain.
*
* @class
* @extends AbstractCard
*/
class MediaPlayerCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {mediaPlayerCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-media-player-card",
icon: undefined,
use_media_info: true,
media_controls: [
"on_off",
"play_pause_stop",
],
show_volume_level: true,
volume_controls: [
"volume_mute",
"volume_set",
"volume_buttons",
],
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {mediaPlayerCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {MediaPlayerCard};

View File

@ -0,0 +1,51 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {MediaPlayerCardConfig} from "../types/lovelace-mushroom/cards/media-player-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Mediaplayer Card Class
*
* Used to create a card for controlling an entity of the media_player domain.
*
* @class
* @extends AbstractCard
*/
class MediaPlayerCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {MediaPlayerCardConfig}
* @private
*/
#defaultConfig: MediaPlayerCardConfig = {
type: "custom:mushroom-media-player-card",
use_media_info: true,
media_controls: [
"on_off",
"play_pause_stop",
],
show_volume_level: true,
volume_controls: [
"volume_mute",
"volume_set",
"volume_buttons",
],
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.MediaPlayerCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.MediaPlayerCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {MediaPlayerCard};

View File

@ -1,39 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Miscellaneous Card Class
*
* Used to create a card an entity of any domain.
*
* @class
* @extends AbstractCard
*/
class MiscellaneousCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {miscellaneousCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-entity-card",
icon_color: "blue-grey",
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {miscellaneousCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {MiscellaneousCard};

View File

@ -0,0 +1,40 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config";
/**
* Miscellaneous Card Class
*
* Used to create a card an entity of any domain.
*
* @class
* @extends AbstractCard
*/
class MiscellaneousCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {EntityCardConfig}
* @private
*/
#defaultConfig: EntityCardConfig = {
type: "custom:mushroom-entity-card",
icon_color: "blue-grey",
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.EntityCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.EntityCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {MiscellaneousCard};

View File

@ -1,37 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Number Card Class
*
* Used to create a card for controlling an entity of the number domain.
*
* @class
* @extends AbstractCard
*/
class NumberCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {numberCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-number-card",
icon: undefined,
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {numberCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(this.#defaultOptions, options);
}
}
export {NumberCard};

41
src/cards/NumberCard.ts Normal file
View File

@ -0,0 +1,41 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {NumberCardConfig} from "../types/lovelace-mushroom/cards/number-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported
/**
* Number Card Class
*
* Used to create a card for controlling an entity of the number domain.
*
* @class
* @extends AbstractCard
*/
class NumberCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {NumberCardConfig}
* @private
*/
#defaultConfig: NumberCardConfig = {
type: "custom:mushroom-number-card",
icon: undefined,
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.NumberCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.NumberCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {NumberCard};

View File

@ -1,42 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Person Card Class
*
* Used to create a card for an entity of the person domain.
*
* @class
* @extends AbstractCard
*/
class PersonCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {personCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-person-card",
layout: "vertical",
primary_info: "none",
secondary_info: "none",
icon_type: "entity-picture",
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {personCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {PersonCard};

43
src/cards/PersonCard.ts Normal file
View File

@ -0,0 +1,43 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {PersonCardConfig} from "../types/lovelace-mushroom/cards/person-card-config";
/**
* Person Card Class
*
* Used to create a card for an entity of the Person domain.
*
* @class
* @extends AbstractCard
*/
class PersonCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {PersonCardConfig}
* @private
*/
#defaultConfig: PersonCardConfig = {
type: "custom:mushroom-person-card",
layout: "vertical",
primary_info: "none",
secondary_info: "none",
icon_type: "entity-picture",
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.PersonCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.PersonCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {PersonCard};

View File

@ -1,42 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Sensor Card Class
*
* Used to create a card for controlling an entity of the sensor domain.
*
* @class
* @extends AbstractCard
*/
class SensorCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {sensorCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-entity-card",
icon: "mdi:information",
animate: true,
line_color: "green",
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {sensorCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {SensorCard};

42
src/cards/SensorCard.ts Normal file
View File

@ -0,0 +1,42 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config";
/**
* Sensor Card Class
*
* Used to create a card for controlling an entity of the sensor domain.
*
* @class
* @extends AbstractCard
*/
class SensorCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {EntityCardConfig}
* @private
*/
#defaultConfig: EntityCardConfig = {
type: "custom:mushroom-entity-card",
icon: "mdi:information",
animate: true,
line_color: "green",
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.EntityCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.EntityCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {SensorCard};

View File

@ -1,42 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Switch Card Class
*
* Used to create a card for controlling an entity of the switch domain.
*
* @class
* @extends AbstractCard
*/
class SwitchCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {switchCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-entity-card",
icon: undefined,
tap_action: {
action: "toggle",
},
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {switchCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {SwitchCard};

44
src/cards/SwitchCard.ts Normal file
View File

@ -0,0 +1,44 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Switch Card Class
*
* Used to create a card for controlling an entity of the switch domain.
*
* @class
* @extends AbstractCard
*/
class SwitchCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {EntityCardConfig}
* @private
*/
#defaultConfig: EntityCardConfig = {
type: "custom:mushroom-entity-card",
icon: undefined,
tap_action: {
action: "toggle",
},
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.EntityCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.EntityCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {SwitchCard};

View File

@ -1,101 +0,0 @@
/**
* Title Card class.
*
* Used for creating a Title Card.
*
* @class
*/
class TitleCard {
/**
* @type {string[]} An array of area ids.
* @private
*/
#areaIds;
/**
* @type {titleCardOptions}
* @private
*/
#options = {
title: undefined,
subtitle: undefined,
showControls: true,
iconOn: "mdi:power-on",
iconOff: "mdi:power-off",
onService: "none",
offService: "none",
};
/**
* Class constructor.
*
* @param {areaEntity[]} areas An array of area entities.
* @param {titleCardOptions} options Title Card options.
*/
constructor(areas, options = {}) {
this.#areaIds = areas.map(area => area.area_id).filter(area_id => area_id);
this.#options = {
...this.#options,
...options,
};
}
/**
* Create a Title card.
*
* @return {Object} A Title card.
*/
createCard() {
/** @type {Object[]} */
const cards = [
{
type: "custom:mushroom-title-card",
title: this.#options.title,
subtitle: this.#options.subtitle,
},
];
if (this.#options.showControls) {
cards.push({
type: "horizontal-stack",
cards: [
{
type: "custom:mushroom-template-card",
icon: this.#options.iconOff,
layout: "vertical",
icon_color: "red",
tap_action: {
action: "call-service",
service: this.#options.offService,
target: {
area_id: this.#areaIds,
},
data: {},
},
},
{
type: "custom:mushroom-template-card",
icon: this.#options.iconOn,
layout: "vertical",
icon_color: "amber",
tap_action: {
action: "call-service",
service: this.#options.onService,
target: {
area_id: this.#areaIds,
},
data: {},
},
},
],
});
}
return {
type: "horizontal-stack",
cards: cards,
};
}
}
export {TitleCard};

46
src/cards/VacuumCard.ts Normal file
View File

@ -0,0 +1,46 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {VACUUM_COMMANDS, VacuumCardConfig} from "../types/lovelace-mushroom/cards/vacuum-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Vacuum Card Class
*
* Used to create a card for controlling an entity of the vacuum domain.
*
* @class
* @extends AbstractCard
*/
class VacuumCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {VacuumCardConfig}
* @private
*/
#defaultConfig: VacuumCardConfig = {
type: "custom:mushroom-vacuum-card",
icon: undefined,
icon_animation: true,
commands: [...VACUUM_COMMANDS],
tap_action: {
action: "more-info",
}
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.VacuumCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.VacuumCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {VacuumCard};

View File

@ -1,154 +0,0 @@
/**
* @namespace typedefs.cards
*/
/**
* @typedef {Object} abstractOptions
* @property {string} [type] The type of the card.
* @property {string} [icon] Icon of the card.
* @property {Object} [double_tap_action] Home assistant action to perform on double_tap.
*/
/**
* @typedef {Object} titleCardOptions Title Card options.
* @property {string} [title] Title to render. May contain templates.
* @property {string} [subtitle] Subtitle to render. May contain templates.
* @property {boolean} [showControls=true] False to hide controls.
* @property {string} [iconOn] Icon to show for switching entities from off state.
* @property {string} [iconOff] Icon to show for switching entities to off state.
* @property {string} [onService=none] Service to call for switching entities from off state.
* @property {string} [offService=none] Service to call for switching entities to off state.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} lightCardOptions Light Card options.
* @property {boolean} [show_brightness_control=true] Show a slider to control brightness
* @property {boolean} [show_color_control=true] Show a slider to control RGB color
* @property {boolean} [use_light_color=true] Colorize the icon and slider according light temperature or color
* @property {{double_tap_action: lightDoubleTapAction}} [action] Home assistant action to perform on double_tap
* @memberOf typedefs.cards
*/
/**
* @typedef {Object} lightDoubleTapAction Home assistant action to perform on double_tap.
* @property {{entity_id: string}} target The target entity id.
* @property {"call-service"} action Calls a hass service.
* @property {"light.turn_on"} service The hass service to call
* @property {{rgb_color: [255, 255, 255]}} data The data payload for the service.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} coverCardOptions Cover Card options.
* @property {boolean} [show_buttons_control=true] Show buttons to open, close and stop cover.
* @property {boolean} [show_position_control=true] Show a slider to control position of the cover.
* @property {boolean} [show_tilt_position_control=true] Show a slider to control tilt position of the cover.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} fanCardOptions Fan Card options.
* @property {boolean} [show_percentage_control=true] Show a slider to control speed.
* @property {boolean} [show_oscillate_control=true] Show a button to control oscillation.
* @property {boolean} [icon_animation=true] Animate the icon when fan is on.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} lockCardOptions Lock Card options.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} numberCardOptions Number Card options.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} switchCardOptions Switch Card options.
* @property {{tap_action: switchTapAction}} [action] Home assistant action to perform on tap.
* @memberOf typedefs.cards
*/
/**
* @typedef {Object} switchTapAction Home assistant action to perform on tap.
* @property {"toggle"} action Toggles a hass entity.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} climateCardOptions Climate Card options.
* @property {["off", "cool", "heat", "fan_only"]} [hvac_modes] Show buttons to control target temperature.
* @property {boolean} [show_temperature_control=true] Show buttons to control target temperature.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions} cameraCardOptions Camera Card options.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} personCardOptions Person Card options.
* @property {string} [layout] Layout of the card. Vertical, horizontal, and default layouts are supported.
* @property {("name" | "state" | "last-changed" | "last-updated" | "none")} [primary_info=name] Info to show as
* primary info.
* @property {("name" | "state" | "last-changed" | "last-updated" | "none")} [secondary_info=sate] Info to show as
* secondary info.
* @property {("icon" | "entity-picture" | "none")} [icon_type]=icon Type of icon to display.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} areaCardOptions Area Card options.
* @property {string} [name] The name of the area
* @property {string} [icon] Icon to render. May contain templates.
* @property {string} [icon_color] Icon color to render. May contain templates.
* @property {string} [primary] Primary info to render. May contain templates.
* @property {areaTapAction} [tap_action] Home assistant action to perform on tap.
* @memberOf typedefs.cards
*/
/**
* @typedef {Object} areaTapAction Home assistant action to perform on tap.
* @property {"navigate"} action Toggles a hass entity.
* @property {string} navigation_path The id of the area to navigate to.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} HaAreaCardOptions HA Area Card options.
* @property {string} area The id of the area.
* @property {string} navigation_path The id of the area to navigate to.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} mediaPlayerCardOptions Media Player Card options.
* @property {boolean} [use_media_info=true] Use media info instead of name, state, and icon when a media is playing
* @property {string[]} [media_controls="on_off", "play_pause_stop"] List of controls to display
* (on_off, shuffle, previous, play_pause_stop, next,
* repeat)
* @property {boolean} [show_volume_level=true] Show volume level next to media state when media is playing
* @property {string[]} [volume_controls="volume_mute", "volume_set", "volume_buttons"] List of controls to display
* (volume_mute, volume_set,
* volume_buttons)
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} sensorCardOptions Sensor Card options.
* @property {string} [icon_color=green] Custom color for icon when entity is state is active.
* @property {boolean} [animate=true] Add a reveal animation to the graph.
* @property {string} [line_color=green] Set a custom color for the graph line.
* Provide a list of colors for multiple graph entries.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} miscellaneousCardOptions Miscellaneous Card options.
* @property {string} [icon_color=blue-grey] Custom color for icon when entity is state is active.
* @memberOf typedefs.cards
*/

64
src/chips/AbstractChip.ts Normal file
View File

@ -0,0 +1,64 @@
import {HassServiceTarget} from "home-assistant-js-websocket";
import {LovelaceChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
import {Helper} from "../Helper";
import {generic} from "../types/strategy/generic";
import isCallServiceActionConfig = generic.isCallServiceActionConfig;
/**
* Abstract Chip class.
*
* To create a new chip, extend this one.
*
* @class
* @abstract
*/
abstract class AbstractChip {
/**
* Configuration of the chip.
*
* @type {LovelaceChipConfig}
*/
config: LovelaceChipConfig = {
type: "template"
};
/**
* Class Constructor.
*/
protected constructor() {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
}
// noinspection JSUnusedGlobalSymbols Method is called on dymanically imported classes.
/**
* Get the chip.
*
* @returns {LovelaceChipConfig} A chip.
*/
getChip(): LovelaceChipConfig {
return this.config;
}
/**
* Set the target to switch.
*
* @param {HassServiceTarget} target Target to switch.
*/
setTapActionTarget(target: HassServiceTarget) {
if ("tap_action" in this.config && isCallServiceActionConfig(this.config.tap_action)) {
this.config.tap_action.target = target;
return;
}
if (Helper.debug) {
console.warn(
this.constructor.name
+ " - Target not set: Invalid target or tap action.");
}
}
}
export {AbstractChip};

View File

@ -1,39 +0,0 @@
import {Helper} from "../Helper";
class ClimateChip {
#areaIds;
#options = {
// No default options.
};
constructor(areaIds, options = {}) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.#areaIds = areaIds.filter(areaId => areaId);
this.#options = {
...this.#options,
...options,
};
}
getChip() {
return {
type: "template",
icon: "mdi:thermostat",
icon_color: "orange",
content: Helper.getCountTemplate("climate", "ne", "off"),
tap_action: {
action: "navigate",
navigation_path: "climates",
},
hold_action: {
action: "navigate",
navigation_path: "climates",
},
};
}
}
export {ClimateChip};

47
src/chips/ClimateChip.ts Normal file
View File

@ -0,0 +1,47 @@
import {Helper} from "../Helper";
import {AbstractChip} from "./AbstractChip";
import {chips} from "../types/strategy/chips";
import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Climate Chip class.
*
* Used to create a chip to indicate how many climates are operating.
*/
class ClimateChip extends AbstractChip {
/**
* Default configuration of the chip.
*
* @type {TemplateChipConfig}
*
* @readonly
* @private
*/
readonly #defaultConfig: TemplateChipConfig = {
type: "template",
icon: "mdi:thermostat",
icon_color: "orange",
content: Helper.getCountTemplate("climate", "ne", "off"),
tap_action: {
action: "none",
},
hold_action: {
action: "navigate",
navigation_path: "climates",
},
};
/**
* Class Constructor.
*
* @param {chips.TemplateChipOptions} options The chip options.
*/
constructor(options: chips.TemplateChipOptions = {}) {
super();
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {ClimateChip};

View File

@ -1,35 +0,0 @@
import {Helper} from "../Helper";
class CoverChip {
#areaIds;
#options = {
// No default options.
};
constructor(areaIds, options = {}) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.#areaIds = areaIds.filter(areaId => areaId);
this.#options = {
...this.#options,
...options,
};
}
getChip() {
return {
type: "template",
icon: "mdi:window-open",
icon_color: "cyan",
content: Helper.getCountTemplate("cover", "eq", "open"),
tap_action: {
action: "navigate",
navigation_path: "covers",
},
};
}
}
export {CoverChip};

47
src/chips/CoverChip.ts Normal file
View File

@ -0,0 +1,47 @@
import {Helper} from "../Helper";
import {chips} from "../types/strategy/chips";
import {AbstractChip} from "./AbstractChip";
import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Cover Chip class.
*
* Used to create a chip to indicate how many covers aren't closed.
*/
class CoverChip extends AbstractChip {
/**
* Default configuration of the chip.
*
* @type {TemplateChipConfig}
*
* @readonly
* @private
*/
readonly #defaultConfig: TemplateChipConfig = {
type: "template",
icon: "mdi:window-open",
icon_color: "cyan",
content: Helper.getCountTemplate("cover", "eq", "open"),
tap_action: {
action: "none",
},
hold_action: {
action: "navigate",
navigation_path: "covers",
},
};
/**
* Class Constructor.
*
* @param {chips.TemplateChipOptions} options The chip options.
*/
constructor(options: chips.TemplateChipOptions = {}) {
super();
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {CoverChip};

View File

@ -1,43 +0,0 @@
import {Helper} from "../Helper";
class FanChip {
#areaIds;
#options = {
// No default options.
};
constructor(areaIds, options = {}) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.#areaIds = areaIds.filter(areaId => areaId);
this.#options = {
...this.#options,
...options,
};
}
getChip() {
return {
type: "template",
icon: "mdi:fan",
icon_color: "green",
content: Helper.getCountTemplate("fan", "eq", "on"),
tap_action: {
action: "call-service",
service: "fan.turn_off",
target: {
area_id: this.#areaIds,
},
data: {},
},
hold_action: {
action: "navigate",
navigation_path: "fans",
},
};
}
}
export {FanChip};

48
src/chips/FanChip.ts Normal file
View File

@ -0,0 +1,48 @@
import {Helper} from "../Helper";
import {chips} from "../types/strategy/chips";
import {AbstractChip} from "./AbstractChip";
import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Fan Chip class.
*
* Used to create a chip to indicate how many fans are on and to turn all off.
*/
class FanChip extends AbstractChip {
/**
* Default configuration of the chip.
*
* @type {TemplateChipConfig}
*
* @readonly
* @private
*/
readonly #defaultConfig: TemplateChipConfig = {
type: "template",
icon: "mdi:fan",
icon_color: "green",
content: Helper.getCountTemplate("fan", "eq", "on"),
tap_action: {
action: "call-service",
service: "fan.turn_off",
},
hold_action: {
action: "navigate",
navigation_path: "fans",
},
};
/**
* Class Constructor.
*
* @param {chips.TemplateChipOptions} options The chip options.
*/
constructor(options: chips.TemplateChipOptions = {}) {
super();
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {FanChip};

View File

@ -1,43 +0,0 @@
import {Helper} from "../Helper";
class LightChip {
#areaIds;
#options = {
// No default options.
};
constructor(areaIds, options = {}) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.#areaIds = areaIds.filter(areaId => areaId);
this.#options = {
...this.#options,
...options,
};
}
getChip() {
return {
type: "template",
icon: "mdi:lightbulb-group",
icon_color: "amber",
content: Helper.getCountTemplate("light", "eq", "on"),
tap_action: {
action: "call-service",
service: "light.turn_off",
target: {
area_id: this.#areaIds,
},
data: {},
},
hold_action: {
action: "navigate",
navigation_path: "lights",
},
};
}
}
export {LightChip};

48
src/chips/LightChip.ts Normal file
View File

@ -0,0 +1,48 @@
import {Helper} from "../Helper";
import {chips} from "../types/strategy/chips";
import {AbstractChip} from "./AbstractChip";
import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Light Chip class.
*
* Used to create a chip to indicate how many lights are on and to turn all off.
*/
class LightChip extends AbstractChip {
/**
* Default configuration of the chip.
*
* @type {TemplateChipConfig}
*
* @readonly
* @private
*/
readonly #defaultConfig: TemplateChipConfig = {
type: "template",
icon: "mdi:lightbulb-group",
icon_color: "amber",
content: Helper.getCountTemplate("light", "eq", "on"),
tap_action: {
action: "call-service",
service: "light.turn_off",
},
hold_action: {
action: "navigate",
navigation_path: "lights",
},
};
/**
* Class Constructor.
*
* @param {chips.TemplateChipOptions} options The chip options.
*/
constructor(options: chips.TemplateChipOptions = {}) {
super();
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {LightChip};

View File

@ -1,43 +0,0 @@
import {Helper} from "../Helper";
class SwitchChip {
#areaIds;
#options = {
// No default options.
};
constructor(areaIds, options = {}) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.#areaIds = areaIds.filter(areaId => areaId);
this.#options = {
...this.#options,
...options,
};
}
getChip() {
return {
type: "template",
icon: "mdi:dip-switch",
icon_color: "blue",
content: Helper.getCountTemplate("switch", "eq", "on"),
tap_action: {
action: "call-service",
service: "switch.turn_off",
target: {
area_id: this.#areaIds,
},
data: {},
},
hold_action: {
action: "navigate",
navigation_path: "switches",
},
};
}
}
export {SwitchChip};

48
src/chips/SwitchChip.ts Normal file
View File

@ -0,0 +1,48 @@
import {Helper} from "../Helper";
import {chips} from "../types/strategy/chips";
import {AbstractChip} from "./AbstractChip";
import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Switch Chip class.
*
* Used to create a chip to indicate how many switches are on and to turn all off.
*/
class SwitchChip extends AbstractChip {
/**
* Default configuration of the chip.
*
* @type {TemplateChipConfig}
*
* @readonly
* @private
*/
readonly #defaultConfig: TemplateChipConfig = {
type: "template",
icon: "mdi:dip-switch",
icon_color: "blue",
content: Helper.getCountTemplate("switch", "eq", "on"),
tap_action: {
action: "call-service",
service: "switch.turn_off",
},
hold_action: {
action: "navigate",
navigation_path: "switches",
},
};
/**
* Class Constructor.
*
* @param {chips.TemplateChipOptions} options The chip options.
*/
constructor(options: chips.TemplateChipOptions = {}) {
super();
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {SwitchChip};

View File

@ -1,25 +0,0 @@
class WeatherChip {
#entityId;
#options = {
show_temperature: true,
show_conditions: true,
};
constructor(entityId, options = {}) {
this.#entityId = entityId;
this.#options = {
...this.#options,
...options,
};
}
getChip() {
return {
type: "weather",
entity: this.#entityId,
...this.#options,
};
}
}
export {WeatherChip};

42
src/chips/WeatherChip.ts Normal file
View File

@ -0,0 +1,42 @@
import {chips} from "../types/strategy/chips";
import {WeatherChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
import {AbstractChip} from "./AbstractChip";
// noinspection JSUnusedGlobalSymbols False positive.
/**
* Weather Chip class.
*
* Used to create a chip for showing the weather.
*/
class WeatherChip extends AbstractChip {
/**
* Default configuration of the chip.
*
* @private
* @readonly
*/
readonly #defaultConfig: WeatherChipConfig = {
type: "weather",
show_temperature: true,
show_conditions: true,
};
/**
* Class Constructor.
*
* @param {string} entityId Id of a weather entity.
* @param {chips.WeatherChipOptions} options Weather Chip options.
*/
constructor(entityId: string, options: chips.WeatherChipOptions = {}) {
super();
this.#defaultConfig = {
...this.#defaultConfig,
...{entity: entityId},
...options,
};
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {WeatherChip};

View File

@ -1,44 +1,20 @@
export const optionDefaults = {
debug: false,
views: {
home: {
order: 1,
hidden: false,
},
light: {
order: 2,
hidden: false,
},
fan: {
order: 3,
hidden: false,
},
cover: {
order: 4,
hidden: false,
},
switch: {
order: 5,
hidden: false,
},
climate: {
order: 6,
hidden: false,
},
camera: {
order: 7,
hidden: false,
}
},
import {generic} from "./types/strategy/generic";
import StrategyDefaults = generic.StrategyDefaults;
/**
* Default configuration for the mushroom strategy.
*/
export const configurationDefaults: StrategyDefaults = {
areas: {
undisclosed: {
aliases: [],
area_id: null,
area_id: "undisclosed",
name: "Undisclosed",
picture: null,
hidden: false,
}
},
debug: false,
domains: {
default: {
title: "Miscellaneous",
@ -116,5 +92,47 @@ export const optionDefaults = {
showControls: false,
hidden: false,
},
vacuum: {
title: "Vacuums",
showControls: true,
hidden: false,
},
},
home_view: {
hidden: [],
},
views: {
home: {
order: 1,
hidden: false,
},
light: {
order: 2,
hidden: false,
},
fan: {
order: 3,
hidden: false,
},
cover: {
order: 4,
hidden: false,
},
switch: {
order: 5,
hidden: false,
},
climate: {
order: 6,
hidden: false,
},
camera: {
order: 7,
hidden: false,
},
vacuum: {
order: 8,
hidden: false,
},
}
};

View File

@ -1,34 +1,40 @@
import {Helper} from "./Helper";
import {SensorCard} from "./cards/SensorCard";
import {TitleCard} from "./cards/TitleCard";
import {ControllerCard} from "./cards/ControllerCard";
import {generic} from "./types/strategy/generic";
import {LovelaceCardConfig, LovelaceConfig, LovelaceViewConfig} from "./types/homeassistant/data/lovelace";
import {StackCardConfig} from "./types/homeassistant/lovelace/cards/types";
import {EntityCardConfig} from "./types/lovelace-mushroom/cards/entity-card-config";
import {HassServiceTarget} from "home-assistant-js-websocket";
import StrategyArea = generic.StrategyArea;
/**
* Mushroom Dashboard Strategy.<br>
* <br>
* Mushroom dashboard strategy provides a strategy for Home-Assistant to create a dashboard automatically.<br>
* The strategy makes use Mushroom, Mini Graph and WebRTC cards to represent your entities.<br>
* The strategy makes use Mushroom and Mini Graph cards to represent your entities.<br>
* <br>
* Features:<br>
* 🛠 Automatically create dashboard with 3 lines of yaml.<br>
* 🛠 Automatically create dashboard with three lines of yaml.<br>
* 😍 Built-in Views for several standard domains.<br>
* 🎨 Many options to customize to your needs.<br>
* <br>
* Check the [Repository]{@link https://github.com/AalianKhan/mushroom-strategy} for more information.
*/
class MushroomStrategy {
class MushroomStrategy extends HTMLTemplateElement {
/**
* Generate a dashboard.
*
* Called when opening a dashboard.
*
* @param {dashBoardInfo} info Dashboard strategy information object.
* @return {Promise<{views: Object[]}>}
* @param {generic.DashBoardInfo} info Dashboard strategy information object.
* @return {Promise<LovelaceConfig>}
*/
static async generateDashboard(info) {
static async generateDashboard(info: generic.DashBoardInfo): Promise<LovelaceConfig> {
await Helper.initialize(info);
// Create views.
const views = [];
const views: LovelaceViewConfig[] = info.config?.views ?? [];
let viewModule;
@ -36,13 +42,14 @@ class MushroomStrategy {
for (let viewId of Helper.getExposedViewIds()) {
try {
const viewType = Helper.sanitizeClassName(viewId + "View");
viewModule = await import(`./views/${viewType}`);
const view = await new viewModule[viewType](Helper.strategyOptions.views[viewId]).getView();
views.push(view);
viewModule = await import(`./views/${viewType}`);
const view: LovelaceViewConfig = await new viewModule[viewType](Helper.strategyOptions.views[viewId]).getView();
if (view.cards?.length) {
views.push(view);
}
} catch (e) {
console.error(Helper.debug ? e : `View '${viewId}' couldn't be loaded!`);
Helper.logError(`View '${viewId}' couldn't be loaded!`, e);
}
}
@ -79,13 +86,18 @@ class MushroomStrategy {
*
* Called when opening a subview.
*
* @param {viewInfo} info The view's strategy information object.
* @return {Promise<{cards: Object[]}>}
* @param {generic.ViewInfo} info The view's strategy information object.
* @return {Promise<LovelaceViewConfig>}
*/
static async generateView(info) {
static async generateView(info: generic.ViewInfo): Promise<LovelaceViewConfig> {
const exposedDomainIds = Helper.getExposedDomainIds();
const area = info.view.strategy.options.area;
const viewCards = [...(area.extra_cards ?? [])];
const area = info.view.strategy?.options?.area ?? {} as StrategyArea;
const viewCards: LovelaceCardConfig[] = [...(area.extra_cards ?? [])];
// Set the target for controller cards to the current area.
let target: HassServiceTarget = {
area_id: [area.area_id],
};
// Create cards for each domain.
for (const domain of exposedDomainIds) {
@ -100,27 +112,34 @@ class MushroomStrategy {
try {
domainCards = await import(`./cards/${className}`).then(cardModule => {
let domainCards = [];
const entities = Helper.getDeviceEntities(area, domain);
const entities = Helper.getDeviceEntities(area, domain);
// Set the target for controller cards to entities without an area.
if (area.area_id === "undisclosed") {
target = {
entity_id: entities.map(entity => entity.entity_id),
}
}
if (entities.length) {
// Create a Title card for the current domain.
const titleCard = new TitleCard(
[area],
Helper.strategyOptions.domains[domain],
// Create a Controller card for the current domain.
const titleCard = new ControllerCard(
target,
Helper.strategyOptions.domains[domain],
).createCard();
if (domain === "sensor") {
// Create a card for each entity-sensor of the current area.
const sensorStates = Helper.getStateEntities(area, "sensor");
const sensorCards = [];
const sensorCards: EntityCardConfig[] = [];
for (const sensor of entities) {
// Find the state of the current sensor.
const sensorState = sensorStates.find(state => state.entity_id === sensor.entity_id);
let cardOptions = Helper.strategyOptions.card_options?.[sensor.entity_id] ?? {};
let deviceOptions = Helper.strategyOptions.card_options?.[sensor.device_id] ?? {};
let cardOptions = Helper.strategyOptions.card_options?.[sensor.entity_id];
let deviceOptions = Helper.strategyOptions.card_options?.[sensor.device_id ?? "null"];
if (!cardOptions.hidden && !deviceOptions.hidden) {
if (!cardOptions?.hidden && !deviceOptions?.hidden) {
if (sensorState?.attributes.unit_of_measurement) {
cardOptions = {
...{
@ -147,12 +166,16 @@ class MushroomStrategy {
return domainCards;
}
// Create a card for each domain-entity of the current area.
// Create a card for each other domain-entity of the current area.
for (const entity of entities) {
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id] ?? {};
let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id] ?? {};
let deviceOptions;
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id];
if (!cardOptions.hidden && !deviceOptions.hidden) {
if (entity.device_id) {
deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id];
}
if (!cardOptions?.hidden && !deviceOptions?.hidden) {
domainCards.push(new cardModule[className](entity, cardOptions).getCard());
}
}
@ -179,7 +202,7 @@ class MushroomStrategy {
return domainCards;
});
} catch (e) {
console.error(Helper.debug ? e : "An error occurred while creating the domain cards!");
Helper.logError("An error occurred while creating the domain cards!", e);
}
if (domainCards.length) {
@ -193,36 +216,37 @@ class MushroomStrategy {
if (!Helper.strategyOptions.domains.default.hidden) {
// Create cards for any other domain.
// Collect device entities of the current area.
const areaDevices = Helper.devices.filter(device => device.area_id === area.area_id)
.map(device => device.id);
const areaDevices = Helper.devices.filter((device) => device.area_id === area.area_id)
.map((device) => device.id);
// Collect the remaining entities of which all conditions below are met:
// 1. The entity is linked to a device which is linked to the current area,
// 1. The entity is not hidden.
// 2. The entity's domain isn't exposed (entities of exposed domains are already included).
// 3. The entity is linked to a device which is linked to the current area,
// or the entity itself is linked to the current area.
// 2. The entity is not hidden and is not disabled.
const miscellaneousEntities = Helper.entities.filter(entity => {
return (areaDevices.includes(entity.device_id) || entity.area_id === area.area_id)
&& entity.hidden_by == null
&& entity.disabled_by == null
&& !exposedDomainIds.includes(entity.entity_id.split(".", 1)[0]);
const miscellaneousEntities = Helper.entities.filter((entity) => {
const entityLinked = areaDevices.includes(entity.device_id ?? "null") || entity.area_id === area.area_id;
const entityUnhidden = entity.hidden_by === null && entity.disabled_by === null;
const domainExposed = exposedDomainIds.includes(entity.entity_id.split(".", 1)[0]);
return entityUnhidden && !domainExposed && entityLinked;
});
// Create a column of miscellaneous entity cards.
if (miscellaneousEntities.length) {
let miscellaneousCards = [];
let miscellaneousCards: (StackCardConfig | EntityCardConfig)[] = [];
try {
miscellaneousCards = await import("./cards/MiscellaneousCard").then(cardModule => {
/** @type Object[] */
const miscellaneousCards = [
new TitleCard([area], Helper.strategyOptions.domains.default).createCard(),
const miscellaneousCards: (StackCardConfig | EntityCardConfig)[] = [
new ControllerCard(target, Helper.strategyOptions.domains.default).createCard(),
];
for (const entity of miscellaneousEntities) {
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id] ?? {};
let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id] ?? {};
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id];
let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id ?? "null"];
if (!cardOptions.hidden && !deviceOptions.hidden) {
if (!cardOptions?.hidden && !deviceOptions?.hidden) {
miscellaneousCards.push(new cardModule.MiscellaneousCard(entity, cardOptions).getCard());
}
}
@ -230,7 +254,7 @@ class MushroomStrategy {
return miscellaneousCards;
});
} catch (e) {
console.error(Helper.debug ? e : "An error occurred while creating the domain cards!");
Helper.logError("An error occurred while creating the domain cards!", e);
}
viewCards.push({
@ -247,5 +271,10 @@ class MushroomStrategy {
}
}
// noinspection JSUnresolvedReference
customElements.define("ll-strategy-mushroom-strategy", MushroomStrategy);
const version = "v2.0.0";
console.info(
"%c Mushroom Strategy %c ".concat(version, " "),
"color: white; background: coral; font-weight: 700;", "color: coral; background: white; font-weight: 700;"
);

View File

@ -1,255 +0,0 @@
/**
* @namespace typedefs.generic
*/
/**
* @typedef {Object} hassEntity Home assistant entity.
* @property {string} name The name of this entity.
* @property {string} original_name The original name of this entity.
* @property {string} entity_id The id of this entity.
* @property {string} device_id The id of the device to which this entity is linked.
* @property {string} area_id The id of the area to which this entity is linked.
* @property {string[]|null} disabled_by Indicates by what this entity is disabled.
* @property {string[]|null} hidden_by Indicates by what this entity is hidden.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} deviceEntity Device Entity.
* @property {string} area_id The Area which the device is placed in.
* @property {string} id Unique ID of a device (generated by Home Assistant).
* @property {string[]|null} disabled_by Indicates by what this entity is disabled.
* @property {string[]|null} hidden_by Indicates by what this entity is hidden.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} areaEntity Area Entity.
* @property {string[]} [aliases] Array of aliases of this entity.
* @property {string|null} area_id The id of this entity.
* @property {string} name Name of this entity.
* @property {string|null} picture URL to a picture that should be used instead of showing the domain icon.
* @property {number} [order] Ordering position of the area in the list of available areas.
* @property {boolean} [hidden] True if the entity should be hidden from the dashboard.
* This property is added by the custom strategy.
* @property {Object[]} [extra_cards] An array of card configurations.
* The configured cards are added to the dashboard.
* This property is added by the custom strategy.
* @property {boolean} [use_ha_area_card] Set to true to use ha area card instead of mushroom.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} viewEntity View Entity.
* This entity is added by the custom strategy.
* @property {string} title Title of this entity.
* @property {string} icon Icon to use for the entity in the frontend.
* Example: `mdi:home`.
* @property {number} [order] Ordering position of the entity in the list of available views.
* @property {boolean} [hidden] True if the entity should be hidden from the dashboard.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object & titleCardOptions} domainEntity Domain Entity.
* This entity is added by the custom strategy.
* @property {number} [order] Ordering position of the entity in the list of available views.
* @property {boolean} [hidden] True if the entity should be hidden from the dashboard.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} titleCardOptions Title Card options.
* @property {string} [title] Title to render. May contain templates.
* @property {string} [subtitle] Subtitle to render. May contain templates.
* @property {boolean} [showControls=true] False to hide controls.
* @property {string} [iconOn] Icon to show for switching entities from off state.
* @property {string} [iconOff] Icon to show for switching entities to off state.
* @property {string} [onService=none] Service to call for switching entities from off state.
* @property {string} [offService=none] Service to call for switching entities to off state.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} dashBoardInfo Strategy information object.
* @property {dashboardConfig} config User supplied dashboard configuration, if any.
* @property {hassObject} hass The Home Assistant object.
* @property {boolean} narrow If the current user interface is rendered in narrow mode or not.
* @memberOf typedefs.generic
* @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#dashboard-strategies
*/
/**
* @typedef {Object} viewInfo Strategy information object.
* @property {Object} view Configuration of the current view.
* @property {viewConfig} config Dashboard configuration.
* @property {hassObject} hass The Home Assistant object.
* @property {boolean} narrow If the current user interface is rendered in narrow mode or not.
* @memberOf typedefs.generic
* @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#view-strategies
*/
/**
* @typedef {Object} dashboardConfig User supplied dashboard configuration.
* @property {strategyObject} strategy User supplied dashboard configuration.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} viewConfig Dashboard configuration.
* @property {Object[]} strategy Array of views generated by the strategy.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} strategyObject User supplied dashboard configuration.
* @property {strategyOptions} options Custom strategy configuration.
* @property {string} type Strategy type.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} customStrategyOptions Custom strategy configuration.
* @property {boolean} [debug] Set to true for more verbose debugging info.
* @property {Object.<areaEntity>} [areas] List of areas.
* @property {Object.<cardOptions>} [card_options] Card options for entities.
* @property {Object.<viewEntity>} [views] List of views.
* @property {Object.<domainEntity>} [domains] List of domains.
* @property {chip[]} [chips] List of chips to show in the Home view.
* @property {Object[]} [quick_access_cards] List of cards to show between welcome card and rooms cards.
* @property {Object[]} [extra_cards] List of cards to show below room cards.
* @property {Object[]} [extra_views] List of views to add to the dashboard.
* @property {Object.<homeViewOptions>} [homeView] Options for the home view.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} chip List of chips to show in the Home view.
* @property {boolean} light_count Chip to display the number of lights on.
* @property {boolean} fan_count Chip to display the number of fans on.
* @property {boolean} cover_count Chip to display the number of unclosed covers.
* @property {boolean} switch_count Chip to display the number of switches on.
* @property {boolean} climate_count Chip to display the number of climates which are not off.
* @property {string} weather_entity Entity ID for the weather chip to use, accepts `weather.` only.
* @property {Object[]} extra_chips List of extra chips.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} homeViewOptions Options for the home view.
* @property {string[]} [hidden] Elements to hide from the home view.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} cardOptions Custom card-configuration for an entity.
* @property {string} type Type of card for the entity
* @property {boolean} hidden True if the entity should be hidden from the dashboard.
* @memberOf typedefs.generic
*/
/**
* The frontend passes a single hass object around.
* This object contains the latest state and allows you to send commands back to the server.
*
* @typedef {Object} hassObject Home Assistant object.
* @property {Object<string, stateObject>} states An object containing the states of all entities in Home Assistant.
* The key is the entity_id, the value is the state object.
* @property {hassUser} user The logged-in user.
* @property {function} callWS Call a WebSocket command on the backend.
* @memberOf typedefs.generic
* @see https://developers.home-assistant.io/docs/frontend/data/
*/
/**
* The logged-in user.
*
* @typedef {Object} hassUser The logged-in user.
* @property {string} name Name of the user.
* @property {boolean} is_owner True if the user is the owner.
* @property {boolean} is_owner True if the user is an administrator.
* @property {Object[]} credentials Authentication credentials.
* @memberOf typedefs.generic
* @see https://developers.home-assistant.io/docs/frontend/data/#hassuser
*/
/**
* States are a current representation of the entity.
*
* All states will always have an entity id, a state and a timestamp when last updated and last changed.
*
* @typedef {Object} stateObject State object.
* @property {string} state String representation of the entity's current state.
* Example `off`.
* @property {string} entity_id Entity ID.
* Format: <domain>.<object_id>.
* Example: `light.kitchen`.
* @property {string} domain Domain of the entity.
* Example: `light`.
* @property {string} object_id Object ID of entity.
* Example: `kitchen`.
* @property {string} name Name of the entity.
* Based on `friendly_name` attribute with fall-back to object ID.
* Example: `Kitchen Ceiling`.
* @property {string} last_updated Time the state was written to the state machine in UTC time.
* Note that writing the exact same state including attributes will not result in this
* field being updated.
* Example: `2017-10-28 08:13:36.715874+00:00`.
* @property {string} last_changed Time the state changed in the state machine in UTC time.
* This is not updated when there are only updated attributes.
* Example: `2017-10-28 08:13:36.715874+00:00`.
* @property {stateAttributes} attributes A dictionary with extra attributes related to the current state.
* @property {stateContext} context A dictionary with extra attributes related to the context of the state.
* @memberOf typedefs.generic
* @see https://www.home-assistant.io/docs/configuration/state_object/
*/
/**
* The attributes of an entity are optional.
*
* There are a few attributes that are used by Home Assistant for representing the entity in a specific way.
* Each integration will also have its own attributes to represent extra state data about the entity.
* For example, the light integration has attributes for the current brightness and color of the light.
*
* When an attribute is not available, Home Assistant will not write it to the state.
*
* @typedef {Object} stateAttributes State attributes.
* @property {string} friendly_name Name of the entity.
* Example: `Kitchen Ceiling`.
* @property {string} icon Icon to use for the entity in the frontend.
* Example: `mdi:home`.
* @property {string} entity_picture URL to a picture that should be used instead of showing the domain icon.
* @property {string} assumed_state Boolean if the current state is an assumption.
* @property {string} unit_of_measurement The unit of measurement the state is expressed in.
* Used for grouping graphs or understanding the entity.
* Example: `°C`.
* @memberOf typedefs.generic
* @see https://www.home-assistant.io/docs/configuration/state_object/#attributes
*/
/**
* Context is used to tie events and states together in Home Assistant. Whenever an automation or user interaction
* causes states to change, a new context is assigned. This context will be attached to all events and states that
* happen as a result of the change.
*
* @typedef {Object} stateContext State context.
* @property {string} context_id Unique identifier for the context.
* @property {string} user_id Unique identifier of the user that started the change.
* Will be None if action was not started by a user (i.e., started by an automation)
* @property {string} parent_id Unique identifier of the parent context that started the change, if available.
* For example, if an automation is triggered, the context of the trigger will be set as
* parent.
* @see https://www.home-assistant.io/docs/configuration/state_object/#context
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} areaFilterContext fer Card options.
* @property {areaEntity} area Area Entity.
* @property {string} domain Domain of the entity.
* Example: `light`.
* @property {string[]} areaDeviceIds The id of devices which are linked to the area entity.
* @memberOf typedefs.cards
*/
export {};

View File

@ -0,0 +1,22 @@
This directory contains partial code from
the [Home Assistant Frontend repository](https://github.com/home-assistant/frontend).
The code mainly defines Home Assistant interfaces/types which are refactored to fit this repository.
This means properties are added/removed from the originals and subtypes may have been changed.
The [Apache 2.0 License](https://github.com/home-assistant/frontend/blob/dev/LICENSE.md) applies to all files in this
directory.
Copyright 2023 Ferry Cools
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,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,14 @@
/**
* Area Entity.
*
* @property {string} area_id The id of the area.
* @property {string} name Name of the area.
* @property {string|null} picture URL to a picture that should be used instead of showing the domain icon.
* @property {string[]} aliases Array of aliases of the area.
*/
export interface AreaRegistryEntry {
area_id: string;
name: string;
picture: string | null;
aliases: string[];
}

View File

@ -0,0 +1,19 @@
export const HVAC_MODES = [
"auto",
"heat_cool",
"heat",
"cool",
"dry",
"fan_only",
"off",
] as const;
export type HvacMode = (typeof HVAC_MODES)[number];
HVAC_MODES.reduce(
(order, mode, index) => {
order[mode] = index;
return order;
},
{} as Record<HvacMode, number>
);

View File

@ -0,0 +1,38 @@
/**
* Device Entity.
*
* @property {string} id Unique ID of a device (generated by Home Assistant).
* @property {string[]} config_entries
* @property {Array} connections
* @property {Array} identifiers
* @property {string | null} manufacturer
* @property {string | null} model
* @property {string | null} name
* @property {string | null} sw_version
* @property {string | null} hw_version
* @property {string | null} serial_number
* @property {string | null} via_device_id
* @property {string} area_id The Area which the device is placed in.
* @property {string | null} name_by_user
* @property {string[] | null} entry_type
* @property {string | null} disabled_by Indicates by what this entity is disabled.
* @property {string | null} configuration_url
*/
export interface DeviceRegistryEntry {
id: string;
config_entries: string[];
connections: Array<[string, string]>;
identifiers: Array<[string, string]>;
manufacturer: string | null;
model: string | null;
name: string | null;
sw_version: string | null;
hw_version: string | null;
serial_number: string | null;
via_device_id: string | null;
area_id: string | null;
name_by_user: string | null;
entry_type: "service" | null;
disabled_by: "user" | "integration" | "config_entry" | null;
configuration_url: string | null;
}

View File

@ -0,0 +1,97 @@
import {LightColor} from "./light";
type EntityCategory = "config" | "diagnostic";
export interface EntityRegistryDisplayEntry {
entity_id: string;
name?: string;
device_id?: string;
area_id?: string;
hidden?: boolean;
entity_category?: EntityCategory;
translation_key?: string;
platform?: string;
display_precision?: number;
}
/**
* Home assistant entity.
*
* @property {string} id
* @property {string} entity_id The id of this entity.
* @property {string} name The name of this entity.
* @property {string | null} icon
* @property {string | null} platform
* @property {string | null} config_entry_id
* @property {string | null} device_id The id of the device to which this entity is linked.
* @property {string | null} area_id The id of the area to which this entity is linked.
* @property {string | null} disabled_by Indicates by what this entity is disabled.
* @property {Object} hidden_by Indicates by what this entity is hidden.
* @property {EntityCategory | null} entity_category
* @property {boolean} has_entity_name
* @property {string} [original_name]
* @property {string} unique_id
* @property {string} [translation_key]
* @property {EntityRegistryOptions | null} options
*/
export interface EntityRegistryEntry {
id: string;
entity_id: string;
name: string | null;
icon: string | null;
platform: string;
config_entry_id: string | null;
device_id: string | null;
area_id: string | null;
disabled_by: "user" | "device" | "integration" | "config_entry" | null;
hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">;
entity_category: EntityCategory | null;
has_entity_name: boolean;
original_name?: string;
unique_id: string;
translation_key?: string;
options: EntityRegistryOptions | null;
}
export interface SensorEntityOptions {
display_precision?: number | null;
suggested_display_precision?: number | null;
unit_of_measurement?: string | null;
}
export interface LightEntityOptions {
favorite_colors?: LightColor[];
}
export interface NumberEntityOptions {
unit_of_measurement?: string | null;
}
export interface LockEntityOptions {
default_code?: string | null;
}
export interface WeatherEntityOptions {
precipitation_unit?: string | null;
pressure_unit?: string | null;
temperature_unit?: string | null;
visibility_unit?: string | null;
wind_speed_unit?: string | null;
}
export interface SwitchAsXEntityOptions {
entity_id: string;
}
export interface EntityRegistryOptions {
number?: NumberEntityOptions;
sensor?: SensorEntityOptions;
lock?: LockEntityOptions;
weather?: WeatherEntityOptions;
light?: LightEntityOptions;
switch_as_x?: SwitchAsXEntityOptions;
conversation?: Record<string, unknown>;
"cloud.alexa"?: Record<string, unknown>;
"cloud.google_assistant"?: Record<string, unknown>;
}

View File

@ -0,0 +1,6 @@
export type LightColor =
| { color_temp_kelvin: number; }
| { hs_color: [number, number]; }
| { rgb_color: [number, number, number]; }
| { rgbw_color: [number, number, number, number]; }
| { rgbww_color: [number, number, number, number, number]; };

View File

@ -0,0 +1,118 @@
import {HassServiceTarget} from "home-assistant-js-websocket";
export type LovelaceStrategyConfig = {
type: string;
[key: string]: any;
};
export interface LovelaceConfig {
title?: string;
strategy?: LovelaceStrategyConfig;
views: LovelaceViewConfig[];
background?: string;
}
/**
* View Config.
*
* @see https://www.home-assistant.io/dashboards/views/
*/
export interface LovelaceViewConfig {
index?: number;
title?: string;
type?: string;
strategy?: LovelaceStrategyConfig;
badges?: Array<string | LovelaceBadgeConfig>;
cards?: LovelaceCardConfig[];
path?: string;
icon?: string;
theme?: string;
panel?: boolean;
background?: string;
visible?: boolean | ShowViewConfig[];
subview?: boolean;
back_path?: string;
}
export interface ShowViewConfig {
user?: string;
}
export interface LovelaceBadgeConfig {
type?: string;
[key: string]: any;
}
export interface LovelaceCardConfig {
index?: number;
view_index?: number;
view_layout?: any;
type: string;
[key: string]: any;
}
export interface ToggleActionConfig extends BaseActionConfig {
action: "toggle";
}
export interface CallServiceActionConfig extends BaseActionConfig {
action: "call-service";
service: string;
target?: HassServiceTarget;
// Property "service_data" is kept for backwards compatibility. Replaced by "data".
service_data?: Record<string, unknown>;
data?: Record<string, unknown>;
}
export interface NavigateActionConfig extends BaseActionConfig {
action: "navigate";
navigation_path: string;
navigation_replace?: boolean;
}
export interface UrlActionConfig extends BaseActionConfig {
action: "url";
url_path: string;
}
export interface MoreInfoActionConfig extends BaseActionConfig {
action: "more-info";
}
export interface AssistActionConfig extends BaseActionConfig {
action: "assist";
pipeline_id?: string;
start_listening?: boolean;
}
export interface NoActionConfig extends BaseActionConfig {
action: "none";
}
export interface CustomActionConfig extends BaseActionConfig {
action: "fire-dom-event";
}
export interface BaseActionConfig {
action: string;
confirmation?: ConfirmationRestrictionConfig;
}
export interface ConfirmationRestrictionConfig {
text?: string;
exemptions?: RestrictionConfig[];
}
export interface RestrictionConfig {
user: string;
}
export type ActionConfig =
| ToggleActionConfig
| CallServiceActionConfig
| NavigateActionConfig
| UrlActionConfig
| MoreInfoActionConfig
| AssistActionConfig
| NoActionConfig
| CustomActionConfig;

View File

@ -0,0 +1,26 @@
import {LovelaceCardConfig} from "../../data/lovelace";
/**
* Home Assistant Stack Card Config.
*
* @property {string} type The stack type.
* @property {Object[]} cards The content of the stack.
*
* @see https://www.home-assistant.io/dashboards/horizontal-stack/
* @see https://www.home-assistant.io/dashboards/vertical-stack/
*/
export interface StackCardConfig extends LovelaceCardConfig {
cards: LovelaceCardConfig[];
title?: string;
}
/**
* Home Assistant Area Card Config.
*
* @see https://www.home-assistant.io/dashboards/area/
*/
export interface AreaCardConfig extends LovelaceCardConfig {
area: string;
navigation_path?: string;
show_camera?: boolean;
}

View File

@ -0,0 +1,41 @@
import {ActionConfig, LovelaceCardConfig} from "../../../data/lovelace";
/**
* Home Assistant Picture Entity Config.
*
* @property {string} entity An entity_id used for the picture.
* @property {string} [name] Overwrite entity name.
* @property {string} [image] URL of an image.
* @property {string} [camera_image] Camera entity_id to use. (not required if entity is already a camera-entity).
* @property {string} [camera_view=auto] “live” will show the live view if stream is enabled.
* @property {Record<string, unknown>} [state_image] Map entity states to images (state: image URL).
* @property {string[]} [state_filter] State-based CSS filters.
* @property {string} [aspect_ratio] Forces the height of the image to be a ratio of the width.
* Valid formats: Height percentage value (23%) or ratio expressed with colon or “x”
* separator (16:9 or 16x9).
* For a ratio, the second element can be omitted and will default to “1”
* (1.78 equals 1.78:1).
* @property {ActionConfig} [tap_action] Action taken on card tap.
* @property {ActionConfig} [hold_action] Action taken on card tap and hold.
* @property {ActionConfig} [double_tap_action] Action taken on card double tap.
* @property {boolean} [show_name=true] Shows name in footer.
* @property {string} [theme=true] Override the used theme for this card with any loaded theme.
*
* @see https://www.home-assistant.io/dashboards/picture-entity/
*/
export interface PictureEntityCardConfig extends LovelaceCardConfig {
entity: string;
name?: string;
image?: string;
camera_image?: string;
camera_view?: "live" | "auto";
state_image?: Record<string, unknown>;
state_filter?: string[];
aspect_ratio?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
show_name?: boolean;
show_state?: boolean;
theme?: string;
}

View File

@ -0,0 +1,95 @@
import {Auth, Connection, HassConfig, HassEntities, HassServices, MessageBase,} from "home-assistant-js-websocket";
import {AreaRegistryEntry} from "./data/area_registry";
import {DeviceRegistryEntry} from "./data/device_registry";
import {EntityRegistryDisplayEntry} from "./data/entity_registry";
export interface Credential {
auth_provider_type: string;
auth_provider_id: string;
}
export interface MFAModule {
id: string;
name: string;
enabled: boolean;
}
export interface CurrentUser {
id: string;
is_owner: boolean;
is_admin: boolean;
name: string;
credentials: Credential[];
mfa_modules: MFAModule[];
}
export interface PanelInfo<T = Record<string, any> | null> {
component_name: string;
config: T;
icon: string | null;
title: string | null;
url_path: string;
config_panel_domain?: string;
}
export interface Panels {
[name: string]: PanelInfo;
}
export interface Translation {
nativeName: string;
isRTL: boolean;
hash: string;
}
export interface TranslationMetadata {
fragments: string[];
translations: {
[lang: string]: Translation;
};
}
export interface Resources {
[language: string]: Record<string, string>;
}
export interface HomeAssistant {
auth: Auth & { external?: any };
connection: Connection;
connected: boolean;
states: HassEntities;
entities: { [id: string]: EntityRegistryDisplayEntry };
devices: { [id: string]: DeviceRegistryEntry };
areas: { [id: string]: AreaRegistryEntry };
services: HassServices;
config: HassConfig;
themes: { [k: string]: any };
selectedTheme: { [k: string]: any } | null;
panels: Panels;
panelUrl: string;
// i18n
// current effective language in that order:
// - backend saved user selected language
// - language in local app storage
// - browser language
// - english (en)
language: string;
// local stored language, keep that name for backward compatibility
selectedLanguage: string | null;
locale: { [k: string]: any };
resources: Resources;
localize: Function;
translationMetadata: TranslationMetadata;
suspendWhenHidden: boolean;
enableShortcuts: boolean;
vibrate: boolean;
debugConnection: boolean;
dockedSidebar: "docked" | "always_hidden" | "auto";
defaultPanel: string;
moreInfoEntityId: string | null;
user?: CurrentUser;
userData?: { [k: string]: any } | null;
callWS<T>(msg: MessageBase): Promise<T>;
}

View File

@ -0,0 +1,22 @@
This directory contains partial code from
the [Lovelace Mushroom repository](https://github.com/piitaya/lovelace-mushroom).
The code mainly defines Home Assistant interfaces/types which are refactored to fit this repository.
This means properties are added/removed from the originals and subtypes may have been changed.
The [Apache 2.0 License](https://github.com/home-assistant/frontend/blob/dev/LICENSE.md) applies to all files in this
directory.
Copyright 2023 Ferry Cools
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,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,15 @@
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {LovelaceChipConfig} from "../utils/lovelace/chip/types";
/**
* Chips Card Configuration
*
* @param {LovelaceChipConfig[]} chips Chips Array
* @param {string} [alignment=start] Chips alignment (end, center, justify), when empty default behavior is start.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/chips.md
*/
export interface ChipsCardConfig extends LovelaceCardConfig {
chips: LovelaceChipConfig[];
alignment?: string;
}

View File

@ -0,0 +1,23 @@
import {HvacMode} from "../../homeassistant/data/climate";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
import {ActionsSharedConfig} from "../shared/config/actions-config";
/**
* Climate Card Config.
*
* @property {boolean} [show_temperature_control=false] Show buttons to control target temperature.
* @property {HvacMode[]} [hvac_modes] List of hvac modes to display (auto, heat_cool, heat, cool, dry, fan_only, off).
* @property {boolean} [collapsible_controls] Collapse controls when off.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/climate.md
*/
export type ClimateCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
show_temperature_control?: boolean;
hvac_modes?: HvacMode[];
collapsible_controls?: boolean;
};

View File

@ -0,0 +1,22 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
/**
* Cover Card Config.
*
* @property {boolean} [show_buttons_control=false] Show buttons to open, close and stop cover.
* @property {boolean} [show_position_control=false] Show a slider to control position of the cover.
* @property {boolean} [show_tilt_position_control=false] Show a slider to control tilt position of the cover.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/cover.md
*/
export type CoverCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
show_buttons_control?: boolean;
show_position_control?: boolean;
show_tilt_position_control?: boolean;
};

View File

@ -0,0 +1,18 @@
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {ActionsSharedConfig} from "../shared/config/actions-config";
/**
* Entity Card Config.
*
* @property {string} [icon_color=blue] Custom color for icon when entity is state is active.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/entity.md
*/
export type EntityCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
icon_color?: string;
};

View File

@ -0,0 +1,24 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
/**
* Fan Card Config.
*
* @property {boolean} [icon_animation=false] Animate the icon when fan is on.
* @property {boolean} [show_percentage_control=false] Show a slider to control speed.
* @property {boolean} [show_oscillate_control=false] Show a button to control oscillation.
* @property {boolean} [icon_animation=false] Animate the icon when fan is on.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/fan.md
*/
export type FanCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
icon_animation?: boolean;
show_percentage_control?: boolean;
show_oscillate_control?: boolean;
collapsible_controls?: boolean;
};

View File

@ -0,0 +1,29 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
/**
* Light Card Config.
*
* @property {string} [icon_color=blue] Custom color for icon and brightness bar when the lights are on and
* use_light_color is false.
* @property {boolean} [show_brightness_control=false] Show a slider to control brightness.
* @property {boolean} [show_color_temp_control=false] Show a slider to control temperature color.
* @property {boolean} [show_color_control=false] Show a slider to control RGB color.
* @property {boolean} [collapsible_controls=false] Collapse controls when off.
* @property {boolean} [use_light_color=false] Colorize the icon and slider according light temperature or color.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/light.md
*/
export type LightCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
icon_color?: string;
show_brightness_control?: boolean;
show_color_temp_control?: boolean;
show_color_control?: boolean;
collapsible_controls?: boolean;
use_light_color?: boolean;
};

View File

@ -0,0 +1,14 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
/**
* Lock Card Config.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/lock.md
*/
export type LockCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig;

View File

@ -0,0 +1,45 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
export const MEDIA_LAYER_MEDIA_CONTROLS = [
"on_off",
"shuffle",
"previous",
"play_pause_stop",
"next",
"repeat",
] as const;
export type MediaPlayerMediaControl = (typeof MEDIA_LAYER_MEDIA_CONTROLS)[number];
export const MEDIA_PLAYER_VOLUME_CONTROLS = [
"volume_mute",
"volume_set",
"volume_buttons",
] as const;
export type MediaPlayerVolumeControl = (typeof MEDIA_PLAYER_VOLUME_CONTROLS)[number];
/**
* Media Player Card Config.
*
* @property {boolean} [use_media_info=false] Use media info instead of name, state, and icon when media is playing.
* @property {boolean} [show_volume_level=false] Show volume level next to media state when media is playing.
* @property {MediaPlayerVolumeControl[]} [volume_controls] List of controls to display (volume_mute, volume_set, volume_buttons)
* @property {MediaPlayerMediaControl[]} [media_controls] List of controls to display (on_off, shuffle, previous, play_pause_stop, next, repeat)
* @property {boolean} [collapsible_controls=false] Collapse controls when off
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/media-player.md
*/
export type MediaPlayerCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
use_media_info?: boolean;
show_volume_level?: boolean;
volume_controls?: MediaPlayerVolumeControl[];
media_controls?: MediaPlayerMediaControl[];
collapsible_controls?: boolean;
};

View File

@ -0,0 +1,24 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
export const DISPLAY_MODES = ["slider", "buttons"] as const;
type DisplayMode = (typeof DISPLAY_MODES)[number];
/**
* Number Card Config.
*
* @property {string} [icon_color=blue] Custom color for icon when entity state is active.
* @property {DisplayMode} [display_mode=slider] Slider or Button controls.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/number.md
*/
export type NumberCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
icon_color?: string;
display_mode?: DisplayMode;
};

View File

@ -0,0 +1,14 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
/**
* Person Card Config.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/person.md
*/
export type PersonCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig;

View File

@ -0,0 +1,36 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
/**
* Template Card Config.
*
* @property {string} [entity]
* @property {string} [icon] Icon to render. May contain templates.
* @property {string} [icon_color] Icon color to render. May contain templates.
* @property {string} [primary] Primary info to render. May contain templates.
* @property {string} [secondary] Secondary info to render. May contain templates.
* @property {string} [badge_icon] Badge icon to render. May contain templates.
* @property {string} [badge_color] Badge icon color to render. May contain templates.
* @property {string} [picture] Picture to render. May contain templates.
* @property {boolean} [multiline_secondary] Enables support for multiline text for the secondary info.
* @property {string | string[]} [entity_id] Only reacts to the state changes of these entities.
* This can be used if the automatic analysis fails to find all relevant
* entities.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/template.md
*/
export type TemplateCardConfig = LovelaceCardConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
entity?: string;
icon?: string;
icon_color?: string;
primary?: string;
secondary?: string;
badge_icon?: string;
badge_color?: string;
picture?: string;
multiline_secondary?: boolean;
entity_id?: string | string[];
};

View File

@ -0,0 +1,21 @@
import {ActionConfig, LovelaceCardConfig} from "../../homeassistant/data/lovelace";
/**
* Title Card Config.
*
* @property {string} [title] Title to render. May contain templates.
* @property {string} [subtitle] Subtitle to render. May contain templates.
* @property {ActionConfig} [title_tap_action=none] Home assistant action to perform on title tap.
* @property {ActionConfig} [subtitle_tap_action=none] Home assistant action to perform on subtitle tap.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/title.md
*/
export interface TitleCardConfig extends LovelaceCardConfig {
title?: string;
subtitle?: string;
alignment?: string;
title_tap_action?: ActionConfig;
subtitle_tap_action?: ActionConfig;
}

View File

@ -0,0 +1,31 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
export const VACUUM_COMMANDS = [
"on_off",
"start_pause",
"stop",
"locate",
"clean_spot",
"return_home",
] as const;
export type VacuumCommand = (typeof VACUUM_COMMANDS)[number];
/**
* Vacuum Card Config.
*
* @param {boolean} icon_animation Animate the icon when vacuum is cleaning.
* @param {VacuumCommand[]} commands List of commands to display (start_pause, stop, locate, clean_spot, return_home).
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/vacuum.md
*/
export type VacuumCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
icon_animation?: boolean;
commands?: VacuumCommand[];
};

View File

@ -0,0 +1,7 @@
import {ActionConfig} from "../../../homeassistant/data/lovelace";
export type ActionsSharedConfig = {
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
};

View File

@ -0,0 +1,13 @@
import {boolean, enums, Infer, object, optional} from "superstruct";
import {layoutStruct} from "./utils/layout";
import {ICON_TYPES, INFOS} from "./utils/info";
export const appearanceSharedConfigStruct = object({
layout: optional(layoutStruct),
fill_container: optional(boolean()),
primary_info: optional(enums(INFOS)),
secondary_info: optional(enums(INFOS)),
icon_type: optional(enums(ICON_TYPES)),
});
export type AppearanceSharedConfig = Infer<typeof appearanceSharedConfigStruct>;

View File

@ -0,0 +1,9 @@
import {Infer, object, optional, string} from "superstruct";
export const entitySharedConfigStruct = object({
entity: optional(string()),
name: optional(string()),
icon: optional(string()),
});
export type EntitySharedConfig = Infer<typeof entitySharedConfigStruct>;

View File

@ -0,0 +1,2 @@
export const INFOS = ["name", "state", "last-changed", "last-updated", "none"] as const;
export const ICON_TYPES = ["icon", "entity-picture", "none"] as const;

View File

@ -0,0 +1,3 @@
import {literal, union} from "superstruct";
export const layoutStruct = union([literal("horizontal"), literal("vertical"), literal("default")]);

View File

@ -0,0 +1,2 @@
export const INFOS = ["name", "state", "last-changed", "last-updated", "none"] as const;
export type Info = (typeof INFOS)[number];

View File

@ -0,0 +1,202 @@
import {ActionConfig} from "../../../../homeassistant/data/lovelace";
import {Info} from "../../info";
/**
* Action Chip Config
*
* @property {"action"} type Type of the chip.
* @property {string} [icon] Custom icon.
* @property {string} [icon_color] Custom color for icon.
* @property {ActionConfig} [tap_action] Home assistant action to perform on tap.
* @property {ActionConfig} [hold_action] Home assistant action to perform on hold.
* @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap.
*/
export type ActionChipConfig = {
type: "action";
icon?: string;
icon_color?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
};
/**
* Alarm Control Panel Chip Config
*
* @property {"alarm-control-panel"} type Type of the chip.
* @property {string} [entity] Entity.
* @property {string} [name] Custom name.
* @property {string} [content_info] Custom content.
* @property {string} [icon] Custom icon.
* @property {string} [icon_color] Custom color for icon.
* @property {ActionConfig} [tap_action] Home assistant action to perform on tap.
* @property {ActionConfig} [hold_action] Home assistant action to perform on hold.
* @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap.
*/
export type AlarmControlPanelChipConfig = {
type: "alarm-control-panel";
entity?: string;
name?: string;
content_info?: Info;
icon?: string;
icon_color?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
};
/**
* Back Chip Config
*
* @property {"back"} type Type of the chip.
* @property {string} [icon] Custom icon.
*/
export type BackChipConfig = {
type: "back";
icon?: string;
};
/**
* Entity Chip Config
*
* @property {"entity"} type Type of the chip.
* @property {string} [entity] Entity.
* @property {string} [name] Custom name.
* @property {string} [content_info] Custom content.
* @property {string} [icon] Custom icon.
* @property {string} [icon_color] Custom color for icon.
* @property {boolean} [use_entity_picture]
* @property {ActionConfig} [tap_action] Home assistant action to perform on tap.
* @property {ActionConfig} [hold_action] Home assistant action to perform on hold.
* @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap.
*/
export type EntityChipConfig = {
type: "entity";
entity?: string;
name?: string;
content_info?: Info;
icon?: string;
icon_color?: string;
use_entity_picture?: boolean;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
};
/**
* Menu Chip Config
*
* @property {"menu"} type Type of the chip.
* @property {string} [icon] Custom icon.
*/
export type MenuChipConfig = {
type: "menu";
icon?: string;
};
/**
* Weather Chip Config
*
* @property {"weather"} type Type of the chip.
* @property {string} [entity] Entity.
* @property {ActionConfig} [tap_action] Home assistant action to perform on tap.
* @property {ActionConfig} [hold_action] Home assistant action to perform on hold.
* @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap.
* @property {boolean} [show_temperature] Show the temperature.
* @property {boolean} [show_conditions] Show the conditions.
*/
export type WeatherChipConfig = {
type: "weather";
entity?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
show_temperature?: boolean;
show_conditions?: boolean;
};
/**
* Template Chip Config
*
* @property {"template"} type Type of the chip.
* @property {string} [entity] Entity.
* @property {ActionConfig} [tap_action] Home assistant action to perform on tap.
* @property {ActionConfig} [hold_action] Home assistant action to perform on hold.
* @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap.
* @property {string} [content]
* @property {string} [icon] Custom icon.
* @property {string} [icon_color] Custom color for icon.
* @property {string} [picture]
* @property {string | string[]} [entity_id]
*/
export type TemplateChipConfig = {
type: "template";
entity?: string;
hold_action?: ActionConfig;
tap_action?: ActionConfig;
double_tap_action?: ActionConfig;
content?: string;
icon?: string;
icon_color?: string;
picture?: string;
entity_id?: string | string[];
};
/**
* Conditional Chip Config
*
* @property {"conditional"} type Type of the chip.
* @property {LovelaceChipConfig} [chip] A chip configuration.
* @property {[]} conditions
*/
export interface ConditionalChipConfig {
type: "conditional";
chip?: LovelaceChipConfig;
conditions: any[];
}
/**
* Light Chip Config
*
* @property {"light"} type Type of the chip.
* @property {string} [entity] Entity.
* @property {string} [name]
* @property {Info} [content_info]
* @property {string} [icon] Custom icon.
* @property {boolean} [use_light_color] Colorize the icon and slider according light temperature or color.
* @property {ActionConfig} [tap_action] Home assistant action to perform on tap.
* @property {ActionConfig} [hold_action] Home assistant action to perform on hold.
* @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap.
*/
export type LightChipConfig = {
type: "light";
entity?: string;
name?: string;
content_info?: Info;
icon?: string;
use_light_color?: boolean;
hold_action?: ActionConfig;
tap_action?: ActionConfig;
double_tap_action?: ActionConfig;
};
/**
* Spacer Chip Config
*
* @property {"spacer"} type Type of the chip.
*/
export type SpacerChipConfig = {
type: "spacer";
};
export type LovelaceChipConfig =
| ActionChipConfig
| AlarmControlPanelChipConfig
| BackChipConfig
| EntityChipConfig
| MenuChipConfig
| WeatherChipConfig
| TemplateChipConfig
| ConditionalChipConfig
| LightChipConfig
| SpacerChipConfig;

View File

@ -0,0 +1,70 @@
import {LovelaceCardConfig} from "../homeassistant/data/lovelace";
import {TitleCardConfig} from "../lovelace-mushroom/cards/title-card-config";
import {EntitySharedConfig} from "../lovelace-mushroom/shared/config/entity-config";
import {AppearanceSharedConfig} from "../lovelace-mushroom/shared/config/appearance-config";
import {ActionsSharedConfig} from "../lovelace-mushroom/shared/config/actions-config";
import {TemplateCardConfig} from "../lovelace-mushroom/cards/template-card-config";
import {EntityCardConfig} from "../lovelace-mushroom/cards/entity-card-config";
import {PictureEntityCardConfig} from "../homeassistant/panels/lovelave/cards/types";
import {ClimateCardConfig} from "../lovelace-mushroom/cards/climate-card-config";
import {CoverCardConfig} from "../lovelace-mushroom/cards/cover-card-config";
import {FanCardConfig} from "../lovelace-mushroom/cards/fan-card-config";
import {AreaCardConfig} from "../homeassistant/lovelace/cards/types";
import {LightCardConfig} from "../lovelace-mushroom/cards/light-card-config";
import {LockCardConfig} from "../lovelace-mushroom/cards/lock-card-config";
import {MediaPlayerCardConfig} from "../lovelace-mushroom/cards/media-player-card-config";
import {NumberCardConfig} from "../lovelace-mushroom/cards/number-card-config";
import {PersonCardConfig} from "../lovelace-mushroom/cards/person-card-config";
import {VacuumCardConfig} from "../lovelace-mushroom/cards/vacuum-card-config";
export namespace cards {
/**
* Abstract Card Config.
*/
export type AbstractCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig;
/**
* Controller Card Config.
*
* @property {boolean} [showControls=true] False to hide controls.
* @property {string} [iconOn] Icon to show for switching entities from off state.
* @property {string} [iconOff] Icon to show for switching entities to off state.
* @property {string} [onService=none] Service to call for switching entities from off state.
* @property {string} [offService=none] Service to call for switching entities to off state.
*/
export interface ControllerCardConfig extends TitleCardConfig {
type: "mushroom-title-card",
showControls?: boolean;
iconOn?: string;
iconOff?: string;
onService?: string;
offService?: string;
}
export type AreaCardOptions = Omit<AreaCardConfig, "type">;
export type ClimateCardOptions = Omit<ClimateCardConfig, "type">;
export type ControllerCardOptions = Omit<ControllerCardConfig, "type">;
export type CoverCardOptions = Omit<CoverCardConfig, "type">;
export type EntityCardOptions = Omit<EntityCardConfig, "type">;
export type FanCardOptions = Omit<FanCardConfig, "type">;
export type LightCardOptions = Omit<LightCardConfig, "type">;
export type LockCardOptions = Omit<LockCardConfig, "type">;
export type MediaPlayerCardOptions = Omit<MediaPlayerCardConfig, "type">;
export type NumberCardOptions = Omit<NumberCardConfig, "type">;
export type PersonCardOptions = Omit<PersonCardConfig, "type">;
export type PictureEntityCardOptions = Omit<PictureEntityCardConfig, "type">;
export type TemplateCardOptions = Omit<TemplateCardConfig, "type">;
export type VacuumCardOptions = Omit<VacuumCardConfig, "type">;
}

View File

@ -0,0 +1,6 @@
import {TemplateChipConfig, WeatherChipConfig} from "../lovelace-mushroom/utils/lovelace/chip/types";
export namespace chips {
export type TemplateChipOptions = Omit<TemplateChipConfig, "type">;
export type WeatherChipOptions = Omit<WeatherChipConfig, "type">;
}

View File

@ -0,0 +1,214 @@
import {
CallServiceActionConfig,
LovelaceCardConfig,
LovelaceConfig,
LovelaceViewConfig
} from "../homeassistant/data/lovelace";
import {HomeAssistant} from "../homeassistant/types";
import {AreaRegistryEntry} from "../homeassistant/data/area_registry";
import {cards} from "./cards";
import {EntityRegistryEntry} from "../homeassistant/data/entity_registry";
import {LovelaceChipConfig} from "../lovelace-mushroom/utils/lovelace/chip/types";
import {HassServiceTarget} from "home-assistant-js-websocket";
export namespace generic {
/**
* An entry out of a Home Assistant Register.
*/
export type RegistryEntry =
| AreaRegistryEntry
| DataTransfer
| EntityRegistryEntry
/**
* View Entity.
*
* @property {number} [order] Ordering position of the entity in the list of available views.
* @property {boolean} [hidden] True if the entity should be hidden from the dashboard.
*/
export interface ViewConfig extends LovelaceViewConfig {
hidden?: boolean;
order?: number;
}
/**
* Domain Configuration.
*
* @property {number} [order] Ordering position of the entity in the list of available views.
* @property {boolean} [hidden] True if the entity should be hidden from the dashboard.
*/
export interface DomainConfig extends Partial<cards.ControllerCardConfig> {
hidden?: boolean;
order?: number;
}
/**
* Dashboard Information Object.
*
* Home Assistant passes this object to the Dashboard Generator method.
*
* @property {LovelaceConfig} config Dashboard configuration.
* @property {HomeAssistant} hass The Home Assistant object.
*
* @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#dashboard-strategies
*/
export interface DashBoardInfo {
config?: LovelaceConfig & {
strategy: {
options?: StrategyConfig
}
};
hass: HomeAssistant;
}
/**
* View Information Object.
*
* Home Assistant passes this object to the View Generator method.
*
* @property {LovelaceViewConfig} view View configuration.
* @property {LovelaceConfig} config Dashboard configuration.
* @property {HomeAssistant} hass The Home Assistant object.
*
* @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#view-strategies
*/
export interface ViewInfo {
view: LovelaceViewConfig & {
strategy?: {
options?: StrategyConfig & { area: StrategyArea }
}
};
config: LovelaceConfig
hass: HomeAssistant;
}
/**
* Strategy Configuration.
*
* @property {Object.<AreaRegistryEntry>} areas List of areas.
* @property {Object.<CustomCardConfig>} [card_options] Card options for entities.
* @property {chips} [chips] List of chips to show in the Home view.
* @property {boolean} [debug] Set to true for more verbose debugging info.
* @property {Object.<DomainConfig>} domains List of domains.
* @property {object[]} [extra_cards] List of cards to show below room cards.
* @property {object[]} [extra_views] List of views to add to the dashboard.
* @property {object[]} [quick_access_cards] List of cards to show between welcome card and rooms cards.
* @property {Object.<ViewConfig>} views List of views.
*/
export interface StrategyConfig {
areas: { [k: string]: StrategyArea };
card_options?: { [k: string]: CustomCardConfig };
chips?: Chips;
debug: boolean;
domains: { [k: string]: DomainConfig };
extra_cards?: LovelaceCardConfig[];
extra_views?: ViewConfig[];
home_view: {
hidden: HiddenSectionType[]
}
quick_access_cards?: LovelaceCardConfig[];
views: { [k: string]: ViewConfig };
}
const hiddenSectionList = ["chips", "persons", "greeting", "areas", "areasTitle"] as const;
export type HiddenSectionType = typeof hiddenSectionList[number];
/**
* Represents the default configuration for a strategy.
*/
export interface StrategyDefaults extends StrategyConfig {
areas: {
undisclosed: StrategyArea & {
area_id: "undisclosed",
},
[k: string]: StrategyArea,
},
domains: {
default: DomainConfig,
[k: string]: DomainConfig,
}
}
/**
* Strategy Area.
*
* @property {number} [order] Ordering position of the area in the list of available areas.
* @property {boolean} [hidden] True if the entity should be hidden from the dashboard.
* @property {object[]} [extra_cards] An array of card configurations.
* The configured cards are added to the dashboard.
* @property {string} [type=default] The type of area card.
*/
export interface StrategyArea extends AreaRegistryEntry {
order?: number;
hidden?: boolean;
extra_cards?: LovelaceCardConfig[];
type?: string;
}
/**
* A list of chips to show in the Home view.
*
* @property {boolean} light_count Chip to display the number of lights on.
* @property {boolean} fan_count Chip to display the number of fans on.
* @property {boolean} cover_count Chip to display the number of unclosed covers.
* @property {boolean} switch_count Chip to display the number of switches on.
* @property {boolean} climate_count Chip to display the number of climates which are not off.
* @property {string} weather_entity Entity ID for the weather chip to use, accepts `weather.` only.
* @property {object[]} extra_chips List of extra chips.
*/
export interface Chips {
extra_chips: LovelaceChipConfig[];
light_count: boolean;
fan_count: boolean;
cover_count: boolean;
switch_count: boolean;
climate_count: boolean;
weather_entity: string;
[key: string]: any;
}
/**
* Custom Card Configuration for an entity.
*
* @property {boolean} hidden True if the entity should be hidden from the dashboard.
*/
export interface CustomCardConfig extends LovelaceCardConfig {
hidden?: boolean;
}
/**
* Area Filter Context.
*
* @property {AreaRegistryEntry} area Area Entity.
* @property {string[]} areaDeviceIds The id of devices which are linked to the area entity.
* @property {string} domain Domain of the entity.
* Example: `light`.
*/
export interface AreaFilterContext {
area: AreaRegistryEntry;
areaDeviceIds: string[];
domain: string;
}
/**
* Checks if the given object is an instance of CallServiceActionConfig.
*
* @param {any} obj - The object to be checked.
* @return {boolean} - Returns true if the object is an instance of CallServiceActionConfig, otherwise false.
*/
export function isCallServiceActionConfig(obj: any): obj is CallServiceActionConfig {
return obj && obj.action === "call-service" && ["action", "service"].every(key => key in obj);
}
/**
* Checks if the given object is an instance of HassServiceTarget.
*
* @param {any} obj - The object to check.
* @return {boolean} - True if the object is an instance of HassServiceTarget, false otherwise.
*/
export function isCallServiceActionTarget(obj: any): obj is HassServiceTarget {
return obj && ["entity_id", "device_id", "area_id"].some(key => key in obj);
}
}

View File

@ -0,0 +1,17 @@
import {cards} from "./cards";
import {LovelaceViewConfig} from "../homeassistant/data/lovelace";
export namespace views {
/**
* Options for the extended View class.
*
* @property {cards.ControllerCardConfig} [controllerCardOptions] Options for the Controller card.
*/
export interface ViewConfig extends LovelaceViewConfig {
controllerCardOptions?: cards.ControllerCardOptions;
}
}

View File

@ -1,131 +0,0 @@
import {Helper} from "../Helper";
import {TitleCard} from "../cards/TitleCard";
/**
* Abstract View Class.
*
* To create a new view, extend the new class with this one.
*
* @class
* @abstract
*/
class AbstractView {
/**
* Options for creating a view.
*
* @type {abstractOptions}
*/
options = {
title: null,
path: null,
icon: "mdi:view-dashboard",
subview: false,
};
/**
* A card to switch all entities in the view.
*
* @type {Object}
*/
viewTitleCard;
/**
* Class constructor.
*
* @throws {Error} If trying to instantiate this class.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor() {
if (this.constructor === AbstractView) {
throw new Error("Abstract classes can't be instantiated.");
}
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
}
/**
* Merge the default options of this class and the custom options into the options of the parent class.
*
* @param {Object} [defaultOptions={}] Default options for the card.
* @param {Object} [customOptions={}] Custom Options for the card.
*/
mergeOptions(defaultOptions, customOptions) {
this.options = {
...defaultOptions,
...customOptions,
};
}
/**
* Create the cards to include in the view.
*
* @return {Object[] | Promise} An array of card objects.
*/
async createViewCards() {
/** @type Object[] */
const viewCards = [];
// Create cards for each area.
for (const area of Helper.areas) {
const areaCards = [];
const entities = Helper.getDeviceEntities(area, this["domain"]);
const className = Helper.sanitizeClassName(this["domain"] + "Card");
const cardModule = await import(`../cards/${className}`);
// Create a card for each domain-entity of the current area.
for (const entity of entities) {
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id] ?? {};
let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id] ?? {};
if (cardOptions.hidden || deviceOptions.hidden) {
continue;
}
areaCards.push(new cardModule[className](entity, cardOptions).getCard());
}
if (areaCards.length) {
// Create a Title card for the current area if it has entities.
areaCards.unshift(new TitleCard(
[area],
{
title: area.name,
...this.options["titleCard"],
},
this["domain"],
).createCard());
viewCards.push({
type: "vertical-stack",
cards: areaCards,
});
}
}
viewCards.unshift(viewCards.length ? this.viewTitleCard : {
type: "custom:mushroom-title-card",
title: "No Entities Available",
subtitle: "They're either hidden by the configuration or by Home Assistant.",
});
return viewCards;
}
/**
* Get a view object.
*
* The view includes the cards which are created by method createViewCards().
*
* @returns {viewOptions & {cards: Object[]}} The view object.
*/
async getView() {
return {
...this.options,
cards: await this.createViewCards(),
};
}
}
export {AbstractView};

158
src/views/AbstractView.ts Normal file
View File

@ -0,0 +1,158 @@
import {Helper} from "../Helper";
import {ControllerCard} from "../cards/ControllerCard";
import {StackCardConfig} from "../types/homeassistant/lovelace/cards/types";
import {LovelaceCardConfig, LovelaceViewConfig} from "../types/homeassistant/data/lovelace";
import {cards} from "../types/strategy/cards";
import {TitleCardConfig} from "../types/lovelace-mushroom/cards/title-card-config";
import {HassServiceTarget} from "home-assistant-js-websocket";
import abstractCardConfig = cards.AbstractCardConfig;
/**
* Abstract View Class.
*
* To create a new view, extend the new class with this one.
*
* @class
* @abstract
*/
abstract class AbstractView {
/**
* Configuration of the view.
*
* @type {LovelaceViewConfig}
*/
config: LovelaceViewConfig = {
icon: "mdi:view-dashboard",
subview: false,
};
/**
* A card to switch all entities in the view.
*
* @type {StackCardConfig}
*/
viewControllerCard: StackCardConfig = {
cards: [],
type: "",
};
/**
* The domain of which we operate the devices.
*
* @private
* @readonly
*/
readonly #domain?: string;
/**
* Class constructor.
*
* @param {string} [domain] The domain which the view is representing.
*
* @throws {Error} If trying to instantiate this class.
* @throws {Error} If the Helper module isn't initialized.
*/
protected constructor(domain: string = "") {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
if (domain) {
this.#domain = domain;
}
}
/**
* Create the cards to include in the view.
*
* @return {Promise<(StackCardConfig | TitleCardConfig)[]>} An array of card objects.
*/
async createViewCards(): Promise<(StackCardConfig | TitleCardConfig)[]> {
const viewCards: LovelaceCardConfig[] = [];
// Create cards for each area.
for (const area of Helper.areas) {
const areaCards: abstractCardConfig[] = [];
const entities = Helper.getDeviceEntities(area, this.#domain ?? "");
const className = Helper.sanitizeClassName(this.#domain + "Card");
const cardModule = await import(`../cards/${className}`);
// Set the target for controller cards to the current area.
let target: HassServiceTarget = {
area_id: [area.area_id],
};
// Set the target for controller cards to entities without an area.
if (area.area_id === "undisclosed") {
target = {
entity_id: entities.map(entity => entity.entity_id),
}
}
// Create a card for each domain-entity of the current area.
for (const entity of entities) {
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id];
let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id ?? "null"];
if (cardOptions?.hidden || deviceOptions?.hidden) {
continue;
}
areaCards.push(new cardModule[className](entity, cardOptions).getCard());
}
// Vertical stack the area cards if it has entities.
if (areaCards.length) {
const titleCardOptions = ("controllerCardOptions" in this.config) ? this.config.controllerCardOptions : {};
// Create and insert a Controller card.
areaCards.unshift(new ControllerCard(target, Object.assign({title: area.name}, titleCardOptions)).createCard());
viewCards.push({
type: "vertical-stack",
cards: areaCards,
} as StackCardConfig);
}
}
// Add a Controller Card for all the entities in the view.
if (viewCards.length) {
viewCards.unshift(this.viewControllerCard);
}
return viewCards;
}
/**
* Get a view object.
*
* The view includes the cards which are created by method createViewCards().
*
* @returns {Promise<LovelaceViewConfig>} The view object.
*/
async getView(): Promise<LovelaceViewConfig> {
return {
...this.config,
cards: await this.createViewCards(),
};
}
/**
* Get a target of entity IDs for the given domain.
*
* @param {string} domain - The target domain to retrieve entity IDs from.
* @return {HassServiceTarget} - A target for a service call.
*/
targetDomain(domain: string): HassServiceTarget {
return {
entity_id: Helper.entities.filter(
entity =>
entity.entity_id.startsWith(domain + ".")
&& !entity.hidden_by
&& !Helper.strategyOptions.card_options?.entity_id.hidden
).map(entity => entity.entity_id),
};
}
}
export {AbstractView};

Some files were not shown because too many files have changed in this diff Show More