forked from DigiLive/mushroom-strategy
Code Cleanup (#7)
Refactor the code base from Procedural to Object-Oriented Programming. The new code base requires webpack (https://webpack.js.org/) to bundle the code into a single javascript file. Webpack can be installed with npm, using file `package-lock.json` or `package.json`. File `package.json` contains two script entries: * `build` for a *release* build (uses file`webpack.config.js`). * `build-dev` for a *debug* build (uses file `webpack.dev.config.js`). Both of the scripts will bundle the separate package files into a single javascript file in the `dist` directory. Includes fixes and changes: * The release badge in readme now displays the latest release version. * Optimize function `getStateEntities()`. * Implement #11. * Merge Home Assistant and User-defined areas. * Add contributors to readme. * Fix getCountTemplate method. The method only counted states which aren't equal to value `off`. States are now counted by specifying a comparison operator and value. * Document default HACS installation. * Fix overriding sensor card configuration. Having an `entity_config` property in the strategy options resulted in overriding each sensor card. Only the cards listed under this property should be overridden. * Cut redundant properties and constants Method `generateDashboard()` passes redundant values to generateView(). Writing those values into properties and reading them into constants is removed. * Fix undefined sensorState. Not all entities (like a RESTful sensor) are bound to a device. SensorStates did contain states which meet all following conditions: 1. The linked entity is linked to the given area or isn't linked to any area. 2. The linked device is linked to the given area. SensorStates now contains states which meet any* of the following conditions: 1. The linked entity is linked to the given area or isn't linked to any area. 2. The entity is linked to a device, and the linked device is linked to the given area. *) Whichever comes first. * Add undisclosed area. * Add ordering of area-cards. Area cards are now ordered by a custom set position first and then by the area name. * Add global sorting of entities by original_name. * Add global sorting of areas by order and name. * Add global sorting of domains by order and title. * Make production build. Known Issue: Title cards or chips won't switch entities assigned to the undisclosed area.
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/node_modules/
|
413
README.md
413
README.md
@ -1,7 +1,7 @@
|
||||
# Mushroom dashboard strategy
|
||||
|
||||
[![hacs][hacs-badge]][hacs-url]
|
||||
[![release][release-badge]][release-url]
|
||||
[![hacs][hacsBadge]][hacsUrl]
|
||||
[![release][releaseBadge]][releaseUrl]
|
||||
|
||||

|
||||
|
||||
@ -11,48 +11,52 @@
|
||||
|
||||

|
||||
|
||||
## What is Mushroom dashboard strategy ?
|
||||
## What is 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 create a dashboard using Mushroom
|
||||
cards, the area configuration and entity configuration.
|
||||
|
||||
My goal is to propose a way to create powerful dashaboards without the need of spending hours manualy creating them.
|
||||
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 recomendations are always welcome
|
||||
**Note:** This is my first javascript code and GitHub repository. Any recommendations are always welcome.
|
||||
|
||||
### Features
|
||||
|
||||
- 🛠 Automatically create dashboard with 3 lines of yaml
|
||||
- 😍 Built-in Views for device specific controls
|
||||
- 🎨 Many options to customize to your needs
|
||||
- 🛠 Automatically create dashboard with 3 lines of yaml.
|
||||
- 😍 Built-in Views for device-specific controls.
|
||||
- 🎨 Many options to customize to fit your needs.
|
||||
|
||||
## Installation
|
||||
|
||||
### Preresquisites
|
||||
### Prerequisites
|
||||
|
||||
You need to install these cards first before using this strategy
|
||||
- [Mushroom cards][mushroom]
|
||||
- [Mini graph card][mini-graph]
|
||||
- [Web RTC][webrtc]
|
||||
You need to install these cards before using this strategy:
|
||||
|
||||
- [Mushroom cards][mushroomUrl]
|
||||
- [Mini graph card][mini-graphUrl]
|
||||
- [Web RTC][webRtcUrl]
|
||||
|
||||
### HACS
|
||||
|
||||
Mushroom dashboard strategy is available in [HACS][hacs] (Home Assistant Community Store).
|
||||
Mushroom dashboard strategy is available in [HACS][hacsUrl] (Home Assistant Community Store).
|
||||
|
||||
1. Install HACS if you don't have it already
|
||||
2. Open HACS in Home Assistant
|
||||
3. Go to "Frontend" section
|
||||
4. Click 3 dots on top right and custom repository
|
||||
5. Add `https://github.com/AalianKhan/mushroom-strategy` with catagory `Lovelace`
|
||||
5. Search for "Mushroom strategy" and install
|
||||
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.
|
||||
|
||||
### Manual
|
||||
|
||||
1. Download `mushroom-strategy.js` file from the [`dist`](https://github.com/AalianKhan/mushroom-strategy/tree/main/dist) directory.
|
||||
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 reference to `mushroom-strategy.js` in Dashboard. There's two way 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`.
|
||||
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 following code to `lovelace` section.
|
||||
- **Using YAML:** Add following code to the `lovelace` section.
|
||||
```yaml
|
||||
resources:
|
||||
- url: /local/mushroom-strategy.js
|
||||
@ -61,80 +65,60 @@ Mushroom dashboard strategy is available in [HACS][hacs] (Home Assistant Communi
|
||||
|
||||
## Usage
|
||||
|
||||
All the Rounded cards can be configured using Dashboard UI editor.
|
||||
All the rounded cards can be configured using the Dashboard UI editor.
|
||||
|
||||
1. In Dashboard UI, click 3 dots in top right corner.
|
||||
1. In the UI of the dashboard, click the 3 dots in the top right corner.
|
||||
2. Click _Edit Dashboard_.
|
||||
3. Click 3 dots again
|
||||
4. Click `Raw configuration editor`
|
||||
5. Add these lines
|
||||
5. Add the following lines:
|
||||
|
||||
```yaml
|
||||
strategy:
|
||||
type: custom:mushroom-strategy
|
||||
views: []
|
||||
views: [ ]
|
||||
```
|
||||
|
||||
### Hidding specific entities
|
||||
|
||||
When first creating this dashboard, you probably have many entities that you don't want to see.
|
||||
When creating this dashboard for the first time, you probably have many entities that you don't want to see.
|
||||
|
||||
You can easily hide these entities by holding the entity > Click the `cog icon` at the top right corner of the popup > Click `Advanced settings` > Set `entity status` to `hidden`. Refresh the page and it should update
|
||||
You can easily hide these entities by holding the entity > Click the `cog icon` in the top right corner of the popup >
|
||||
Click `Advanced settings` > Set `entity status` to `hidden`.
|
||||
The view should update when the page is refreshed.
|
||||
|
||||

|
||||
|
||||
### Adding devices to areas
|
||||
|
||||
You can easiy add devices to an area by going to `Settings` found at the bottom of the sidebar > Click `Devices and integration` > Select the integration of your device > Click the device you wish to add > Click the `pencil icon` found at the top right corner > Enter an area in area field. You can also set an entity of that device to a different area by going to advanced settings of that entity.
|
||||
You can easily add devices to an area by going to `Settings` found at the bottom of the sidebar >
|
||||
Click `Devices and integration` > Select the integration of your device > Click the device you wish to add > Click
|
||||
the `pencil icon` found in the top right corner > 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 a entity in your `configuratation.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/)
|
||||
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. It has the following available options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------|:-----------------------|:--------------------------------------------------------|:-----------------------------------------------------------------------------------------|
|
||||
| `areas` | list | Optional | One or more areas in a list, see [areas object](#area-object) |
|
||||
| `entity_config` | list of cards | Optional | Card defination for an entity, see [entity config](#entity-config) |
|
||||
| `views` | object | All views enabled | Setting which pre-built views to show, 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` | list of cards | Optional | List of cards to show between welcome card and rooms cards |
|
||||
| `extra_cards` | list of cards | Optional | List of cards to show below room cards |
|
||||
| `extra_views` | list of view | Optional | List of views to add to the dashboard |
|
||||
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.
|
||||
|
||||
#### Example
|
||||
The options available are:
|
||||
|
||||
```yaml
|
||||
strategy:
|
||||
type: custom:mushroom-strategy
|
||||
options:
|
||||
areas:
|
||||
- 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. The order of defination is used to sort the rooms and pre-built views
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| :-------------------- | :-------------- | :---------- | :---------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `name` | string | Required | The name of the area |
|
||||
| `icon` | string | Optional | Icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). |
|
||||
| `icon_color` | string | Optional | Icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). |
|
||||
| `primary` | string | Optional | Primary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). |
|
||||
| `secondary` | string | Optional | Secondary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). |
|
||||
| `badge_icon` | string | Optional | Badge icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). |
|
||||
| `badge_color` | string | Optional | Badge icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). |
|
||||
| `picture` | string | Optional | 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 | 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` `list` | Optional | 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 |
|
||||
| `extra_cards` | list of cards | Optional | A list of cards to show on the top of the area subview |
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------|:--------------------------|:--------------------------------------------------------|:--------------------------------------------------------------------|
|
||||
| `areas` | object (optional) | unset | One or more areas in a list, see [areas object](#area-object). |
|
||||
| `entity_config` | array of cards (optional) | unset | Card definition for an entity, see [entity config](#entity-config). |
|
||||
| `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). |
|
||||
|
||||
#### Example
|
||||
|
||||
@ -143,28 +127,85 @@ strategy:
|
||||
type: custom:mushroom-strategy
|
||||
options:
|
||||
areas:
|
||||
- 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
|
||||
- name: Kitchen
|
||||
icon: mdi:silverware-fork-knife
|
||||
icon_color: red
|
||||
views: []
|
||||
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. |
|
||||
|
||||
*) `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
|
||||
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`.
|
||||
|
||||
### Entity Config
|
||||
|
||||
The `entity_config` essentially enables you to give a specific entity any card you wish.
|
||||
The `entity_config` essentially enables you to give a specific entity any card you wish.
|
||||
|
||||
#### Example
|
||||
|
||||
```yaml
|
||||
strategy:
|
||||
type: custom:mushroom-strategy
|
||||
@ -172,23 +213,40 @@ strategy:
|
||||
entity_config:
|
||||
- entity: fan.master_bedroom_fan
|
||||
type: custom:mushroom-fan-card
|
||||
views: []
|
||||
views: [ ]
|
||||
```
|
||||
|
||||
### Pre-built views
|
||||
|
||||

|
||||
|
||||
Mushroom strategy includes pre-built views to control/view specific domains. Only the devices that are in an area defined in `areas` are shown. If `areas` is not defined then devices in all areas are shown. By default, all views are shown
|
||||
Mushroom strategy includes pre-built views to control/view specific domains.
|
||||
Only devices that are in an area as defined in `areas` are shown.
|
||||
If `areas` is undefined then the devices of all areas are shown.
|
||||
|
||||
| Available views | type | Description |
|
||||
|:----------------|:-----|:------------|
|
||||
| `lights` | boolean | View to control all lights and lights of each area |
|
||||
| `fans` | boolean | View to control all fans and fans of each area |
|
||||
| `covers` | boolean | View to control all covers and covers of each area |
|
||||
| `switches` | boolean | View to control all switches and switches of each area |
|
||||
| `climates` | boolean | View to control climate devices such as thermostats. Seperated by each area |
|
||||
| `cameras` | boolean | View to show all cameras using WebRTC cards. Seperated by each area |
|
||||
By default, all pre-built views below are shown:
|
||||
|
||||
| Available views | type | Description |
|
||||
|:----------------|:--------|:-----------------------------------------------------------------------------|
|
||||
| `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
|
||||
|
||||
@ -197,31 +255,63 @@ strategy:
|
||||
type: custom:mushroom-strategy
|
||||
options:
|
||||
views:
|
||||
lights: true
|
||||
switches: true
|
||||
covers: false
|
||||
cameras: true
|
||||
climates: false
|
||||
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
|
||||
* climate
|
||||
* media_player
|
||||
* sensor
|
||||
* binary_sensor
|
||||
* 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 int a view, to switch all entities of the domain. |
|
||||
| `hidden` | boolean | Set to `true` to exclude the view from the dashboard. |
|
||||
| `order` | number | Ordering position of the domain entities in a view. |
|
||||
|
||||
### Chips
|
||||
|
||||
|
||||

|
||||
|
||||
Mushroom strategy has chips that count the number of devices active for a specific domain. Only the devices that are defined in `areas` are counted. if `areas` is not defined then devices in all areas are counted. By default, all chips are enabled. You can also manually configure a weather entity to use. There is also an option to add more [Mushroom Chips][mushroom-chips] using `extra_chips`
|
||||
##### Note: setting the status to hidden for the unwanted weather entity is recomended
|
||||
Mushroom strategy has chips that indicate the number of devices which are active for a specific domain.
|
||||
Only devices of an area as defined in `areas` are counted.
|
||||
If `areas` is not defined then the devices in all areas are counted.
|
||||
By default, all chips are enabled.
|
||||
|
||||
| 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 naviagetes to climates view |
|
||||
| `weather_entity` | Entity ID | Entity ID for the weather chip to use, accepts `weather.` only |
|
||||
| `extra_chips` | List | List of extra chips to display, see [Mushroom Chips][mushroom-chips] |
|
||||
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
|
||||
|
||||
@ -254,11 +344,11 @@ strategy:
|
||||
type: custom:mushroom-strategy
|
||||
options:
|
||||
views:
|
||||
lights: true
|
||||
switches: true
|
||||
covers: false
|
||||
cameras: true
|
||||
thermostats: false
|
||||
light:
|
||||
title: illumination
|
||||
switches:
|
||||
hidden: true
|
||||
icon: mdi:toggle-switch
|
||||
chips:
|
||||
weather_entity: weather.forecast_home
|
||||
climate_count: false
|
||||
@ -289,7 +379,8 @@ strategy:
|
||||
tap_action:
|
||||
action: toggle
|
||||
areas:
|
||||
- name: Family Room
|
||||
family_room_id:
|
||||
name: Family Room
|
||||
icon: mdi:television
|
||||
icon_color: green
|
||||
extra_cards:
|
||||
@ -300,25 +391,33 @@ strategy:
|
||||
icon: mdi:thermometer
|
||||
icon_color: pink
|
||||
alignment: center
|
||||
- name: Kitchen
|
||||
kitchen_id:
|
||||
name: Kitchen
|
||||
icon: mdi:silverware-fork-knife
|
||||
icon_color: red
|
||||
- name: Master Bedroom
|
||||
master_bedroom_id:
|
||||
name: Master Bedroom
|
||||
icon: mdi:bed-king
|
||||
icon_color: blue
|
||||
- name: Abia's Bedroom
|
||||
abias_bedroom_id:
|
||||
name: Abia's Bedroom
|
||||
icon: mdi:flower-tulip
|
||||
icon_color: green
|
||||
- name: Aalian's Bedroom
|
||||
aalians_bedroom_id:
|
||||
name: Aalian's Bedroom
|
||||
icon: mdi:rocket-launch
|
||||
icon_color: yellow
|
||||
- name: Rohaan's Bedroom
|
||||
rohaans_bedroom_id:
|
||||
name: Rohaan's Bedroom
|
||||
icon: mdi:controller
|
||||
icon_color: red
|
||||
- name: Hallway
|
||||
- name: Living Room
|
||||
hallway_id:
|
||||
name: Hallway
|
||||
living_room_id:
|
||||
name: Living Room
|
||||
icon: mdi:sofa
|
||||
- name: Front Door
|
||||
front_door_id:
|
||||
name: Front Door
|
||||
icon: mdi:door-closed
|
||||
entity_config:
|
||||
- entity: fan.master_bedroom_fan
|
||||
@ -349,34 +448,40 @@ strategy:
|
||||
title: cool view
|
||||
path: cool-view
|
||||
icon: mdi:emoticon-cool
|
||||
badges: []
|
||||
badges: [ ]
|
||||
cards:
|
||||
- type: markdown
|
||||
content: I am cool
|
||||
```
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
* The cards used are from [Mushroom][mushroom], [Mini graph card][mini-graph] and [Web RTC][webrtc]
|
||||
* Took inspiration from [Balloob battery strategy][balloobBattery]
|
||||
* The cards used are from [Mushroom][mushroomUrl], [Mini graph card][mini-graphUrl] and [WebRTC][webRtcUrl]
|
||||
* Took inspiration from [Balloob battery strategy][balloobBatteryUrl]
|
||||
|
||||
<!-- Badges -->
|
||||
## Contributors
|
||||
|
||||
[hacs-url]: https://github.com/hacs/integration
|
||||
[hacs-badge]: https://img.shields.io/badge/HACS-Custom-41BDF5.svg
|
||||
[release-badge]: https://img.shields.io/github/v/release/lovelace-rounded/ui?style=flat-square
|
||||
[downloads-badge]: https://img.shields.io/github/downloads/lovelace-rounded/ui/total?style=flat-square
|
||||
[build-badge]: https://img.shields.io/github/actions/workflow/status/lovelace-rounded/ui/build.yml?branch=main&style=flat-square
|
||||
* [DigiLive](https://github.com/DigiLive)
|
||||
|
||||
<!-- References -->
|
||||
<!-- Badges References -->
|
||||
|
||||
[hacsBadge]: https://img.shields.io/badge/HACS-Default-41BDF5.svg
|
||||
|
||||
[releaseBadge]: https://img.shields.io/github/v/release/AalianKhan/mushroom-strategy
|
||||
|
||||
<!-- Other References -->
|
||||
|
||||
[hacsUrl]: https://hacs.xyz
|
||||
|
||||
[releaseUrl]: https://github.com/AalianKhan/mushroom-strategy/releases
|
||||
|
||||
[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
|
||||
|
||||
[balloobBatteryUrl]: https://gist.github.com/balloob/4a70c83287ddba4e9085cb578ffb161f
|
||||
|
||||
[home-assistant]: https://www.home-assistant.io/
|
||||
[home-assitant-theme-docs]: https://www.home-assistant.io/integrations/frontend/#defining-themes
|
||||
[hacs]: https://hacs.xyz
|
||||
[mushroom]: https://github.com/piitaya/lovelace-mushroom
|
||||
[mushroom-chips]: https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/chips.md
|
||||
[mini-graph]: https://github.com/kalkih/mini-graph-card
|
||||
[webrtc]: https://github.com/AlexxIT/WebRTC
|
||||
[balloobBattery]: https://gist.github.com/balloob/4a70c83287ddba4e9085cb578ffb161f
|
||||
[release-url]: https://github.com/AalianKhan/mushroom-strategy/releases
|
||||
|
1336
dist/mushroom-strategy.js
vendored
1336
dist/mushroom-strategy.js
vendored
File diff suppressed because one or more lines are too long
1335
package-lock.json
generated
Normal file
1335
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
package.json
Normal file
32
package.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"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.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"build-dev": "webpack --config webpack.dev.config.js"
|
||||
}
|
||||
}
|
447
src/Helper.js
Normal file
447
src/Helper.js
Normal file
@ -0,0 +1,447 @@
|
||||
import {optionDefaults} from "./optionDefaults";
|
||||
|
||||
/**
|
||||
* Helper Class
|
||||
*
|
||||
* Contains the objects of Home Assistant's registries and helper methods.
|
||||
*/
|
||||
class Helper {
|
||||
/**
|
||||
* An array of entities from Home Assistant's entity registry.
|
||||
*
|
||||
* @type {hassEntity[]}
|
||||
* @private
|
||||
*/
|
||||
static #entities;
|
||||
/**
|
||||
* An array of entities from Home Assistant's device registry.
|
||||
*
|
||||
* @type {deviceEntity[]}
|
||||
* @private
|
||||
*/
|
||||
static #devices;
|
||||
/**
|
||||
* An array of entities from Home Assistant's area registry.
|
||||
*
|
||||
* @type {areaEntity[]}
|
||||
* @private
|
||||
*/
|
||||
static #areas = [];
|
||||
/**
|
||||
* An array of state entities from Home Assistant's Hass object.
|
||||
*
|
||||
* @type {hassObject["states"]}
|
||||
* @private
|
||||
*/
|
||||
static #hassStates;
|
||||
|
||||
/**
|
||||
* Indicates whether this module is initialized.
|
||||
*
|
||||
* @type {boolean} True if initialized.
|
||||
* @private
|
||||
*/
|
||||
static #initialized = false;
|
||||
|
||||
/**
|
||||
* The Custom strategy configuration.
|
||||
*
|
||||
* @type {customStrategyOptions | {}}
|
||||
* @private
|
||||
*/
|
||||
static #strategyOptions = {};
|
||||
|
||||
/**
|
||||
* Set to true for more verbose information in the console.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
static debug = optionDefaults.debug;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* This class shouldn't be instantiated directly. Instead, it should be initialized with method initialize().
|
||||
* @throws {Error} If trying to instantiate this class.
|
||||
*/
|
||||
constructor() {
|
||||
throw new Error("This class should be invoked with method initialize() instead of using the keyword new!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom strategy configuration.
|
||||
*
|
||||
* @returns {customStrategyOptions|{}}
|
||||
* @static
|
||||
*/
|
||||
static get strategyOptions() {
|
||||
return this.#strategyOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {areaEntity[]}
|
||||
* @static
|
||||
*/
|
||||
static get areas() {
|
||||
return this.#areas;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {deviceEntity[]}
|
||||
* @static
|
||||
*/
|
||||
static get devices() {
|
||||
return this.#devices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {hassEntity[]}
|
||||
* @static
|
||||
*/
|
||||
static get entities() {
|
||||
return this.#entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
* @static
|
||||
*/
|
||||
static get debug() {
|
||||
return this.debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this module.
|
||||
*
|
||||
* @param {dashBoardInfo | viewInfo} info Strategy information object.
|
||||
* @returns {Promise<void>}
|
||||
* @static
|
||||
*/
|
||||
static async initialize(info) {
|
||||
this.#hassStates = info.hass.states;
|
||||
|
||||
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"}),
|
||||
]);
|
||||
} catch (e) {
|
||||
console.error(Helper.debug ? e : "An error occurred while querying Home assistant's registries!");
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (!this.#strategyOptions.areas.undisclosed?.hidden) {
|
||||
this.#strategyOptions.areas.undisclosed = {
|
||||
...optionDefaults.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;
|
||||
|
||||
this.#areas.push(this.#strategyOptions.areas.undisclosed);
|
||||
}
|
||||
|
||||
// Merge custom areas of the strategy options into hass areas.
|
||||
this.#areas = Helper.areas.map(area => {
|
||||
return {...area, ...this.#strategyOptions.areas[area.area_id ?? "undisclosed"]};
|
||||
});
|
||||
|
||||
// Sort hass 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.
|
||||
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);
|
||||
}),
|
||||
);
|
||||
|
||||
// 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.
|
||||
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);
|
||||
}),
|
||||
);
|
||||
|
||||
this.#initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the initialization status of the Helper class.
|
||||
*
|
||||
* @returns {boolean} True if this module is initialized.
|
||||
* @static
|
||||
*/
|
||||
static isInitialized() {
|
||||
return this.#initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a template string to define the number of a given domain's entities with a certain state.
|
||||
*
|
||||
* States are compared against a given value by a given operator.
|
||||
*
|
||||
* @param {string} domain The domain of the entities.
|
||||
* @param {string} operator The Comparison operator between state and value.
|
||||
* @param {string} value The value to which the state is compared against.
|
||||
*
|
||||
* @return {string} The template string.
|
||||
* @static
|
||||
*/
|
||||
static getCountTemplate(domain, operator, value) {
|
||||
// noinspection JSMismatchedCollectionQueryUpdate (False positive per 17-04-2023)
|
||||
/**
|
||||
* Array of entity state-entries, filtered by domain.
|
||||
*
|
||||
* Each element contains a template-string which is used to access home assistant's state machine (state object) in
|
||||
* a template.
|
||||
* E.g. "states['light.kitchen']"
|
||||
*
|
||||
* The array excludes hidden and disabled entities.
|
||||
*
|
||||
* @type {string[]}
|
||||
*/
|
||||
const states = [];
|
||||
|
||||
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.
|
||||
for (const area of this.#areas) {
|
||||
const areaDeviceIds = this.#devices.filter(device => {
|
||||
return device.area_id === area.area_id;
|
||||
}).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}']`);
|
||||
|
||||
states.push(...newStates);
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* The entity registry is a registry where Home-Assistant keeps track of all entities.
|
||||
* A device is represented in Home Assistant via one or more entities.
|
||||
*
|
||||
* The result excludes hidden and disabled entities.
|
||||
*
|
||||
* @param {areaEntity} area Area entity.
|
||||
* @param {string} domain The domain of the entity-id.
|
||||
*
|
||||
* @return {hassEntity[]} Array of device entities.
|
||||
* @static
|
||||
*/
|
||||
static getDeviceEntities(area, domain) {
|
||||
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 => {
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get state entities, filtered by area and domain.
|
||||
*
|
||||
* The result excludes hidden and disabled entities.
|
||||
*
|
||||
* @param {areaEntity} area Area entity.
|
||||
* @param {string} domain Domain of the entity-id.
|
||||
*
|
||||
* @return {stateObject[]} Array of state entities.
|
||||
*/
|
||||
static getStateEntities(area, domain) {
|
||||
if (!this.isInitialized()) {
|
||||
console.warn("Helper class should be initialized before calling this method!");
|
||||
}
|
||||
|
||||
const states = [];
|
||||
|
||||
// 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]));
|
||||
|
||||
// Get states whose entity-id starts with the given string.
|
||||
const stateEntities = Object.values(this.#hassStates).filter(
|
||||
state => state.entity_id.startsWith(`${domain}.`),
|
||||
);
|
||||
|
||||
for (const state of stateEntities) {
|
||||
const hassEntity = entityMap[state.entity_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)
|
||||
) {
|
||||
states.push(state);
|
||||
}
|
||||
}
|
||||
|
||||
return states;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a classname.
|
||||
*
|
||||
* The name is sanitized by capitalizing the first character of the name or after an underscore.
|
||||
* Underscores are removed.
|
||||
*
|
||||
* @param {string} className Name of the class to sanitize.
|
||||
* @returns {string} The sanitized classname.
|
||||
*/
|
||||
static sanitizeClassName(className) {
|
||||
className = className.charAt(0).toUpperCase() + className.slice(1);
|
||||
|
||||
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() {
|
||||
if (!this.isInitialized()) {
|
||||
console.warn("Helper class should be initialized before calling this method!");
|
||||
}
|
||||
|
||||
return this.#getObjectKeysByPropertyValue(this.#strategyOptions.views, "hidden", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ids of the domain ids which aren't set to hidden in the strategy options.
|
||||
*
|
||||
* @return {string[]} An array of domain ids.
|
||||
*/
|
||||
static getExposedDomainIds() {
|
||||
if (!this.isInitialized()) {
|
||||
console.warn("Helper class should be initialized before calling this method!");
|
||||
}
|
||||
|
||||
return this.#getObjectKeysByPropertyValue(this.#strategyOptions.domains, "hidden", false);
|
||||
}
|
||||
}
|
||||
|
||||
export {Helper};
|
81
src/cards/AbstractCard.js
Normal file
81
src/cards/AbstractCard.js
Normal file
@ -0,0 +1,81 @@
|
||||
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};
|
56
src/cards/AreaCard.js
Normal file
56
src/cards/AreaCard.js
Normal file
@ -0,0 +1,56 @@
|
||||
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;
|
||||
|
||||
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};
|
41
src/cards/BinarySensorCard.js
Normal file
41
src/cards/BinarySensorCard.js
Normal file
@ -0,0 +1,41 @@
|
||||
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};
|
38
src/cards/CameraCard.js
Normal file
38
src/cards/CameraCard.js
Normal file
@ -0,0 +1,38 @@
|
||||
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: "custom:webrtc-camera",
|
||||
};
|
||||
|
||||
/**
|
||||
* 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};
|
46
src/cards/ClimateCard.js
Normal file
46
src/cards/ClimateCard.js
Normal file
@ -0,0 +1,46 @@
|
||||
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};
|
42
src/cards/CoverCard.js
Normal file
42
src/cards/CoverCard.js
Normal file
@ -0,0 +1,42 @@
|
||||
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};
|
42
src/cards/FanCard.js
Normal file
42
src/cards/FanCard.js
Normal file
@ -0,0 +1,42 @@
|
||||
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};
|
52
src/cards/LightCard.js
Normal file
52
src/cards/LightCard.js
Normal file
@ -0,0 +1,52 @@
|
||||
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,
|
||||
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};
|
49
src/cards/MediaPlayerCard.js
Normal file
49
src/cards/MediaPlayerCard.js
Normal file
@ -0,0 +1,49 @@
|
||||
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",
|
||||
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};
|
39
src/cards/MiscellaneousCard.js
Normal file
39
src/cards/MiscellaneousCard.js
Normal file
@ -0,0 +1,39 @@
|
||||
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};
|
42
src/cards/PersonCard.js
Normal file
42
src/cards/PersonCard.js
Normal file
@ -0,0 +1,42 @@
|
||||
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};
|
42
src/cards/SensorCard.js
Normal file
42
src/cards/SensorCard.js
Normal file
@ -0,0 +1,42 @@
|
||||
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/SwitchCard.js
Normal file
42
src/cards/SwitchCard.js
Normal file
@ -0,0 +1,42 @@
|
||||
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};
|
101
src/cards/TitleCard.js
Normal file
101
src/cards/TitleCard.js
Normal file
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 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};
|
137
src/cards/typedefs.js
Normal file
137
src/cards/typedefs.js
Normal file
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* @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} 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} 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
|
||||
*/
|
||||
|
35
src/chips/ClimateChip.js
Normal file
35
src/chips/ClimateChip.js
Normal file
@ -0,0 +1,35 @@
|
||||
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: "thermostats",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export {ClimateChip};
|
35
src/chips/CoverChip.js
Normal file
35
src/chips/CoverChip.js
Normal file
@ -0,0 +1,35 @@
|
||||
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};
|
43
src/chips/FanChip.js
Normal file
43
src/chips/FanChip.js
Normal file
@ -0,0 +1,43 @@
|
||||
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};
|
43
src/chips/LightChip.js
Normal file
43
src/chips/LightChip.js
Normal file
@ -0,0 +1,43 @@
|
||||
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};
|
43
src/chips/SwitchChip.js
Normal file
43
src/chips/SwitchChip.js
Normal file
@ -0,0 +1,43 @@
|
||||
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};
|
25
src/chips/WeatherChip.js
Normal file
25
src/chips/WeatherChip.js
Normal file
@ -0,0 +1,25 @@
|
||||
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};
|
246
src/mushroom-strategy.js
Normal file
246
src/mushroom-strategy.js
Normal file
@ -0,0 +1,246 @@
|
||||
import {Helper} from "./Helper";
|
||||
import {SensorCard} from "./cards/SensorCard";
|
||||
import {TitleCard} from "./cards/TitleCard";
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* <br>
|
||||
* Features:<br>
|
||||
* 🛠 Automatically create dashboard with 3 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 {
|
||||
/**
|
||||
* Generate a dashboard.
|
||||
*
|
||||
* Called when opening a dashboard.
|
||||
*
|
||||
* @param {dashBoardInfo} info Dashboard strategy information object.
|
||||
* @return {Promise<{views: Object[]}>}
|
||||
*/
|
||||
static async generateDashboard(info) {
|
||||
await Helper.initialize(info);
|
||||
|
||||
// Create views.
|
||||
const views = [];
|
||||
|
||||
let viewModule;
|
||||
|
||||
// Create a view for each exposed domain.
|
||||
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);
|
||||
|
||||
} catch (e) {
|
||||
console.error(Helper.debug ? e : `View '${viewId}' couldn't be loaded!`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create subviews for each area.
|
||||
for (let area of Helper.areas) {
|
||||
if (!area.hidden) {
|
||||
views.push({
|
||||
title: area.name,
|
||||
path: area.area_id ?? area.name,
|
||||
subview: true,
|
||||
strategy: {
|
||||
type: "custom:mushroom-strategy",
|
||||
options: {
|
||||
area,
|
||||
"entity_config": Helper.strategyOptions.entity_config,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add custom views.
|
||||
if (Helper.strategyOptions.extra_views) {
|
||||
views.push(...Helper.strategyOptions.extra_views);
|
||||
}
|
||||
|
||||
// Return the created views.
|
||||
return {
|
||||
views: views,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a view.
|
||||
*
|
||||
* Called when opening a subview.
|
||||
*
|
||||
* @param {viewInfo} info The view's strategy information object.
|
||||
* @return {Promise<{cards: Object[]}>}
|
||||
*/
|
||||
static async generateView(info) {
|
||||
const exposedDomainIds = Helper.getExposedDomainIds();
|
||||
const area = info.view.strategy.options.area;
|
||||
const viewCards = [...(area.extra_cards ?? [])];
|
||||
const strategyOptions = {
|
||||
entityConfig: info.view.strategy.options.entity_config,
|
||||
};
|
||||
|
||||
// Create cards for each domain.
|
||||
for (const domain of exposedDomainIds) {
|
||||
if (domain === "default") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const className = Helper.sanitizeClassName(domain + "Card");
|
||||
|
||||
let domainCards = [];
|
||||
|
||||
try {
|
||||
domainCards = await import(`./cards/${className}`).then(cardModule => {
|
||||
let domainCards = [];
|
||||
const entities = Helper.getDeviceEntities(area, domain);
|
||||
|
||||
if (entities.length) {
|
||||
// Create a Title card for the current domain.
|
||||
const titleCard = new TitleCard(
|
||||
[area],
|
||||
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 = [];
|
||||
|
||||
for (const sensor of entities) {
|
||||
let card = (strategyOptions.entityConfig?.find(config => config.entity_id === sensor.entity_id));
|
||||
|
||||
if (card) {
|
||||
sensorCards.push(card);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the state of the current sensor.
|
||||
const sensorState = sensorStates.find(state => state.entity_id === sensor.entity_id);
|
||||
let cardOptions = {};
|
||||
|
||||
if (sensorState?.attributes.unit_of_measurement) {
|
||||
cardOptions = {
|
||||
type: "custom:mini-graph-card",
|
||||
entities: [sensor.entity_id],
|
||||
};
|
||||
}
|
||||
|
||||
sensorCards.push(new SensorCard(sensor, cardOptions).getCard());
|
||||
}
|
||||
|
||||
domainCards.push({
|
||||
type: "vertical-stack",
|
||||
cards: sensorCards,
|
||||
});
|
||||
|
||||
domainCards.unshift(titleCard);
|
||||
return domainCards;
|
||||
}
|
||||
|
||||
// Create a card for each domain-entity of the current area.
|
||||
for (const entity of entities) {
|
||||
const card = (Helper.strategyOptions.entity_config ?? []).find(
|
||||
config => config.entity === entity.entity_id,
|
||||
) ?? new cardModule[className](entity).getCard();
|
||||
|
||||
domainCards.push(card);
|
||||
}
|
||||
|
||||
if (domain === "binary_sensor") {
|
||||
// Horizontally group every two binary sensor cards.
|
||||
const horizontalCards = [];
|
||||
|
||||
for (let i = 0; i < domainCards.length; i += 2) {
|
||||
horizontalCards.push({
|
||||
type: "horizontal-stack",
|
||||
cards: domainCards.slice(i, i + 2),
|
||||
});
|
||||
}
|
||||
|
||||
domainCards = horizontalCards;
|
||||
}
|
||||
|
||||
domainCards.unshift(titleCard);
|
||||
}
|
||||
|
||||
return domainCards;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(Helper.debug ? e : "An error occurred while creating the domain cards!");
|
||||
}
|
||||
|
||||
if (domainCards.length) {
|
||||
viewCards.push({
|
||||
type: "vertical-stack",
|
||||
cards: domainCards,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 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,
|
||||
// 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]);
|
||||
});
|
||||
|
||||
// Create a column of miscellaneous entity cards.
|
||||
if (miscellaneousEntities.length) {
|
||||
let miscellaneousCards = [];
|
||||
|
||||
try {
|
||||
miscellaneousCards = await import("./cards/MiscellaneousCard").then(cardModule => {
|
||||
/** @type Object[] */
|
||||
const miscellaneousCards = [
|
||||
new TitleCard([area], Helper.strategyOptions.domains.default).createCard(),
|
||||
];
|
||||
for (const entity of miscellaneousEntities) {
|
||||
const card = (Helper.strategyOptions.entity_config ?? []).find(
|
||||
config => config.entity === entity.entity_id,
|
||||
) ?? new cardModule.MiscellaneousCard(entity).getCard();
|
||||
|
||||
miscellaneousCards.push(card);
|
||||
}
|
||||
|
||||
return miscellaneousCards;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(Helper.debug ? e : "An error occurred while creating the domain cards!");
|
||||
}
|
||||
|
||||
viewCards.push({
|
||||
type: "vertical-stack",
|
||||
cards: miscellaneousCards,
|
||||
});
|
||||
}
|
||||
|
||||
// Return cards.
|
||||
return {
|
||||
cards: viewCards,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// noinspection JSUnresolvedReference
|
||||
customElements.define("ll-strategy-mushroom-strategy", MushroomStrategy);
|
110
src/optionDefaults.js
Normal file
110
src/optionDefaults.js
Normal file
@ -0,0 +1,110 @@
|
||||
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,
|
||||
}
|
||||
},
|
||||
areas: {
|
||||
undisclosed: {
|
||||
aliases: [],
|
||||
area_id: null,
|
||||
name: "Undisclosed",
|
||||
picture: null,
|
||||
hidden: false,
|
||||
}
|
||||
},
|
||||
domains: {
|
||||
default: {
|
||||
title: "Miscellaneous",
|
||||
showControls: false,
|
||||
hidden: false,
|
||||
},
|
||||
light: {
|
||||
title: "Lights",
|
||||
showControls: true,
|
||||
iconOn: "mdi:lightbulb",
|
||||
iconOff: "mdi:lightbulb-off",
|
||||
onService: "light.turn_on",
|
||||
offService: "light.turn_off",
|
||||
hidden: false,
|
||||
},
|
||||
fan: {
|
||||
title: "Fans",
|
||||
showControls: true,
|
||||
iconOn: "mdi:fan",
|
||||
iconOff: "mdi:fan-off",
|
||||
onService: "fan.turn_on",
|
||||
offService: "fan.turn_off",
|
||||
hidden: false,
|
||||
},
|
||||
cover: {
|
||||
title: "Covers",
|
||||
showControls: true,
|
||||
iconOn: "mdi:arrow-up",
|
||||
iconOff: "mdi:arrow-down",
|
||||
onService: "cover.open_cover",
|
||||
offService: "cover.close_cover",
|
||||
hidden: false,
|
||||
},
|
||||
switch: {
|
||||
title: "Switches",
|
||||
showControls: true,
|
||||
iconOn: "mdi:power-plug",
|
||||
iconOff: "mdi:power-plug-off",
|
||||
onService: "switch.turn_on",
|
||||
offService: "switch.turn_off",
|
||||
hidden: false,
|
||||
},
|
||||
camera: {
|
||||
title: "Cameras",
|
||||
showControls: false,
|
||||
hidden: false,
|
||||
},
|
||||
climate: {
|
||||
title: "Climates",
|
||||
showControls: false,
|
||||
hidden: false,
|
||||
},
|
||||
media_player: {
|
||||
title: "Media Players",
|
||||
showControls: false,
|
||||
hidden: false,
|
||||
},
|
||||
sensor: {
|
||||
title: "Sensors",
|
||||
showControls: false,
|
||||
hidden: false,
|
||||
},
|
||||
binary_sensor: {
|
||||
title: "Binary Sensors",
|
||||
showControls: false,
|
||||
hidden: false,
|
||||
},
|
||||
}
|
||||
}
|
247
src/typedefs.js
Normal file
247
src/typedefs.js
Normal file
@ -0,0 +1,247 @@
|
||||
/**
|
||||
* @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.
|
||||
* @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[]} [entity_config] Card definition 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.
|
||||
* @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} entityConfig Custom card-configuration for an entity on a view card.
|
||||
* @property {string} entity The id of the entity to create a card for.
|
||||
* @property {string} type Type of card for the entity
|
||||
* @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 {};
|
121
src/views/AbstractView.js
Normal file
121
src/views/AbstractView.js
Normal file
@ -0,0 +1,121 @@
|
||||
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.
|
||||
*/
|
||||
createViewCards() {
|
||||
/** @type Object[] */
|
||||
const viewCards = [this.viewTitleCard];
|
||||
|
||||
// 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");
|
||||
|
||||
import((`../cards/${className}`)).then(cardModule => {
|
||||
if (entities.length) {
|
||||
// Create a Title card for the current area.
|
||||
areaCards.push(
|
||||
new TitleCard([area], {
|
||||
title: area.name,
|
||||
...this.options["titleCard"],
|
||||
}).createCard(),
|
||||
);
|
||||
|
||||
// Create a card for each domain-entity of the current area.
|
||||
for (const entity of entities) {
|
||||
const card = (Helper.strategyOptions.entity_config ?? []).find(
|
||||
config => config.entity === entity.entity_id,
|
||||
) ?? new cardModule[className](entity).getCard();
|
||||
|
||||
areaCards.push(card);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
viewCards.push({
|
||||
type: "vertical-stack",
|
||||
cards: areaCards,
|
||||
});
|
||||
}
|
||||
|
||||
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};
|
70
src/views/CameraView.js
Normal file
70
src/views/CameraView.js
Normal file
@ -0,0 +1,70 @@
|
||||
import {Helper} from "../Helper";
|
||||
import {TitleCard} from "../cards/TitleCard";
|
||||
import {AbstractView} from "./AbstractView";
|
||||
|
||||
/**
|
||||
* Camera View Class.
|
||||
*
|
||||
* Used to create a view for entities of the camera domain.
|
||||
*
|
||||
* @class CameraView
|
||||
* @extends AbstractView
|
||||
*/
|
||||
class CameraView extends AbstractView {
|
||||
/**
|
||||
* Domain of the view's entities.
|
||||
* @type {string}
|
||||
*/
|
||||
#domain = "camera";
|
||||
|
||||
/**
|
||||
* Default options for the view.
|
||||
*
|
||||
* @type {viewOptions}
|
||||
* @private
|
||||
*/
|
||||
#defaultOptions = {
|
||||
title: "Cameras",
|
||||
path: "cameras",
|
||||
icon: "mdi:cctv",
|
||||
subview: false,
|
||||
titleCard: {
|
||||
showControls: false,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for the view's title card.
|
||||
*
|
||||
* @type {viewTitleCardOptions}
|
||||
*/
|
||||
#viewTitleCardOption = {
|
||||
title: "All Cameras",
|
||||
...this.options["titleCard"],
|
||||
};
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param {viewOptions} [options={}] Options for the view.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.mergeOptions(
|
||||
this.#defaultOptions,
|
||||
options,
|
||||
);
|
||||
|
||||
// Create a title card to switch all entities of the domain.
|
||||
this.viewTitleCard = new TitleCard(Helper.areas, {
|
||||
...this.#viewTitleCardOption,
|
||||
...this.options["titleCard"],
|
||||
}).createCard();
|
||||
}
|
||||
|
||||
get domain() {
|
||||
return this.#domain;
|
||||
}
|
||||
}
|
||||
|
||||
export {CameraView};
|
71
src/views/ClimateView.js
Normal file
71
src/views/ClimateView.js
Normal file
@ -0,0 +1,71 @@
|
||||
import {Helper} from "../Helper";
|
||||
import {TitleCard} from "../cards/TitleCard";
|
||||
import {AbstractView} from "./AbstractView";
|
||||
|
||||
/**
|
||||
* Climate View Class.
|
||||
*
|
||||
* Used to create a view for entities of the climate domain.
|
||||
*
|
||||
* @class ClimateView
|
||||
* @extends AbstractView
|
||||
*/
|
||||
class ClimateView extends AbstractView {
|
||||
/**
|
||||
* Domain of the view's entities.
|
||||
* @type {string}
|
||||
*/
|
||||
#domain = "climate";
|
||||
|
||||
/**
|
||||
* Default options for the view.
|
||||
*
|
||||
* @type {viewOptions}
|
||||
* @private
|
||||
*/
|
||||
#defaultOptions = {
|
||||
title: "Climates",
|
||||
path: "climates",
|
||||
icon: "mdi:thermostat",
|
||||
subview: false,
|
||||
titleCard: {
|
||||
showControls: false,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for the view's title card.
|
||||
*
|
||||
* @type {viewTitleCardOptions}
|
||||
*/
|
||||
#viewTitleCardOption = {
|
||||
title: "All Climates",
|
||||
subtitle: Helper.getCountTemplate(this.domain, "ne", "off") + " climates on",
|
||||
...this.options["titleCard"],
|
||||
};
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param {viewOptions} [options={}] Options for the view.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.mergeOptions(
|
||||
this.#defaultOptions,
|
||||
options,
|
||||
);
|
||||
|
||||
// Create a title card to switch all entities of the domain.
|
||||
this.viewTitleCard = new TitleCard(Helper.areas, {
|
||||
...this.#viewTitleCardOption,
|
||||
...this.options["titleCard"],
|
||||
}).createCard();
|
||||
}
|
||||
|
||||
get domain() {
|
||||
return this.#domain;
|
||||
}
|
||||
}
|
||||
|
||||
export {ClimateView};
|
73
src/views/CoverView.js
Normal file
73
src/views/CoverView.js
Normal file
@ -0,0 +1,73 @@
|
||||
import {Helper} from "../Helper";
|
||||
import {TitleCard} from "../cards/TitleCard";
|
||||
import {AbstractView} from "./AbstractView";
|
||||
|
||||
/**
|
||||
* Cover View Class.
|
||||
*
|
||||
* Used to create a view for entities of the cover domain.
|
||||
*
|
||||
* @class CoverView
|
||||
* @extends AbstractView
|
||||
*/
|
||||
class CoverView extends AbstractView {
|
||||
/**
|
||||
* Domain of the view's entities.
|
||||
* @type {string}
|
||||
*/
|
||||
#domain = "cover";
|
||||
|
||||
/**
|
||||
* Default options for the view.
|
||||
*
|
||||
* @type {viewOptions}
|
||||
* @private
|
||||
*/
|
||||
#defaultOptions = {
|
||||
title: "Covers",
|
||||
path: "covers",
|
||||
icon: "mdi:window-open",
|
||||
subview: false,
|
||||
titleCard: {
|
||||
iconOn: "mdi:arrow-up",
|
||||
iconOff: "mdi:arrow-down",
|
||||
onService: "cover.open_cover",
|
||||
offService: "cover.close_cover",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for the view's title card.
|
||||
*
|
||||
* @type {viewTitleCardOptions}
|
||||
*/
|
||||
#viewTitleCardOption = {
|
||||
title: "All Covers",
|
||||
subtitle: Helper.getCountTemplate(this.domain, "eq", "open") + " covers open",
|
||||
};
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param {viewOptions} [options={}] Options for the view.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.mergeOptions(
|
||||
this.#defaultOptions,
|
||||
options,
|
||||
);
|
||||
|
||||
// Create a title card to switch all entities of the domain.
|
||||
this.viewTitleCard = new TitleCard(Helper.areas, {
|
||||
...this.#viewTitleCardOption,
|
||||
...this.options["titleCard"],
|
||||
}).createCard();
|
||||
}
|
||||
|
||||
get domain() {
|
||||
return this.#domain;
|
||||
}
|
||||
}
|
||||
|
||||
export {CoverView};
|
73
src/views/FanView.js
Normal file
73
src/views/FanView.js
Normal file
@ -0,0 +1,73 @@
|
||||
import {Helper} from "../Helper";
|
||||
import {TitleCard} from "../cards/TitleCard";
|
||||
import {AbstractView} from "./AbstractView";
|
||||
|
||||
/**
|
||||
* Fan View Class.
|
||||
*
|
||||
* Used to create a view for entities of the fan domain.
|
||||
*
|
||||
* @class FanView
|
||||
* @extends AbstractView
|
||||
*/
|
||||
class FanView extends AbstractView {
|
||||
/**
|
||||
* Domain of the view's entities.
|
||||
* @type {string}
|
||||
*/
|
||||
#domain = "fan";
|
||||
|
||||
/**
|
||||
* Default options for the view.
|
||||
*
|
||||
* @type {viewOptions}
|
||||
* @private
|
||||
*/
|
||||
#defaultOptions = {
|
||||
title: "Fans",
|
||||
path: "fans",
|
||||
icon: "mdi:fan",
|
||||
subview: false,
|
||||
titleCard: {
|
||||
iconOn: "mdi:fan",
|
||||
iconOff: "mdi:fan-off",
|
||||
onService: "fan.turn_on",
|
||||
offService: "fan.turn_off",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for the view's title card.
|
||||
*
|
||||
* @type {viewTitleCardOptions}
|
||||
*/
|
||||
#viewTitleCardOption = {
|
||||
title: "All Fans",
|
||||
subtitle: Helper.getCountTemplate(this.domain, "eq", "on") + " fans on",
|
||||
};
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param {viewOptions} [options={}] Options for the view.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.mergeOptions(
|
||||
this.#defaultOptions,
|
||||
options,
|
||||
);
|
||||
|
||||
// Create a title card to switch all entities of the domain.
|
||||
this.viewTitleCard = new TitleCard(Helper.areas, {
|
||||
...this.#viewTitleCardOption,
|
||||
...this.options["titleCard"],
|
||||
}).createCard();
|
||||
}
|
||||
|
||||
get domain() {
|
||||
return this.#domain;
|
||||
}
|
||||
}
|
||||
|
||||
export {FanView};
|
205
src/views/HomeView.js
Normal file
205
src/views/HomeView.js
Normal file
@ -0,0 +1,205 @@
|
||||
import {Helper} from "../Helper";
|
||||
import {AbstractView} from "./AbstractView";
|
||||
|
||||
/**
|
||||
* Home View Class.
|
||||
*
|
||||
* Used to create a Home view.
|
||||
*
|
||||
* @class HomeView
|
||||
* @extends AbstractView
|
||||
*/
|
||||
class HomeView extends AbstractView {
|
||||
/**
|
||||
* Default options for the view.
|
||||
*
|
||||
* @type {viewOptions}
|
||||
* @private
|
||||
*/
|
||||
#defaultOptions = {
|
||||
title: "Home",
|
||||
path: "home",
|
||||
subview: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param {viewOptions} [options={}] Options for the view.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.mergeOptions(
|
||||
this.#defaultOptions,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the cards to include in the view.
|
||||
*
|
||||
* @return {Promise} A promise of a card object array.
|
||||
* @override
|
||||
*/
|
||||
async createViewCards() {
|
||||
return await Promise.all([
|
||||
this.#createChips(),
|
||||
this.#createPersonCards(),
|
||||
this.#createAreaCards(),
|
||||
]).then(([chips, personCards, areaCards]) => {
|
||||
const options = Helper.strategyOptions;
|
||||
const homeViewCards = [
|
||||
{
|
||||
type: "custom:mushroom-chips-card",
|
||||
alignment: "center",
|
||||
chips: chips,
|
||||
},
|
||||
{
|
||||
type: "horizontal-stack",
|
||||
cards: personCards,
|
||||
},
|
||||
{
|
||||
type: "custom:mushroom-template-card",
|
||||
primary: "{% set time = now().hour %} {% if (time >= 18) %} Good Evening, {{user}}! {% elif (time >= 12) %} Good Afternoon, {{user}}! {% elif (time >= 5) %} Good Morning, {{user}}! {% else %} Hello, {{user}}! {% endif %}",
|
||||
icon: "mdi:hand-wave",
|
||||
icon_color: "orange",
|
||||
tap_action: {
|
||||
action: "none",
|
||||
},
|
||||
double_tap_action: {
|
||||
action: "none",
|
||||
},
|
||||
hold_action: {
|
||||
action: "none",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Add quick access cards.
|
||||
if (options.quick_access_cards) {
|
||||
homeViewCards.push(...options.quick_access_cards);
|
||||
}
|
||||
|
||||
// Add area cards.
|
||||
homeViewCards.push({
|
||||
type: "custom:mushroom-title-card",
|
||||
title: "Areas",
|
||||
},
|
||||
{
|
||||
type: "vertical-stack",
|
||||
cards: areaCards,
|
||||
});
|
||||
|
||||
// Add custom cards.
|
||||
if (options.extra_cards) {
|
||||
homeViewCards.push(...options.extra_cards);
|
||||
}
|
||||
|
||||
return homeViewCards;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the chips to include in the view.
|
||||
*
|
||||
* @return {Object[]} A chip object array.
|
||||
*/
|
||||
async #createChips() {
|
||||
const chips = [];
|
||||
const chipOptions = Helper.strategyOptions.chips;
|
||||
|
||||
// TODO: Get domains from config.
|
||||
const exposed_chips = ["light", "fan", "cover", "switch", "climate"];
|
||||
// Create a list of area-ids, used for switching all devices via chips
|
||||
const areaIds = Helper.areas.map(area => area.area_id);
|
||||
|
||||
let chipModule;
|
||||
|
||||
// Weather chip.
|
||||
const weatherEntityId = chipOptions?.weather_entity ?? Helper.entities.find(
|
||||
entity => entity.entity_id.startsWith("weather.") && entity.disabled_by == null && entity.hidden_by == null,
|
||||
).entity_id;
|
||||
|
||||
if (weatherEntityId) {
|
||||
try {
|
||||
chipModule = await import("../chips/WeatherChip");
|
||||
const weatherChip = new chipModule.WeatherChip(weatherEntityId);
|
||||
chips.push(weatherChip.getChip());
|
||||
} catch (e) {
|
||||
console.error(Helper.debug ? e : "An error occurred while creating the weather chip!");
|
||||
}
|
||||
}
|
||||
|
||||
// Numeric chips.
|
||||
for (let chipType of exposed_chips) {
|
||||
if (chipOptions?.[`${chipType}_count`] ?? true) {
|
||||
const className = Helper.sanitizeClassName(chipType + "Chip");
|
||||
try {
|
||||
chipModule = await import((`../chips/${className}`));
|
||||
const chip = new chipModule[className](areaIds);
|
||||
chips.push(chip.getChip());
|
||||
} catch (e) {
|
||||
console.error(Helper.debug ? e : `An error occurred while creating the ${chipType} chip!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extra chips.
|
||||
if (chipOptions?.extra_chips) {
|
||||
chips.push(...chipOptions.extra_chips);
|
||||
}
|
||||
|
||||
return chips;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the person cards to include in the view.
|
||||
*
|
||||
* @return {Object[]} A card object array.
|
||||
*/
|
||||
#createPersonCards() {
|
||||
const cards = [];
|
||||
|
||||
import("../cards/PersonCard").then(personModule => {
|
||||
for (const person of Helper.entities.filter(entity => entity.entity_id.startsWith("person."))) {
|
||||
cards.push(new personModule.PersonCard(person).getCard());
|
||||
}
|
||||
});
|
||||
|
||||
return cards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the area cards to include in the view.
|
||||
*
|
||||
* Area cards are grouped into two areas per row.
|
||||
*
|
||||
* @return {Object[]} A card object array.
|
||||
*/
|
||||
#createAreaCards() {
|
||||
const groupedCards = [];
|
||||
|
||||
import("../cards/AreaCard").then(areaModule => {
|
||||
const areaCards = [];
|
||||
|
||||
for (const area of Helper.areas) {
|
||||
if (!Helper.strategyOptions.areas[area.area_id]?.hidden) {
|
||||
areaCards.push(
|
||||
new areaModule.AreaCard(area, Helper.strategyOptions.areas[area.area_id ?? "undisclosed"]).getCard());
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontally group every two area cards.
|
||||
for (let i = 0; i < areaCards.length; i += 2) {
|
||||
groupedCards.push({
|
||||
type: "horizontal-stack",
|
||||
cards: areaCards.slice(i, i + 2),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return groupedCards;
|
||||
}
|
||||
}
|
||||
|
||||
export {HomeView};
|
73
src/views/LightView.js
Normal file
73
src/views/LightView.js
Normal file
@ -0,0 +1,73 @@
|
||||
import {Helper} from "../Helper";
|
||||
import {TitleCard} from "../cards/TitleCard";
|
||||
import {AbstractView} from "./AbstractView";
|
||||
|
||||
/**
|
||||
* Light View Class.
|
||||
*
|
||||
* Used to create a view for entities of the light domain.
|
||||
*
|
||||
* @class LightView
|
||||
* @extends AbstractView
|
||||
*/
|
||||
class LightView extends AbstractView {
|
||||
/**
|
||||
* Domain of the view's entities.
|
||||
* @type {string}
|
||||
*/
|
||||
#domain = "light";
|
||||
|
||||
/**
|
||||
* Default options for the view.
|
||||
*
|
||||
* @type {viewOptions}
|
||||
* @private
|
||||
*/
|
||||
#defaultOptions = {
|
||||
title: "Lights",
|
||||
path: "lights",
|
||||
icon: "mdi:lightbulb-group",
|
||||
subview: false,
|
||||
titleCard: {
|
||||
iconOn: "mdi:lightbulb",
|
||||
iconOff: "mdi:lightbulb-off",
|
||||
onService: "light.turn_on",
|
||||
offService: "light.turn_off",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for the view's title card.
|
||||
*
|
||||
* @type {viewTitleCardOptions}
|
||||
*/
|
||||
#viewTitleCardOption = {
|
||||
title: "All Lights",
|
||||
subtitle: Helper.getCountTemplate(this.domain, "eq", "on") + " lights on",
|
||||
};
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param {viewOptions} [options={}] Options for the view.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.mergeOptions(
|
||||
this.#defaultOptions,
|
||||
options,
|
||||
);
|
||||
|
||||
// Create a title card to switch all entities of the domain.
|
||||
this.viewTitleCard = new TitleCard(Helper.areas, {
|
||||
...this.#viewTitleCardOption,
|
||||
...this.options["titleCard"],
|
||||
}).createCard();
|
||||
}
|
||||
|
||||
get domain() {
|
||||
return this.#domain;
|
||||
}
|
||||
}
|
||||
|
||||
export {LightView};
|
73
src/views/SwitchView.js
Normal file
73
src/views/SwitchView.js
Normal file
@ -0,0 +1,73 @@
|
||||
import {Helper} from "../Helper";
|
||||
import {TitleCard} from "../cards/TitleCard";
|
||||
import {AbstractView} from "./AbstractView";
|
||||
|
||||
/**
|
||||
* Switch View Class.
|
||||
*
|
||||
* Used to create a view for entities of the switch domain.
|
||||
*
|
||||
* @class SwitchView
|
||||
* @extends AbstractView
|
||||
*/
|
||||
class SwitchView extends AbstractView {
|
||||
/**
|
||||
* Domain of the view's entities.
|
||||
* @type {string}
|
||||
*/
|
||||
#domain = "switch";
|
||||
|
||||
/**
|
||||
* Default options for the view.
|
||||
*
|
||||
* @type {viewOptions}
|
||||
* @private
|
||||
*/
|
||||
#defaultOptions = {
|
||||
title: "Switches",
|
||||
path: "switches",
|
||||
icon: "mdi:dip-switch",
|
||||
subview: false,
|
||||
titleCard: {
|
||||
iconOn: "mdi:power-plug",
|
||||
iconOff: "mdi:power-plug-off",
|
||||
onService: "switch.turn_on",
|
||||
offService: "switch.turn_off",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for the view's title card.
|
||||
*
|
||||
* @type {viewTitleCardOptions}
|
||||
*/
|
||||
#viewTitleCardOption = {
|
||||
title: "All Switches",
|
||||
subtitle: Helper.getCountTemplate(this.domain, "eq", "on") + " switches on",
|
||||
};
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param {viewOptions} [options={}] Options for the view.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.mergeOptions(
|
||||
this.#defaultOptions,
|
||||
options,
|
||||
);
|
||||
|
||||
// Create a title card to switch all entities of the domain.
|
||||
this.viewTitleCard = new TitleCard(Helper.areas, {
|
||||
...this.#viewTitleCardOption,
|
||||
...this.options["titleCard"],
|
||||
}).createCard();
|
||||
}
|
||||
|
||||
get domain() {
|
||||
return this.#domain;
|
||||
}
|
||||
}
|
||||
|
||||
export {SwitchView};
|
38
src/views/typedefs.js
Normal file
38
src/views/typedefs.js
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @namespace typedefs.views
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} abstractOptions Options to create a view.
|
||||
* @property {string} [title] The title or name.
|
||||
* @property {string} [path] Paths are used in the URL.
|
||||
* @property {string} [icon] The icon of the view.
|
||||
* @property {boolean} subview Mark the view as “Subview”.
|
||||
* @memberOf typedefs.views
|
||||
* @see https://www.home-assistant.io/dashboards/views/
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {abstractOptions & Object} viewOptions Options for the extended View class.
|
||||
* @property {titleCardOptions} [titleCard] Options for the title card of the view.
|
||||
* @memberOf typedefs.views
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} titleCardOptions Options for the title card of the view.
|
||||
* @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 Service to call for switching entities from off state.
|
||||
* @property {string} offService Service to call for switching entities to off state.
|
||||
* @memberOf typedefs.views
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} viewTitleCardOptions Options for the view's title card.
|
||||
* @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.
|
||||
* @memberOf typedefs.views
|
||||
*/
|
||||
|
||||
export {};
|
17
webpack.config.js
Normal file
17
webpack.config.js
Normal file
@ -0,0 +1,17 @@
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
|
||||
module.exports = {
|
||||
mode: "production",
|
||||
entry: "./src/mushroom-strategy.js",
|
||||
output: {
|
||||
filename: "mushroom-strategy.js",
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
clean: true,
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 1,
|
||||
}),
|
||||
],
|
||||
};
|
20
webpack.dev.config.js
Normal file
20
webpack.dev.config.js
Normal file
@ -0,0 +1,20 @@
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
devtool: "source-map",
|
||||
entry: "./src/mushroom-strategy.js",
|
||||
output: {
|
||||
filename: "mushroom-strategy.js",
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 1,
|
||||
}),
|
||||
],
|
||||
};
|
Reference in New Issue
Block a user