diff --git a/.editorconfig b/.editorconfig index ad22e8c..d7968e1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,19 +1,19 @@ [*] charset = utf-8 end_of_line = lf -indent_size = 4 -indent_style = space -insert_final_newline = true -max_line_length = 120 -tab_width = 4 -trim_trailing_whitespace = true -ij_continuation_indent_size = 8 +ij_continuation_indent_size = 2 ij_formatter_off_tag = @formatter:off ij_formatter_on_tag = @formatter:on ij_formatter_tags_enabled = true ij_smart_tabs = false ij_visual_guides = ij_wrap_on_typing = false +indent_size = 2 +indent_style = space +insert_final_newline = true +max_line_length = 120 +tab_width = 2 +trim_trailing_whitespace = true [.editorconfig] ij_editorconfig_align_group_field_declarations = false @@ -43,7 +43,6 @@ ij_xml_space_inside_empty_tag = false ij_xml_text_wrap = normal [{*.ats,*.cts,*.mts,*.ts}] -ij_continuation_indent_size = 4 ij_typescript_align_imports = false ij_typescript_align_multiline_array_initializer_expression = false ij_typescript_align_multiline_binary_operation = false @@ -55,7 +54,7 @@ ij_typescript_align_multiline_parameters_in_calls = false ij_typescript_align_multiline_ternary_operation = false ij_typescript_align_object_properties = 0 ij_typescript_align_union_types = false -ij_typescript_align_var_statements = 0 +ij_typescript_align_var_statements = 1 ij_typescript_array_initializer_new_line_after_left_brace = false ij_typescript_array_initializer_right_brace_on_new_line = false ij_typescript_array_initializer_wrap = off @@ -64,7 +63,7 @@ ij_typescript_binary_operation_sign_on_next_line = false ij_typescript_binary_operation_wrap = off ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** ij_typescript_blank_lines_after_imports = 1 -ij_typescript_blank_lines_around_class = 1 +ij_typescript_blank_lines_around_class = 0 ij_typescript_blank_lines_around_field = 0 ij_typescript_blank_lines_around_field_in_interface = 0 ij_typescript_blank_lines_around_function = 1 @@ -112,7 +111,7 @@ ij_typescript_keep_blank_lines_in_code = 2 ij_typescript_keep_first_column_comment = true ij_typescript_keep_indents_on_empty_lines = false ij_typescript_keep_line_breaks = true -ij_typescript_keep_simple_blocks_in_one_line = false +ij_typescript_keep_simple_blocks_in_one_line = true ij_typescript_keep_simple_methods_in_one_line = false ij_typescript_line_comment_add_space = true ij_typescript_line_comment_at_first_column = false @@ -132,7 +131,7 @@ ij_typescript_prefer_explicit_types_function_returns = false ij_typescript_prefer_explicit_types_vars_fields = false ij_typescript_prefer_parameters_wrap = false ij_typescript_property_prefix = -ij_typescript_reformat_c_style_comments = false +ij_typescript_reformat_c_style_comments = true ij_typescript_space_after_colon = true ij_typescript_space_after_comma = true ij_typescript_space_after_dots_in_rest_parameter = false @@ -207,6 +206,7 @@ ij_typescript_union_types_wrap = on_every_item ij_typescript_use_chained_calls_group_indents = false ij_typescript_use_double_quotes = true ij_typescript_use_explicit_js_extension = auto +ij_typescript_use_import_type = auto ij_typescript_use_path_mapping = always ij_typescript_use_public_modifier = false ij_typescript_use_semicolon_after_statement = true @@ -215,20 +215,7 @@ ij_typescript_while_brace_force = never ij_typescript_while_on_new_line = false ij_typescript_wrap_comments = false -[{*.bash,*.sh,*.zsh}] -indent_size = 2 -tab_width = 2 -ij_shell_binary_ops_start_line = false -ij_shell_keep_column_alignment_padding = false -ij_shell_minify_program = false -ij_shell_redirect_followed_by_space = false -ij_shell_switch_cases_indented = false -ij_shell_use_unix_line_separator = true - [{*.cjs,*.js}] -indent_size = 2 -tab_width = 2 -ij_continuation_indent_size = 2 ij_javascript_align_imports = false ij_javascript_align_multiline_array_initializer_expression = false ij_javascript_align_multiline_binary_operation = false @@ -397,7 +384,6 @@ ij_javascript_while_on_new_line = false ij_javascript_wrap_comments = false [{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.remarkrc,.stylelintrc,bowerrc,composer.lock,jest.config}] -indent_size = 2 ij_json_array_wrapping = split_into_lines ij_json_keep_blank_lines_in_code = 0 ij_json_keep_indents_on_empty_lines = false @@ -460,7 +446,6 @@ ij_markdown_wrap_text_if_long = true ij_markdown_wrap_text_inside_blockquotes = true [{*.yaml,*.yml}] -indent_size = 2 ij_yaml_align_values_properties = do_not_align ij_yaml_autoinsert_sequence_marker = true ij_yaml_block_mapping_on_new_line = false diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..e69de29 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd3f53e..19a857b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,27 +7,28 @@ assignees: '' --- -**Describe the bug** +**Describe the bug** A clear and concise description of what the bug is. -**Versions** +**Versions** * Mushroom-Strategy: * HACS: * Mushroom: * Home Assistant: -**To Reproduce** +**To Reproduce** Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' + +1. Go to '…' +2. Click on '…' +3. Scroll down to '…' 4. See error -**Expected behavior** +**Expected behavior** A clear and concise description of what you expected to happen. -**Screenshots** +**Screenshots** If applicable, add screenshots to help explain your problem. -**Additional context** +**Additional context** Add any other context about the problem here. diff --git a/.github/workflows/webpack.yml b/.github/workflows/webpack.yml index 0a4bd28..7a4f05b 100644 --- a/.github/workflows/webpack.yml +++ b/.github/workflows/webpack.yml @@ -4,7 +4,7 @@ on: push: branches: [ "main" ] # Ignore changes in folders that are affected by the auto commit. (Node.js project) - paths-ignore: + paths-ignore: - 'dist/**' # pull_request: # branches: [ "main" ] @@ -24,7 +24,7 @@ jobs: steps: - uses: actions/checkout@v3 with: - token: ${{ secrets.WORKFLOW_GIT_ACCESS_TOKEN }} + token: ${{ secrets.WORKFLOW_GIT_ACCESS_TOKEN }} # Build steps - name: Use Node.js ${{ matrix.node-version }} @@ -34,7 +34,7 @@ jobs: - name: Node Install run: npm ci - + - name: Build Distribution run: | npm run build @@ -44,10 +44,10 @@ jobs: run: | git diff --quiet . || echo "changed=true" >> $GITHUB_OUTPUT - # Commit and push all changed files. + # Commit and push all changed files. # Must only affect files that are listed in "paths-ignore". - name: GIT Commit Distribution Build - # Only run on main branch push (e.g. pull request merge). + # Only run on main branch push (e.g., pull request merge). if: github.event_name == 'push' && steps.checkDiff.outputs.changed == 'true' run: | git config --global user.name "${{ env.CI_COMMIT_AUTHOR }}" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index ca70b0f..ed5bcf4 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -3,16 +3,17 @@ ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make -participation in our project and our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, -socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. +participation in our project and our community a harassment-free experience for everyone. +This is regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socioeconomic status, nationality, personal appearance, race, religion, or +sexual identity and orientation. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people. -* Being respectful of differing opinions, viewpoints and experiences. +* Being respectful of differing opinions, viewpoints, and experiences. * Giving and gracefully accepting constructive feedback. * Accepting responsibility and apologizing to those affected by our mistakes and learning from the experience. * Focusing on what is best not just for us as individuals, but for the overall community. @@ -28,18 +29,18 @@ Examples of unacceptable behavior include: ## Our Responsibilities Project maintainers are responsible for clarifying and enforcing our standards of acceptable behavior and will take -appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive +appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, -issues and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation +issues, and other contributions that are not aligned to this Code of Conduct and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting -via an official social media account or acting as an appointed representative at an online or offline event. +via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement @@ -80,8 +81,8 @@ enforcing the Code of Conduct, is allowed during this period. Violating these te ### 4. Permanent Ban -**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate -behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. +**Community Impact**: Demonstrating violations of community standards, including sustained inappropriate +behavior, harassment of an individual, or aggression toward or disparagement of their class. **Consequence**: A permanent ban from any sort of public interaction within the community. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c405c53..54c7a7f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ It will make it a lot easier for us maintainers and smooth out the experience fo The community looks forward to your contributions. > And if you like the project, but just don't have time to contribute, that's fine. -> There are other easy ways to support the project and show your appreciation, which we would also be very happy about: +> There are other easy ways to support the project and show your appreciation, which we would also be thrilled about: > - Star the project > - Tweet about it > - Refer this project in your project's readme @@ -58,14 +58,14 @@ We will then take care of the issue as soon as possible. > ### Legal Notice > > When contributing to this project, you must agree that you have authored 100% of the content, that you have the -> necessary rights to the content and that the content you contribute may be provided under the project license. +> necessary rights to the content, and that the content you contribute may be provided under the project license. ### Reporting Bugs #### Before Submitting a Bug Report A good bug report shouldn't leave others needing to chase you up for more information. -Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. +Therefore, we ask you to investigate carefully, collect information, and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. - Make sure that you are using the latest version. @@ -87,7 +87,7 @@ Please complete the following steps in advance to help us fix any potential bug #### How Do I Submit a Good Bug Report? -> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue +> You must never report security related issues, vulnerabilities, or bugs including sensitive information to the issue > tracker, or elsewhere in public. > Instead, sensitive bugs must be sent by email to . diff --git a/README.md b/README.md index 4589e95..256af07 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,28 @@ # Mushroom dashboard strategy -[![hacs][hacsBadge]][hacsUrl] [![release][releaseBadge]][releaseUrl] +[![hacs][hacsBadge]][hacsUrl] ![Preview GIF](./docs/preview.gif) +
+ More images... + ![Automatic](./docs/auto.png) ![Views](./docs/views.png) ![customizable](./docs/customizable.png) +
-## 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. +## What is the Mushroom Dashboard Strategy? -My goal is to propose a way to create powerful dashboards without the need of spending hours manually creating them. +Mushroom Dashboard Strategy provides a strategy for Home Assistant to automatically generate a dashboard using Mushroom +cards. -**Note:** This is my first javascript code and GitHub repository. Any recommendations are always welcome. +It generates cards for your Home Assistants entities and areas, divided over several views. +Besides a Home view from where you can enter a subview of your areas, a separate view for lights, fans and other domains +are generated for easy access to your entities. ### Features @@ -26,555 +30,53 @@ My goal is to propose a way to create powerful dashboards without the need of sp - 😍 Built-in Views for device-specific controls. - 🎨 Many options to customize to fit your needs. -## Installation +> [!TIP] +> If you like this package, please star the [project at GitHub](https://github.com/AalianKhan/mushroom-strategy)! 🌟 -### Prerequisites +## Getting started -You need to install these cards before using this strategy: +The strategy is easily installable from [HACS][hacsUrl] (Home Assistant Community Store). +Please visit [Installation Guide](https://github.com/AalianKhan/mushroom-strategy/wiki/#installation) at our Wiki. -- [Mushroom cards][mushroomUrl] -- [Mini graph card][mini-graphUrl] +## Need some help? -### HACS +Visit the [discussions](https://github.com/AalianKhan/mushroom-strategy/discussions) page. -Mushroom dashboard strategy is available in [HACS][hacsUrl] (Home Assistant Community Store). +## Have an idea or want to report a bug? -1. Install HACS if you don't have it already. -2. Open HACS in Home Assistant. -3. Go to the "Frontend" section. -4. Click the button with the "+" icon -5. Search for "Mushroom dashboard" and install. +Make sure your idea or bug isn't discussed already in our Discussions or Issues! +Visit the [issues](https://github.com/AalianKhan/mushroom-strategy/issues/new/choose) page. -### Manual -1. Download `mushroom-strategy.js` file from - the [`dist`](https://github.com/AalianKhan/mushroom-strategy/tree/main/dist) directory. -2. Put `mushroom-strategy.js` file into your `config/www` folder. -3. Add a reference to `mushroom-strategy.js` in Dashboard. - There are two ways to do that: - - **Using UI:** _Settings_ → _Dashboards_ → _More Options icon_ → _Resources_ → _Add Resource_ → Set _Url_ - as `/local/mushroom-strategy.js` → Set _Resource type_ as `JavaScript Module`. - **Note:** If you do not see the Resources menu, you will need to enable _Advanced Mode_ in your _User Profile_ - - **Using YAML:** Add the following code to the `lovelace` section. - ```yaml - resources: - - url: /local/mushroom-strategy.js - type: module - ``` +## Contributors -## Usage +* [DigiLive](https://github.com/DigiLive) -All the rounded cards can be configured using the Dashboard UI editor. - -1. In the UI of the dashboard, click the three dots in the top right corner. -2. Click _Edit Dashboard_. -3. Click 3 dots again -4. Click `Raw configuration editor` -5. Add the following lines: - -```yaml -strategy: - type: custom:mushroom-strategy -views: [] -``` - -### Hidding specific entities - -When creating this dashboard for the first time, you probably have many entities that you don't want to see. - -You can hide these entities by following the steps below: - -1. Click and hold the entity -2. Click the `cog icon` in the top right corner of the popup. -3. Set `Visible` to `off`. - -The view should update when the page is refreshed. - -If you don't want to hide the entity from all dashboards, you can use [Card Options](#card-options) to hide specific -entities and devices. - -![Views](./docs/Hidden.png) - -### Adding devices to areas - -You can add devices to an area by going to `Settings` found at the bottom of the sidebar. - -1. Click `Devices and integration` -2. Select the integration of your device -3. Click the device you wish to add -4. Click the `pencil icon` found in the top right corner -5. Enter an area in area field. - -You can also set an entity of that device to a different area by going to the advanced settings of that entity. - -If you created an entity in your `configuration.yaml` you may need to enter a `unique_id` first before you set an area -to it. -See [docs](https://www.home-assistant.io/faq/unique_id/) - -## Strategy options - -You can set strategy options to further customize the dashboard. -By default, all views are enabled which include lights, fans, covers, switches, climates and cameras. All chips are also -enabled which count the number of devices on for the platforms light, fan, cover and climate. It also auto-selects a -weather entity for the weather chip. - -The options available are: - -| Name | Type | Default | Description | -|:---------------------|:--------------------------|:--------------------------------------------------------|:-----------------------------------------------------------------------| -| `areas` | object (optional) | unset | One or more areas in a list, see [areas object](#area-object). | -| `card_options` | object (optional) | unset | Card options for cards, see [Card Options](#card-options). | -| `views` | object (optional) | All default views | See available [Pre-built views](#pre-built-views). | -| `chips` | object | All count chips enabled with auto selected weather card | See [chips](#chips). | -| `quick_access_cards` | array of cards (optional) | unset | List of cards to show between welcome card and rooms cards. | -| `extra_cards` | array of cards (optional | unset | List of cards to show below room cards. | -| `extra_views` | array of views (optional) | unset | List of views to add to the dashboard. | -| `domains` | object (optional) | All supported domains | See [Supported domains](#supported-domains). | -| `homeView` | object (optional) | unset | Options for the home view, see [Home View Options](#home-view-options) | - -#### Example - -```yaml -strategy: - type: custom:mushroom-strategy - options: - areas: - family_room_id: - name: Family Room - icon: mdi:sofa - icon_color: green -views: [] -``` - -### Area Object - -The area object includes all options from the template mushroom card and `extra_cards` which is a list of cards to show -at the top of the area subview. - -| Name | Type | Default | Description | -|:----------------------|:------------------|:---------------|:------------------------------------------------------------------------------------------------------------------------------------| -| `name` | string | N.A. | The name of the area. | -| `icon` | string (optional) | unset or empty | Icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `icon_color` | string (optional) | unset or empty | Icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `primary` | string (optional) | unset or empty | Primary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `secondary` | string (optional) | unset or empty | Secondary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `badge_icon` | string (optional) | unset or empty | Badge icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `badge_color` | string (optional) | unset or empty | Badge icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `picture` | string (optional) | unset or empty | Picture to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `multiline_secondary` | boolean | `false` | Enables support for multiline text for the secondary info. | -| `layout` | string (optional) | unset or empty | Layout of the card. Vertical, horizontal and default layout are supported. | -| `fill_container` | boolean | `false` | Fill container or not. Useful when card is in a grid, vertical or horizontal layout. | -| `tap_action` | action* | `none` | Home assistant action to perform on tap. | -| `hold_action` | action* | `none` | Home assistant action to perform on hold. | -| `entity_id` | `string` `array` | unset or empty | Only reacts to the state changes of these entities. This can be used if the automatic analysis fails to find all relevant entities. | -| `double_tap_action` | action* | `more-info` | Home assistant action to perform on double_tap. | -| `hidden` | boolean | false | Set to `true` to exclude the area from the dashboard and views. | -| `order` | number | Infinity | Ordering position of the area in the list of available areas. | -| `extra_cards` | array of cards | unset or empty | A list of cards to show on the top of the area subview. | -| `type` | string | `default` | Set to a type of area card. (Currently supported: `default` & `HaAreaCard` | - -*) `more-info` `toggle` `call-service` `navigate` `url` `none` - -#### Example - -```yaml -strategy: - type: custom:mushroom-strategy - options: - areas: - family_room_id: - name: Family Room - icon: mdi:television - icon_color: green - order: 1 - extra_cards: - - type: custom:mushroom-chips-card - chips: - - type: entity - entity: sensor.family_room_temperature - icon: mdi:thermometer - icon_color: pink - alignment: center - kitchen_id: - name: Kitchen - icon: mdi:silverware-fork-knife - icon_color: red - order: 2 - garage_id: - hidden: true - hallway_id: - type: HaAreaCard -views: [] -``` - -#### Undisclosed Area - -The strategy has a special area, named `undisclosed`. -This area is enabled by default and includes the entities that aren't linked to any Home Assistant area. - -The area can be configured like any other area as described above. -To exclude this area from the dashboard and views, set its property `hidden` to `true`. - -#### Setting options for all areas - -Use `_` as an identifier to set the options for all areas. - -The following example sets the type of all area-cards to Home Assistant's area card: - -```yaml -strategy: - type: custom:mushroom-strategy - options: - areas: - _: - type: HaAreaCard -views: [] -``` - -### Card Options - -The `card_options` entry enables you to specify a card type for an entity or to hide the card from the dashboard. -You can also provide a device ID and hide all entities linked to that device. -See [Instructions on to find a device ID](https://community.home-assistant.io/t/device-id-entity-id-where-to-find/289230/4?u=aaliankhan). - -#### Example - -```yaml -strategy: - type: custom:mushroom-strategy - options: - card_options: - fan.master_bedroom_fan: - type: custom:mushroom-fan-card - remote.harmony_hub_wk: - hidden: true - 077ba0492c9bb3b31ffac34f1f3a626a: - hidden: true - -views: [] -``` - -### Pre-built views - -![Light Views](./docs/light_view.png) - -Mushroom strategy includes pre-built views to control/view specific domains. -All devices that are in an area where `hidden` is set to false/undefined are shown*. - -By default, all pre-built views below are shown: - -| Available views | type | Description | -|:----------------|:--------|:-----------------------------------------------------------------------------| -| `light` | object* | View to control all lights and lights of each area. | -| `fan` | object* | View to control all fans and fans of each area. | -| `cover` | object* | View to control all covers and covers of each area. | -| `switch` | object* | View to control all switches and switches of each area. | -| `climate` | object* | View to control climate devices such as thermostats. Seperated by each area. | -| `camera` | object* | View to show all cameras using WebRTC cards. Seperated by each area. | - -* See [View Options](#view-options). - -#### View Options - -For each of the pre-built views, the following options are available: - -| name | type | description | -|:---------|:--------|:----------------------------------------------------------------------------------------------| -| `title` | string | Title of the view in the navigation bar. (Shown when no icon is defined or hovering above it. | -| `icon` | string | Icon of the view in the navigation bar. | -| `order` | string | Ordering position of the view in the navigation bar. | -| `hidden` | boolean | Set to `true` to exclude the view from the dashboard | - -#### Example - -```yaml -strategy: - type: custom:mushroom-strategy - options: - views: - light: - order: 0 - title: illumination - switch: - order: 1 - hidden: true - icon: mdi:toggle-switch -views: [] -``` - -### Supported domains - -The following domains are supported and enabled by default: - -* light -* fan -* cover -* switch -* camera -* lock -* climate -* media_player -* sensor -* binary_sensor -* number -* default (Miscellaneous) - -For these domains, the following options are supported: - -| Option | type | Description | -|:---------------|:--------|:--------------------------------------------------------------------------| -| `title` | string | Title of the domain in a view. | -| `showControls` | boolean | Weather to show controls in a view, to switch all entities of the domain. | -| `hidden` | boolean | Set to `true` to exclude the domain from the dashboard. | -| `order` | number | Ordering position of the domain entities in a view. | - -#### Example - -```YAML -strategy: - type: custom:mushroom-strategy - options: - domains: - lights: - title: "My cool lights" - order: 1 - switch: - showControls: false - default: - hidden: true -views: [] -``` - -### Home View Options - -Home View options will let you configure the Home View. - -| Option | type | Description | -|:---------|:------|:----------------------------------------------| -| `hidden` | array | Array of elements to hide from the home view. | - -#### hidden - -The following elements are supported: -* chips -* persons -* greeting -* areasTitle -* areas - -#### Example - -```YAML -strategy: - type: custom:mushroom-strategy - options: - homeView: - hidden: - - greeting - - areasTitle -views: [] -``` - -### Chips - -![Chips](./docs/chips.png) - -Mushroom strategy has chips that indicate the number of devices which are active for a specific domain. -All devices that are in an area where `hidden` is set to false/undefined are counted. -By default, all chips are enabled. - -You can manually configure a weather entity-id to use, and there's also an option to add -more [Mushroom Chips][mushroom-chipsUrl] using `extra_chips`. - -**Note: To hide the weather chip, you should hide or disable the entity itself.** - -| Available chips | type | Description | -|:-----------------|:------------------|:---------------------------------------------------------------------------------------------------------------| -| `light_count` | boolean | Chip to display the number of lights on, tapping turns off all lights, holding navigates to lights view. | -| `fan_count` | boolean | Chip to display the number of fans on, tapping turns off all fans, holding navigates to fans view. | -| `cover_count` | boolean | Chip to display the number of covers not closed, tapping navigates to covers view. | -| `switch_count` | boolean | Chip to display the number of switches on, tapping turns off all switches, holding navigates to switches view. | -| `climate_count` | boolean | Chip to display the number of climate not off, tapping navigates to climates view. | -| `weather_entity` | string (optional) | Entity ID for the weather chip to use, accepts `weather.` only. | -| `extra_chips` | array (optional) | List of extra chips to display, see [Mushroom Chips][mushroom-chipsUrl]. | - -#### Example - -```yaml -strategy: - type: custom:mushroom-strategy - options: - chips: - climate_count: false - cover_count: false - weather_entity: weather.forecast_home - extra_chips: - - type: conditional - conditions: - - entity: lock.front_door - state: unlocked - chip: - type: entity - entity: lock.front_door - icon_color: red - content_info: none - tap_action: - action: toggle -``` - -## Full Example - -```yaml -strategy: - type: custom:mushroom-strategy - options: - views: - light: - title: illumination - switches: - hidden: true - icon: mdi:toggle-switch - homeView: - hidden: - - Greeting - - AreaTitle - chips: - weather_entity: weather.forecast_home - climate_count: false - cover_count: false - extra_chips: - - type: conditional - conditions: - - entity: lock.front_door - state: unlocked - chip: - type: entity - entity: lock.front_door - icon_color: red - content_info: none - icon: '' - use_entity_picture: false - tap_action: - action: toggle - - type: conditional - conditions: - - entity: cover.garage_door - state_not: closed - chip: - type: entity - entity: cover.garage_door - icon_color: red - content_info: none - tap_action: - action: toggle - areas: - family_room_id: - name: Family Room - icon: mdi:television - icon_color: green - extra_cards: - - type: custom:mushroom-chips-card - chips: - - type: entity - entity: sensor.family_room_temperature - icon: mdi:thermometer - icon_color: pink - alignment: center - kitchen_id: - name: Kitchen - icon: mdi:silverware-fork-knife - icon_color: red - master_bedroom_id: - name: Master Bedroom - icon: mdi:bed-king - icon_color: blue - abias_bedroom_id: - name: Abia's Bedroom - icon: mdi:flower-tulip - icon_color: green - aalians_bedroom_id: - name: Aalian's Bedroom - icon: mdi:rocket-launch - icon_color: yellow - rohaans_bedroom_id: - name: Rohaan's Bedroom - icon: mdi:controller - icon_color: red - hallway_id: - name: Hallway - living_room_id: - name: Living Room - icon: mdi:sofa - front_door_id: - name: Front Door - icon: mdi:door-closed - card_options: - fan.master_bedroom_fan: - type: custom:mushroom-fan-card - remote.harmony_hub_wk: - hidden: true - quick_access_cards: - - type: custom:mushroom-title-card - title: Security - - type: custom:mushroom-cover-card - entity: cover.garage_door - show_buttons_control: true - - type: horizontal-stack - cards: - - type: custom:mushroom-lock-card - entity: lock.front_door - - type: custom:mushroom-entity-card - entity: sensor.front_door_lock_battery - name: Battery - extra_cards: - - type: custom:xiaomi-vacuum-map-card - map_source: - camera: camera.xiaomi_cloud_map_extractor - calibration_source: - camera: true - entity: vacuum.robot_vacuum - vacuum_platform: default - extra_views: - - theme: Backend-selected - title: cool view - path: cool-view - icon: mdi:emoticon-cool - badges: [] - cards: - - type: markdown - content: I am cool -views: [] -``` + [![Sponsor DigiLive][sponsorBadge]](https://github.com/sponsors/DigiLive) -## Credits +* [Johan Frick](https://github.com/johanfrick) -* The cards used are from [Mushroom][mushroomUrl], [Mini graph card][mini-graphUrl] and [WebRTC][webRtcUrl] -* Took inspiration from [Balloob battery strategy][balloobBatteryUrl] +## Credits -## Contributors +* The cards used are from [Mushroom][mushroomUrl] and [Mini graph card][miniGraphUrl]. +* Took inspiration from [Balloob battery strategy][balloobBatteryUrl]. -* [DigiLive](https://github.com/DigiLive) -* [Johan Frick](https://github.com/johanfrick) + - +[hacsBadge]: https://img.shields.io/badge/HACS-Default-blue -[hacsBadge]: https://img.shields.io/badge/HACS-Default-41BDF5.svg +[sponsorBadge]: https://img.shields.io/badge/Sponsor_him-%E2%9D%A4-%23db61a2.svg?&logo=github&color=%23fe8e86 -[releaseBadge]: https://img.shields.io/github/v/release/AalianKhan/mushroom-strategy?include_prereleases +[releaseBadge]: https://img.shields.io/badge/Release-v2.0.0-blue [hacsUrl]: https://hacs.xyz -[releaseUrl]: https://github.com/AalianKhan/mushroom-strategy/releases +[releaseUrl]: https://github.com/AalianKhan/mushroom-strategy/releases/tag/v2.0.0 [mushroomUrl]: https://github.com/piitaya/lovelace-mushroom -[mushroom-chipsUrl]: https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/chips.md - -[mini-graphUrl]: https://github.com/kalkih/mini-graph-card - -[webRtcUrl]: https://github.com/AlexxIT/WebRTC +[miniGraphUrl]: https://github.com/kalkih/mini-graph-card [balloobBatteryUrl]: https://gist.github.com/balloob/4a70c83287ddba4e9085cb578ffb161f - diff --git a/dist/mushroom-strategy.js b/dist/mushroom-strategy.js index 4b733e9..167120f 100644 --- a/dist/mushroom-strategy.js +++ b/dist/mushroom-strategy.js @@ -1 +1 @@ -(()=>{var t,e,i={84:(t,e,i)=>{"use strict";i.d(e,{W:()=>o});const s={home:{order:1,hidden:!1},light:{order:2,hidden:!1},fan:{order:3,hidden:!1},cover:{order:4,hidden:!1},switch:{order:5,hidden:!1},climate:{order:6,hidden:!1},camera:{order:7,hidden:!1}},r={aliases:[],area_id:null,name:"Undisclosed",picture:null,hidden:!1},a={default:{title:"Miscellaneous",showControls:!1,hidden:!1},light:{title:"Lights",showControls:!0,iconOn:"mdi:lightbulb",iconOff:"mdi:lightbulb-off",onService:"light.turn_on",offService:"light.turn_off",hidden:!1},fan:{title:"Fans",showControls:!0,iconOn:"mdi:fan",iconOff:"mdi:fan-off",onService:"fan.turn_on",offService:"fan.turn_off",hidden:!1},cover:{title:"Covers",showControls:!0,iconOn:"mdi:arrow-up",iconOff:"mdi:arrow-down",onService:"cover.open_cover",offService:"cover.close_cover",hidden:!1},switch:{title:"Switches",showControls:!0,iconOn:"mdi:power-plug",iconOff:"mdi:power-plug-off",onService:"switch.turn_on",offService:"switch.turn_off",hidden:!1},camera:{title:"Cameras",showControls:!1,hidden:!1},lock:{title:"Locks",showControls:!1,hidden:!1},climate:{title:"Climates",showControls:!1,hidden:!1},media_player:{title:"Media Players",showControls:!1,hidden:!1},sensor:{title:"Sensors",showControls:!1,hidden:!1},binary_sensor:{title:"Binary Sensors",showControls:!1,hidden:!1},number:{title:"Numbers",showControls:!1,hidden:!1}};class o{static#t;static#e;static#i=[];static#s;static#r=!1;static#a={};static debug=false;constructor(){throw new Error("This class should be invoked with method initialize() instead of using the keyword new!")}static get strategyOptions(){return this.#a}static get areas(){return this.#i}static get devices(){return this.#e}static get entities(){return this.#t}static get debug(){return this.debug}static async initialize(t){this.#s=t.hass.states;try{[this.#t,this.#e,this.#i]=await Promise.all([t.hass.callWS({type:"config/entity_registry/list"}),t.hass.callWS({type:"config/device_registry/list"}),t.hass.callWS({type:"config/area_registry/list"})])}catch(t){console.error(o.debug?t:"An error occurred while querying Home assistant's registries!")}this.#a=structuredClone(t.config.strategy.options||{}),this.debug=this.#a.debug,this.#a.areas=this.#a.areas??{},this.#a.views=this.#a.views??{},this.#a.domains=this.#a.domains??{},this.#a.areas.undisclosed?.hidden||(this.#a.areas.undisclosed={...r,...this.#a.areas.undisclosed},this.#a.areas.undisclosed.area_id=null,this.#i.push(this.#a.areas.undisclosed)),this.#i=o.areas.map((t=>({...t,...this.#a.areas[t.area_id??"undisclosed"]}))),this.#i.sort(((t,e)=>(t.order??1/0)-(e.order??1/0)||t.name.localeCompare(e.name)));for(const t of Object.keys(s))this.#a.views[t]={...s[t],...this.#a.views[t]};this.#a.views=Object.fromEntries(Object.entries(this.#a.views).sort((([,t],[,e])=>(t.order??1/0)-(e.order??1/0)||t.title?.localeCompare(e.title))));for(const t of Object.keys(a))this.#a.domains[t]={...a[t],...this.#a.domains[t]};this.#a.domains=Object.fromEntries(Object.entries(this.#a.domains).sort((([,t],[,e])=>(t.order??1/0)-(e.order??1/0)||t.title?.localeCompare(e.title)))),this.#r=!0}static isInitialized(){return this.#r}static getCountTemplate(t,e,i){const s=[];this.isInitialized()||console.warn("Helper class should be initialized before calling this method!");for(const e of this.#i){const i=this.#e.filter((t=>t.area_id===e.area_id)).map((t=>t.id)),r=this.#t.filter(this.#o,{area:e,domain:t,areaDeviceIds:i}).map((t=>`states['${t.entity_id}']`));s.push(...r)}return`{% set entities = [${s}] %} {{ entities | selectattr('state','${e}','${i}') | list | count }}`}static#o(t){return(this.area.area_id?this.areaDeviceIds.includes(t.device_id)||t.area_id===this.area.area_id:(this.areaDeviceIds.includes(t.device_id)||!t.device_id)&&!t.area_id)&&t.entity_id.startsWith(`${this.domain}.`)&&null==t.hidden_by&&null==t.disabled_by}static getDeviceEntities(t,e){this.isInitialized()||console.warn("Helper class should be initialized before calling this method!");const i=this.#e.filter((e=>e.area_id===t.area_id)).map((t=>t.id));return this.#t.filter(this.#o,{area:t,domain:e,areaDeviceIds:i}).sort(((t,e)=>t.original_name?.localeCompare(e.original_name)))}static getStateEntities(t,e){this.isInitialized()||console.warn("Helper class should be initialized before calling this method!");const i=[],s=Object.fromEntries(this.#t.map((t=>[t.entity_id,t]))),r=Object.fromEntries(this.#e.map((t=>[t.id,t]))),a=Object.values(this.#s).filter((t=>t.entity_id.startsWith(`${e}.`)));for(const e of a){const a=s[e.entity_id],o=r[a?.device_id];(a?.area_id===t.area_id||o&&o.area_id===t.area_id)&&i.push(e)}return i}static sanitizeClassName(t){return(t=t.charAt(0).toUpperCase()+t.slice(1)).replace(/([-_][a-z])/g,(t=>t.toUpperCase().replace("-","").replace("_","")))}static#n(t,e,i){const s=[];for(const r of Object.keys(t))t[r][e]===i&&s.push(r);return s}static getExposedViewIds(){return this.isInitialized()||console.warn("Helper class should be initialized before calling this method!"),this.#n(this.#a.views,"hidden",!1)}static getExposedDomainIds(){return this.isInitialized()||console.warn("Helper class should be initialized before calling this method!"),this.#n(this.#a.domains,"hidden",!1)}}},981:(t,e,i)=>{"use strict";i.r(e),i.d(e,{AbstractCard:()=>r});var s=i(84);class r{entity;options={type:"custom:mushroom-entity-card",icon:"mdi:help-circle",double_tap_action:{action:null}};constructor(t){if(this.constructor===r)throw new Error("Abstract classes can't be instantiated.");if(!s.W.isInitialized())throw new Error("The Helper module must be initialized before using this one.");this.entity=t}mergeOptions(t,e){this.options={...this.options,...t,...e};try{this.options.double_tap_action.target.entity_id=this.entity.entity_id}catch{}}getCard(){return{entity:this.entity.entity_id,...this.options}}}},138:(t,e,i)=>{"use strict";i.r(e),i.d(e,{AreaCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"custom:mushroom-template-card",primary:void 0,icon:"mdi:texture-box",icon_color:"blue",tap_action:{action:"navigate",navigation_path:void 0},hold_action:{action:"none"}};constructor(t,e={}){super(t),this.#d.primary=t.name,this.#d.tap_action.navigation_path=t.area_id??t.name,"default"===e.type&&(e.type=this.#d.type),this.mergeOptions(this.#d,e),!e.primary&&e.name&&(this.options.primary=e.name)}}},917:(t,e,i)=>{"use strict";i.r(e),i.d(e,{BinarySensorCard:()=>r});var s=i(408);class r extends s.SensorCard{#d={type:"custom:mushroom-entity-card",icon:"mdi:power-cycle",icon_color:"green"};constructor(t,e={}){super(t),this.mergeOptions(this.#d,e)}}},497:(t,e,i)=>{"use strict";i.r(e),i.d(e,{CameraCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"picture-entity",show_name:!1,show_state:!1,camera_view:"live"};constructor(t,e={}){super(t),this.mergeOptions(this.#d,e)}}},898:(t,e,i)=>{"use strict";i.r(e),i.d(e,{ClimateCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"custom:mushroom-climate-card",icon:void 0,hvac_modes:["off","cool","heat","fan_only"],show_temperature_control:!0};constructor(t,e={}){super(t),this.mergeOptions(this.#d,e)}}},499:(t,e,i)=>{"use strict";i.r(e),i.d(e,{CoverCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"custom:mushroom-cover-card",icon:void 0,show_buttons_control:!0,show_position_control:!0,show_tilt_position_control:!0};constructor(t,e={}){super(t),this.mergeOptions(this.#d,e)}}},297:(t,e,i)=>{"use strict";i.r(e),i.d(e,{FanCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"custom:mushroom-fan-card",icon:void 0,show_percentage_control:!0,show_oscillate_control:!0,icon_animation:!0};constructor(t,e={}){super(t),this.mergeOptions(this.#d,e)}}},194:(t,e,i)=>{"use strict";i.r(e),i.d(e,{AreaCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"area",area:void 0,navigation_path:void 0};constructor(t,e={}){super(t),this.#d.area=t.area_id??t.name,this.#d.navigation_path=t.area_id??t.name,e.type=this.#d.type,this.mergeOptions(this.#d,e)}}},698:(t,e,i)=>{"use strict";i.r(e),i.d(e,{LightCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"custom:mushroom-light-card",icon:void 0,show_brightness_control:!0,show_color_control:!0,use_light_color:!0,double_tap_action:{target:{entity_id:void 0},action:"call-service",service:"light.turn_on",data:{rgb_color:[255,255,255]}}};constructor(t,e={}){super(t),this.mergeOptions(this.#d,e)}}},315:(t,e,i)=>{"use strict";i.r(e),i.d(e,{LockCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"custom:mushroom-lock-card",icon:void 0};constructor(t,e={}){super(t),this.mergeOptions(this.#d,e)}}},568:(t,e,i)=>{"use strict";i.r(e),i.d(e,{MediaPlayerCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"custom:mushroom-media-player-card",icon:void 0,use_media_info:!0,media_controls:["on_off","play_pause_stop"],show_volume_level:!0,volume_controls:["volume_mute","volume_set","volume_buttons"]};constructor(t,e={}){super(t),this.mergeOptions(this.#d,e)}}},190:(t,e,i)=>{"use strict";i.r(e),i.d(e,{MiscellaneousCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"custom:mushroom-entity-card",icon_color:"blue-grey"};constructor(t,e={}){super(t),this.mergeOptions(this.#d,e)}}},889:(t,e,i)=>{"use strict";i.r(e),i.d(e,{NumberCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"custom:mushroom-number-card",icon:void 0};constructor(t,e={}){super(t),this.mergeOptions(this.#d,e)}}},543:(t,e,i)=>{"use strict";i.r(e),i.d(e,{PersonCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"custom:mushroom-person-card",layout:"vertical",primary_info:"none",secondary_info:"none",icon_type:"entity-picture"};constructor(t,e={}){super(t),this.mergeOptions(this.#d,e)}}},408:(t,e,i)=>{"use strict";i.r(e),i.d(e,{SensorCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"custom:mushroom-entity-card",icon:"mdi:information",animate:!0,line_color:"green"};constructor(t,e={}){super(t),this.mergeOptions(this.#d,e)}}},177:(t,e,i)=>{"use strict";i.r(e),i.d(e,{SwitchCard:()=>r});var s=i(981);class r extends s.AbstractCard{#d={type:"custom:mushroom-entity-card",icon:void 0,tap_action:{action:"toggle"}};constructor(t,e={}){super(t),this.mergeOptions(this.#d,e)}}},402:(t,e,i)=>{"use strict";i.r(e),i.d(e,{TitleCard:()=>s});class s{#c;#l={title:void 0,subtitle:void 0,showControls:!0,iconOn:"mdi:power-on",iconOff:"mdi:power-off",onService:"none",offService:"none"};constructor(t,e={}){this.#c=t.map((t=>t.area_id)).filter((t=>t)),this.#l={...this.#l,...e}}createCard(){const t=[{type:"custom:mushroom-title-card",title:this.#l.title,subtitle:this.#l.subtitle}];return this.#l.showControls&&t.push({type:"horizontal-stack",cards:[{type:"custom:mushroom-template-card",icon:this.#l.iconOff,layout:"vertical",icon_color:"red",tap_action:{action:"call-service",service:this.#l.offService,target:{area_id:this.#c},data:{}}},{type:"custom:mushroom-template-card",icon:this.#l.iconOn,layout:"vertical",icon_color:"amber",tap_action:{action:"call-service",service:this.#l.onService,target:{area_id:this.#c},data:{}}}]}),{type:"horizontal-stack",cards:t}}}},244:()=>{},175:(t,e,i)=>{var s={"./AbstractCard":[981,9],"./AbstractCard.js":[981,9],"./AreaCard":[138,9,179],"./AreaCard.js":[138,9,179],"./BinarySensorCard":[917,9,179],"./BinarySensorCard.js":[917,9,179],"./CameraCard":[497,9,179],"./CameraCard.js":[497,9,179],"./ClimateCard":[898,9,179],"./ClimateCard.js":[898,9,179],"./CoverCard":[499,9,179],"./CoverCard.js":[499,9,179],"./FanCard":[297,9,179],"./FanCard.js":[297,9,179],"./HaAreaCard":[194,9,179],"./HaAreaCard.js":[194,9,179],"./LightCard":[698,9,179],"./LightCard.js":[698,9,179],"./LockCard":[315,9,179],"./LockCard.js":[315,9,179],"./MediaPlayerCard":[568,9,179],"./MediaPlayerCard.js":[568,9,179],"./MiscellaneousCard":[190,9,179],"./MiscellaneousCard.js":[190,9,179],"./NumberCard":[889,9,179],"./NumberCard.js":[889,9,179],"./PersonCard":[543,9,179],"./PersonCard.js":[543,9,179],"./SensorCard":[408,9],"./SensorCard.js":[408,9],"./SwitchCard":[177,9,179],"./SwitchCard.js":[177,9,179],"./TitleCard":[402,9],"./TitleCard.js":[402,9],"./typedefs":[244,7,179],"./typedefs.js":[244,7,179]};function r(t){if(!i.o(s,t))return Promise.resolve().then((()=>{var e=new Error("Cannot find module '"+t+"'");throw e.code="MODULE_NOT_FOUND",e}));var e=s[t],r=e[0];return Promise.all(e.slice(2).map(i.e)).then((()=>i.t(r,16|e[1])))}r.keys=()=>Object.keys(s),r.id=175,t.exports=r},354:(t,e,i)=>{"use strict";i.r(e),i.d(e,{ClimateChip:()=>r});var s=i(84);class r{#c;#l={};constructor(t,e={}){if(!s.W.isInitialized())throw new Error("The Helper module must be initialized before using this one.");this.#c=t.filter((t=>t)),this.#l={...this.#l,...e}}getChip(){return{type:"template",icon:"mdi:thermostat",icon_color:"orange",content:s.W.getCountTemplate("climate","ne","off"),tap_action:{action:"navigate",navigation_path:"climates"},hold_action:{action:"navigate",navigation_path:"climates"}}}}},454:(t,e,i)=>{"use strict";i.r(e),i.d(e,{CoverChip:()=>r});var s=i(84);class r{#c;#l={};constructor(t,e={}){if(!s.W.isInitialized())throw new Error("The Helper module must be initialized before using this one.");this.#c=t.filter((t=>t)),this.#l={...this.#l,...e}}getChip(){return{type:"template",icon:"mdi:window-open",icon_color:"cyan",content:s.W.getCountTemplate("cover","eq","open"),tap_action:{action:"navigate",navigation_path:"covers"}}}}},955:(t,e,i)=>{"use strict";i.r(e),i.d(e,{FanChip:()=>r});var s=i(84);class r{#c;#l={};constructor(t,e={}){if(!s.W.isInitialized())throw new Error("The Helper module must be initialized before using this one.");this.#c=t.filter((t=>t)),this.#l={...this.#l,...e}}getChip(){return{type:"template",icon:"mdi:fan",icon_color:"green",content:s.W.getCountTemplate("fan","eq","on"),tap_action:{action:"call-service",service:"fan.turn_off",target:{area_id:this.#c},data:{}},hold_action:{action:"navigate",navigation_path:"fans"}}}}},980:(t,e,i)=>{"use strict";i.r(e),i.d(e,{LightChip:()=>r});var s=i(84);class r{#c;#l={};constructor(t,e={}){if(!s.W.isInitialized())throw new Error("The Helper module must be initialized before using this one.");this.#c=t.filter((t=>t)),this.#l={...this.#l,...e}}getChip(){return{type:"template",icon:"mdi:lightbulb-group",icon_color:"amber",content:s.W.getCountTemplate("light","eq","on"),tap_action:{action:"call-service",service:"light.turn_off",target:{area_id:this.#c},data:{}},hold_action:{action:"navigate",navigation_path:"lights"}}}}},25:(t,e,i)=>{"use strict";i.r(e),i.d(e,{SwitchChip:()=>r});var s=i(84);class r{#c;#l={};constructor(t,e={}){if(!s.W.isInitialized())throw new Error("The Helper module must be initialized before using this one.");this.#c=t.filter((t=>t)),this.#l={...this.#l,...e}}getChip(){return{type:"template",icon:"mdi:dip-switch",icon_color:"blue",content:s.W.getCountTemplate("switch","eq","on"),tap_action:{action:"call-service",service:"switch.turn_off",target:{area_id:this.#c},data:{}},hold_action:{action:"navigate",navigation_path:"switches"}}}}},369:(t,e,i)=>{"use strict";i.r(e),i.d(e,{WeatherChip:()=>s});class s{#h;#l={show_temperature:!0,show_conditions:!0};constructor(t,e={}){this.#h=t,this.#l={...this.#l,...e}}getChip(){return{type:"weather",entity:this.#h,...this.#l}}}},837:(t,e,i)=>{var s={"./ClimateChip":[354,179],"./ClimateChip.js":[354,179],"./CoverChip":[454,179],"./CoverChip.js":[454,179],"./FanChip":[955,179],"./FanChip.js":[955,179],"./LightChip":[980,179],"./LightChip.js":[980,179],"./SwitchChip":[25,179],"./SwitchChip.js":[25,179],"./WeatherChip":[369,179],"./WeatherChip.js":[369,179]};function r(t){if(!i.o(s,t))return Promise.resolve().then((()=>{var e=new Error("Cannot find module '"+t+"'");throw e.code="MODULE_NOT_FOUND",e}));var e=s[t],r=e[0];return i.e(e[1]).then((()=>i(r)))}r.keys=()=>Object.keys(s),r.id=837,t.exports=r},721:(t,e,i)=>{"use strict";i.r(e),i.d(e,{AbstractView:()=>a});var s=i(84),r=i(402);class a{options={title:null,path:null,icon:"mdi:view-dashboard",subview:!1};viewTitleCard;constructor(){if(this.constructor===a)throw new Error("Abstract classes can't be instantiated.");if(!s.W.isInitialized())throw new Error("The Helper module must be initialized before using this one.")}mergeOptions(t,e){this.options={...t,...e}}async createViewCards(){const t=[];for(const e of s.W.areas){const a=[],o=s.W.getDeviceEntities(e,this.domain),n=s.W.sanitizeClassName(this.domain+"Card"),d=await i(175)(`./${n}`);for(const t of o){let e=s.W.strategyOptions.card_options?.[t.entity_id]??{},i=s.W.strategyOptions.card_options?.[t.device_id]??{};e.hidden||i.hidden||a.push(new d[n](t,e).getCard())}a.length&&(a.unshift(new r.TitleCard([e],{title:e.name,...this.options.titleCard},this.domain).createCard()),t.push({type:"vertical-stack",cards:a}))}return t.unshift(t.length?this.viewTitleCard:{type:"custom:mushroom-title-card",title:"No Entities Available",subtitle:"They're either hidden by the configuration or by Home Assistant."}),t}async getView(){return{...this.options,cards:await this.createViewCards()}}}},458:(t,e,i)=>{"use strict";i.r(e),i.d(e,{CameraView:()=>o});var s=i(84),r=i(402),a=i(721);class o extends a.AbstractView{#p="camera";#d={title:"Cameras",path:"cameras",icon:"mdi:cctv",subview:!1,titleCard:{showControls:!1}};#u={title:"All Cameras",...this.options.titleCard};constructor(t={}){super(),this.mergeOptions(this.#d,t),this.viewTitleCard=new r.TitleCard(s.W.areas,{...this.#u,...this.options.titleCard}).createCard()}get domain(){return this.#p}}},310:(t,e,i)=>{"use strict";i.r(e),i.d(e,{ClimateView:()=>o});var s=i(84),r=i(402),a=i(721);class o extends a.AbstractView{#p="climate";#d={title:"Climates",path:"climates",icon:"mdi:thermostat",subview:!1,titleCard:{showControls:!1}};#u={title:"All Climates",subtitle:s.W.getCountTemplate(this.domain,"ne","off")+" climates on",...this.options.titleCard};constructor(t={}){super(),this.mergeOptions(this.#d,t),this.viewTitleCard=new r.TitleCard(s.W.areas,{...this.#u,...this.options.titleCard}).createCard()}get domain(){return this.#p}}},401:(t,e,i)=>{"use strict";i.r(e),i.d(e,{CoverView:()=>o});var s=i(84),r=i(402),a=i(721);class o extends a.AbstractView{#p="cover";#d={title:"Covers",path:"covers",icon:"mdi:window-open",subview:!1,titleCard:{iconOn:"mdi:arrow-up",iconOff:"mdi:arrow-down",onService:"cover.open_cover",offService:"cover.close_cover"}};#u={title:"All Covers",subtitle:s.W.getCountTemplate(this.domain,"eq","open")+" covers open"};constructor(t={}){super(),this.mergeOptions(this.#d,t),this.viewTitleCard=new r.TitleCard(s.W.areas,{...this.#u,...this.options.titleCard}).createCard()}get domain(){return this.#p}}},902:(t,e,i)=>{"use strict";i.r(e),i.d(e,{FanView:()=>o});var s=i(84),r=i(402),a=i(721);class o extends a.AbstractView{#p="fan";#d={title:"Fans",path:"fans",icon:"mdi:fan",subview:!1,titleCard:{iconOn:"mdi:fan",iconOff:"mdi:fan-off",onService:"fan.turn_on",offService:"fan.turn_off"}};#u={title:"All Fans",subtitle:s.W.getCountTemplate(this.domain,"eq","on")+" fans on"};constructor(t={}){super(),this.mergeOptions(this.#d,t),this.viewTitleCard=new r.TitleCard(s.W.areas,{...this.#u,...this.options.titleCard}).createCard()}get domain(){return this.#p}}},530:(t,e,i)=>{"use strict";i.r(e),i.d(e,{HomeView:()=>a});var s=i(84),r=i(721);class a extends r.AbstractView{#d={title:"Home",path:"home",subview:!1};constructor(t={}){super(),this.mergeOptions(this.#d,t)}async createViewCards(){return await Promise.all([this.#m(),this.#f(),this.#g()]).then((([t,e,i])=>{const r=s.W.strategyOptions,a=[];return t.length&&a.push({type:"custom:mushroom-chips-card",alignment:"center",chips:t}),e.length&&a.push({type:"horizontal-stack",cards:e}),s.W.strategyOptions.homeView?.hidden?.includes("greeting")||a.push({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"}}),r.quick_access_cards&&a.push(...r.quick_access_cards),a.push({type:"vertical-stack",cards:i}),r.extra_cards&&a.push(...r.extra_cards),a}))}async#m(){if(s.W.strategyOptions.homeView?.hidden?.includes("chips"))return[];const t=[],e=s.W.strategyOptions.chips,r=["light","fan","cover","switch","climate"],a=s.W.areas.map((t=>t.area_id));let o;const n=e?.weather_entity??s.W.entities.find((t=>t.entity_id.startsWith("weather.")&&null==t.disabled_by&&null==t.hidden_by))?.entity_id;if(n)try{o=await Promise.resolve().then(i.bind(i,369));const e=new o.WeatherChip(n);t.push(e.getChip())}catch(t){console.error(s.W.debug?t:"An error occurred while creating the weather chip!")}for(let n of r)if(e?.[`${n}_count`]??1){const e=s.W.sanitizeClassName(n+"Chip");try{o=await i(837)(`./${e}`);const s=new o[e](a);t.push(s.getChip())}catch(t){console.error(s.W.debug?t:`An error occurred while creating the ${n} chip!`)}}return e?.extra_chips&&t.push(...e.extra_chips),t}#f(){if(s.W.strategyOptions.homeView?.hidden?.includes("persons"))return[];const t=[];return Promise.resolve().then(i.bind(i,543)).then((e=>{for(const i of s.W.entities.filter((t=>t.entity_id.startsWith("person.")&&null==t.hidden_by&&null==t.disabled_by)))t.push(new e.PersonCard(i).getCard())})),t}async#g(){if(s.W.strategyOptions.homeView?.hidden?.includes("areas"))return[];const t=[];let e=[];s.W.strategyOptions.homeView?.hidden?.includes("areasTitle")||t.push({type:"custom:mushroom-title-card",title:"Areas"});for(const[r,a]of s.W.areas.entries()){let o,n=s.W.strategyOptions.areas[a.area_id??"undisclosed"]?.type??s.W.strategyOptions.areas._?.type??"default";try{o=await i(175)(`./${n}`)}catch(t){o=await Promise.resolve().then(i.bind(i,138)),s.W.strategyOptions.debug&&"default"!==n&&console.error(t)}if(!s.W.strategyOptions.areas[a.area_id]?.hidden){let t={...s.W.strategyOptions.areas._,...s.W.strategyOptions.areas[a.area_id??"undisclosed"]};e.push(new o.AreaCard(a,t).getCard())}if(r===s.W.areas.length-1)for(let i=0;i{"use strict";i.r(e),i.d(e,{LightView:()=>o});var s=i(84),r=i(402),a=i(721);class o extends a.AbstractView{#p="light";#d={title:"Lights",path:"lights",icon:"mdi:lightbulb-group",subview:!1,titleCard:{iconOn:"mdi:lightbulb",iconOff:"mdi:lightbulb-off",onService:"light.turn_on",offService:"light.turn_off"}};#u={title:"All Lights",subtitle:s.W.getCountTemplate(this.domain,"eq","on")+" lights on"};constructor(t={}){super(),this.mergeOptions(this.#d,t),this.viewTitleCard=new r.TitleCard(s.W.areas,{...this.#u,...this.options.titleCard}).createCard()}get domain(){return this.#p}}},133:(t,e,i)=>{"use strict";i.r(e),i.d(e,{SwitchView:()=>o});var s=i(84),r=i(402),a=i(721);class o extends a.AbstractView{#p="switch";#d={title:"Switches",path:"switches",icon:"mdi:dip-switch",subview:!1,titleCard:{iconOn:"mdi:power-plug",iconOff:"mdi:power-plug-off",onService:"switch.turn_on",offService:"switch.turn_off"}};#u={title:"All Switches",subtitle:s.W.getCountTemplate(this.domain,"eq","on")+" switches on"};constructor(t={}){super(),this.mergeOptions(this.#d,t),this.viewTitleCard=new r.TitleCard(s.W.areas,{...this.#u,...this.options.titleCard}).createCard()}get domain(){return this.#p}}},654:(t,e,i)=>{"use strict";i.r(e)},968:(t,e,i)=>{var s={"./AbstractView":[721,179],"./AbstractView.js":[721,179],"./CameraView":[458,179],"./CameraView.js":[458,179],"./ClimateView":[310,179],"./ClimateView.js":[310,179],"./CoverView":[401,179],"./CoverView.js":[401,179],"./FanView":[902,179],"./FanView.js":[902,179],"./HomeView":[530,179],"./HomeView.js":[530,179],"./LightView":[587,179],"./LightView.js":[587,179],"./SwitchView":[133,179],"./SwitchView.js":[133,179],"./typedefs":[654,179],"./typedefs.js":[654,179]};function r(t){if(!i.o(s,t))return Promise.resolve().then((()=>{var e=new Error("Cannot find module '"+t+"'");throw e.code="MODULE_NOT_FOUND",e}));var e=s[t],r=e[0];return i.e(e[1]).then((()=>i(r)))}r.keys=()=>Object.keys(s),r.id=968,t.exports=r}},s={};function r(t){var e=s[t];if(void 0!==e)return e.exports;var a=s[t]={exports:{}};return i[t](a,a.exports,r),a.exports}e=Object.getPrototypeOf?t=>Object.getPrototypeOf(t):t=>t.__proto__,r.t=function(i,s){if(1&s&&(i=this(i)),8&s)return i;if("object"==typeof i&&i){if(4&s&&i.__esModule)return i;if(16&s&&"function"==typeof i.then)return i}var a=Object.create(null);r.r(a);var o={};t=t||[null,e({}),e([]),e(e)];for(var n=2&s&&i;"object"==typeof n&&!~t.indexOf(n);n=e(n))Object.getOwnPropertyNames(n).forEach((t=>o[t]=()=>i[t]));return o.default=()=>i,r.d(a,o),a},r.d=(t,e)=>{for(var i in e)r.o(e,i)&&!r.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},r.e=()=>Promise.resolve(),r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},(()=>{"use strict";var t=r(84),e=r(408),i=r(402);customElements.define("ll-strategy-mushroom-strategy",class{static async generateDashboard(e){await t.W.initialize(e);const i=[];let s;for(let e of t.W.getExposedViewIds())try{const a=t.W.sanitizeClassName(e+"View");s=await r(968)(`./${a}`);const o=await new s[a](t.W.strategyOptions.views[e]).getView();i.push(o)}catch(i){console.error(t.W.debug?i:`View '${e}' couldn't be loaded!`)}for(let e of t.W.areas)e.hidden||i.push({title:e.name,path:e.area_id??e.name,subview:!0,strategy:{type:"custom:mushroom-strategy",options:{area:e}}});return t.W.strategyOptions.extra_views&&i.push(...t.W.strategyOptions.extra_views),{views:i}}static async generateView(s){const a=t.W.getExposedDomainIds(),o=s.view.strategy.options.area,n=[...o.extra_cards??[]];for(const s of a){if("default"===s)continue;const a=t.W.sanitizeClassName(s+"Card");let d=[];try{d=await r(175)(`./${a}`).then((r=>{let n=[];const d=t.W.getDeviceEntities(o,s);if(d.length){const c=new i.TitleCard([o],t.W.strategyOptions.domains[s]).createCard();if("sensor"===s){const i=t.W.getStateEntities(o,"sensor"),s=[];for(const r of d){const a=i.find((t=>t.entity_id===r.entity_id));let o=t.W.strategyOptions.card_options?.[r.entity_id]??{},n=t.W.strategyOptions.card_options?.[r.device_id]??{};o.hidden||n.hidden||(a?.attributes.unit_of_measurement&&(o={type:"custom:mini-graph-card",entities:[r.entity_id],...o}),s.push(new e.SensorCard(r,o).getCard()))}return s.length&&(n.push({type:"vertical-stack",cards:s}),n.unshift(c)),n}for(const e of d){let i=t.W.strategyOptions.card_options?.[e.entity_id]??{},s=t.W.strategyOptions.card_options?.[e.device_id]??{};i.hidden||s.hidden||n.push(new r[a](e,i).getCard())}if("binary_sensor"===s){const t=[];for(let e=0;et.area_id===o.area_id)).map((t=>t.id)),s=t.W.entities.filter((t=>(e.includes(t.device_id)||t.area_id===o.area_id)&&null==t.hidden_by&&null==t.disabled_by&&!a.includes(t.entity_id.split(".",1)[0])));if(s.length){let e=[];try{e=await Promise.resolve().then(r.bind(r,190)).then((e=>{const r=[new i.TitleCard([o],t.W.strategyOptions.domains.default).createCard()];for(const i of s){let s=t.W.strategyOptions.card_options?.[i.entity_id]??{},a=t.W.strategyOptions.card_options?.[i.device_id]??{};s.hidden||a.hidden||r.push(new e.MiscellaneousCard(i,s).getCard())}return r}))}catch(e){console.error(t.W.debug?e:"An error occurred while creating the domain cards!")}n.push({type:"vertical-stack",cards:e})}}return{cards:n}}})})()})(); \ No newline at end of file +(()=>{var e={996:e=>{"use strict";var t=function(e){return function(e){return!!e&&"object"==typeof e}(e)&&!function(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||function(e){return e.$$typeof===r}(e)}(e)},r="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function i(e,t){return!1!==t.clone&&t.isMergeableObject(e)?s((r=e,Array.isArray(r)?[]:{}),e,t):e;var r}function a(e,t,r){return e.concat(t).map((function(e){return i(e,r)}))}function o(e){return Object.keys(e).concat(function(e){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter((function(t){return Object.propertyIsEnumerable.call(e,t)})):[]}(e))}function n(e,t){try{return t in e}catch(e){return!1}}function s(e,r,c){(c=c||{}).arrayMerge=c.arrayMerge||a,c.isMergeableObject=c.isMergeableObject||t,c.cloneUnlessOtherwiseSpecified=i;var d=Array.isArray(r);return d===Array.isArray(e)?d?c.arrayMerge(e,r,c):function(e,t,r){var a={};return r.isMergeableObject(e)&&o(e).forEach((function(t){a[t]=i(e[t],r)})),o(t).forEach((function(o){(function(e,t){return n(e,t)&&!(Object.hasOwnProperty.call(e,t)&&Object.propertyIsEnumerable.call(e,t))})(e,o)||(n(e,o)&&r.isMergeableObject(t[o])?a[o]=function(e,t){if(!t.customMerge)return s;var r=t.customMerge(e);return"function"==typeof r?r:s}(o,r)(e[o],t[o],r):a[o]=i(t[o],r))})),a}(e,r,c):i(r,c)}s.all=function(e,t){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce((function(e,r){return s(e,r,t)}),{})};var c=s;e.exports=c},371:(e,t,r)=>{"use strict";r.d(t,{W:()=>v});const i={areas:{undisclosed:{aliases:[],area_id:"undisclosed",name:"Undisclosed",picture:null,hidden:!1}},debug:!1,domains:{default:{title:"Miscellaneous",showControls:!1,hidden:!1},light:{title:"Lights",showControls:!0,iconOn:"mdi:lightbulb",iconOff:"mdi:lightbulb-off",onService:"light.turn_on",offService:"light.turn_off",hidden:!1},fan:{title:"Fans",showControls:!0,iconOn:"mdi:fan",iconOff:"mdi:fan-off",onService:"fan.turn_on",offService:"fan.turn_off",hidden:!1},cover:{title:"Covers",showControls:!0,iconOn:"mdi:arrow-up",iconOff:"mdi:arrow-down",onService:"cover.open_cover",offService:"cover.close_cover",hidden:!1},switch:{title:"Switches",showControls:!0,iconOn:"mdi:power-plug",iconOff:"mdi:power-plug-off",onService:"switch.turn_on",offService:"switch.turn_off",hidden:!1},camera:{title:"Cameras",showControls:!1,hidden:!1},lock:{title:"Locks",showControls:!1,hidden:!1},climate:{title:"Climates",showControls:!1,hidden:!1},media_player:{title:"Media Players",showControls:!1,hidden:!1},sensor:{title:"Sensors",showControls:!1,hidden:!1},binary_sensor:{title:"Binary Sensors",showControls:!1,hidden:!1},number:{title:"Numbers",showControls:!1,hidden:!1},vacuum:{title:"Vacuums",showControls:!0,hidden:!1}},home_view:{hidden:[]},views:{home:{order:1,hidden:!1},light:{order:2,hidden:!1},fan:{order:3,hidden:!1},cover:{order:4,hidden:!1},switch:{order:5,hidden:!1},climate:{order:6,hidden:!1},camera:{order:7,hidden:!1},vacuum:{order:8,hidden:!1}}};var a,o,n,s,c,d,l,h,f,u,p=r(996),w=r.n(p),m=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)},g=function(e,t,r,i,a){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!a)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===i?a.call(e,r):a?a.value=r:t.set(e,r),r};class v{constructor(){throw new Error("This class should be invoked with method initialize() instead of using the keyword new!")}static get strategyOptions(){return m(this,a,"f",l)}static get areas(){return m(this,a,"f",s)}static get devices(){return m(this,a,"f",n)}static get entities(){return m(this,a,"f",o)}static get debug(){return m(this,a,"f",h)}static async initialize(e){g(this,a,e.hass.states,"f",c),g(this,a,w()(i,e.config?.strategy?.options??{}),"f",l),g(this,a,m(this,a,"f",l).debug,"f",h);try{[{set value(e){g(a,a,e,"f",o)}}.value,{set value(e){g(a,a,e,"f",n)}}.value,{set value(e){g(a,a,e,"f",s)}}.value]=await Promise.all([e.hass.callWS({type:"config/entity_registry/list"}),e.hass.callWS({type:"config/device_registry/list"}),e.hass.callWS({type:"config/area_registry/list"})])}catch(e){throw a.logError("An error occurred while querying Home assistant's registries!",e),"Check the console for details"}m(this,a,"f",l).areas.undisclosed?.hidden||(m(this,a,"f",l).areas.undisclosed={...i.areas.undisclosed,...m(this,a,"f",l).areas.undisclosed},m(this,a,"f",l).areas.undisclosed.area_id="undisclosed",m(this,a,"f",s).push(m(this,a,"f",l).areas.undisclosed)),g(this,a,a.areas.map((e=>({...e,...m(this,a,"f",l).areas?.[e.area_id]}))),"f",s),m(this,a,"f",s).sort(((e,t)=>(e.order??1/0)-(t.order??1/0)||e.name.localeCompare(t.name))),m(this,a,"f",l).views=Object.fromEntries(Object.entries(m(this,a,"f",l).views).sort((([,e],[,t])=>(e.order??1/0)-(t.order??1/0)||(e.title??"undefined").localeCompare(t.title??"undefined")))),m(this,a,"f",l).domains=Object.fromEntries(Object.entries(m(this,a,"f",l).domains).sort((([,e],[,t])=>(e.order??1/0)-(t.order??1/0)||(e.title??"undefined").localeCompare(t.title??"undefined")))),g(this,a,!0,"f",d)}static isInitialized(){return m(this,a,"f",d)}static getCountTemplate(e,t,r){const i=[];this.isInitialized()||console.warn("Helper class should be initialized before calling this method!");for(const t of m(this,a,"f",s)){const r=m(this,a,"f",n).filter((e=>e.area_id===t.area_id)).map((e=>e.id)),s=m(this,a,"f",o).filter(m(this,a,"m",f),{area:t,domain:e,areaDeviceIds:r}).map((e=>`states['${e.entity_id}']`));i.push(...s)}return`{% set entities = [${i}] %} {{ entities | selectattr('state','${t}','${r}') | list | count }}`}static getDeviceEntities(e,t){this.isInitialized()||console.warn("Helper class should be initialized before calling this method!");const r=m(this,a,"f",n).filter((t=>(t.area_id??"undisclosed")===e.area_id)).map((e=>e.id));return m(this,a,"f",o).filter(m(this,a,"m",f),{area:e,domain:t,areaDeviceIds:r}).sort(((e,t)=>(e.original_name??"undefined").localeCompare(t.original_name??"undefined")))}static getStateEntities(e,t){this.isInitialized()||console.warn("Helper class should be initialized before calling this method!");const r=[],i=Object.fromEntries(m(this,a,"f",o).map((e=>[e.entity_id,e]))),s=Object.fromEntries(m(this,a,"f",n).map((e=>[e.id,e]))),d=Object.values(m(this,a,"f",c)).filter((e=>e.entity_id.startsWith(`${t}.`)));for(const t of d){const a=i[t.entity_id],o=s[a?.device_id??""];(a?.area_id===e.area_id||o&&o.area_id===e.area_id)&&r.push(t)}return r}static sanitizeClassName(e){return(e=e.charAt(0).toUpperCase()+e.slice(1)).replace(/([-_][a-z])/g,(e=>e.toUpperCase().replace("-","").replace("_","")))}static getExposedViewIds(){return this.isInitialized()||console.warn("Helper class should be initialized before calling this method!"),m(this,a,"m",u).call(this,m(this,a,"f",l).views,"hidden",!1)}static getExposedDomainIds(){return this.isInitialized()||console.warn("Helper class should be initialized before calling this method!"),m(this,a,"m",u).call(this,m(this,a,"f",l).domains,"hidden",!1)}static logError(e,t){a.debug?console.error(e,t):console.error(e)}}a=v,f=function(e){const t=null===e.hidden_by&&null===e.disabled_by,r=e.entity_id.startsWith(`${this.domain}.`),i="undisclosed"===this.area.area_id?!e.area_id&&(this.areaDeviceIds.includes(e.device_id??"")||!e.device_id):this.areaDeviceIds.includes(e.device_id??"")||e.area_id===this.area.area_id;return t&&r&&i},u=function(e,t,r){const i=[];for(const a of Object.keys(e))e[a][t]===r&&i.push(a);return i},o={value:void 0},n={value:void 0},s={value:[]},c={value:void 0},d={value:!1},l={value:void 0},h={value:void 0}},976:(e,t,r)=>{"use strict";r.r(t),r.d(t,{AbstractCard:()=>a});var i=r(371);class a{constructor(e){if(this.config={type:"custom:mushroom-entity-card",icon:"mdi:help-circle"},!i.W.isInitialized())throw new Error("The Helper module must be initialized before using this one.");this.entity=e}getCard(){return{...this.config,entity:"entity_id"in this.entity?this.entity.entity_id:void 0}}}},396:(e,t,r)=>{"use strict";r.r(t),r.d(t,{AreaCard:()=>n});var i,a=r(976),o=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class n extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{type:"custom:mushroom-template-card",primary:void 0,icon:"mdi:texture-box",icon_color:"blue",tap_action:{action:"navigate",navigation_path:""},hold_action:{action:"none"}}),"default"===t.type&&delete t.type,o(this,i,"f").primary=e.name,o(this,i,"f").tap_action&&"navigation_path"in o(this,i,"f").tap_action&&(o(this,i,"f").tap_action.navigation_path=e.area_id),this.config=Object.assign(this.config,o(this,i,"f"),t)}}i=new WeakMap},418:(e,t,r)=>{"use strict";r.r(t),r.d(t,{BinarySensorCard:()=>o});var i,a=r(642);class o extends a.SensorCard{constructor(e,t={}){super(e),i.set(this,{type:"custom:mushroom-entity-card",icon:"mdi:power-cycle",icon_color:"green"}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),t)}}i=new WeakMap},604:(e,t,r)=>{"use strict";r.r(t),r.d(t,{CameraCard:()=>o});var i,a=r(976);class o extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{entity:"",type:"picture-entity",show_name:!1,show_state:!1,camera_view:"live"}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),t)}}i=new WeakMap},782:(e,t,r)=>{"use strict";r.r(t),r.d(t,{ClimateCard:()=>o});var i,a=r(976);class o extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{type:"custom:mushroom-climate-card",icon:void 0,hvac_modes:["off","cool","heat","fan_only"],show_temperature_control:!0}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),t)}}i=new WeakMap},607:(e,t,r)=>{"use strict";r.r(t),r.d(t,{ControllerCard:()=>s});var i,a,o=function(e,t,r,i,a){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!a)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===i?a.call(e,r):a?a.value=r:t.set(e,r),r},n=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class s{constructor(e,t={}){i.set(this,void 0),a.set(this,{type:"mushroom-title-card",showControls:!0,iconOn:"mdi:power-on",iconOff:"mdi:power-off",onService:"none",offService:"none"}),o(this,i,e,"f"),o(this,a,{...n(this,a,"f"),...t},"f")}createCard(){const e=[{type:"custom:mushroom-title-card",title:n(this,a,"f").title,subtitle:n(this,a,"f").subtitle}];return n(this,a,"f").showControls&&e.push({type:"horizontal-stack",cards:[{type:"custom:mushroom-template-card",icon:n(this,a,"f").iconOff,layout:"vertical",icon_color:"red",tap_action:{action:"call-service",service:n(this,a,"f").offService,target:n(this,i,"f"),data:{}}},{type:"custom:mushroom-template-card",icon:n(this,a,"f").iconOn,layout:"vertical",icon_color:"amber",tap_action:{action:"call-service",service:n(this,a,"f").onService,target:n(this,i,"f"),data:{}}}]}),{type:"horizontal-stack",cards:e}}}i=new WeakMap,a=new WeakMap},758:(e,t,r)=>{"use strict";r.r(t),r.d(t,{CoverCard:()=>o});var i,a=r(976);class o extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{type:"custom:mushroom-cover-card",icon:void 0,show_buttons_control:!0,show_position_control:!0,show_tilt_position_control:!0}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),t)}}i=new WeakMap},287:(e,t,r)=>{"use strict";r.r(t),r.d(t,{FanCard:()=>o});var i,a=r(976);class o extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{type:"custom:mushroom-fan-card",icon:void 0,show_percentage_control:!0,show_oscillate_control:!0,icon_animation:!0}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),t)}}i=new WeakMap},387:(e,t,r)=>{"use strict";r.r(t),r.d(t,{AreaCard:()=>n});var i,a=r(976),o=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class n extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{type:"area",area:""}),o(this,i,"f").area=e.area_id,o(this,i,"f").navigation_path=o(this,i,"f").area,delete t.type,this.config=Object.assign(this.config,o(this,i,"f"),t)}}i=new WeakMap},475:(e,t,r)=>{"use strict";r.r(t),r.d(t,{LightCard:()=>d});var i,a=r(976),o=r(596),n=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)},s=o.R.isCallServiceActionConfig,c=o.R.isCallServiceActionTarget;class d extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{type:"custom:mushroom-light-card",icon:void 0,show_brightness_control:!0,show_color_control:!0,show_color_temp_control:!0,use_light_color:!0,double_tap_action:{action:"call-service",service:"light.turn_on",target:{entity_id:void 0},data:{rgb_color:[255,255,255]}}}),s(n(this,i,"f").double_tap_action)&&c(n(this,i,"f").double_tap_action.target)&&(n(this,i,"f").double_tap_action.target.entity_id=e.entity_id),this.config=Object.assign(this.config,n(this,i,"f"),t)}}i=new WeakMap},696:(e,t,r)=>{"use strict";r.r(t),r.d(t,{LockCard:()=>o});var i,a=r(976);class o extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{type:"custom:mushroom-lock-card",icon:void 0}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),t)}}i=new WeakMap},499:(e,t,r)=>{"use strict";r.r(t),r.d(t,{MediaPlayerCard:()=>o});var i,a=r(976);class o extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{type:"custom:mushroom-media-player-card",use_media_info:!0,media_controls:["on_off","play_pause_stop"],show_volume_level:!0,volume_controls:["volume_mute","volume_set","volume_buttons"]}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),t)}}i=new WeakMap},571:(e,t,r)=>{"use strict";r.r(t),r.d(t,{MiscellaneousCard:()=>o});var i,a=r(976);class o extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{type:"custom:mushroom-entity-card",icon_color:"blue-grey"}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),t)}}i=new WeakMap},420:(e,t,r)=>{"use strict";r.r(t),r.d(t,{NumberCard:()=>o});var i,a=r(976);class o extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{type:"custom:mushroom-number-card",icon:void 0}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),t)}}i=new WeakMap},877:(e,t,r)=>{"use strict";r.r(t),r.d(t,{PersonCard:()=>o});var i,a=r(976);class o extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{type:"custom:mushroom-person-card",layout:"vertical",primary_info:"none",secondary_info:"none",icon_type:"entity-picture"}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),t)}}i=new WeakMap},642:(e,t,r)=>{"use strict";r.r(t),r.d(t,{SensorCard:()=>o});var i,a=r(976);class o extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{type:"custom:mushroom-entity-card",icon:"mdi:information",animate:!0,line_color:"green"}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),t)}}i=new WeakMap},330:(e,t,r)=>{"use strict";r.r(t),r.d(t,{SwitchCard:()=>o});var i,a=r(976);class o extends a.AbstractCard{constructor(e,t={}){super(e),i.set(this,{type:"custom:mushroom-entity-card",icon:void 0,tap_action:{action:"toggle"}}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),t)}}i=new WeakMap},695:(e,t,r)=>{"use strict";r.r(t),r.d(t,{VacuumCard:()=>n});var i=r(976);const a=["on_off","start_pause","stop","locate","clean_spot","return_home"];var o;class n extends i.AbstractCard{constructor(e,t={}){super(e),o.set(this,{type:"custom:mushroom-vacuum-card",icon:void 0,icon_animation:!0,commands:[...a],tap_action:{action:"more-info"}}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,o,"f"),t)}}o=new WeakMap},370:(e,t,r)=>{"use strict";r.r(t),r.d(t,{AbstractChip:()=>o});var i=r(371),a=r(596).R.isCallServiceActionConfig;class o{constructor(){if(this.config={type:"template"},!i.W.isInitialized())throw new Error("The Helper module must be initialized before using this one.")}getChip(){return this.config}setTapActionTarget(e){"tap_action"in this.config&&a(this.config.tap_action)?this.config.tap_action.target=e:i.W.debug&&console.warn(this.constructor.name+" - Target not set: Invalid target or tap action.")}}},660:(e,t,r)=>{"use strict";r.r(t),r.d(t,{ClimateChip:()=>n});var i,a=r(371),o=r(370);class n extends o.AbstractChip{constructor(e={}){super(),i.set(this,{type:"template",icon:"mdi:thermostat",icon_color:"orange",content:a.W.getCountTemplate("climate","ne","off"),tap_action:{action:"none"},hold_action:{action:"navigate",navigation_path:"climates"}}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),e)}}i=new WeakMap},993:(e,t,r)=>{"use strict";r.r(t),r.d(t,{CoverChip:()=>n});var i,a=r(371),o=r(370);class n extends o.AbstractChip{constructor(e={}){super(),i.set(this,{type:"template",icon:"mdi:window-open",icon_color:"cyan",content:a.W.getCountTemplate("cover","eq","open"),tap_action:{action:"none"},hold_action:{action:"navigate",navigation_path:"covers"}}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),e)}}i=new WeakMap},966:(e,t,r)=>{"use strict";r.r(t),r.d(t,{FanChip:()=>n});var i,a=r(371),o=r(370);class n extends o.AbstractChip{constructor(e={}){super(),i.set(this,{type:"template",icon:"mdi:fan",icon_color:"green",content:a.W.getCountTemplate("fan","eq","on"),tap_action:{action:"call-service",service:"fan.turn_off"},hold_action:{action:"navigate",navigation_path:"fans"}}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),e)}}i=new WeakMap},95:(e,t,r)=>{"use strict";r.r(t),r.d(t,{LightChip:()=>n});var i,a=r(371),o=r(370);class n extends o.AbstractChip{constructor(e={}){super(),i.set(this,{type:"template",icon:"mdi:lightbulb-group",icon_color:"amber",content:a.W.getCountTemplate("light","eq","on"),tap_action:{action:"call-service",service:"light.turn_off"},hold_action:{action:"navigate",navigation_path:"lights"}}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),e)}}i=new WeakMap},817:(e,t,r)=>{"use strict";r.r(t),r.d(t,{SwitchChip:()=>n});var i,a=r(371),o=r(370);class n extends o.AbstractChip{constructor(e={}){super(),i.set(this,{type:"template",icon:"mdi:dip-switch",icon_color:"blue",content:a.W.getCountTemplate("switch","eq","on"),tap_action:{action:"call-service",service:"switch.turn_off"},hold_action:{action:"navigate",navigation_path:"switches"}}),this.config=Object.assign(this.config,function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)}(this,i,"f"),e)}}i=new WeakMap},977:(e,t,r)=>{"use strict";r.r(t),r.d(t,{WeatherChip:()=>n});var i,a=r(370),o=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class n extends a.AbstractChip{constructor(e,t={}){super(),i.set(this,{type:"weather",show_temperature:!0,show_conditions:!0}),function(e,t,r,i,a){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!a)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");"a"===i?a.call(e,r):a?a.value=r:t.set(e,r)}(this,i,{...o(this,i,"f"),entity:e,...t},"f"),this.config=Object.assign(this.config,o(this,i,"f"),t)}}i=new WeakMap},596:(e,t,r)=>{"use strict";var i;r.d(t,{R:()=>i}),function(e){e.isCallServiceActionConfig=function(e){return e&&"call-service"===e.action&&["action","service"].every((t=>t in e))},e.isCallServiceActionTarget=function(e){return e&&["entity_id","device_id","area_id"].some((t=>t in e))}}(i||(i={}))},1:(e,t,r)=>{"use strict";r.r(t),r.d(t,{AbstractView:()=>s});var i,a=r(371),o=r(607),n=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class s{constructor(e=""){if(this.config={icon:"mdi:view-dashboard",subview:!1},this.viewControllerCard={cards:[],type:""},i.set(this,void 0),!a.W.isInitialized())throw new Error("The Helper module must be initialized before using this one.");e&&function(e,t,r,i,a){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!a)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");"a"===i?a.call(e,r):a?a.value=r:t.set(e,r)}(this,i,e,"f")}async createViewCards(){const e=[];for(const t of a.W.areas){const s=[],c=a.W.getDeviceEntities(t,n(this,i,"f")??""),d=a.W.sanitizeClassName(n(this,i,"f")+"Card"),l=await r(175)(`./${d}`);let h={area_id:[t.area_id]};"undisclosed"===t.area_id&&(h={entity_id:c.map((e=>e.entity_id))});for(const e of c){let t=a.W.strategyOptions.card_options?.[e.entity_id],r=a.W.strategyOptions.card_options?.[e.device_id??"null"];t?.hidden||r?.hidden||s.push(new l[d](e,t).getCard())}if(s.length){const r="controllerCardOptions"in this.config?this.config.controllerCardOptions:{};s.unshift(new o.ControllerCard(h,Object.assign({title:t.name},r)).createCard()),e.push({type:"vertical-stack",cards:s})}}return e.length&&e.unshift(this.viewControllerCard),e}async getView(){return{...this.config,cards:await this.createViewCards()}}targetDomain(e){return{entity_id:a.W.entities.filter((t=>t.entity_id.startsWith(e+".")&&!t.hidden_by&&!a.W.strategyOptions.card_options?.entity_id.hidden)).map((e=>e.entity_id))}}}i=new WeakMap},89:(e,t,r)=>{"use strict";r.r(t),r.d(t,{CameraView:()=>h});var i,a,o,n,s=r(607),c=r(1),d=r(371),l=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class h extends c.AbstractView{constructor(e={}){super(l(i,i,"f",a)),o.set(this,{title:"Cameras",path:"cameras",icon:"mdi:cctv",subview:!1,controllerCardOptions:{showControls:!1}}),n.set(this,{title:"All Cameras",subtitle:d.W.getCountTemplate(l(i,i,"f",a),"ne","off")+" cameras on"}),this.config=Object.assign(this.config,l(this,o,"f"),e),this.viewControllerCard=new s.ControllerCard({},{...l(this,n,"f"),..."controllerCardOptions"in this.config?this.config.controllerCardOptions:{}}).createCard()}}i=h,o=new WeakMap,n=new WeakMap,a={value:"camera"}},81:(e,t,r)=>{"use strict";r.r(t),r.d(t,{ClimateView:()=>h});var i,a,o,n,s=r(371),c=r(607),d=r(1),l=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class h extends d.AbstractView{constructor(e={}){super(l(i,i,"f",a)),o.set(this,{title:"Climates",path:"climates",icon:"mdi:thermostat",subview:!1,controllerCardOptions:{showControls:!1}}),n.set(this,{title:"All Climates",subtitle:s.W.getCountTemplate(l(i,i,"f",a),"ne","off")+" climates on"}),this.config=Object.assign(this.config,l(this,o,"f"),e),this.viewControllerCard=new c.ControllerCard(this.targetDomain(l(i,i,"f",a)),{...l(this,n,"f"),..."controllerCardOptions"in this.config?this.config.controllerCardOptions:{}}).createCard()}}i=h,o=new WeakMap,n=new WeakMap,a={value:"climate"}},136:(e,t,r)=>{"use strict";r.r(t),r.d(t,{CoverView:()=>h});var i,a,o,n,s=r(371),c=r(607),d=r(1),l=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class h extends d.AbstractView{constructor(e={}){super(l(i,i,"f",a)),o.set(this,{title:"Covers",path:"covers",icon:"mdi:window-open",subview:!1,controllerCardOptions:{iconOn:"mdi:arrow-up",iconOff:"mdi:arrow-down",onService:"cover.open_cover",offService:"cover.close_cover"}}),n.set(this,{title:"All Covers",subtitle:s.W.getCountTemplate(l(i,i,"f",a),"eq","open")+" covers open"}),this.config=Object.assign(this.config,l(this,o,"f"),e),this.viewControllerCard=new c.ControllerCard(this.targetDomain(l(i,i,"f",a)),{...l(this,n,"f"),..."controllerCardOptions"in this.config?this.config.controllerCardOptions:{}}).createCard()}}i=h,o=new WeakMap,n=new WeakMap,a={value:"cover"}},964:(e,t,r)=>{"use strict";r.r(t),r.d(t,{FanView:()=>h});var i,a,o,n,s=r(371),c=r(607),d=r(1),l=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class h extends d.AbstractView{constructor(e={}){super(l(i,i,"f",a)),o.set(this,{title:"Fans",path:"fans",icon:"mdi:fan",subview:!1,controllerCardOptions:{iconOn:"mdi:fan",iconOff:"mdi:fan-off",onService:"fan.turn_on",offService:"fan.turn_off"}}),n.set(this,{title:"All Fans",subtitle:s.W.getCountTemplate(l(i,i,"f",a),"eq","on")+" fans on"}),this.config=Object.assign(this.config,l(this,o,"f"),e),this.viewControllerCard=new c.ControllerCard(this.targetDomain(l(i,i,"f",a)),{...l(this,n,"f"),..."controllerCardOptions"in this.config?this.config.controllerCardOptions:{}}).createCard()}}i=h,o=new WeakMap,n=new WeakMap,a={value:"fan"}},985:(e,t,r)=>{"use strict";r.r(t),r.d(t,{HomeView:()=>h});var i,a,o,n,s,c=r(371),d=r(1),l=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class h extends d.AbstractView{constructor(e={}){super(),i.add(this),a.set(this,{title:"Home",icon:"mdi:home-assistant",path:"home",subview:!1}),this.config=Object.assign(this.config,l(this,a,"f"),e)}async createViewCards(){return await Promise.all([l(this,i,"m",o).call(this),l(this,i,"m",n).call(this),l(this,i,"m",s).call(this)]).then((([e,t,r])=>{const i=c.W.strategyOptions,a=[];return e.length&&a.push({type:"custom:mushroom-chips-card",alignment:"center",chips:e}),t.length&&a.push({type:"horizontal-stack",cards:t}),c.W.strategyOptions.home_view.hidden.includes("greeting")||a.push({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"}}),i.quick_access_cards&&a.push(...i.quick_access_cards),a.push({type:"vertical-stack",cards:r}),i.extra_cards&&a.push(...i.extra_cards),a}))}}a=new WeakMap,i=new WeakSet,o=async function(){if(c.W.strategyOptions.home_view.hidden.includes("chips"))return[];const e=[],t=c.W.strategyOptions.chips,i=["light","fan","cover","switch","climate"],a=c.W.areas.map((e=>e.area_id??""));let o;const n=t?.weather_entity??c.W.entities.find((e=>e.entity_id.startsWith("weather.")&&null===e.disabled_by&&null===e.hidden_by))?.entity_id;if(n)try{o=await Promise.resolve().then(r.bind(r,977));const t=new o.WeatherChip(n);e.push(t.getChip())}catch(e){c.W.logError("An error occurred while creating the weather chip!",e)}for(let n of i)if(t?.[`${n}_count`]??1){const t=c.W.sanitizeClassName(n+"Chip");try{o=await r(837)(`./${t}`);const i=new o[t];i.setTapActionTarget({area_id:a}),e.push(i.getChip())}catch(e){c.W.logError(`An error occurred while creating the ${n} chip!`,e)}}return t?.extra_chips&&e.push(...t.extra_chips),e},n=function(){if(c.W.strategyOptions.home_view.hidden.includes("persons"))return[];const e=[];return Promise.resolve().then(r.bind(r,877)).then((t=>{for(const r of c.W.entities.filter((e=>e.entity_id.startsWith("person.")&&null==e.hidden_by&&null==e.disabled_by)))e.push(new t.PersonCard(r).getCard())})),e},s=async function(){if(c.W.strategyOptions.home_view.hidden.includes("areas"))return[];const e=[];let t=[];c.W.strategyOptions.home_view.hidden.includes("areasTitle")||e.push({type:"custom:mushroom-title-card",title:"Areas"});for(const[i,a]of c.W.areas.entries()){let o,n=c.W.strategyOptions.areas[a.area_id]?.type??c.W.strategyOptions.areas._?.type??"default";try{o=await r(175)(`./${n}`)}catch(e){o=await Promise.resolve().then(r.bind(r,396)),c.W.strategyOptions.debug&&"default"!==n&&console.error(e)}if(!c.W.strategyOptions.areas[a.area_id]?.hidden){let e={...c.W.strategyOptions.areas._,...c.W.strategyOptions.areas[a.area_id]};t.push(new o.AreaCard(a,e).getCard())}if(i===c.W.areas.length-1)for(let r=0;r{"use strict";r.r(t),r.d(t,{LightView:()=>h});var i,a,o,n,s=r(371),c=r(607),d=r(1),l=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class h extends d.AbstractView{constructor(e={}){super(l(i,i,"f",a)),o.set(this,{title:"Lights",path:"lights",icon:"mdi:lightbulb-group",subview:!1,controllerCardOptions:{iconOn:"mdi:lightbulb",iconOff:"mdi:lightbulb-off",onService:"light.turn_on",offService:"light.turn_off"}}),n.set(this,{title:"All Lights",subtitle:s.W.getCountTemplate(l(i,i,"f",a),"eq","on")+" lights on"}),this.config=Object.assign(this.config,l(this,o,"f"),e),this.viewControllerCard=new c.ControllerCard(this.targetDomain(l(i,i,"f",a)),{...l(this,n,"f"),..."controllerCardOptions"in this.config?this.config.controllerCardOptions:{}}).createCard()}}i=h,o=new WeakMap,n=new WeakMap,a={value:"light"}},683:(e,t,r)=>{"use strict";r.r(t),r.d(t,{SwitchView:()=>h});var i,a,o,n,s=r(371),c=r(607),d=r(1),l=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class h extends d.AbstractView{constructor(e={}){super(l(i,i,"f",a)),o.set(this,{title:"Switches",path:"switches",icon:"mdi:dip-switch",subview:!1,controllerCardOptions:{iconOn:"mdi:power-plug",iconOff:"mdi:power-plug-off",onService:"switch.turn_on",offService:"switch.turn_off"}}),n.set(this,{title:"All Switches",subtitle:s.W.getCountTemplate(l(i,i,"f",a),"eq","on")+" switches on"}),this.config=Object.assign(this.config,l(this,o,"f"),e),this.viewControllerCard=new c.ControllerCard(this.targetDomain(l(i,i,"f",a)),{...l(this,n,"f"),..."controllerCardOptions"in this.config?this.config.controllerCardOptions:{}}).createCard()}}i=h,o=new WeakMap,n=new WeakMap,a={value:"switch"}},610:(e,t,r)=>{"use strict";r.r(t),r.d(t,{VacuumView:()=>h});var i,a,o,n,s=r(371),c=r(607),d=r(1),l=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class h extends d.AbstractView{constructor(e={}){super(l(i,i,"f",a)),o.set(this,{title:"Vacuums",path:"vacuums",icon:"mdi:robot-vacuum",subview:!1,controllerCardOptions:{iconOn:"mdi:robot-vacuum",iconOff:"mdi:robot-vacuum-off",onService:"vacuum.start",offService:"vacuum.stop"}}),n.set(this,{title:"All Vacuums",subtitle:s.W.getCountTemplate(l(i,i,"f",a),"ne","off")+" vacuums on"}),this.config=Object.assign(this.config,l(this,o,"f"),e),this.viewControllerCard=new c.ControllerCard(this.targetDomain(l(i,i,"f",a)),{...l(this,n,"f"),..."controllerCardOptions"in this.config?this.config.controllerCardOptions:{}}).createCard()}}i=h,o=new WeakMap,n=new WeakMap,a={value:"vacuum"}},175:(e,t,r)=>{var i={"./AbstractCard":[976],"./AbstractCard.ts":[976],"./AreaCard":[396,179],"./AreaCard.ts":[396,179],"./BinarySensorCard":[418,179],"./BinarySensorCard.ts":[418,179],"./CameraCard":[604,179],"./CameraCard.ts":[604,179],"./ClimateCard":[782,179],"./ClimateCard.ts":[782,179],"./ControllerCard":[607],"./ControllerCard.ts":[607],"./CoverCard":[758,179],"./CoverCard.ts":[758,179],"./FanCard":[287,179],"./FanCard.ts":[287,179],"./HaAreaCard":[387,179],"./HaAreaCard.ts":[387,179],"./LightCard":[475,179],"./LightCard.ts":[475,179],"./LockCard":[696,179],"./LockCard.ts":[696,179],"./MediaPlayerCard":[499,179],"./MediaPlayerCard.ts":[499,179],"./MiscellaneousCard":[571,179],"./MiscellaneousCard.ts":[571,179],"./NumberCard":[420,179],"./NumberCard.ts":[420,179],"./PersonCard":[877,179],"./PersonCard.ts":[877,179],"./SensorCard":[642],"./SensorCard.ts":[642],"./SwitchCard":[330,179],"./SwitchCard.ts":[330,179],"./VacuumCard":[695,179],"./VacuumCard.ts":[695,179]};function a(e){if(!r.o(i,e))return Promise.resolve().then((()=>{var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}));var t=i[e],a=t[0];return Promise.all(t.slice(1).map(r.e)).then((()=>r(a)))}a.keys=()=>Object.keys(i),a.id=175,e.exports=a},837:(e,t,r)=>{var i={"./AbstractChip":[370,179],"./AbstractChip.ts":[370,179],"./ClimateChip":[660,179],"./ClimateChip.ts":[660,179],"./CoverChip":[993,179],"./CoverChip.ts":[993,179],"./FanChip":[966,179],"./FanChip.ts":[966,179],"./LightChip":[95,179],"./LightChip.ts":[95,179],"./SwitchChip":[817,179],"./SwitchChip.ts":[817,179],"./WeatherChip":[977,179],"./WeatherChip.ts":[977,179]};function a(e){if(!r.o(i,e))return Promise.resolve().then((()=>{var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}));var t=i[e],a=t[0];return r.e(t[1]).then((()=>r(a)))}a.keys=()=>Object.keys(i),a.id=837,e.exports=a},968:(e,t,r)=>{var i={"./AbstractView":[1,179],"./AbstractView.ts":[1,179],"./CameraView":[89,179],"./CameraView.ts":[89,179],"./ClimateView":[81,179],"./ClimateView.ts":[81,179],"./CoverView":[136,179],"./CoverView.ts":[136,179],"./FanView":[964,179],"./FanView.ts":[964,179],"./HomeView":[985,179],"./HomeView.ts":[985,179],"./LightView":[144,179],"./LightView.ts":[144,179],"./SwitchView":[683,179],"./SwitchView.ts":[683,179],"./VacuumView":[610,179],"./VacuumView.ts":[610,179]};function a(e){if(!r.o(i,e))return Promise.resolve().then((()=>{var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}));var t=i[e],a=t[0];return r.e(t[1]).then((()=>r(a)))}a.keys=()=>Object.keys(i),a.id=968,e.exports=a}},t={};function r(i){var a=t[i];if(void 0!==a)return a.exports;var o=t[i]={exports:{}};return e[i](o,o.exports,r),o.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var i in t)r.o(t,i)&&!r.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},r.e=()=>Promise.resolve(),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{"use strict";var e=r(371),t=r(642),i=r(607);class a extends HTMLTemplateElement{static async generateDashboard(t){await e.W.initialize(t);const i=t.config?.views??[];let a;for(let t of e.W.getExposedViewIds())try{const o=e.W.sanitizeClassName(t+"View");a=await r(968)(`./${o}`);const n=await new a[o](e.W.strategyOptions.views[t]).getView();n.cards?.length&&i.push(n)}catch(r){e.W.logError(`View '${t}' couldn't be loaded!`,r)}for(let t of e.W.areas)t.hidden||i.push({title:t.name,path:t.area_id??t.name,subview:!0,strategy:{type:"custom:mushroom-strategy",options:{area:t}}});return e.W.strategyOptions.extra_views&&i.push(...e.W.strategyOptions.extra_views),{views:i}}static async generateView(a){const o=e.W.getExposedDomainIds(),n=a.view.strategy?.options?.area??{},s=[...n.extra_cards??[]];let c={area_id:[n.area_id]};for(const a of o){if("default"===a)continue;const o=e.W.sanitizeClassName(a+"Card");let d=[];try{d=await r(175)(`./${o}`).then((r=>{let s=[];const d=e.W.getDeviceEntities(n,a);if("undisclosed"===n.area_id&&(c={entity_id:d.map((e=>e.entity_id))}),d.length){const l=new i.ControllerCard(c,e.W.strategyOptions.domains[a]).createCard();if("sensor"===a){const r=e.W.getStateEntities(n,"sensor"),i=[];for(const a of d){const o=r.find((e=>e.entity_id===a.entity_id));let n=e.W.strategyOptions.card_options?.[a.entity_id],s=e.W.strategyOptions.card_options?.[a.device_id??"null"];n?.hidden||s?.hidden||(o?.attributes.unit_of_measurement&&(n={type:"custom:mini-graph-card",entities:[a.entity_id],...n}),i.push(new t.SensorCard(a,n).getCard()))}return i.length&&(s.push({type:"vertical-stack",cards:i}),s.unshift(l)),s}for(const t of d){let i,a=e.W.strategyOptions.card_options?.[t.entity_id];t.device_id&&(i=e.W.strategyOptions.card_options?.[t.device_id]),a?.hidden||i?.hidden||s.push(new r[o](t,a).getCard())}if("binary_sensor"===a){const e=[];for(let t=0;te.area_id===n.area_id)).map((e=>e.id)),a=e.W.entities.filter((e=>{const r=t.includes(e.device_id??"null")||e.area_id===n.area_id,i=null===e.hidden_by&&null===e.disabled_by,a=o.includes(e.entity_id.split(".",1)[0]);return i&&!a&&r}));if(a.length){let t=[];try{t=await Promise.resolve().then(r.bind(r,571)).then((t=>{const r=[new i.ControllerCard(c,e.W.strategyOptions.domains.default).createCard()];for(const i of a){let a=e.W.strategyOptions.card_options?.[i.entity_id],o=e.W.strategyOptions.card_options?.[i.device_id??"null"];a?.hidden||o?.hidden||r.push(new t.MiscellaneousCard(i,a).getCard())}return r}))}catch(t){e.W.logError("An error occurred while creating the domain cards!",t)}s.push({type:"vertical-stack",cards:t})}}return{cards:s}}}customElements.define("ll-strategy-mushroom-strategy",a),console.info("%c Mushroom Strategy %c ".concat("v2.0.0"," "),"color: white; background: coral; font-weight: 700;","color: coral; background: white; font-weight: 700;")})()})(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 26eec92..778aab1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1335 +1,2428 @@ { - "name": "mushroom-strategy", - "version": "v1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "mushroom-strategy", - "version": "v1.0.0", - "license": "MIT", - "devDependencies": { - "webpack": "^5", - "webpack-cli": "^5.1.4" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@types/eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", - "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "18.15.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", - "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", - "dev": true - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.5.tgz", - "integrity": "sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.5", - "@webassemblyjs/helper-wasm-bytecode": "1.11.5" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz", - "integrity": "sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz", - "integrity": "sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz", - "integrity": "sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz", - "integrity": "sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA==", - "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.5", - "@webassemblyjs/helper-api-error": "1.11.5", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz", - "integrity": "sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz", - "integrity": "sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.5", - "@webassemblyjs/helper-buffer": "1.11.5", - "@webassemblyjs/helper-wasm-bytecode": "1.11.5", - "@webassemblyjs/wasm-gen": "1.11.5" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz", - "integrity": "sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.5.tgz", - "integrity": "sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.5.tgz", - "integrity": "sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz", - "integrity": "sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.5", - "@webassemblyjs/helper-buffer": "1.11.5", - "@webassemblyjs/helper-wasm-bytecode": "1.11.5", - "@webassemblyjs/helper-wasm-section": "1.11.5", - "@webassemblyjs/wasm-gen": "1.11.5", - "@webassemblyjs/wasm-opt": "1.11.5", - "@webassemblyjs/wasm-parser": "1.11.5", - "@webassemblyjs/wast-printer": "1.11.5" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz", - "integrity": "sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.5", - "@webassemblyjs/helper-wasm-bytecode": "1.11.5", - "@webassemblyjs/ieee754": "1.11.5", - "@webassemblyjs/leb128": "1.11.5", - "@webassemblyjs/utf8": "1.11.5" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz", - "integrity": "sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.5", - "@webassemblyjs/helper-buffer": "1.11.5", - "@webassemblyjs/wasm-gen": "1.11.5", - "@webassemblyjs/wasm-parser": "1.11.5" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz", - "integrity": "sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.5", - "@webassemblyjs/helper-api-error": "1.11.5", - "@webassemblyjs/helper-wasm-bytecode": "1.11.5", - "@webassemblyjs/ieee754": "1.11.5", - "@webassemblyjs/leb128": "1.11.5", - "@webassemblyjs/utf8": "1.11.5" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz", - "integrity": "sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.5", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001481", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz", - "integrity": "sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.369", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.369.tgz", - "integrity": "sha512-LfxbHXdA/S+qyoTEA4EbhxGjrxx7WK2h6yb5K2v0UCOufUKX+VZaHbl3svlzZfv9sGseym/g3Ne4DpsgRULmqg==", - "dev": true - }, - "node_modules/enhanced-resolve": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz", - "integrity": "sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", - "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "dev": true - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, - "dependencies": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/schema-utils": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", - "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz", - "integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", - "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.5" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack": { - "version": "5.80.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz", - "integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.13.0", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "dev": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true + "name": "mushroom-strategy", + "version": "2.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mushroom-strategy", + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "deepmerge": "^4" + }, + "devDependencies": { + "home-assistant-js-websocket": "^9", + "superstruct": "^1", + "ts-loader": "^9", + "ts-node": "^10", + "typescript": "^5", + "version-bump-prompt": "^6", + "webpack": "^5", + "webpack-cli": "^5" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsdevtools/ez-spawn": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@jsdevtools/ez-spawn/-/ez-spawn-3.0.4.tgz", + "integrity": "sha512-f5DRIOZf7wxogefH03RjMPMdBF7ADTWUMoOs9kaJo06EfwF+aFhMZMDZxHg/Xe12hptN9xoZjGso2fdjapBRIA==", + "dev": true, + "dependencies": { + "call-me-maybe": "^1.0.1", + "cross-spawn": "^7.0.3", + "string-argv": "^0.3.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jsdevtools/version-bump-prompt": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@jsdevtools/version-bump-prompt/-/version-bump-prompt-6.1.0.tgz", + "integrity": "sha512-NJFLJRiD3LLFBgSxAb6B255xhWCGgdtzmh6UjHK2b7SRGX2DDKJH5O4BJ0GTStBu4NnaNgMbkr1TLW3pLOBkOQ==", + "dev": true, + "dependencies": { + "@jsdevtools/ez-spawn": "^3.0.4", + "command-line-args": "^5.1.1", + "detect-indent": "^6.0.0", + "detect-newline": "^3.1.0", + "globby": "^11.0.1", + "inquirer": "^7.3.3", + "log-symbols": "^4.0.0", + "semver": "^7.3.2" + }, + "bin": { + "bump": "bin/bump.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/eslint": { + "version": "8.44.6", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.6.tgz", + "integrity": "sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.6.tgz", + "integrity": "sha512-zfM4ipmxVKWdxtDaJ3MP3pBurDXOCoyjvlpE3u6Qzrmw4BPbfm4/ambIeTk/r/J0iq/+2/xp0Fmt+gFvXJY2PQ==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.4.tgz", + "integrity": "sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", + "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.8.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", + "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", + "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001561", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz", + "integrity": "sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "dev": true, + "dependencies": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.576", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.576.tgz", + "integrity": "sha512-yXsZyXJfAqzWk1WKryr0Wl0MN2D47xodPvEEwlVePBnhU5E7raevLQR+E6b9JAD3GfL/7MbAL9ZtWQQPcLx7wA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", + "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/home-assistant-js-websocket": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/home-assistant-js-websocket/-/home-assistant-js-websocket-9.1.0.tgz", + "integrity": "sha512-R2LEMX0h5r6lfDydrobgHaA/HkZv45B8UHC96j9oLPJ1qETSfSmWLy8AF/RthjT+6kWWnZDlt7VU1EfNVT0wuQ==", + "dev": true + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/superstruct": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.3.tgz", + "integrity": "sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", + "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-loader": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz", + "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/version-bump-prompt": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/version-bump-prompt/-/version-bump-prompt-6.1.0.tgz", + "integrity": "sha512-GYC83GP8QOunWueKf2mbtZkdmisXhnBZPhIHWUmN/Yi4XXAQlIi9avM/IGWdI7KkJLfMENzGN1Xee+Zl3VJ5jg==", + "dev": true, + "dependencies": { + "@jsdevtools/version-bump-prompt": "6.1.0" + }, + "bin": { + "bump": "bump.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } } + } } diff --git a/package.json b/package.json index c50fcf1..67e50e7 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,42 @@ { - "name": "mushroom-strategy", - "version": "v1.0.0", - "description": "Automatically create a dashboard using Mushroom cards", - "keywords": [ - "strategy", - "mushroom" - ], - "homepage": "https://github.com/AalianKhan/mushroom-strategy", - "bugs": "https://github.com/AalianKhan/mushroom-strategy/issues", - "license": "MIT", - "author": { - "name": "Aalian Khan" - }, - "contributors": [ - { - "name": "Ferry Cools" - } - ], - "repository": { - "type": "git", - "url": "https://github.com/AalianKhan/mushroom-strategy" - }, - "devDependencies": { - "webpack": "^5", - "webpack-cli": "^5.1.4" - }, - "scripts": { - "build": "webpack", - "build-dev": "webpack --config webpack.dev.config.js" + "name": "mushroom-strategy", + "version": "2.0.0", + "description": "Automatically create a dashboard using Mushroom cards", + "keywords": [ + "strategy", + "mushroom" + ], + "homepage": "https://github.com/AalianKhan/mushroom-strategy", + "bugs": "https://github.com/AalianKhan/mushroom-strategy/issues", + "license": "MIT", + "author": { + "name": "Aalian Khan" + }, + "contributors": [ + { + "name": "Ferry Cools" } + ], + "repository": { + "type": "git", + "url": "https://github.com/AalianKhan/mushroom-strategy" + }, + "dependencies": { + "deepmerge": "^4" + }, + "devDependencies": { + "home-assistant-js-websocket": "^9", + "superstruct": "^1", + "ts-node": "^10", + "ts-loader": "^9", + "typescript": "^5", + "version-bump-prompt": "^6", + "webpack": "^5", + "webpack-cli": "^5" + }, + "scripts": { + "build-dev": "webpack --config webpack.dev.config.ts", + "build": "webpack", + "bump": "bump -t --preid alpha package.json package-lock.json README.md ./src/mushroom-strategy.ts" + } } diff --git a/src/Helper.js b/src/Helper.ts similarity index 50% rename from src/Helper.js rename to src/Helper.ts index dd87fe6..d5daa53 100644 --- a/src/Helper.js +++ b/src/Helper.ts @@ -1,4 +1,11 @@ -import {optionDefaults} from "./optionDefaults"; +import {configurationDefaults} from "./configurationDefaults"; +import {HassEntities, HassEntity} from "home-assistant-js-websocket"; +import deepmerge from "deepmerge"; +import {EntityRegistryEntry} from "./types/homeassistant/data/entity_registry"; +import {DeviceRegistryEntry} from "./types/homeassistant/data/device_registry"; +import {AreaRegistryEntry} from "./types/homeassistant/data/area_registry"; +import {generic} from "./types/strategy/generic"; +import StrategyArea = generic.StrategyArea; /** * Helper Class @@ -9,31 +16,34 @@ class Helper { /** * An array of entities from Home Assistant's entity registry. * - * @type {hassEntity[]} + * @type {EntityRegistryEntry[]} * @private */ - static #entities; + static #entities: EntityRegistryEntry[]; + /** * An array of entities from Home Assistant's device registry. * - * @type {deviceEntity[]} + * @type {DeviceRegistryEntry[]} * @private */ - static #devices; + static #devices: DeviceRegistryEntry[]; + /** * An array of entities from Home Assistant's area registry. * - * @type {areaEntity[]} + * @type {StrategyArea[]} * @private */ - static #areas = []; + static #areas: StrategyArea[] = []; + /** * An array of state entities from Home Assistant's Hass object. * - * @type {hassObject["states"]} + * @type {HassEntities} * @private */ - static #hassStates; + static #hassStates: HassEntities; /** * Indicates whether this module is initialized. @@ -41,27 +51,30 @@ class Helper { * @type {boolean} True if initialized. * @private */ - static #initialized = false; + static #initialized: boolean = false; /** * The Custom strategy configuration. * - * @type {customStrategyOptions | {}} + * @type {generic.StrategyConfig} * @private */ - static #strategyOptions = {}; + static #strategyOptions: generic.StrategyConfig; /** * Set to true for more verbose information in the console. * * @type {boolean} + * @private */ - static debug = optionDefaults.debug; + static #debug: boolean; /** * Class constructor. * - * This class shouldn't be instantiated directly. Instead, it should be initialized with method initialize(). + * This class shouldn't be instantiated directly. + * Instead, it should be initialized with method initialize(). + * * @throws {Error} If trying to instantiate this class. */ constructor() { @@ -71,127 +84,115 @@ class Helper { /** * Custom strategy configuration. * - * @returns {customStrategyOptions|{}} + * @returns {generic.StrategyConfig} * @static */ - static get strategyOptions() { + static get strategyOptions(): generic.StrategyConfig { return this.#strategyOptions; } /** - * @returns {areaEntity[]} + * Get the entities from Home Assistant's area registry. + * + * @returns {StrategyArea[]} * @static */ - static get areas() { + static get areas(): StrategyArea[] { return this.#areas; } /** - * @returns {deviceEntity[]} + * Get the devices from Home Assistant's device registry. + * + * @returns {DeviceRegistryEntry[]} * @static */ - static get devices() { + static get devices(): DeviceRegistryEntry[] { return this.#devices; } /** - * @returns {hassEntity[]} + * Get the entities from Home Assistant's entity registry. + * + * @returns {EntityRegistryEntry[]} * @static */ - static get entities() { + static get entities(): EntityRegistryEntry[] { return this.#entities; } /** + * Get the current debug mode of the mushroom strategy. + * * @returns {boolean} * @static */ - static get debug() { - return this.debug; + static get debug(): boolean { + return this.#debug; } /** * Initialize this module. * - * @param {dashBoardInfo | viewInfo} info Strategy information object. + * @param {generic.DashBoardInfo} info Strategy information object. * @returns {Promise} * @static */ - static async initialize(info) { + static async initialize(info: generic.DashBoardInfo): Promise { + // Initialize properties. this.#hassStates = info.hass.states; + this.#strategyOptions = deepmerge(configurationDefaults, info.config?.strategy?.options ?? {}); + this.#debug = this.#strategyOptions.debug; try { // Query the registries of Home Assistant. - [this.#entities, this.#devices, this.#areas] = await Promise.all([ - info.hass.callWS({type: "config/entity_registry/list"}), - info.hass.callWS({type: "config/device_registry/list"}), - info.hass.callWS({type: "config/area_registry/list"}), + + // noinspection ES6MissingAwait False positive? https://youtrack.jetbrains.com/issue/WEB-63746 + [Helper.#entities, Helper.#devices, Helper.#areas] = await Promise.all([ + info.hass.callWS({type: "config/entity_registry/list"}) as Promise, + info.hass.callWS({type: "config/device_registry/list"}) as Promise, + info.hass.callWS({type: "config/area_registry/list"}) as Promise, ]); } catch (e) { - console.error(Helper.debug ? e : "An error occurred while querying Home assistant's registries!"); + Helper.logError("An error occurred while querying Home assistant's registries!", e); + throw 'Check the console for details'; } - // Cloning is required for the purpose of the required undisclosed area. - this.#strategyOptions = structuredClone(info.config.strategy.options || {}); - this.debug = this.#strategyOptions.debug; - - // Setup required configuration entries. - // TODO: Refactor to something smarter than repeating code for areas, views and domains. - this.#strategyOptions.areas = this.#strategyOptions.areas ?? {}; - this.#strategyOptions.views = this.#strategyOptions.views ?? {}; - this.#strategyOptions.domains = this.#strategyOptions.domains ?? {}; - - // Setup and add the undisclosed area if not hidden in the strategy options. + // Create and add the undisclosed area if not hidden in the strategy options. if (!this.#strategyOptions.areas.undisclosed?.hidden) { this.#strategyOptions.areas.undisclosed = { - ...optionDefaults.areas.undisclosed, + ...configurationDefaults.areas.undisclosed, ...this.#strategyOptions.areas.undisclosed, }; - // Make sure the area_id of the custom undisclosed area remains null. - this.#strategyOptions.areas.undisclosed.area_id = null; + // Make sure the custom configuration of the undisclosed area doesn't overwrite the area_id. + this.#strategyOptions.areas.undisclosed.area_id = "undisclosed"; this.#areas.push(this.#strategyOptions.areas.undisclosed); } - // Merge custom areas of the strategy options into hass areas. + // Merge custom areas of the strategy options into strategy areas. this.#areas = Helper.areas.map(area => { - return {...area, ...this.#strategyOptions.areas[area.area_id ?? "undisclosed"]}; + return {...area, ...this.#strategyOptions.areas?.[area.area_id]}; }); - // Sort hass areas by order first and then by name. + // Sort strategy areas by order first and then by name. this.#areas.sort((a, b) => { return (a.order ?? Infinity) - (b.order ?? Infinity) || a.name.localeCompare(b.name); }); - // Merge the views of the strategy options and the default views. - for (const view of Object.keys(optionDefaults.views)) { - this.#strategyOptions.views[view] = { - ...optionDefaults.views[view], - ...(this.#strategyOptions.views[view]), - }; - } - - // Sort views of the strategy options by order first and then by title. + // Sort custom and default views of the strategy options by order first and then by title. this.#strategyOptions.views = Object.fromEntries( - Object.entries(this.#strategyOptions.views).sort(([, a], [, b]) => { - return (a.order ?? Infinity) - (b.order ?? Infinity) || a.title?.localeCompare(b.title); - }), + Object.entries(this.#strategyOptions.views).sort(([, a], [, b]) => { + return (a.order ?? Infinity) - (b.order ?? Infinity) || (a.title ?? "undefined").localeCompare(b.title ?? "undefined"); + }), ); - // Merge the domains of the strategy options and the default domains. - for (const domain of Object.keys(optionDefaults.domains)) { - this.#strategyOptions.domains[domain] = { - ...optionDefaults.domains[domain], - ...(this.#strategyOptions.domains[domain]), - }; - } - - // Sort domains of the strategy options by order first and then by title. + // Sort custom and default domains of the strategy options by order first and then by title. this.#strategyOptions.domains = Object.fromEntries( - Object.entries(this.#strategyOptions.domains).sort(([, a], [, b]) => { - return (a.order ?? Infinity) - (b.order ?? Infinity) || a.title?.localeCompare(b.title); - }), + Object.entries(this.#strategyOptions.domains).sort(([, a], [, b]) => { + return (a.order ?? Infinity) - (b.order ?? Infinity) || (a.title ?? "undefined").localeCompare(b.title ?? "undefined"); + }), ); this.#initialized = true; @@ -203,7 +204,7 @@ class Helper { * @returns {boolean} True if this module is initialized. * @static */ - static isInitialized() { + static isInitialized(): boolean { return this.#initialized; } @@ -219,7 +220,7 @@ class Helper { * @return {string} The template string. * @static */ - static getCountTemplate(domain, operator, value) { + static getCountTemplate(domain: string, operator: string, value: string): string { // noinspection JSMismatchedCollectionQueryUpdate (False positive per 17-04-2023) /** * Array of entity state-entries, filtered by domain. @@ -232,7 +233,7 @@ class Helper { * * @type {string[]} */ - const states = []; + const states: string[] = []; if (!this.isInitialized()) { console.warn("Helper class should be initialized before calling this method!"); @@ -240,20 +241,20 @@ class Helper { // Get the ID of the devices which are linked to the given area. for (const area of this.#areas) { - const areaDeviceIds = this.#devices.filter(device => { + const areaDeviceIds = this.#devices.filter((device) => { return device.area_id === area.area_id; - }).map(device => { + }).map((device) => { return device.id; }); // Get the entities of which all conditions of the callback function are met. @see areaFilterCallback. const newStates = this.#entities.filter( - this.#areaFilterCallback, { - area: area, - domain: domain, - areaDeviceIds: areaDeviceIds, - }) - .map(entity => `states['${entity.entity_id}']`); + this.#areaFilterCallback, { + area: area, + domain: domain, + areaDeviceIds: areaDeviceIds, + }) + .map((entity) => `states['${entity.entity_id}']`); states.push(...newStates); } @@ -261,35 +262,6 @@ class Helper { return `{% set entities = [${states}] %} {{ entities | selectattr('state','${operator}','${value}') | list | count }}`; } - /** - * Callback function for filtering entities. - * - * Entities of which all the conditions below are met are kept: - * 1. Or/Neither the entity's linked device (if any) or/nor the entity itself is lined to the given area. - * (See variable areaMatch) - * 2. The entity's domain matches the given domain. - * 3. The entity is not hidden and is not disabled. - * - * @param {hassEntity} entity The current hass entity to evaluate. - * @this {areaFilterContext} - * - * @return {boolean} True to keep the entity. - * @static - */ - static #areaFilterCallback(entity) { - const areaMatch = this.area.area_id - // Area is a hass entity; The entity's linked device or the entity itself is linked to the given area. - ? this.areaDeviceIds.includes(entity.device_id) || entity.area_id === this.area.area_id - // Undisclosed area; Neither the entity's linked device (if any), nor the entity itself is linked to any area. - : (this.areaDeviceIds.includes(entity.device_id) || !entity.device_id) && !entity.area_id; - - return ( - areaMatch - && entity.entity_id.startsWith(`${this.domain}.`) - && entity.hidden_by == null && entity.disabled_by == null - ); - } - /** * Get device entities from the entity registry, filtered by area and domain. * @@ -298,36 +270,35 @@ class Helper { * * The result excludes hidden and disabled entities. * - * @param {areaEntity} area Area entity. + * @param {AreaRegistryEntry} area Area entity. * @param {string} domain The domain of the entity-id. * - * @return {hassEntity[]} Array of device entities. + * @return {EntityRegistryEntry[]} Array of device entities. * @static */ - static getDeviceEntities(area, domain) { + static getDeviceEntities(area: AreaRegistryEntry, domain: string): EntityRegistryEntry[] { if (!this.isInitialized()) { console.warn("Helper class should be initialized before calling this method!"); } // Get the ID of the devices which are linked to the given area. - const areaDeviceIds = this.#devices.filter(device => { - return device.area_id === area.area_id; - }).map(device => { + const areaDeviceIds = this.#devices.filter((device) => { + return (device.area_id ?? "undisclosed") === area.area_id; + }).map((device: DeviceRegistryEntry) => { return device.id; }); // Return the entities of which all conditions of the callback function are met. @see areaFilterCallback. return this.#entities.filter( - this.#areaFilterCallback, { - area: area, - domain: domain, - areaDeviceIds: areaDeviceIds, - }) - .sort((a, b) => { - /** @type hassEntity */ - return a.original_name?.localeCompare(b.original_name); - }); + this.#areaFilterCallback, { + area: area, + domain: domain, + areaDeviceIds: areaDeviceIds, + }) + .sort((a, b) => { + return (a.original_name ?? "undefined").localeCompare(b.original_name ?? "undefined"); + }); } /** @@ -335,39 +306,41 @@ class Helper { * * The result excludes hidden and disabled entities. * - * @param {areaEntity} area Area entity. + * @param {AreaRegistryEntry} area Area entity. * @param {string} domain Domain of the entity-id. * - * @return {stateObject[]} Array of state entities. + * @return {HassEntity[]} Array of state entities. */ - static getStateEntities(area, domain) { + static getStateEntities(area: AreaRegistryEntry, domain: string): HassEntity[] { if (!this.isInitialized()) { console.warn("Helper class should be initialized before calling this method!"); } - const states = []; + const states: HassEntity[] = []; // Create a map for the hassEntities and devices {id: object} to improve lookup speed. - /** @type {Object} */ - const entityMap = Object.fromEntries(this.#entities.map(entity => [entity.entity_id, entity])); - /** @type {Object} */ - const deviceMap = Object.fromEntries(this.#devices.map(device => [device.id, device])); + const entityMap: { + [s: string]: EntityRegistryEntry; + } = Object.fromEntries(this.#entities.map((entity) => [entity.entity_id, entity])); + const deviceMap: { + [s: string]: DeviceRegistryEntry; + } = Object.fromEntries(this.#devices.map((device) => [device.id, device])); // Get states whose entity-id starts with the given string. const stateEntities = Object.values(this.#hassStates).filter( - state => state.entity_id.startsWith(`${domain}.`), + (state) => state.entity_id.startsWith(`${domain}.`), ); for (const state of stateEntities) { const hassEntity = entityMap[state.entity_id]; - const device = deviceMap[hassEntity?.device_id]; + const device = deviceMap[hassEntity?.device_id ?? ""]; // Collect states of which any (whichever comes first) of the conditions below are met: // 1. The linked entity is linked to the given area. // 2. The entity is linked to a device, and the linked device is linked to the given area. if ( - (hassEntity?.area_id === area.area_id) - || (device && device.area_id === area.area_id) + (hassEntity?.area_id === area.area_id) + || (device && device.area_id === area.area_id) ) { states.push(state); } @@ -385,28 +358,91 @@ class Helper { * @param {string} className Name of the class to sanitize. * @returns {string} The sanitized classname. */ - static sanitizeClassName(className) { + static sanitizeClassName(className: string): string { className = className.charAt(0).toUpperCase() + className.slice(1); - return className.replace(/([-_][a-z])/g, group => - group - .toUpperCase() - .replace("-", "") - .replace("_", ""), + return className.replace(/([-_][a-z])/g, (group) => group + .toUpperCase() + .replace("-", "") + .replace("_", ""), ); } + /** + * Get the ids of the views which aren't set to hidden in the strategy options. + * + * @return {string[]} An array of view ids. + */ + static getExposedViewIds(): string[] { + 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(): string[] { + if (!this.isInitialized()) { + console.warn("Helper class should be initialized before calling this method!"); + } + + return this.#getObjectKeysByPropertyValue(this.#strategyOptions.domains, "hidden", false); + } + + /** + * Callback function for filtering entities. + * + * Entities of which all the conditions below are met are kept: + * 1. The entity is not hidden and is not disabled. + * 2. The entity's domain matches the given domain. + * 3. Or/Neither the entity's linked device (if any) or/nor the entity itself is linked to the given area. + * (See variable areaMatch) + * + * @param {EntityRegistryEntry} entity The current hass entity to evaluate. + * @this {AreaFilterContext} + * + * @return {boolean} True to keep the entity. + * @static + */ + static #areaFilterCallback( + this: { + area: AreaRegistryEntry, + areaDeviceIds: string[], + domain: string, + }, + entity: EntityRegistryEntry): boolean { + const entityUnhidden = entity.hidden_by === null && entity.disabled_by === null; + const domainMatches = entity.entity_id.startsWith(`${this.domain}.`); + const entityLinked = this.area.area_id === "undisclosed" + // Undisclosed area; + // nor the entity itself, neither the entity's linked device (if any) is linked to any area. + ? !entity.area_id && (this.areaDeviceIds.includes(entity.device_id ?? "") || !entity.device_id) + // Area is a hass entity; + // The entity's linked device or the entity itself is linked to the given area. + : this.areaDeviceIds.includes(entity.device_id ?? "") || entity.area_id === this.area.area_id; + + return (entityUnhidden && domainMatches && entityLinked); + } + /** * Get the keys of nested objects by its property value. * - * @param {Object} object An object of objects. + * @param {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. + * @return {string[]} An array with keys. */ - static #getObjectKeysByPropertyValue(object, property, value) { - const keys = []; + static #getObjectKeysByPropertyValue( + object: { [k: string]: any }, + property: string, value: any + ): string[] { + const keys: string[] = []; for (const key of Object.keys(object)) { if (object[key][property] === value) { @@ -418,29 +454,21 @@ class Helper { } /** - * Get the ids of the views which aren't set to hidden in the strategy options. + * Logs an error message to the console. * - * @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. + * @param {string} userMessage - The error message to display. + * @param {unknown} [e] - (Optional) The error object or additional information. * - * @return {string[]} An array of domain ids. + * @return {void} */ - static getExposedDomainIds() { - if (!this.isInitialized()) { - console.warn("Helper class should be initialized before calling this method!"); + static logError(userMessage: string, e?: unknown): void { + if (Helper.debug) { + console.error(userMessage, e); + + return; } - return this.#getObjectKeysByPropertyValue(this.#strategyOptions.domains, "hidden", false); + console.error(userMessage); } } diff --git a/src/cards/AbstractCard.js b/src/cards/AbstractCard.js deleted file mode 100644 index 6aeea91..0000000 --- a/src/cards/AbstractCard.js +++ /dev/null @@ -1,81 +0,0 @@ -import {Helper} from "../Helper"; - -/** - * Abstract Card Class - * - * To create a new card, extend the new class with this one. - * - * @class - * @abstract - */ -class AbstractCard { - /** - * Entity to create the card for. - * - * @type {hassEntity | areaEntity} - */ - entity; - - /** - * Options for creating a card. - * - * @type {abstractOptions} - */ - options = { - type: "custom:mushroom-entity-card", - icon: "mdi:help-circle", - double_tap_action: { - action: null, - }, - }; - - /** - * Class constructor. - * - * @param {hassEntity | areaEntity} entity The hass entity to create a card for. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(entity) { - if (this.constructor === AbstractCard) { - throw new Error("Abstract classes can't be instantiated."); - } - - if (!Helper.isInitialized()) { - throw new Error("The Helper module must be initialized before using this one."); - } - - this.entity = entity; - } - - /** - * Merge the default options of this class and the custom options into the options of the parent class. - * - * @param {Object} [defaultOptions={}] Default options for the card. - * @param {Object} [customOptions={}] Custom Options for the card. - */ - mergeOptions(defaultOptions, customOptions) { - this.options = { - ...this.options, - ...defaultOptions, - ...customOptions, - }; - - try { - this.options.double_tap_action.target.entity_id = this.entity.entity_id; - } catch { } - } - - /** - * Get a card for an entity. - * - * @return {abstractOptions & Object} A card object. - */ - getCard() { - return { - entity: this.entity.entity_id, - ...this.options, - }; - } -} - -export {AbstractCard}; diff --git a/src/cards/AbstractCard.ts b/src/cards/AbstractCard.ts new file mode 100644 index 0000000..3337c75 --- /dev/null +++ b/src/cards/AbstractCard.ts @@ -0,0 +1,59 @@ +import {Helper} from "../Helper"; +import {cards} from "../types/strategy/cards"; +import {generic} from "../types/strategy/generic"; +import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config"; + +/** + * Abstract Card Class + * + * To create a new card, extend the new class with this one. + * + * @class + * @abstract + */ +abstract class AbstractCard { + /** + * Entity to create the card for. + * + * @type {generic.RegistryEntry} + */ + entity: generic.RegistryEntry; + + /** + * Configuration of the card. + * + * @type {EntityCardConfig} + */ + config: EntityCardConfig = { + type: "custom:mushroom-entity-card", + icon: "mdi:help-circle", + }; + + /** + * Class constructor. + * + * @param {generic.RegistryEntry} entity The hass entity to create a card for. + * @throws {Error} If the Helper module isn't initialized. + */ + protected constructor(entity: generic.RegistryEntry) { + if (!Helper.isInitialized()) { + throw new Error("The Helper module must be initialized before using this one."); + } + + this.entity = entity; + } + + /** + * Get a card. + * + * @return {cards.AbstractCardConfig} A card object. + */ + getCard(): cards.AbstractCardConfig { + return { + ...this.config, + entity: "entity_id" in this.entity ? this.entity.entity_id : undefined, + }; + } +} + +export {AbstractCard}; diff --git a/src/cards/AreaCard.js b/src/cards/AreaCard.js deleted file mode 100644 index 16b4212..0000000 --- a/src/cards/AreaCard.js +++ /dev/null @@ -1,62 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * Area Card Class - * - * Used to create a card for an entity of the area domain. - * - * @class - * @extends AbstractCard - */ -class AreaCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {areaCardOptions} - * @private - */ - #defaultOptions = { - type: "custom:mushroom-template-card", - primary: undefined, - icon: "mdi:texture-box", - icon_color: "blue", - tap_action: { - action: "navigate", - navigation_path: undefined, - }, - hold_action: { - action: "none", - }, - }; - - /** - * Class constructor. - * - * @param {areaEntity} area The area entity to create a card for. - * @param {areaCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(area, options = {}) { - super(area); - - this.#defaultOptions.primary = area.name; - this.#defaultOptions.tap_action.navigation_path = area.area_id ?? area.name; - - // Set card type to default if a type "default" is given in strategy options. - if (options.type === "default") { - options.type = this.#defaultOptions.type; - } - - this.mergeOptions( - this.#defaultOptions, - options, - ); - - // Override the area's name with a custom name, unless a custom primary text is set. - if (!options.primary && options.name) { - this.options.primary = options.name; - } - } -} - -export {AreaCard}; diff --git a/src/cards/AreaCard.ts b/src/cards/AreaCard.ts new file mode 100644 index 0000000..fe28595 --- /dev/null +++ b/src/cards/AreaCard.ts @@ -0,0 +1,62 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {AreaRegistryEntry} from "../types/homeassistant/data/area_registry"; +import {TemplateCardConfig} from "../types/lovelace-mushroom/cards/template-card-config"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Area Card Class + * + * Used to create a card for an entity of the area domain. + * + * @class + * @extends AbstractCard + */ +class AreaCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {TemplateCardConfig} + * @private + */ + #defaultConfig: TemplateCardConfig = { + type: "custom:mushroom-template-card", + primary: undefined, + icon: "mdi:texture-box", + icon_color: "blue", + tap_action: { + action: "navigate", + navigation_path: "", + }, + hold_action: { + action: "none", + }, + }; + + /** + * Class constructor. + * + * @param {AreaRegistryEntry} area The area entity to create a card for. + * @param {cards.TemplateCardOptions} [options={}] Options for the card. + * + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(area: AreaRegistryEntry, options: cards.TemplateCardOptions = {}) { + super(area); + + // Don't override the default card type if default is set in the strategy options. + if (options.type === "default") { + delete options.type; + } + + // Initialize the default configuration. + this.#defaultConfig.primary = area.name; + if (this.#defaultConfig.tap_action && ("navigation_path" in this.#defaultConfig.tap_action)) { + this.#defaultConfig.tap_action.navigation_path = area.area_id; + } + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {AreaCard}; diff --git a/src/cards/BinarySensorCard.js b/src/cards/BinarySensorCard.js deleted file mode 100644 index bd2cfdd..0000000 --- a/src/cards/BinarySensorCard.js +++ /dev/null @@ -1,41 +0,0 @@ -import {SensorCard} from "./SensorCard"; - -/** - * Sensor Card Class - * - * Used to create a card for controlling an entity of the binary_sensor domain. - * - * @class - * @extends SensorCard - */ -class BinarySensorCard extends SensorCard { - /** - * Default options of the card. - * - * @type {sensorCardOptions} - * @private - */ - #defaultOptions = { - type: "custom:mushroom-entity-card", - icon: "mdi:power-cycle", - icon_color: "green", - }; - - /** - * Class constructor. - * - * @param {hassEntity} entity The hass entity to create a card for. - * @param {sensorCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(entity, options = {}) { - super(entity); - - this.mergeOptions( - this.#defaultOptions, - options, - ); - } -} - -export {BinarySensorCard}; diff --git a/src/cards/BinarySensorCard.ts b/src/cards/BinarySensorCard.ts new file mode 100644 index 0000000..7d7d8a6 --- /dev/null +++ b/src/cards/BinarySensorCard.ts @@ -0,0 +1,42 @@ +import {SensorCard} from "./SensorCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Sensor Card Class + * + * Used to create a card for controlling an entity of the binary_sensor domain. + * + * @class + * @extends SensorCard + */ +class BinarySensorCard extends SensorCard { + /** + * Default configuration of the card. + * + * @type {EntityCardConfig} + * @private + */ + #defaultConfig: EntityCardConfig = { + type: "custom:mushroom-entity-card", + icon: "mdi:power-cycle", + icon_color: "green", + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.EntityCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.EntityCardOptions = {}) { + super(entity); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {BinarySensorCard}; diff --git a/src/cards/CameraCard.js b/src/cards/CameraCard.js deleted file mode 100644 index feb252e..0000000 --- a/src/cards/CameraCard.js +++ /dev/null @@ -1,41 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * Camera Card Class - * - * Used to create a card for controlling an entity of the camera domain. - * - * @class - * @extends AbstractCard - */ -class CameraCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {cameraCardOptions} - * @private - */ - #defaultOptions = { - type: "picture-entity", - show_name: false, - show_state: false, - camera_view: "live", - }; - - /** - * Class constructor. - * - * @param {hassEntity} entity The hass entity to create a card for. - * @param {cameraCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(entity, options = {}) { - super(entity); - this.mergeOptions( - this.#defaultOptions, - options, - ); - } -} - -export {CameraCard}; diff --git a/src/cards/CameraCard.ts b/src/cards/CameraCard.ts new file mode 100644 index 0000000..499007e --- /dev/null +++ b/src/cards/CameraCard.ts @@ -0,0 +1,44 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {PictureEntityCardConfig} from "../types/homeassistant/panels/lovelave/cards/types"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Camera Card Class + * + * Used to create a card for controlling an entity of the camera domain. + * + * @class + * @extends AbstractCard + */ +class CameraCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {PictureEntityCardConfig} + * @private + */ + #defaultConfig: PictureEntityCardConfig = { + entity: "", + type: "picture-entity", + show_name: false, + show_state: false, + camera_view: "live", + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.PictureEntityCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.PictureEntityCardOptions = {}) { + super(entity); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {CameraCard}; diff --git a/src/cards/ClimateCard.js b/src/cards/ClimateCard.js deleted file mode 100644 index d7573df..0000000 --- a/src/cards/ClimateCard.js +++ /dev/null @@ -1,46 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * Climate Card Class - * - * Used to create a card for controlling an entity of the climate domain. - * - * @class - * @extends AbstractCard - */ -class ClimateCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {climateCardOptions} - * @private - */ - #defaultOptions = { - type: "custom:mushroom-climate-card", - icon: undefined, - hvac_modes: [ - "off", - "cool", - "heat", - "fan_only", - ], - show_temperature_control: true, - }; - - /** - * Class constructor. - * - * @param {hassEntity} entity The hass entity to create a card for. - * @param {climateCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(entity, options = {}) { - super(entity); - this.mergeOptions( - this.#defaultOptions, - options, - ); - } -} - -export {ClimateCard}; diff --git a/src/cards/ClimateCard.ts b/src/cards/ClimateCard.ts new file mode 100644 index 0000000..14fc24f --- /dev/null +++ b/src/cards/ClimateCard.ts @@ -0,0 +1,48 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {ClimateCardConfig} from "../types/lovelace-mushroom/cards/climate-card-config"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Climate Card Class + * + * Used to create a card for controlling an entity of the climate domain. + * + * @class + * @extends AbstractCard + */ +class ClimateCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {ClimateCardConfig} + * @private + */ + #defaultConfig: ClimateCardConfig = { + type: "custom:mushroom-climate-card", + icon: undefined, + hvac_modes: [ + "off", + "cool", + "heat", + "fan_only", + ], + show_temperature_control: true, + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.ClimateCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.ClimateCardOptions = {}) { + super(entity); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {ClimateCard}; diff --git a/src/cards/ControllerCard.ts b/src/cards/ControllerCard.ts new file mode 100644 index 0000000..273d96f --- /dev/null +++ b/src/cards/ControllerCard.ts @@ -0,0 +1,102 @@ +import {cards} from "../types/strategy/cards"; +import {StackCardConfig} from "../types/homeassistant/lovelace/cards/types"; +import {LovelaceCardConfig} from "../types/homeassistant/data/lovelace"; +import {HassServiceTarget} from "home-assistant-js-websocket"; + +/** + * Controller Card class. + * + * Used for creating a Title Card with controls. + * + * @class + */ +class ControllerCard { + /** + * @type {HassServiceTarget} The target to control the entities of. + * @private + */ + readonly #target: HassServiceTarget; + + /** + * Default configuration of the card. + * + * @type {cards.ControllerCardConfig} + * @private + */ + readonly #defaultConfig: cards.ControllerCardConfig = { + type: "mushroom-title-card", + showControls: true, + iconOn: "mdi:power-on", + iconOff: "mdi:power-off", + onService: "none", + offService: "none", + }; + + /** + * Class constructor. + * + * @param {HassServiceTarget} target The target to control the entities of. + * @param {cards.ControllerCardOptions} options Controller Card options. + */ + constructor(target: HassServiceTarget, options: cards.ControllerCardOptions = {}) { + this.#target = target; + this.#defaultConfig = { + ...this.#defaultConfig, + ...options, + }; + } + + /** + * Create a Controller card. + * + * @return {StackCardConfig} A Controller card. + */ + createCard(): StackCardConfig { + const cards: LovelaceCardConfig[] = [ + { + type: "custom:mushroom-title-card", + title: this.#defaultConfig.title, + subtitle: this.#defaultConfig.subtitle, + }, + ]; + + if (this.#defaultConfig.showControls) { + cards.push({ + type: "horizontal-stack", + cards: [ + { + type: "custom:mushroom-template-card", + icon: this.#defaultConfig.iconOff, + layout: "vertical", + icon_color: "red", + tap_action: { + action: "call-service", + service: this.#defaultConfig.offService, + target: this.#target, + data: {}, + }, + }, + { + type: "custom:mushroom-template-card", + icon: this.#defaultConfig.iconOn, + layout: "vertical", + icon_color: "amber", + tap_action: { + action: "call-service", + service: this.#defaultConfig.onService, + target: this.#target, + data: {}, + }, + }, + ], + }); + } + + return { + type: "horizontal-stack", + cards: cards, + }; + } +} + +export {ControllerCard}; diff --git a/src/cards/CoverCard.js b/src/cards/CoverCard.js deleted file mode 100644 index e4c53d6..0000000 --- a/src/cards/CoverCard.js +++ /dev/null @@ -1,42 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * Cover Card Class - * - * Used to create a card for controlling an entity of the cover domain. - * - * @class - * @extends AbstractCard - */ -class CoverCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {coverCardOptions} - * @private - */ - #defaultOptions = { - type: "custom:mushroom-cover-card", - icon: undefined, - show_buttons_control: true, - show_position_control: true, - show_tilt_position_control: true, - }; - - /** - * Class constructor. - * - * @param {hassEntity} entity The hass entity to create a card for. - * @param {coverCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(entity, options = {}) { - super(entity); - this.mergeOptions( - this.#defaultOptions, - options, - ); - } -} - -export {CoverCard}; diff --git a/src/cards/CoverCard.ts b/src/cards/CoverCard.ts new file mode 100644 index 0000000..a1486cb --- /dev/null +++ b/src/cards/CoverCard.ts @@ -0,0 +1,44 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {CoverCardConfig} from "../types/lovelace-mushroom/cards/cover-card-config"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Cover Card Class + * + * Used to create a card for controlling an entity of the cover domain. + * + * @class + * @extends AbstractCard + */ +class CoverCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {CoverCardConfig} + * @private + */ + #defaultConfig: CoverCardConfig = { + type: "custom:mushroom-cover-card", + icon: undefined, + show_buttons_control: true, + show_position_control: true, + show_tilt_position_control: true, + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.CoverCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.CoverCardOptions = {}) { + super(entity); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {CoverCard}; diff --git a/src/cards/FanCard.js b/src/cards/FanCard.js deleted file mode 100644 index 3fe2806..0000000 --- a/src/cards/FanCard.js +++ /dev/null @@ -1,42 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * Fan Card Class - * - * Used to create a card for controlling an entity of the fan domain. - * - * @class - * @extends AbstractCard - */ -class FanCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {fanCardOptions} - * @private - */ - #defaultOptions = { - type: "custom:mushroom-fan-card", - icon: undefined, - show_percentage_control: true, - show_oscillate_control: true, - icon_animation: true, - }; - - /** - * Class constructor. - * - * @param {hassEntity} entity The hass entity to create a card for. - * @param {fanCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(entity, options = {}) { - super(entity); - this.mergeOptions( - this.#defaultOptions, - options, - ); - } -} - -export {FanCard}; diff --git a/src/cards/FanCard.ts b/src/cards/FanCard.ts new file mode 100644 index 0000000..4cf1103 --- /dev/null +++ b/src/cards/FanCard.ts @@ -0,0 +1,44 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {FanCardConfig} from "../types/lovelace-mushroom/cards/fan-card-config"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Fan Card Class + * + * Used to create a card for controlling an entity of the fan domain. + * + * @class + * @extends AbstractCard + */ +class FanCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {FanCardConfig} + * @private + */ + #defaultConfig: FanCardConfig = { + type: "custom:mushroom-fan-card", + icon: undefined, + show_percentage_control: true, + show_oscillate_control: true, + icon_animation: true, + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.FanCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.FanCardOptions = {}) { + super(entity); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {FanCard}; diff --git a/src/cards/HaAreaCard.js b/src/cards/HaAreaCard.js deleted file mode 100644 index bf4c921..0000000 --- a/src/cards/HaAreaCard.js +++ /dev/null @@ -1,47 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * HA Area Card Class - * - * Used to create a card for an entity of the area domain using the built in type 'area'. - * - * @class - * @extends AbstractCard - */ -class AreaCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {HaAreaCardOptions} - * @private - */ - #defaultOptions = { - type: "area", - area: undefined, - navigation_path: undefined, - }; - - /** - * Class constructor. - * - * @param {areaEntity} area The area entity to create a card for. - * @param {HaAreaCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - - constructor(area, options = {}) { - super(area); - this.#defaultOptions.area = area.area_id ?? area.name; - this.#defaultOptions.navigation_path = area.area_id ?? area.name; - - // Enforce the card type. - options.type = this.#defaultOptions.type; - - this.mergeOptions( - this.#defaultOptions, - options, - ); - } -} - -export {AreaCard}; diff --git a/src/cards/HaAreaCard.ts b/src/cards/HaAreaCard.ts new file mode 100644 index 0000000..8652f44 --- /dev/null +++ b/src/cards/HaAreaCard.ts @@ -0,0 +1,49 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {AreaRegistryEntry} from "../types/homeassistant/data/area_registry"; +import {AreaCardConfig} from "../types/homeassistant/lovelace/cards/types"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * HA Area Card Class + * + * Used to create a card for an entity of the area domain using the built-in type 'area'. + * + * @class + * @extends AbstractCard + */ +class AreaCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {AreaCardConfig} + * @private + */ + #defaultConfig: AreaCardConfig = { + type: "area", + area: "", + }; + + /** + * Class constructor. + * + * @param {AreaRegistryEntry} area The area entity to create a card for. + * @param {cards.AreaCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + + constructor(area: AreaRegistryEntry, options: cards.AreaCardOptions = {}) { + super(area); + + // Initialize the default configuration. + this.#defaultConfig.area = area.area_id; + this.#defaultConfig.navigation_path = this.#defaultConfig.area; + + // Enforce the card type. + delete options.type; + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {AreaCard}; diff --git a/src/cards/LightCard.js b/src/cards/LightCard.js deleted file mode 100644 index 3278b0a..0000000 --- a/src/cards/LightCard.js +++ /dev/null @@ -1,52 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * Light Card Class - * - * Used to create a card for controlling an entity of the light domain. - * - * @class - * @extends AbstractCard - */ -class LightCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {lightCardOptions} - * @private - */ - #defaultOptions = { - type: "custom:mushroom-light-card", - icon: undefined, - show_brightness_control: true, - show_color_control: true, - use_light_color: true, - 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}; diff --git a/src/cards/LightCard.ts b/src/cards/LightCard.ts new file mode 100644 index 0000000..2045eaf --- /dev/null +++ b/src/cards/LightCard.ts @@ -0,0 +1,67 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {LightCardConfig} from "../types/lovelace-mushroom/cards/light-card-config"; +import {generic} from "../types/strategy/generic"; +import isCallServiceActionConfig = generic.isCallServiceActionConfig; +import isCallServiceActionTarget = generic.isCallServiceActionTarget; + + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Light Card Class + * + * Used to create a card for controlling an entity of the light domain. + * + * @class + * @extends AbstractCard + */ +class LightCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {LightCardConfig} + * @private + */ + #defaultConfig: LightCardConfig = { + type: "custom:mushroom-light-card", + icon: undefined, + show_brightness_control: true, + show_color_control: true, + show_color_temp_control: true, + use_light_color: true, + double_tap_action: { + action: "call-service", + service: "light.turn_on", + target: { + entity_id: undefined, + }, + data: { + rgb_color: [255, 255, 255], + }, + }, + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.LightCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.LightCardOptions = {}) { + super(entity); + + // Set the target for double-tap action. + if ( + isCallServiceActionConfig(this.#defaultConfig.double_tap_action) + && isCallServiceActionTarget(this.#defaultConfig.double_tap_action.target) + ) { + this.#defaultConfig.double_tap_action.target.entity_id = entity.entity_id; + } + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {LightCard}; diff --git a/src/cards/LockCard.js b/src/cards/LockCard.js deleted file mode 100644 index 05a911c..0000000 --- a/src/cards/LockCard.js +++ /dev/null @@ -1,39 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * Lock Card Class - * - * Used to create a card for controlling an entity of the lock domain. - * - * @class - * @extends AbstractCard - */ -class LockCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {lockCardOptions} - * @private - */ - #defaultOptions = { - type: "custom:mushroom-lock-card", - icon: undefined, - }; - - /** - * Class constructor. - * - * @param {hassEntity} entity The hass entity to create a card for. - * @param {lockCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(entity, options = {}) { - super(entity); - this.mergeOptions( - this.#defaultOptions, - options, - ); - } -} - -export {LockCard}; diff --git a/src/cards/LockCard.ts b/src/cards/LockCard.ts new file mode 100644 index 0000000..4e1fdf8 --- /dev/null +++ b/src/cards/LockCard.ts @@ -0,0 +1,41 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {LockCardConfig} from "../types/lovelace-mushroom/cards/lock-card-config"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Lock Card Class + * + * Used to create a card for controlling an entity of the lock domain. + * + * @class + * @extends AbstractCard + */ +class LockCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {LockCardConfig} + * @private + */ + #defaultConfig: LockCardConfig = { + type: "custom:mushroom-lock-card", + icon: undefined, + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.LockCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.LockCardOptions = {}) { + super(entity); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {LockCard}; diff --git a/src/cards/MediaPlayerCard.js b/src/cards/MediaPlayerCard.js deleted file mode 100644 index c76164d..0000000 --- a/src/cards/MediaPlayerCard.js +++ /dev/null @@ -1,50 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * Mediaplayer Card Class - * - * Used to create a card for controlling an entity of the media_player domain. - * - * @class - * @extends AbstractCard - */ -class MediaPlayerCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {mediaPlayerCardOptions} - * @private - */ - #defaultOptions = { - type: "custom:mushroom-media-player-card", - icon: undefined, - use_media_info: true, - media_controls: [ - "on_off", - "play_pause_stop", - ], - show_volume_level: true, - volume_controls: [ - "volume_mute", - "volume_set", - "volume_buttons", - ], - }; - - /** - * Class constructor. - * - * @param {hassEntity} entity The hass entity to create a card for. - * @param {mediaPlayerCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(entity, options = {}) { - super(entity); - this.mergeOptions( - this.#defaultOptions, - options, - ); - } -} - -export {MediaPlayerCard}; diff --git a/src/cards/MediaPlayerCard.ts b/src/cards/MediaPlayerCard.ts new file mode 100644 index 0000000..964dae5 --- /dev/null +++ b/src/cards/MediaPlayerCard.ts @@ -0,0 +1,51 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {MediaPlayerCardConfig} from "../types/lovelace-mushroom/cards/media-player-card-config"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Mediaplayer Card Class + * + * Used to create a card for controlling an entity of the media_player domain. + * + * @class + * @extends AbstractCard + */ +class MediaPlayerCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {MediaPlayerCardConfig} + * @private + */ + #defaultConfig: MediaPlayerCardConfig = { + type: "custom:mushroom-media-player-card", + use_media_info: true, + media_controls: [ + "on_off", + "play_pause_stop", + ], + show_volume_level: true, + volume_controls: [ + "volume_mute", + "volume_set", + "volume_buttons", + ], + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.MediaPlayerCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.MediaPlayerCardOptions = {}) { + super(entity); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {MediaPlayerCard}; diff --git a/src/cards/MiscellaneousCard.js b/src/cards/MiscellaneousCard.js deleted file mode 100644 index d657855..0000000 --- a/src/cards/MiscellaneousCard.js +++ /dev/null @@ -1,39 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * Miscellaneous Card Class - * - * Used to create a card an entity of any domain. - * - * @class - * @extends AbstractCard - */ -class MiscellaneousCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {miscellaneousCardOptions} - * @private - */ - #defaultOptions = { - type: "custom:mushroom-entity-card", - icon_color: "blue-grey", - }; - - /** - * Class constructor. - * - * @param {hassEntity} entity The hass entity to create a card for. - * @param {miscellaneousCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(entity, options = {}) { - super(entity); - this.mergeOptions( - this.#defaultOptions, - options, - ); - } -} - -export {MiscellaneousCard}; diff --git a/src/cards/MiscellaneousCard.ts b/src/cards/MiscellaneousCard.ts new file mode 100644 index 0000000..3c75282 --- /dev/null +++ b/src/cards/MiscellaneousCard.ts @@ -0,0 +1,40 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config"; + +/** + * Miscellaneous Card Class + * + * Used to create a card an entity of any domain. + * + * @class + * @extends AbstractCard + */ +class MiscellaneousCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {EntityCardConfig} + * @private + */ + #defaultConfig: EntityCardConfig = { + type: "custom:mushroom-entity-card", + icon_color: "blue-grey", + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.EntityCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.EntityCardOptions = {}) { + super(entity); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {MiscellaneousCard}; diff --git a/src/cards/NumberCard.js b/src/cards/NumberCard.js deleted file mode 100644 index 273c772..0000000 --- a/src/cards/NumberCard.js +++ /dev/null @@ -1,37 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * Number Card Class - * - * Used to create a card for controlling an entity of the number domain. - * - * @class - * @extends AbstractCard - */ -class NumberCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {numberCardOptions} - * @private - */ - #defaultOptions = { - type: "custom:mushroom-number-card", - icon: undefined, - }; - - /** - * Class constructor. - * - * @param {hassEntity} entity The hass entity to create a card for. - * @param {numberCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(entity, options = {}) { - super(entity); - - this.mergeOptions(this.#defaultOptions, options); - } -} - -export {NumberCard}; diff --git a/src/cards/NumberCard.ts b/src/cards/NumberCard.ts new file mode 100644 index 0000000..c5808b7 --- /dev/null +++ b/src/cards/NumberCard.ts @@ -0,0 +1,41 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {NumberCardConfig} from "../types/lovelace-mushroom/cards/number-card-config"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported +/** + * Number Card Class + * + * Used to create a card for controlling an entity of the number domain. + * + * @class + * @extends AbstractCard + */ +class NumberCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {NumberCardConfig} + * @private + */ + #defaultConfig: NumberCardConfig = { + type: "custom:mushroom-number-card", + icon: undefined, + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.NumberCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.NumberCardOptions = {}) { + super(entity); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {NumberCard}; diff --git a/src/cards/PersonCard.js b/src/cards/PersonCard.js deleted file mode 100644 index db3bae6..0000000 --- a/src/cards/PersonCard.js +++ /dev/null @@ -1,42 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * Person Card Class - * - * Used to create a card for an entity of the person domain. - * - * @class - * @extends AbstractCard - */ -class PersonCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {personCardOptions} - * @private - */ - #defaultOptions = { - type: "custom:mushroom-person-card", - layout: "vertical", - primary_info: "none", - secondary_info: "none", - icon_type: "entity-picture", - }; - - /** - * Class constructor. - * - * @param {hassEntity} entity The hass entity to create a card for. - * @param {personCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(entity, options = {}) { - super(entity); - this.mergeOptions( - this.#defaultOptions, - options, - ); - } -} - -export {PersonCard}; diff --git a/src/cards/PersonCard.ts b/src/cards/PersonCard.ts new file mode 100644 index 0000000..10d300f --- /dev/null +++ b/src/cards/PersonCard.ts @@ -0,0 +1,43 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {PersonCardConfig} from "../types/lovelace-mushroom/cards/person-card-config"; + +/** + * Person Card Class + * + * Used to create a card for an entity of the Person domain. + * + * @class + * @extends AbstractCard + */ +class PersonCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {PersonCardConfig} + * @private + */ + #defaultConfig: PersonCardConfig = { + type: "custom:mushroom-person-card", + layout: "vertical", + primary_info: "none", + secondary_info: "none", + icon_type: "entity-picture", + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.PersonCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.PersonCardOptions = {}) { + super(entity); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {PersonCard}; diff --git a/src/cards/SensorCard.js b/src/cards/SensorCard.js deleted file mode 100644 index ef8de22..0000000 --- a/src/cards/SensorCard.js +++ /dev/null @@ -1,42 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * Sensor Card Class - * - * Used to create a card for controlling an entity of the sensor domain. - * - * @class - * @extends AbstractCard - */ -class SensorCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {sensorCardOptions} - * @private - */ - #defaultOptions = { - type: "custom:mushroom-entity-card", - icon: "mdi:information", - animate: true, - line_color: "green", - }; - - /** - * Class constructor. - * - * @param {hassEntity} entity The hass entity to create a card for. - * @param {sensorCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(entity, options = {}) { - super(entity); - - this.mergeOptions( - this.#defaultOptions, - options, - ); - } -} - -export {SensorCard}; diff --git a/src/cards/SensorCard.ts b/src/cards/SensorCard.ts new file mode 100644 index 0000000..92e9107 --- /dev/null +++ b/src/cards/SensorCard.ts @@ -0,0 +1,42 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config"; + +/** + * Sensor Card Class + * + * Used to create a card for controlling an entity of the sensor domain. + * + * @class + * @extends AbstractCard + */ +class SensorCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {EntityCardConfig} + * @private + */ + #defaultConfig: EntityCardConfig = { + type: "custom:mushroom-entity-card", + icon: "mdi:information", + animate: true, + line_color: "green", + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.EntityCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.EntityCardOptions = {}) { + super(entity); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {SensorCard}; diff --git a/src/cards/SwitchCard.js b/src/cards/SwitchCard.js deleted file mode 100644 index 1701866..0000000 --- a/src/cards/SwitchCard.js +++ /dev/null @@ -1,42 +0,0 @@ -import {AbstractCard} from "./AbstractCard"; - -/** - * Switch Card Class - * - * Used to create a card for controlling an entity of the switch domain. - * - * @class - * @extends AbstractCard - */ -class SwitchCard extends AbstractCard { - /** - * Default options of the card. - * - * @type {switchCardOptions} - * @private - */ - #defaultOptions = { - type: "custom:mushroom-entity-card", - icon: undefined, - tap_action: { - action: "toggle", - }, - }; - - /** - * Class constructor. - * - * @param {hassEntity} entity The hass entity to create a card for. - * @param {switchCardOptions} [options={}] Options for the card. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor(entity, options = {}) { - super(entity); - this.mergeOptions( - this.#defaultOptions, - options, - ); - } -} - -export {SwitchCard}; diff --git a/src/cards/SwitchCard.ts b/src/cards/SwitchCard.ts new file mode 100644 index 0000000..5d3f78a --- /dev/null +++ b/src/cards/SwitchCard.ts @@ -0,0 +1,44 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Switch Card Class + * + * Used to create a card for controlling an entity of the switch domain. + * + * @class + * @extends AbstractCard + */ +class SwitchCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {EntityCardConfig} + * @private + */ + #defaultConfig: EntityCardConfig = { + type: "custom:mushroom-entity-card", + icon: undefined, + tap_action: { + action: "toggle", + }, + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.EntityCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.EntityCardOptions = {}) { + super(entity); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {SwitchCard}; diff --git a/src/cards/TitleCard.js b/src/cards/TitleCard.js deleted file mode 100644 index 93a5e2d..0000000 --- a/src/cards/TitleCard.js +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Title Card class. - * - * Used for creating a Title Card. - * - * @class - */ -class TitleCard { - /** - * @type {string[]} An array of area ids. - * @private - */ - #areaIds; - - /** - * @type {titleCardOptions} - * @private - */ - #options = { - title: undefined, - subtitle: undefined, - showControls: true, - iconOn: "mdi:power-on", - iconOff: "mdi:power-off", - onService: "none", - offService: "none", - }; - - /** - * Class constructor. - * - * @param {areaEntity[]} areas An array of area entities. - * @param {titleCardOptions} options Title Card options. - */ - constructor(areas, options = {}) { - this.#areaIds = areas.map(area => area.area_id).filter(area_id => area_id); - this.#options = { - ...this.#options, - ...options, - }; - } - - /** - * Create a Title card. - * - * @return {Object} A Title card. - */ - createCard() { - /** @type {Object[]} */ - const cards = [ - { - type: "custom:mushroom-title-card", - title: this.#options.title, - subtitle: this.#options.subtitle, - }, - ]; - - if (this.#options.showControls) { - cards.push({ - type: "horizontal-stack", - cards: [ - { - type: "custom:mushroom-template-card", - icon: this.#options.iconOff, - layout: "vertical", - icon_color: "red", - tap_action: { - action: "call-service", - service: this.#options.offService, - target: { - area_id: this.#areaIds, - }, - data: {}, - }, - }, - { - type: "custom:mushroom-template-card", - icon: this.#options.iconOn, - layout: "vertical", - icon_color: "amber", - tap_action: { - action: "call-service", - service: this.#options.onService, - target: { - area_id: this.#areaIds, - }, - data: {}, - }, - }, - ], - }); - } - - return { - type: "horizontal-stack", - cards: cards, - }; - } -} - -export {TitleCard}; diff --git a/src/cards/VacuumCard.ts b/src/cards/VacuumCard.ts new file mode 100644 index 0000000..f76b4fa --- /dev/null +++ b/src/cards/VacuumCard.ts @@ -0,0 +1,46 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {VACUUM_COMMANDS, VacuumCardConfig} from "../types/lovelace-mushroom/cards/vacuum-card-config"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Vacuum Card Class + * + * Used to create a card for controlling an entity of the vacuum domain. + * + * @class + * @extends AbstractCard + */ +class VacuumCard extends AbstractCard { + /** + * Default configuration of the card. + * + * @type {VacuumCardConfig} + * @private + */ + #defaultConfig: VacuumCardConfig = { + type: "custom:mushroom-vacuum-card", + icon: undefined, + icon_animation: true, + commands: [...VACUUM_COMMANDS], + tap_action: { + action: "more-info", + } + }; + + /** + * Class constructor. + * + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.VacuumCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. + */ + constructor(entity: EntityRegistryEntry, options: cards.VacuumCardOptions = {}) { + super(entity); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {VacuumCard}; diff --git a/src/cards/typedefs.js b/src/cards/typedefs.js deleted file mode 100644 index cfe29f3..0000000 --- a/src/cards/typedefs.js +++ /dev/null @@ -1,154 +0,0 @@ -/** - * @namespace typedefs.cards - */ - -/** - * @typedef {Object} abstractOptions - * @property {string} [type] The type of the card. - * @property {string} [icon] Icon of the card. - * @property {Object} [double_tap_action] Home assistant action to perform on double_tap. - */ - -/** - * @typedef {Object} titleCardOptions Title Card options. - * @property {string} [title] Title to render. May contain templates. - * @property {string} [subtitle] Subtitle to render. May contain templates. - * @property {boolean} [showControls=true] False to hide controls. - * @property {string} [iconOn] Icon to show for switching entities from off state. - * @property {string} [iconOff] Icon to show for switching entities to off state. - * @property {string} [onService=none] Service to call for switching entities from off state. - * @property {string} [offService=none] Service to call for switching entities to off state. - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions & Object} lightCardOptions Light Card options. - * @property {boolean} [show_brightness_control=true] Show a slider to control brightness - * @property {boolean} [show_color_control=true] Show a slider to control RGB color - * @property {boolean} [use_light_color=true] Colorize the icon and slider according light temperature or color - * @property {{double_tap_action: lightDoubleTapAction}} [action] Home assistant action to perform on double_tap - * @memberOf typedefs.cards - */ - -/** - * @typedef {Object} lightDoubleTapAction Home assistant action to perform on double_tap. - * @property {{entity_id: string}} target The target entity id. - * @property {"call-service"} action Calls a hass service. - * @property {"light.turn_on"} service The hass service to call - * @property {{rgb_color: [255, 255, 255]}} data The data payload for the service. - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions & Object} coverCardOptions Cover Card options. - * @property {boolean} [show_buttons_control=true] Show buttons to open, close and stop cover. - * @property {boolean} [show_position_control=true] Show a slider to control position of the cover. - * @property {boolean} [show_tilt_position_control=true] Show a slider to control tilt position of the cover. - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions & Object} fanCardOptions Fan Card options. - * @property {boolean} [show_percentage_control=true] Show a slider to control speed. - * @property {boolean} [show_oscillate_control=true] Show a button to control oscillation. - * @property {boolean} [icon_animation=true] Animate the icon when fan is on. - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions & Object} lockCardOptions Lock Card options. - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions & Object} numberCardOptions Number Card options. - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions & Object} switchCardOptions Switch Card options. - * @property {{tap_action: switchTapAction}} [action] Home assistant action to perform on tap. - * @memberOf typedefs.cards - */ - -/** - * @typedef {Object} switchTapAction Home assistant action to perform on tap. - * @property {"toggle"} action Toggles a hass entity. - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions & Object} climateCardOptions Climate Card options. - * @property {["off", "cool", "heat", "fan_only"]} [hvac_modes] Show buttons to control target temperature. - * @property {boolean} [show_temperature_control=true] Show buttons to control target temperature. - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions} cameraCardOptions Camera Card options. - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions & Object} personCardOptions Person Card options. - * @property {string} [layout] Layout of the card. Vertical, horizontal, and default layouts are supported. - * @property {("name" | "state" | "last-changed" | "last-updated" | "none")} [primary_info=name] Info to show as - * primary info. - * @property {("name" | "state" | "last-changed" | "last-updated" | "none")} [secondary_info=sate] Info to show as - * secondary info. - * @property {("icon" | "entity-picture" | "none")} [icon_type]=icon Type of icon to display. - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions & Object} areaCardOptions Area Card options. - * @property {string} [name] The name of the area - * @property {string} [icon] Icon to render. May contain templates. - * @property {string} [icon_color] Icon color to render. May contain templates. - * @property {string} [primary] Primary info to render. May contain templates. - * @property {areaTapAction} [tap_action] Home assistant action to perform on tap. - * @memberOf typedefs.cards - */ - -/** - * @typedef {Object} areaTapAction Home assistant action to perform on tap. - * @property {"navigate"} action Toggles a hass entity. - * @property {string} navigation_path The id of the area to navigate to. - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions & Object} HaAreaCardOptions HA Area Card options. - * @property {string} area The id of the area. - * @property {string} navigation_path The id of the area to navigate to. - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions & Object} mediaPlayerCardOptions Media Player Card options. - * @property {boolean} [use_media_info=true] Use media info instead of name, state, and icon when a media is playing - * @property {string[]} [media_controls="on_off", "play_pause_stop"] List of controls to display - * (on_off, shuffle, previous, play_pause_stop, next, - * repeat) - * @property {boolean} [show_volume_level=true] Show volume level next to media state when media is playing - * @property {string[]} [volume_controls="volume_mute", "volume_set", "volume_buttons"] List of controls to display - * (volume_mute, volume_set, - * volume_buttons) - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions & Object} sensorCardOptions Sensor Card options. - * @property {string} [icon_color=green] Custom color for icon when entity is state is active. - * @property {boolean} [animate=true] Add a reveal animation to the graph. - * @property {string} [line_color=green] Set a custom color for the graph line. - * Provide a list of colors for multiple graph entries. - * @memberOf typedefs.cards - */ - -/** - * @typedef {abstractOptions & Object} miscellaneousCardOptions Miscellaneous Card options. - * @property {string} [icon_color=blue-grey] Custom color for icon when entity is state is active. - * @memberOf typedefs.cards - */ - diff --git a/src/chips/AbstractChip.ts b/src/chips/AbstractChip.ts new file mode 100644 index 0000000..0af89f5 --- /dev/null +++ b/src/chips/AbstractChip.ts @@ -0,0 +1,64 @@ +import {HassServiceTarget} from "home-assistant-js-websocket"; +import {LovelaceChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types"; +import {Helper} from "../Helper"; +import {generic} from "../types/strategy/generic"; +import isCallServiceActionConfig = generic.isCallServiceActionConfig; + +/** + * Abstract Chip class. + * + * To create a new chip, extend this one. + * + * @class + * @abstract + */ +abstract class AbstractChip { + /** + * Configuration of the chip. + * + * @type {LovelaceChipConfig} + */ + config: LovelaceChipConfig = { + type: "template" + }; + + /** + * Class Constructor. + */ + protected constructor() { + if (!Helper.isInitialized()) { + throw new Error("The Helper module must be initialized before using this one."); + } + } + + // noinspection JSUnusedGlobalSymbols Method is called on dymanically imported classes. + /** + * Get the chip. + * + * @returns {LovelaceChipConfig} A chip. + */ + getChip(): LovelaceChipConfig { + return this.config; + } + + /** + * Set the target to switch. + * + * @param {HassServiceTarget} target Target to switch. + */ + setTapActionTarget(target: HassServiceTarget) { + if ("tap_action" in this.config && isCallServiceActionConfig(this.config.tap_action)) { + this.config.tap_action.target = target; + + return; + } + + if (Helper.debug) { + console.warn( + this.constructor.name + + " - Target not set: Invalid target or tap action."); + } + } +} + +export {AbstractChip}; diff --git a/src/chips/ClimateChip.js b/src/chips/ClimateChip.js deleted file mode 100644 index 3bddabe..0000000 --- a/src/chips/ClimateChip.js +++ /dev/null @@ -1,39 +0,0 @@ -import {Helper} from "../Helper"; - -class ClimateChip { - #areaIds; - #options = { - // No default options. - }; - - constructor(areaIds, options = {}) { - if (!Helper.isInitialized()) { - throw new Error("The Helper module must be initialized before using this one."); - } - - this.#areaIds = areaIds.filter(areaId => areaId); - this.#options = { - ...this.#options, - ...options, - }; - } - - getChip() { - return { - type: "template", - icon: "mdi:thermostat", - icon_color: "orange", - content: Helper.getCountTemplate("climate", "ne", "off"), - tap_action: { - action: "navigate", - navigation_path: "climates", - }, - hold_action: { - action: "navigate", - navigation_path: "climates", - }, - }; - } -} - -export {ClimateChip}; diff --git a/src/chips/ClimateChip.ts b/src/chips/ClimateChip.ts new file mode 100644 index 0000000..5cb6357 --- /dev/null +++ b/src/chips/ClimateChip.ts @@ -0,0 +1,47 @@ +import {Helper} from "../Helper"; +import {AbstractChip} from "./AbstractChip"; +import {chips} from "../types/strategy/chips"; +import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Climate Chip class. + * + * Used to create a chip to indicate how many climates are operating. + */ +class ClimateChip extends AbstractChip { + /** + * Default configuration of the chip. + * + * @type {TemplateChipConfig} + * + * @readonly + * @private + */ + readonly #defaultConfig: TemplateChipConfig = { + type: "template", + icon: "mdi:thermostat", + icon_color: "orange", + content: Helper.getCountTemplate("climate", "ne", "off"), + tap_action: { + action: "none", + }, + hold_action: { + action: "navigate", + navigation_path: "climates", + }, + }; + + /** + * Class Constructor. + * + * @param {chips.TemplateChipOptions} options The chip options. + */ + constructor(options: chips.TemplateChipOptions = {}) { + super(); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {ClimateChip}; diff --git a/src/chips/CoverChip.js b/src/chips/CoverChip.js deleted file mode 100644 index 0180239..0000000 --- a/src/chips/CoverChip.js +++ /dev/null @@ -1,35 +0,0 @@ -import {Helper} from "../Helper"; - -class CoverChip { - #areaIds; - #options = { - // No default options. - }; - - constructor(areaIds, options = {}) { - if (!Helper.isInitialized()) { - throw new Error("The Helper module must be initialized before using this one."); - } - - this.#areaIds = areaIds.filter(areaId => areaId); - this.#options = { - ...this.#options, - ...options, - }; - } - - getChip() { - return { - type: "template", - icon: "mdi:window-open", - icon_color: "cyan", - content: Helper.getCountTemplate("cover", "eq", "open"), - tap_action: { - action: "navigate", - navigation_path: "covers", - }, - }; - } -} - -export {CoverChip}; diff --git a/src/chips/CoverChip.ts b/src/chips/CoverChip.ts new file mode 100644 index 0000000..5fae7b8 --- /dev/null +++ b/src/chips/CoverChip.ts @@ -0,0 +1,47 @@ +import {Helper} from "../Helper"; +import {chips} from "../types/strategy/chips"; +import {AbstractChip} from "./AbstractChip"; +import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Cover Chip class. + * + * Used to create a chip to indicate how many covers aren't closed. + */ +class CoverChip extends AbstractChip { + /** + * Default configuration of the chip. + * + * @type {TemplateChipConfig} + * + * @readonly + * @private + */ + readonly #defaultConfig: TemplateChipConfig = { + type: "template", + icon: "mdi:window-open", + icon_color: "cyan", + content: Helper.getCountTemplate("cover", "eq", "open"), + tap_action: { + action: "none", + }, + hold_action: { + action: "navigate", + navigation_path: "covers", + }, + }; + + /** + * Class Constructor. + * + * @param {chips.TemplateChipOptions} options The chip options. + */ + constructor(options: chips.TemplateChipOptions = {}) { + super(); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {CoverChip}; diff --git a/src/chips/FanChip.js b/src/chips/FanChip.js deleted file mode 100644 index 46880ed..0000000 --- a/src/chips/FanChip.js +++ /dev/null @@ -1,43 +0,0 @@ -import {Helper} from "../Helper"; - -class FanChip { - #areaIds; - #options = { - // No default options. - }; - - constructor(areaIds, options = {}) { - if (!Helper.isInitialized()) { - throw new Error("The Helper module must be initialized before using this one."); - } - - this.#areaIds = areaIds.filter(areaId => areaId); - this.#options = { - ...this.#options, - ...options, - }; - } - - getChip() { - return { - type: "template", - icon: "mdi:fan", - icon_color: "green", - content: Helper.getCountTemplate("fan", "eq", "on"), - tap_action: { - action: "call-service", - service: "fan.turn_off", - target: { - area_id: this.#areaIds, - }, - data: {}, - }, - hold_action: { - action: "navigate", - navigation_path: "fans", - }, - }; - } -} - -export {FanChip}; diff --git a/src/chips/FanChip.ts b/src/chips/FanChip.ts new file mode 100644 index 0000000..5d0b0ec --- /dev/null +++ b/src/chips/FanChip.ts @@ -0,0 +1,48 @@ +import {Helper} from "../Helper"; +import {chips} from "../types/strategy/chips"; +import {AbstractChip} from "./AbstractChip"; +import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Fan Chip class. + * + * Used to create a chip to indicate how many fans are on and to turn all off. + */ +class FanChip extends AbstractChip { + /** + * Default configuration of the chip. + * + * @type {TemplateChipConfig} + * + * @readonly + * @private + */ + readonly #defaultConfig: TemplateChipConfig = { + type: "template", + icon: "mdi:fan", + icon_color: "green", + content: Helper.getCountTemplate("fan", "eq", "on"), + tap_action: { + action: "call-service", + service: "fan.turn_off", + }, + hold_action: { + action: "navigate", + navigation_path: "fans", + }, + }; + + /** + * Class Constructor. + * + * @param {chips.TemplateChipOptions} options The chip options. + */ + constructor(options: chips.TemplateChipOptions = {}) { + super(); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {FanChip}; diff --git a/src/chips/LightChip.js b/src/chips/LightChip.js deleted file mode 100644 index 557c502..0000000 --- a/src/chips/LightChip.js +++ /dev/null @@ -1,43 +0,0 @@ -import {Helper} from "../Helper"; - -class LightChip { - #areaIds; - #options = { - // No default options. - }; - - constructor(areaIds, options = {}) { - if (!Helper.isInitialized()) { - throw new Error("The Helper module must be initialized before using this one."); - } - - this.#areaIds = areaIds.filter(areaId => areaId); - this.#options = { - ...this.#options, - ...options, - }; - } - - getChip() { - return { - type: "template", - icon: "mdi:lightbulb-group", - icon_color: "amber", - content: Helper.getCountTemplate("light", "eq", "on"), - tap_action: { - action: "call-service", - service: "light.turn_off", - target: { - area_id: this.#areaIds, - }, - data: {}, - }, - hold_action: { - action: "navigate", - navigation_path: "lights", - }, - }; - } -} - -export {LightChip}; diff --git a/src/chips/LightChip.ts b/src/chips/LightChip.ts new file mode 100644 index 0000000..9cf596a --- /dev/null +++ b/src/chips/LightChip.ts @@ -0,0 +1,48 @@ +import {Helper} from "../Helper"; +import {chips} from "../types/strategy/chips"; +import {AbstractChip} from "./AbstractChip"; +import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Light Chip class. + * + * Used to create a chip to indicate how many lights are on and to turn all off. + */ +class LightChip extends AbstractChip { + /** + * Default configuration of the chip. + * + * @type {TemplateChipConfig} + * + * @readonly + * @private + */ + readonly #defaultConfig: TemplateChipConfig = { + type: "template", + icon: "mdi:lightbulb-group", + icon_color: "amber", + content: Helper.getCountTemplate("light", "eq", "on"), + tap_action: { + action: "call-service", + service: "light.turn_off", + }, + hold_action: { + action: "navigate", + navigation_path: "lights", + }, + }; + + /** + * Class Constructor. + * + * @param {chips.TemplateChipOptions} options The chip options. + */ + constructor(options: chips.TemplateChipOptions = {}) { + super(); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {LightChip}; diff --git a/src/chips/SwitchChip.js b/src/chips/SwitchChip.js deleted file mode 100644 index 4e4eac0..0000000 --- a/src/chips/SwitchChip.js +++ /dev/null @@ -1,43 +0,0 @@ -import {Helper} from "../Helper"; - -class SwitchChip { - #areaIds; - #options = { - // No default options. - }; - - constructor(areaIds, options = {}) { - if (!Helper.isInitialized()) { - throw new Error("The Helper module must be initialized before using this one."); - } - - this.#areaIds = areaIds.filter(areaId => areaId); - this.#options = { - ...this.#options, - ...options, - }; - } - - getChip() { - return { - type: "template", - icon: "mdi:dip-switch", - icon_color: "blue", - content: Helper.getCountTemplate("switch", "eq", "on"), - tap_action: { - action: "call-service", - service: "switch.turn_off", - target: { - area_id: this.#areaIds, - }, - data: {}, - }, - hold_action: { - action: "navigate", - navigation_path: "switches", - }, - }; - } -} - -export {SwitchChip}; diff --git a/src/chips/SwitchChip.ts b/src/chips/SwitchChip.ts new file mode 100644 index 0000000..26a18ab --- /dev/null +++ b/src/chips/SwitchChip.ts @@ -0,0 +1,48 @@ +import {Helper} from "../Helper"; +import {chips} from "../types/strategy/chips"; +import {AbstractChip} from "./AbstractChip"; +import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Switch Chip class. + * + * Used to create a chip to indicate how many switches are on and to turn all off. + */ +class SwitchChip extends AbstractChip { + /** + * Default configuration of the chip. + * + * @type {TemplateChipConfig} + * + * @readonly + * @private + */ + readonly #defaultConfig: TemplateChipConfig = { + type: "template", + icon: "mdi:dip-switch", + icon_color: "blue", + content: Helper.getCountTemplate("switch", "eq", "on"), + tap_action: { + action: "call-service", + service: "switch.turn_off", + }, + hold_action: { + action: "navigate", + navigation_path: "switches", + }, + }; + + /** + * Class Constructor. + * + * @param {chips.TemplateChipOptions} options The chip options. + */ + constructor(options: chips.TemplateChipOptions = {}) { + super(); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {SwitchChip}; diff --git a/src/chips/WeatherChip.js b/src/chips/WeatherChip.js deleted file mode 100644 index 76a99f1..0000000 --- a/src/chips/WeatherChip.js +++ /dev/null @@ -1,25 +0,0 @@ -class WeatherChip { - #entityId; - #options = { - show_temperature: true, - show_conditions: true, - }; - - constructor(entityId, options = {}) { - this.#entityId = entityId; - this.#options = { - ...this.#options, - ...options, - }; - } - - getChip() { - return { - type: "weather", - entity: this.#entityId, - ...this.#options, - }; - } -} - -export {WeatherChip}; diff --git a/src/chips/WeatherChip.ts b/src/chips/WeatherChip.ts new file mode 100644 index 0000000..2a78fad --- /dev/null +++ b/src/chips/WeatherChip.ts @@ -0,0 +1,42 @@ +import {chips} from "../types/strategy/chips"; +import {WeatherChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types"; +import {AbstractChip} from "./AbstractChip"; + +// noinspection JSUnusedGlobalSymbols False positive. +/** + * Weather Chip class. + * + * Used to create a chip for showing the weather. + */ +class WeatherChip extends AbstractChip { + /** + * Default configuration of the chip. + * + * @private + * @readonly + */ + readonly #defaultConfig: WeatherChipConfig = { + type: "weather", + show_temperature: true, + show_conditions: true, + }; + + /** + * Class Constructor. + * + * @param {string} entityId Id of a weather entity. + * @param {chips.WeatherChipOptions} options Weather Chip options. + */ + constructor(entityId: string, options: chips.WeatherChipOptions = {}) { + super(); + this.#defaultConfig = { + ...this.#defaultConfig, + ...{entity: entityId}, + ...options, + }; + + this.config = Object.assign(this.config, this.#defaultConfig, options); + } +} + +export {WeatherChip}; diff --git a/src/optionDefaults.js b/src/configurationDefaults.ts similarity index 83% rename from src/optionDefaults.js rename to src/configurationDefaults.ts index 88acb6f..77dd892 100644 --- a/src/optionDefaults.js +++ b/src/configurationDefaults.ts @@ -1,44 +1,20 @@ -export const optionDefaults = { - debug: false, - views: { - home: { - order: 1, - hidden: false, - }, - light: { - order: 2, - hidden: false, - }, - fan: { - order: 3, - hidden: false, - }, - cover: { - order: 4, - hidden: false, - }, - switch: { - order: 5, - hidden: false, - }, - climate: { - order: 6, - hidden: false, - }, - camera: { - order: 7, - hidden: false, - } - }, +import {generic} from "./types/strategy/generic"; +import StrategyDefaults = generic.StrategyDefaults; + +/** + * Default configuration for the mushroom strategy. + */ +export const configurationDefaults: StrategyDefaults = { areas: { undisclosed: { aliases: [], - area_id: null, + area_id: "undisclosed", name: "Undisclosed", picture: null, hidden: false, } }, + debug: false, domains: { default: { title: "Miscellaneous", @@ -116,5 +92,47 @@ export const optionDefaults = { showControls: false, hidden: false, }, + vacuum: { + title: "Vacuums", + showControls: true, + hidden: false, + }, + }, + home_view: { + hidden: [], + }, + views: { + home: { + order: 1, + hidden: false, + }, + light: { + order: 2, + hidden: false, + }, + fan: { + order: 3, + hidden: false, + }, + cover: { + order: 4, + hidden: false, + }, + switch: { + order: 5, + hidden: false, + }, + climate: { + order: 6, + hidden: false, + }, + camera: { + order: 7, + hidden: false, + }, + vacuum: { + order: 8, + hidden: false, + }, } }; diff --git a/src/mushroom-strategy.js b/src/mushroom-strategy.ts similarity index 54% rename from src/mushroom-strategy.js rename to src/mushroom-strategy.ts index 770dcf2..d53adb1 100644 --- a/src/mushroom-strategy.js +++ b/src/mushroom-strategy.ts @@ -1,34 +1,40 @@ import {Helper} from "./Helper"; import {SensorCard} from "./cards/SensorCard"; -import {TitleCard} from "./cards/TitleCard"; +import {ControllerCard} from "./cards/ControllerCard"; +import {generic} from "./types/strategy/generic"; +import {LovelaceCardConfig, LovelaceConfig, LovelaceViewConfig} from "./types/homeassistant/data/lovelace"; +import {StackCardConfig} from "./types/homeassistant/lovelace/cards/types"; +import {EntityCardConfig} from "./types/lovelace-mushroom/cards/entity-card-config"; +import {HassServiceTarget} from "home-assistant-js-websocket"; +import StrategyArea = generic.StrategyArea; /** * Mushroom Dashboard Strategy.
*
* Mushroom dashboard strategy provides a strategy for Home-Assistant to create a dashboard automatically.
- * The strategy makes use Mushroom, Mini Graph and WebRTC cards to represent your entities.
+ * The strategy makes use Mushroom and Mini Graph cards to represent your entities.
*
* Features:
- * 🛠 Automatically create dashboard with 3 lines of yaml.
+ * 🛠 Automatically create dashboard with three lines of yaml.
* 😍 Built-in Views for several standard domains.
* 🎨 Many options to customize to your needs.
*
* Check the [Repository]{@link https://github.com/AalianKhan/mushroom-strategy} for more information. */ -class MushroomStrategy { +class MushroomStrategy extends HTMLTemplateElement { /** * Generate a dashboard. * * Called when opening a dashboard. * - * @param {dashBoardInfo} info Dashboard strategy information object. - * @return {Promise<{views: Object[]}>} + * @param {generic.DashBoardInfo} info Dashboard strategy information object. + * @return {Promise} */ - static async generateDashboard(info) { + static async generateDashboard(info: generic.DashBoardInfo): Promise { await Helper.initialize(info); // Create views. - const views = []; + const views: LovelaceViewConfig[] = info.config?.views ?? []; let viewModule; @@ -36,13 +42,14 @@ class MushroomStrategy { for (let viewId of Helper.getExposedViewIds()) { try { const viewType = Helper.sanitizeClassName(viewId + "View"); - viewModule = await import(`./views/${viewType}`); - const view = await new viewModule[viewType](Helper.strategyOptions.views[viewId]).getView(); - - views.push(view); + viewModule = await import(`./views/${viewType}`); + const view: LovelaceViewConfig = await new viewModule[viewType](Helper.strategyOptions.views[viewId]).getView(); + if (view.cards?.length) { + views.push(view); + } } catch (e) { - console.error(Helper.debug ? e : `View '${viewId}' couldn't be loaded!`); + Helper.logError(`View '${viewId}' couldn't be loaded!`, e); } } @@ -79,13 +86,18 @@ class MushroomStrategy { * * Called when opening a subview. * - * @param {viewInfo} info The view's strategy information object. - * @return {Promise<{cards: Object[]}>} + * @param {generic.ViewInfo} info The view's strategy information object. + * @return {Promise} */ - static async generateView(info) { + static async generateView(info: generic.ViewInfo): Promise { const exposedDomainIds = Helper.getExposedDomainIds(); - const area = info.view.strategy.options.area; - const viewCards = [...(area.extra_cards ?? [])]; + const area = info.view.strategy?.options?.area ?? {} as StrategyArea; + const viewCards: LovelaceCardConfig[] = [...(area.extra_cards ?? [])]; + + // Set the target for controller cards to the current area. + let target: HassServiceTarget = { + area_id: [area.area_id], + }; // Create cards for each domain. for (const domain of exposedDomainIds) { @@ -100,27 +112,34 @@ class MushroomStrategy { try { domainCards = await import(`./cards/${className}`).then(cardModule => { let domainCards = []; - const entities = Helper.getDeviceEntities(area, domain); + const entities = Helper.getDeviceEntities(area, domain); + + // Set the target for controller cards to entities without an area. + if (area.area_id === "undisclosed") { + target = { + entity_id: entities.map(entity => entity.entity_id), + } + } if (entities.length) { - // Create a Title card for the current domain. - const titleCard = new TitleCard( - [area], - Helper.strategyOptions.domains[domain], + // Create a Controller card for the current domain. + const titleCard = new ControllerCard( + target, + Helper.strategyOptions.domains[domain], ).createCard(); if (domain === "sensor") { // Create a card for each entity-sensor of the current area. const sensorStates = Helper.getStateEntities(area, "sensor"); - const sensorCards = []; + const sensorCards: EntityCardConfig[] = []; for (const sensor of entities) { // Find the state of the current sensor. const sensorState = sensorStates.find(state => state.entity_id === sensor.entity_id); - let cardOptions = Helper.strategyOptions.card_options?.[sensor.entity_id] ?? {}; - let deviceOptions = Helper.strategyOptions.card_options?.[sensor.device_id] ?? {}; + let cardOptions = Helper.strategyOptions.card_options?.[sensor.entity_id]; + let deviceOptions = Helper.strategyOptions.card_options?.[sensor.device_id ?? "null"]; - if (!cardOptions.hidden && !deviceOptions.hidden) { + if (!cardOptions?.hidden && !deviceOptions?.hidden) { if (sensorState?.attributes.unit_of_measurement) { cardOptions = { ...{ @@ -147,12 +166,16 @@ class MushroomStrategy { return domainCards; } - // Create a card for each domain-entity of the current area. + // Create a card for each other domain-entity of the current area. for (const entity of entities) { - let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id] ?? {}; - let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id] ?? {}; + let deviceOptions; + let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id]; + + if (entity.device_id) { + deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id]; + } - if (!cardOptions.hidden && !deviceOptions.hidden) { + if (!cardOptions?.hidden && !deviceOptions?.hidden) { domainCards.push(new cardModule[className](entity, cardOptions).getCard()); } } @@ -179,7 +202,7 @@ class MushroomStrategy { return domainCards; }); } catch (e) { - console.error(Helper.debug ? e : "An error occurred while creating the domain cards!"); + Helper.logError("An error occurred while creating the domain cards!", e); } if (domainCards.length) { @@ -193,36 +216,37 @@ class MushroomStrategy { if (!Helper.strategyOptions.domains.default.hidden) { // Create cards for any other domain. // Collect device entities of the current area. - const areaDevices = Helper.devices.filter(device => device.area_id === area.area_id) - .map(device => device.id); + const areaDevices = Helper.devices.filter((device) => device.area_id === area.area_id) + .map((device) => device.id); // Collect the remaining entities of which all conditions below are met: - // 1. The entity is linked to a device which is linked to the current area, + // 1. The entity is not hidden. + // 2. The entity's domain isn't exposed (entities of exposed domains are already included). + // 3. The entity is linked to a device which is linked to the current area, // or the entity itself is linked to the current area. - // 2. The entity is not hidden and is not disabled. - const miscellaneousEntities = Helper.entities.filter(entity => { - return (areaDevices.includes(entity.device_id) || entity.area_id === area.area_id) - && entity.hidden_by == null - && entity.disabled_by == null - && !exposedDomainIds.includes(entity.entity_id.split(".", 1)[0]); + const miscellaneousEntities = Helper.entities.filter((entity) => { + const entityLinked = areaDevices.includes(entity.device_id ?? "null") || entity.area_id === area.area_id; + const entityUnhidden = entity.hidden_by === null && entity.disabled_by === null; + const domainExposed = exposedDomainIds.includes(entity.entity_id.split(".", 1)[0]); + + return entityUnhidden && !domainExposed && entityLinked; }); // Create a column of miscellaneous entity cards. if (miscellaneousEntities.length) { - let miscellaneousCards = []; + let miscellaneousCards: (StackCardConfig | EntityCardConfig)[] = []; try { miscellaneousCards = await import("./cards/MiscellaneousCard").then(cardModule => { - /** @type Object[] */ - const miscellaneousCards = [ - new TitleCard([area], Helper.strategyOptions.domains.default).createCard(), + const miscellaneousCards: (StackCardConfig | EntityCardConfig)[] = [ + new ControllerCard(target, Helper.strategyOptions.domains.default).createCard(), ]; for (const entity of miscellaneousEntities) { - let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id] ?? {}; - let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id] ?? {}; + let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id]; + let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id ?? "null"]; - if (!cardOptions.hidden && !deviceOptions.hidden) { + if (!cardOptions?.hidden && !deviceOptions?.hidden) { miscellaneousCards.push(new cardModule.MiscellaneousCard(entity, cardOptions).getCard()); } } @@ -230,7 +254,7 @@ class MushroomStrategy { return miscellaneousCards; }); } catch (e) { - console.error(Helper.debug ? e : "An error occurred while creating the domain cards!"); + Helper.logError("An error occurred while creating the domain cards!", e); } viewCards.push({ @@ -247,5 +271,10 @@ class MushroomStrategy { } } -// noinspection JSUnresolvedReference customElements.define("ll-strategy-mushroom-strategy", MushroomStrategy); + +const version = "v2.0.0"; +console.info( + "%c Mushroom Strategy %c ".concat(version, " "), + "color: white; background: coral; font-weight: 700;", "color: coral; background: white; font-weight: 700;" +); diff --git a/src/typedefs.js b/src/typedefs.js deleted file mode 100644 index e5c35e5..0000000 --- a/src/typedefs.js +++ /dev/null @@ -1,255 +0,0 @@ -/** - * @namespace typedefs.generic - */ - -/** - * @typedef {Object} hassEntity Home assistant entity. - * @property {string} name The name of this entity. - * @property {string} original_name The original name of this entity. - * @property {string} entity_id The id of this entity. - * @property {string} device_id The id of the device to which this entity is linked. - * @property {string} area_id The id of the area to which this entity is linked. - * @property {string[]|null} disabled_by Indicates by what this entity is disabled. - * @property {string[]|null} hidden_by Indicates by what this entity is hidden. - * @memberOf typedefs.generic - */ - -/** - * @typedef {Object} deviceEntity Device Entity. - * @property {string} area_id The Area which the device is placed in. - * @property {string} id Unique ID of a device (generated by Home Assistant). - * @property {string[]|null} disabled_by Indicates by what this entity is disabled. - * @property {string[]|null} hidden_by Indicates by what this entity is hidden. - * @memberOf typedefs.generic - */ - -/** - * @typedef {Object} areaEntity Area Entity. - * @property {string[]} [aliases] Array of aliases of this entity. - * @property {string|null} area_id The id of this entity. - * @property {string} name Name of this entity. - * @property {string|null} picture URL to a picture that should be used instead of showing the domain icon. - * @property {number} [order] Ordering position of the area in the list of available areas. - * @property {boolean} [hidden] True if the entity should be hidden from the dashboard. - * This property is added by the custom strategy. - * @property {Object[]} [extra_cards] An array of card configurations. - * The configured cards are added to the dashboard. - * This property is added by the custom strategy. - * @property {boolean} [use_ha_area_card] Set to true to use ha area card instead of mushroom. - * @memberOf typedefs.generic - */ - -/** - * @typedef {Object} viewEntity View Entity. - * This entity is added by the custom strategy. - * @property {string} title Title of this entity. - * @property {string} icon Icon to use for the entity in the frontend. - * Example: `mdi:home`. - * @property {number} [order] Ordering position of the entity in the list of available views. - * @property {boolean} [hidden] True if the entity should be hidden from the dashboard. - * @memberOf typedefs.generic - */ - -/** - * @typedef {Object & titleCardOptions} domainEntity Domain Entity. - * This entity is added by the custom strategy. - * @property {number} [order] Ordering position of the entity in the list of available views. - * @property {boolean} [hidden] True if the entity should be hidden from the dashboard. - * @memberOf typedefs.generic - */ - -/** - * @typedef {Object} titleCardOptions Title Card options. - * @property {string} [title] Title to render. May contain templates. - * @property {string} [subtitle] Subtitle to render. May contain templates. - * @property {boolean} [showControls=true] False to hide controls. - * @property {string} [iconOn] Icon to show for switching entities from off state. - * @property {string} [iconOff] Icon to show for switching entities to off state. - * @property {string} [onService=none] Service to call for switching entities from off state. - * @property {string} [offService=none] Service to call for switching entities to off state. - * @memberOf typedefs.generic - */ - -/** - * @typedef {Object} dashBoardInfo Strategy information object. - * @property {dashboardConfig} config User supplied dashboard configuration, if any. - * @property {hassObject} hass The Home Assistant object. - * @property {boolean} narrow If the current user interface is rendered in narrow mode or not. - * @memberOf typedefs.generic - * @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#dashboard-strategies - */ - -/** - * @typedef {Object} viewInfo Strategy information object. - * @property {Object} view Configuration of the current view. - * @property {viewConfig} config Dashboard configuration. - * @property {hassObject} hass The Home Assistant object. - * @property {boolean} narrow If the current user interface is rendered in narrow mode or not. - * @memberOf typedefs.generic - * @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#view-strategies - */ - -/** - * @typedef {Object} dashboardConfig User supplied dashboard configuration. - * @property {strategyObject} strategy User supplied dashboard configuration. - * @memberOf typedefs.generic - */ - -/** - * @typedef {Object} viewConfig Dashboard configuration. - * @property {Object[]} strategy Array of views generated by the strategy. - * @memberOf typedefs.generic - */ - -/** - * @typedef {Object} strategyObject User supplied dashboard configuration. - * @property {strategyOptions} options Custom strategy configuration. - * @property {string} type Strategy type. - * @memberOf typedefs.generic - */ - -/** - * @typedef {Object} customStrategyOptions Custom strategy configuration. - * @property {boolean} [debug] Set to true for more verbose debugging info. - * @property {Object.} [areas] List of areas. - * @property {Object.} [card_options] Card options for entities. - * @property {Object.} [views] List of views. - * @property {Object.} [domains] List of domains. - * @property {chip[]} [chips] List of chips to show in the Home view. - * @property {Object[]} [quick_access_cards] List of cards to show between welcome card and rooms cards. - * @property {Object[]} [extra_cards] List of cards to show below room cards. - * @property {Object[]} [extra_views] List of views to add to the dashboard. - * @property {Object.} [homeView] Options for the home view. - * @memberOf typedefs.generic - */ - -/** - * @typedef {Object} chip List of chips to show in the Home view. - * @property {boolean} light_count Chip to display the number of lights on. - * @property {boolean} fan_count Chip to display the number of fans on. - * @property {boolean} cover_count Chip to display the number of unclosed covers. - * @property {boolean} switch_count Chip to display the number of switches on. - * @property {boolean} climate_count Chip to display the number of climates which are not off. - * @property {string} weather_entity Entity ID for the weather chip to use, accepts `weather.` only. - * @property {Object[]} extra_chips List of extra chips. - * @memberOf typedefs.generic - */ - -/** - * @typedef {Object} homeViewOptions Options for the home view. - * @property {string[]} [hidden] Elements to hide from the home view. - * @memberOf typedefs.generic - */ - -/** - * @typedef {Object} cardOptions Custom card-configuration for an entity. - * @property {string} type Type of card for the entity - * @property {boolean} hidden True if the entity should be hidden from the dashboard. - * @memberOf typedefs.generic - */ - -/** - * The frontend passes a single hass object around. - * This object contains the latest state and allows you to send commands back to the server. - * - * @typedef {Object} hassObject Home Assistant object. - * @property {Object} 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: .. - * 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 {}; diff --git a/src/types/homeassistant/README.md b/src/types/homeassistant/README.md new file mode 100644 index 0000000..2e556c8 --- /dev/null +++ b/src/types/homeassistant/README.md @@ -0,0 +1,22 @@ +This directory contains partial code from +the [Home Assistant Frontend repository](https://github.com/home-assistant/frontend). + +The code mainly defines Home Assistant interfaces/types which are refactored to fit this repository. +This means properties are added/removed from the originals and subtypes may have been changed. + +The [Apache 2.0 License](https://github.com/home-assistant/frontend/blob/dev/LICENSE.md) applies to all files in this +directory. + +Copyright 2023 Ferry Cools + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/types/homeassistant/data/area_registry.ts b/src/types/homeassistant/data/area_registry.ts new file mode 100644 index 0000000..2e5d3c1 --- /dev/null +++ b/src/types/homeassistant/data/area_registry.ts @@ -0,0 +1,14 @@ +/** + * Area Entity. + * + * @property {string} area_id The id of the area. + * @property {string} name Name of the area. + * @property {string|null} picture URL to a picture that should be used instead of showing the domain icon. + * @property {string[]} aliases Array of aliases of the area. + */ +export interface AreaRegistryEntry { + area_id: string; + name: string; + picture: string | null; + aliases: string[]; +} diff --git a/src/types/homeassistant/data/climate.ts b/src/types/homeassistant/data/climate.ts new file mode 100644 index 0000000..468350a --- /dev/null +++ b/src/types/homeassistant/data/climate.ts @@ -0,0 +1,19 @@ +export const HVAC_MODES = [ + "auto", + "heat_cool", + "heat", + "cool", + "dry", + "fan_only", + "off", +] as const; + +export type HvacMode = (typeof HVAC_MODES)[number]; + +HVAC_MODES.reduce( + (order, mode, index) => { + order[mode] = index; + return order; + }, + {} as Record +); diff --git a/src/types/homeassistant/data/device_registry.ts b/src/types/homeassistant/data/device_registry.ts new file mode 100644 index 0000000..6b0623e --- /dev/null +++ b/src/types/homeassistant/data/device_registry.ts @@ -0,0 +1,38 @@ +/** + * Device Entity. + * + * @property {string} id Unique ID of a device (generated by Home Assistant). + * @property {string[]} config_entries + * @property {Array} connections + * @property {Array} identifiers + * @property {string | null} manufacturer + * @property {string | null} model + * @property {string | null} name + * @property {string | null} sw_version + * @property {string | null} hw_version + * @property {string | null} serial_number + * @property {string | null} via_device_id + * @property {string} area_id The Area which the device is placed in. + * @property {string | null} name_by_user + * @property {string[] | null} entry_type + * @property {string | null} disabled_by Indicates by what this entity is disabled. + * @property {string | null} configuration_url + */ +export interface DeviceRegistryEntry { + id: string; + config_entries: string[]; + connections: Array<[string, string]>; + identifiers: Array<[string, string]>; + manufacturer: string | null; + model: string | null; + name: string | null; + sw_version: string | null; + hw_version: string | null; + serial_number: string | null; + via_device_id: string | null; + area_id: string | null; + name_by_user: string | null; + entry_type: "service" | null; + disabled_by: "user" | "integration" | "config_entry" | null; + configuration_url: string | null; +} diff --git a/src/types/homeassistant/data/entity_registry.ts b/src/types/homeassistant/data/entity_registry.ts new file mode 100644 index 0000000..f538a27 --- /dev/null +++ b/src/types/homeassistant/data/entity_registry.ts @@ -0,0 +1,97 @@ +import {LightColor} from "./light"; + +type EntityCategory = "config" | "diagnostic"; + +export interface EntityRegistryDisplayEntry { + entity_id: string; + name?: string; + device_id?: string; + area_id?: string; + hidden?: boolean; + entity_category?: EntityCategory; + translation_key?: string; + platform?: string; + display_precision?: number; +} + +/** + * Home assistant entity. + * + * @property {string} id + * @property {string} entity_id The id of this entity. + * @property {string} name The name of this entity. + * @property {string | null} icon + * @property {string | null} platform + * @property {string | null} config_entry_id + * @property {string | null} device_id The id of the device to which this entity is linked. + * @property {string | null} area_id The id of the area to which this entity is linked. + * @property {string | null} disabled_by Indicates by what this entity is disabled. + * @property {Object} hidden_by Indicates by what this entity is hidden. + * @property {EntityCategory | null} entity_category + * @property {boolean} has_entity_name + * @property {string} [original_name] + * @property {string} unique_id + * @property {string} [translation_key] + * @property {EntityRegistryOptions | null} options + */ +export interface EntityRegistryEntry { + id: string; + entity_id: string; + name: string | null; + icon: string | null; + platform: string; + config_entry_id: string | null; + device_id: string | null; + area_id: string | null; + disabled_by: "user" | "device" | "integration" | "config_entry" | null; + hidden_by: Exclude; + entity_category: EntityCategory | null; + has_entity_name: boolean; + original_name?: string; + unique_id: string; + translation_key?: string; + options: EntityRegistryOptions | null; +} + +export interface SensorEntityOptions { + display_precision?: number | null; + suggested_display_precision?: number | null; + unit_of_measurement?: string | null; +} + +export interface LightEntityOptions { + favorite_colors?: LightColor[]; +} + +export interface NumberEntityOptions { + unit_of_measurement?: string | null; +} + +export interface LockEntityOptions { + default_code?: string | null; +} + +export interface WeatherEntityOptions { + precipitation_unit?: string | null; + pressure_unit?: string | null; + temperature_unit?: string | null; + visibility_unit?: string | null; + wind_speed_unit?: string | null; +} + +export interface SwitchAsXEntityOptions { + entity_id: string; +} + +export interface EntityRegistryOptions { + number?: NumberEntityOptions; + sensor?: SensorEntityOptions; + lock?: LockEntityOptions; + weather?: WeatherEntityOptions; + light?: LightEntityOptions; + switch_as_x?: SwitchAsXEntityOptions; + conversation?: Record; + "cloud.alexa"?: Record; + "cloud.google_assistant"?: Record; +} + diff --git a/src/types/homeassistant/data/light.ts b/src/types/homeassistant/data/light.ts new file mode 100644 index 0000000..35186fb --- /dev/null +++ b/src/types/homeassistant/data/light.ts @@ -0,0 +1,6 @@ +export type LightColor = + | { color_temp_kelvin: number; } + | { hs_color: [number, number]; } + | { rgb_color: [number, number, number]; } + | { rgbw_color: [number, number, number, number]; } + | { rgbww_color: [number, number, number, number, number]; }; diff --git a/src/types/homeassistant/data/lovelace.ts b/src/types/homeassistant/data/lovelace.ts new file mode 100644 index 0000000..e893952 --- /dev/null +++ b/src/types/homeassistant/data/lovelace.ts @@ -0,0 +1,118 @@ +import {HassServiceTarget} from "home-assistant-js-websocket"; + +export type LovelaceStrategyConfig = { + type: string; + [key: string]: any; +}; + +export interface LovelaceConfig { + title?: string; + strategy?: LovelaceStrategyConfig; + views: LovelaceViewConfig[]; + background?: string; +} + +/** + * View Config. + * + * @see https://www.home-assistant.io/dashboards/views/ + */ +export interface LovelaceViewConfig { + index?: number; + title?: string; + type?: string; + strategy?: LovelaceStrategyConfig; + badges?: Array; + cards?: LovelaceCardConfig[]; + path?: string; + icon?: string; + theme?: string; + panel?: boolean; + background?: string; + visible?: boolean | ShowViewConfig[]; + subview?: boolean; + back_path?: string; +} + +export interface ShowViewConfig { + user?: string; +} + +export interface LovelaceBadgeConfig { + type?: string; + [key: string]: any; +} + +export interface LovelaceCardConfig { + index?: number; + view_index?: number; + view_layout?: any; + type: string; + [key: string]: any; +} + +export interface ToggleActionConfig extends BaseActionConfig { + action: "toggle"; +} + +export interface CallServiceActionConfig extends BaseActionConfig { + action: "call-service"; + service: string; + target?: HassServiceTarget; + // Property "service_data" is kept for backwards compatibility. Replaced by "data". + service_data?: Record; + data?: Record; +} + +export interface NavigateActionConfig extends BaseActionConfig { + action: "navigate"; + navigation_path: string; + navigation_replace?: boolean; +} + +export interface UrlActionConfig extends BaseActionConfig { + action: "url"; + url_path: string; +} + +export interface MoreInfoActionConfig extends BaseActionConfig { + action: "more-info"; +} + +export interface AssistActionConfig extends BaseActionConfig { + action: "assist"; + pipeline_id?: string; + start_listening?: boolean; +} + +export interface NoActionConfig extends BaseActionConfig { + action: "none"; +} + +export interface CustomActionConfig extends BaseActionConfig { + action: "fire-dom-event"; +} + +export interface BaseActionConfig { + action: string; + confirmation?: ConfirmationRestrictionConfig; +} + +export interface ConfirmationRestrictionConfig { + text?: string; + exemptions?: RestrictionConfig[]; +} + +export interface RestrictionConfig { + user: string; +} + +export type ActionConfig = + | ToggleActionConfig + | CallServiceActionConfig + | NavigateActionConfig + | UrlActionConfig + | MoreInfoActionConfig + | AssistActionConfig + | NoActionConfig + | CustomActionConfig; diff --git a/src/types/homeassistant/lovelace/cards/types.ts b/src/types/homeassistant/lovelace/cards/types.ts new file mode 100644 index 0000000..279841d --- /dev/null +++ b/src/types/homeassistant/lovelace/cards/types.ts @@ -0,0 +1,26 @@ +import {LovelaceCardConfig} from "../../data/lovelace"; + +/** + * Home Assistant Stack Card Config. + * + * @property {string} type The stack type. + * @property {Object[]} cards The content of the stack. + * + * @see https://www.home-assistant.io/dashboards/horizontal-stack/ + * @see https://www.home-assistant.io/dashboards/vertical-stack/ + */ +export interface StackCardConfig extends LovelaceCardConfig { + cards: LovelaceCardConfig[]; + title?: string; +} + +/** + * Home Assistant Area Card Config. + * + * @see https://www.home-assistant.io/dashboards/area/ + */ +export interface AreaCardConfig extends LovelaceCardConfig { + area: string; + navigation_path?: string; + show_camera?: boolean; +} diff --git a/src/types/homeassistant/panels/lovelave/cards/types.ts b/src/types/homeassistant/panels/lovelave/cards/types.ts new file mode 100644 index 0000000..790046a --- /dev/null +++ b/src/types/homeassistant/panels/lovelave/cards/types.ts @@ -0,0 +1,41 @@ +import {ActionConfig, LovelaceCardConfig} from "../../../data/lovelace"; + +/** + * Home Assistant Picture Entity Config. + * + * @property {string} entity An entity_id used for the picture. + * @property {string} [name] Overwrite entity name. + * @property {string} [image] URL of an image. + * @property {string} [camera_image] Camera entity_id to use. (not required if entity is already a camera-entity). + * @property {string} [camera_view=auto] “live” will show the live view if stream is enabled. + * @property {Record} [state_image] Map entity states to images (state: image URL). + * @property {string[]} [state_filter] State-based CSS filters. + * @property {string} [aspect_ratio] Forces the height of the image to be a ratio of the width. + * Valid formats: Height percentage value (23%) or ratio expressed with colon or “x” + * separator (16:9 or 16x9). + * For a ratio, the second element can be omitted and will default to “1” + * (1.78 equals 1.78:1). + * @property {ActionConfig} [tap_action] Action taken on card tap. + * @property {ActionConfig} [hold_action] Action taken on card tap and hold. + * @property {ActionConfig} [double_tap_action] Action taken on card double tap. + * @property {boolean} [show_name=true] Shows name in footer. + * @property {string} [theme=true] Override the used theme for this card with any loaded theme. + * + * @see https://www.home-assistant.io/dashboards/picture-entity/ + */ +export interface PictureEntityCardConfig extends LovelaceCardConfig { + entity: string; + name?: string; + image?: string; + camera_image?: string; + camera_view?: "live" | "auto"; + state_image?: Record; + state_filter?: string[]; + aspect_ratio?: string; + tap_action?: ActionConfig; + hold_action?: ActionConfig; + double_tap_action?: ActionConfig; + show_name?: boolean; + show_state?: boolean; + theme?: string; +} diff --git a/src/types/homeassistant/types.ts b/src/types/homeassistant/types.ts new file mode 100644 index 0000000..da65978 --- /dev/null +++ b/src/types/homeassistant/types.ts @@ -0,0 +1,95 @@ +import {Auth, Connection, HassConfig, HassEntities, HassServices, MessageBase,} from "home-assistant-js-websocket"; +import {AreaRegistryEntry} from "./data/area_registry"; +import {DeviceRegistryEntry} from "./data/device_registry"; +import {EntityRegistryDisplayEntry} from "./data/entity_registry"; + +export interface Credential { + auth_provider_type: string; + auth_provider_id: string; +} + +export interface MFAModule { + id: string; + name: string; + enabled: boolean; +} + +export interface CurrentUser { + id: string; + is_owner: boolean; + is_admin: boolean; + name: string; + credentials: Credential[]; + mfa_modules: MFAModule[]; +} + +export interface PanelInfo | null> { + component_name: string; + config: T; + icon: string | null; + title: string | null; + url_path: string; + config_panel_domain?: string; +} + +export interface Panels { + [name: string]: PanelInfo; +} + + +export interface Translation { + nativeName: string; + isRTL: boolean; + hash: string; +} + +export interface TranslationMetadata { + fragments: string[]; + translations: { + [lang: string]: Translation; + }; +} + +export interface Resources { + [language: string]: Record; +} + +export interface HomeAssistant { + auth: Auth & { external?: any }; + connection: Connection; + connected: boolean; + states: HassEntities; + entities: { [id: string]: EntityRegistryDisplayEntry }; + devices: { [id: string]: DeviceRegistryEntry }; + areas: { [id: string]: AreaRegistryEntry }; + services: HassServices; + config: HassConfig; + themes: { [k: string]: any }; + selectedTheme: { [k: string]: any } | null; + panels: Panels; + panelUrl: string; + // i18n + // current effective language in that order: + // - backend saved user selected language + // - language in local app storage + // - browser language + // - english (en) + language: string; + // local stored language, keep that name for backward compatibility + selectedLanguage: string | null; + locale: { [k: string]: any }; + resources: Resources; + localize: Function; + translationMetadata: TranslationMetadata; + suspendWhenHidden: boolean; + enableShortcuts: boolean; + vibrate: boolean; + debugConnection: boolean; + dockedSidebar: "docked" | "always_hidden" | "auto"; + defaultPanel: string; + moreInfoEntityId: string | null; + user?: CurrentUser; + userData?: { [k: string]: any } | null; + + callWS(msg: MessageBase): Promise; +} diff --git a/src/types/lovelace-mushroom/README.md b/src/types/lovelace-mushroom/README.md new file mode 100644 index 0000000..c53b62a --- /dev/null +++ b/src/types/lovelace-mushroom/README.md @@ -0,0 +1,22 @@ +This directory contains partial code from +the [Lovelace Mushroom repository](https://github.com/piitaya/lovelace-mushroom). + +The code mainly defines Home Assistant interfaces/types which are refactored to fit this repository. +This means properties are added/removed from the originals and subtypes may have been changed. + +The [Apache 2.0 License](https://github.com/home-assistant/frontend/blob/dev/LICENSE.md) applies to all files in this +directory. + +Copyright 2023 Ferry Cools + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/types/lovelace-mushroom/cards/chips-card.ts b/src/types/lovelace-mushroom/cards/chips-card.ts new file mode 100644 index 0000000..fff7b4f --- /dev/null +++ b/src/types/lovelace-mushroom/cards/chips-card.ts @@ -0,0 +1,15 @@ +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {LovelaceChipConfig} from "../utils/lovelace/chip/types"; + +/** + * Chips Card Configuration + * + * @param {LovelaceChipConfig[]} chips Chips Array + * @param {string} [alignment=start] Chips alignment (end, center, justify), when empty default behavior is start. + * + * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/chips.md + */ +export interface ChipsCardConfig extends LovelaceCardConfig { + chips: LovelaceChipConfig[]; + alignment?: string; +} diff --git a/src/types/lovelace-mushroom/cards/climate-card-config.ts b/src/types/lovelace-mushroom/cards/climate-card-config.ts new file mode 100644 index 0000000..f7299da --- /dev/null +++ b/src/types/lovelace-mushroom/cards/climate-card-config.ts @@ -0,0 +1,23 @@ +import {HvacMode} from "../../homeassistant/data/climate"; +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {EntitySharedConfig} from "../shared/config/entity-config"; +import {AppearanceSharedConfig} from "../shared/config/appearance-config"; +import {ActionsSharedConfig} from "../shared/config/actions-config"; + +/** + * Climate Card Config. + * + * @property {boolean} [show_temperature_control=false] Show buttons to control target temperature. + * @property {HvacMode[]} [hvac_modes] List of hvac modes to display (auto, heat_cool, heat, cool, dry, fan_only, off). + * @property {boolean} [collapsible_controls] Collapse controls when off. + * + * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/climate.md + */ +export type ClimateCardConfig = LovelaceCardConfig & + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig & { + show_temperature_control?: boolean; + hvac_modes?: HvacMode[]; + collapsible_controls?: boolean; +}; diff --git a/src/types/lovelace-mushroom/cards/cover-card-config.ts b/src/types/lovelace-mushroom/cards/cover-card-config.ts new file mode 100644 index 0000000..209ba59 --- /dev/null +++ b/src/types/lovelace-mushroom/cards/cover-card-config.ts @@ -0,0 +1,22 @@ +import {ActionsSharedConfig} from "../shared/config/actions-config"; +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {EntitySharedConfig} from "../shared/config/entity-config"; +import {AppearanceSharedConfig} from "../shared/config/appearance-config"; + +/** + * Cover Card Config. + * + * @property {boolean} [show_buttons_control=false] Show buttons to open, close and stop cover. + * @property {boolean} [show_position_control=false] Show a slider to control position of the cover. + * @property {boolean} [show_tilt_position_control=false] Show a slider to control tilt position of the cover. + * + * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/cover.md + */ +export type CoverCardConfig = LovelaceCardConfig & + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig & { + show_buttons_control?: boolean; + show_position_control?: boolean; + show_tilt_position_control?: boolean; +}; diff --git a/src/types/lovelace-mushroom/cards/entity-card-config.ts b/src/types/lovelace-mushroom/cards/entity-card-config.ts new file mode 100644 index 0000000..83c520d --- /dev/null +++ b/src/types/lovelace-mushroom/cards/entity-card-config.ts @@ -0,0 +1,18 @@ +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {AppearanceSharedConfig} from "../shared/config/appearance-config"; +import {EntitySharedConfig} from "../shared/config/entity-config"; +import {ActionsSharedConfig} from "../shared/config/actions-config"; + +/** + * Entity Card Config. + * + * @property {string} [icon_color=blue] Custom color for icon when entity is state is active. + * + * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/entity.md + */ +export type EntityCardConfig = LovelaceCardConfig & + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig & { + icon_color?: string; +}; diff --git a/src/types/lovelace-mushroom/cards/fan-card-config.ts b/src/types/lovelace-mushroom/cards/fan-card-config.ts new file mode 100644 index 0000000..3dae196 --- /dev/null +++ b/src/types/lovelace-mushroom/cards/fan-card-config.ts @@ -0,0 +1,24 @@ +import {ActionsSharedConfig} from "../shared/config/actions-config"; +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {EntitySharedConfig} from "../shared/config/entity-config"; +import {AppearanceSharedConfig} from "../shared/config/appearance-config"; + +/** + * Fan Card Config. + * + * @property {boolean} [icon_animation=false] Animate the icon when fan is on. + * @property {boolean} [show_percentage_control=false] Show a slider to control speed. + * @property {boolean} [show_oscillate_control=false] Show a button to control oscillation. + * @property {boolean} [icon_animation=false] Animate the icon when fan is on. + * + * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/fan.md + */ +export type FanCardConfig = LovelaceCardConfig & + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig & { + icon_animation?: boolean; + show_percentage_control?: boolean; + show_oscillate_control?: boolean; + collapsible_controls?: boolean; +}; diff --git a/src/types/lovelace-mushroom/cards/light-card-config.ts b/src/types/lovelace-mushroom/cards/light-card-config.ts new file mode 100644 index 0000000..35c745e --- /dev/null +++ b/src/types/lovelace-mushroom/cards/light-card-config.ts @@ -0,0 +1,29 @@ +import {ActionsSharedConfig} from "../shared/config/actions-config"; +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {EntitySharedConfig} from "../shared/config/entity-config"; +import {AppearanceSharedConfig} from "../shared/config/appearance-config"; + +/** + * Light Card Config. + * + * @property {string} [icon_color=blue] Custom color for icon and brightness bar when the lights are on and + * use_light_color is false. + * @property {boolean} [show_brightness_control=false] Show a slider to control brightness. + * @property {boolean} [show_color_temp_control=false] Show a slider to control temperature color. + * @property {boolean} [show_color_control=false] Show a slider to control RGB color. + * @property {boolean} [collapsible_controls=false] Collapse controls when off. + * @property {boolean} [use_light_color=false] Colorize the icon and slider according light temperature or color. + * + * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/light.md + */ +export type LightCardConfig = LovelaceCardConfig & + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig & { + icon_color?: string; + show_brightness_control?: boolean; + show_color_temp_control?: boolean; + show_color_control?: boolean; + collapsible_controls?: boolean; + use_light_color?: boolean; +}; diff --git a/src/types/lovelace-mushroom/cards/lock-card-config.ts b/src/types/lovelace-mushroom/cards/lock-card-config.ts new file mode 100644 index 0000000..86df397 --- /dev/null +++ b/src/types/lovelace-mushroom/cards/lock-card-config.ts @@ -0,0 +1,14 @@ +import {ActionsSharedConfig} from "../shared/config/actions-config"; +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {EntitySharedConfig} from "../shared/config/entity-config"; +import {AppearanceSharedConfig} from "../shared/config/appearance-config"; + +/** + * Lock Card Config. + * + * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/lock.md + */ +export type LockCardConfig = LovelaceCardConfig & + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig; diff --git a/src/types/lovelace-mushroom/cards/media-player-card-config.ts b/src/types/lovelace-mushroom/cards/media-player-card-config.ts new file mode 100644 index 0000000..388a647 --- /dev/null +++ b/src/types/lovelace-mushroom/cards/media-player-card-config.ts @@ -0,0 +1,45 @@ +import {ActionsSharedConfig} from "../shared/config/actions-config"; +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {EntitySharedConfig} from "../shared/config/entity-config"; +import {AppearanceSharedConfig} from "../shared/config/appearance-config"; + +export const MEDIA_LAYER_MEDIA_CONTROLS = [ + "on_off", + "shuffle", + "previous", + "play_pause_stop", + "next", + "repeat", +] as const; + +export type MediaPlayerMediaControl = (typeof MEDIA_LAYER_MEDIA_CONTROLS)[number]; + +export const MEDIA_PLAYER_VOLUME_CONTROLS = [ + "volume_mute", + "volume_set", + "volume_buttons", +] as const; + +export type MediaPlayerVolumeControl = (typeof MEDIA_PLAYER_VOLUME_CONTROLS)[number]; + +/** + * Media Player Card Config. + * + * @property {boolean} [use_media_info=false] Use media info instead of name, state, and icon when media is playing. + * @property {boolean} [show_volume_level=false] Show volume level next to media state when media is playing. + * @property {MediaPlayerVolumeControl[]} [volume_controls] List of controls to display (volume_mute, volume_set, volume_buttons) + * @property {MediaPlayerMediaControl[]} [media_controls] List of controls to display (on_off, shuffle, previous, play_pause_stop, next, repeat) + * @property {boolean} [collapsible_controls=false] Collapse controls when off + * + * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/media-player.md + */ +export type MediaPlayerCardConfig = LovelaceCardConfig & + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig & { + use_media_info?: boolean; + show_volume_level?: boolean; + volume_controls?: MediaPlayerVolumeControl[]; + media_controls?: MediaPlayerMediaControl[]; + collapsible_controls?: boolean; +}; diff --git a/src/types/lovelace-mushroom/cards/number-card-config.ts b/src/types/lovelace-mushroom/cards/number-card-config.ts new file mode 100644 index 0000000..9239f4b --- /dev/null +++ b/src/types/lovelace-mushroom/cards/number-card-config.ts @@ -0,0 +1,24 @@ +import {ActionsSharedConfig} from "../shared/config/actions-config"; +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {EntitySharedConfig} from "../shared/config/entity-config"; +import {AppearanceSharedConfig} from "../shared/config/appearance-config"; + +export const DISPLAY_MODES = ["slider", "buttons"] as const; + +type DisplayMode = (typeof DISPLAY_MODES)[number]; + +/** + * Number Card Config. + * + * @property {string} [icon_color=blue] Custom color for icon when entity state is active. + * @property {DisplayMode} [display_mode=slider] Slider or Button controls. + * + * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/number.md + */ +export type NumberCardConfig = LovelaceCardConfig & + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig & { + icon_color?: string; + display_mode?: DisplayMode; +}; diff --git a/src/types/lovelace-mushroom/cards/person-card-config.ts b/src/types/lovelace-mushroom/cards/person-card-config.ts new file mode 100644 index 0000000..869b74f --- /dev/null +++ b/src/types/lovelace-mushroom/cards/person-card-config.ts @@ -0,0 +1,14 @@ +import {ActionsSharedConfig} from "../shared/config/actions-config"; +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {EntitySharedConfig} from "../shared/config/entity-config"; +import {AppearanceSharedConfig} from "../shared/config/appearance-config"; + +/** + * Person Card Config. + * + * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/person.md + */ +export type PersonCardConfig = LovelaceCardConfig & + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig; diff --git a/src/types/lovelace-mushroom/cards/template-card-config.ts b/src/types/lovelace-mushroom/cards/template-card-config.ts new file mode 100644 index 0000000..b71ae31 --- /dev/null +++ b/src/types/lovelace-mushroom/cards/template-card-config.ts @@ -0,0 +1,36 @@ +import {ActionsSharedConfig} from "../shared/config/actions-config"; +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {AppearanceSharedConfig} from "../shared/config/appearance-config"; + +/** + * Template Card Config. + * + * @property {string} [entity] + * @property {string} [icon] Icon to render. May contain templates. + * @property {string} [icon_color] Icon color to render. May contain templates. + * @property {string} [primary] Primary info to render. May contain templates. + * @property {string} [secondary] Secondary info to render. May contain templates. + * @property {string} [badge_icon] Badge icon to render. May contain templates. + * @property {string} [badge_color] Badge icon color to render. May contain templates. + * @property {string} [picture] Picture to render. May contain templates. + * @property {boolean} [multiline_secondary] Enables support for multiline text for the secondary info. + * @property {string | string[]} [entity_id] Only reacts to the state changes of these entities. + * This can be used if the automatic analysis fails to find all relevant + * entities. + * + * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/template.md + */ +export type TemplateCardConfig = LovelaceCardConfig & + AppearanceSharedConfig & + ActionsSharedConfig & { + entity?: string; + icon?: string; + icon_color?: string; + primary?: string; + secondary?: string; + badge_icon?: string; + badge_color?: string; + picture?: string; + multiline_secondary?: boolean; + entity_id?: string | string[]; +}; diff --git a/src/types/lovelace-mushroom/cards/title-card-config.ts b/src/types/lovelace-mushroom/cards/title-card-config.ts new file mode 100644 index 0000000..db3f379 --- /dev/null +++ b/src/types/lovelace-mushroom/cards/title-card-config.ts @@ -0,0 +1,21 @@ +import {ActionConfig, LovelaceCardConfig} from "../../homeassistant/data/lovelace"; + +/** + * Title Card Config. + * + * @property {string} [title] Title to render. May contain templates. + * @property {string} [subtitle] Subtitle to render. May contain templates. + * @property {ActionConfig} [title_tap_action=none] Home assistant action to perform on title tap. + * @property {ActionConfig} [subtitle_tap_action=none] Home assistant action to perform on subtitle tap. + * + * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/title.md + */ +export interface TitleCardConfig extends LovelaceCardConfig { + title?: string; + subtitle?: string; + alignment?: string; + title_tap_action?: ActionConfig; + subtitle_tap_action?: ActionConfig; +} + + diff --git a/src/types/lovelace-mushroom/cards/vacuum-card-config.ts b/src/types/lovelace-mushroom/cards/vacuum-card-config.ts new file mode 100644 index 0000000..f08307f --- /dev/null +++ b/src/types/lovelace-mushroom/cards/vacuum-card-config.ts @@ -0,0 +1,31 @@ +import {ActionsSharedConfig} from "../shared/config/actions-config"; +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {EntitySharedConfig} from "../shared/config/entity-config"; +import {AppearanceSharedConfig} from "../shared/config/appearance-config"; + +export const VACUUM_COMMANDS = [ + "on_off", + "start_pause", + "stop", + "locate", + "clean_spot", + "return_home", +] as const; + +export type VacuumCommand = (typeof VACUUM_COMMANDS)[number]; + +/** + * Vacuum Card Config. + * + * @param {boolean} icon_animation Animate the icon when vacuum is cleaning. + * @param {VacuumCommand[]} commands List of commands to display (start_pause, stop, locate, clean_spot, return_home). + * + * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/vacuum.md + */ +export type VacuumCardConfig = LovelaceCardConfig & + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig & { + icon_animation?: boolean; + commands?: VacuumCommand[]; +}; diff --git a/src/types/lovelace-mushroom/shared/config/actions-config.ts b/src/types/lovelace-mushroom/shared/config/actions-config.ts new file mode 100644 index 0000000..a864df8 --- /dev/null +++ b/src/types/lovelace-mushroom/shared/config/actions-config.ts @@ -0,0 +1,7 @@ +import {ActionConfig} from "../../../homeassistant/data/lovelace"; + +export type ActionsSharedConfig = { + tap_action?: ActionConfig; + hold_action?: ActionConfig; + double_tap_action?: ActionConfig; +}; diff --git a/src/types/lovelace-mushroom/shared/config/appearance-config.ts b/src/types/lovelace-mushroom/shared/config/appearance-config.ts new file mode 100644 index 0000000..1a586d3 --- /dev/null +++ b/src/types/lovelace-mushroom/shared/config/appearance-config.ts @@ -0,0 +1,13 @@ +import {boolean, enums, Infer, object, optional} from "superstruct"; +import {layoutStruct} from "./utils/layout"; +import {ICON_TYPES, INFOS} from "./utils/info"; + +export const appearanceSharedConfigStruct = object({ + layout: optional(layoutStruct), + fill_container: optional(boolean()), + primary_info: optional(enums(INFOS)), + secondary_info: optional(enums(INFOS)), + icon_type: optional(enums(ICON_TYPES)), +}); + +export type AppearanceSharedConfig = Infer; diff --git a/src/types/lovelace-mushroom/shared/config/entity-config.ts b/src/types/lovelace-mushroom/shared/config/entity-config.ts new file mode 100644 index 0000000..4703cd1 --- /dev/null +++ b/src/types/lovelace-mushroom/shared/config/entity-config.ts @@ -0,0 +1,9 @@ +import {Infer, object, optional, string} from "superstruct"; + +export const entitySharedConfigStruct = object({ + entity: optional(string()), + name: optional(string()), + icon: optional(string()), +}); + +export type EntitySharedConfig = Infer; diff --git a/src/types/lovelace-mushroom/shared/config/utils/info.ts b/src/types/lovelace-mushroom/shared/config/utils/info.ts new file mode 100644 index 0000000..8ee680e --- /dev/null +++ b/src/types/lovelace-mushroom/shared/config/utils/info.ts @@ -0,0 +1,2 @@ +export const INFOS = ["name", "state", "last-changed", "last-updated", "none"] as const; +export const ICON_TYPES = ["icon", "entity-picture", "none"] as const; diff --git a/src/types/lovelace-mushroom/shared/config/utils/layout.ts b/src/types/lovelace-mushroom/shared/config/utils/layout.ts new file mode 100644 index 0000000..6a47661 --- /dev/null +++ b/src/types/lovelace-mushroom/shared/config/utils/layout.ts @@ -0,0 +1,3 @@ +import {literal, union} from "superstruct"; + +export const layoutStruct = union([literal("horizontal"), literal("vertical"), literal("default")]); diff --git a/src/types/lovelace-mushroom/utils/info.ts b/src/types/lovelace-mushroom/utils/info.ts new file mode 100644 index 0000000..e3fd13c --- /dev/null +++ b/src/types/lovelace-mushroom/utils/info.ts @@ -0,0 +1,2 @@ +export const INFOS = ["name", "state", "last-changed", "last-updated", "none"] as const; +export type Info = (typeof INFOS)[number]; diff --git a/src/types/lovelace-mushroom/utils/lovelace/chip/types.ts b/src/types/lovelace-mushroom/utils/lovelace/chip/types.ts new file mode 100644 index 0000000..7769b99 --- /dev/null +++ b/src/types/lovelace-mushroom/utils/lovelace/chip/types.ts @@ -0,0 +1,202 @@ +import {ActionConfig} from "../../../../homeassistant/data/lovelace"; +import {Info} from "../../info"; + +/** + * Action Chip Config + * + * @property {"action"} type Type of the chip. + * @property {string} [icon] Custom icon. + * @property {string} [icon_color] Custom color for icon. + * @property {ActionConfig} [tap_action] Home assistant action to perform on tap. + * @property {ActionConfig} [hold_action] Home assistant action to perform on hold. + * @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap. + */ +export type ActionChipConfig = { + type: "action"; + icon?: string; + icon_color?: string; + tap_action?: ActionConfig; + hold_action?: ActionConfig; + double_tap_action?: ActionConfig; +}; + +/** + * Alarm Control Panel Chip Config + * + * @property {"alarm-control-panel"} type Type of the chip. + * @property {string} [entity] Entity. + * @property {string} [name] Custom name. + * @property {string} [content_info] Custom content. + * @property {string} [icon] Custom icon. + * @property {string} [icon_color] Custom color for icon. + * @property {ActionConfig} [tap_action] Home assistant action to perform on tap. + * @property {ActionConfig} [hold_action] Home assistant action to perform on hold. + * @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap. + */ +export type AlarmControlPanelChipConfig = { + type: "alarm-control-panel"; + entity?: string; + name?: string; + content_info?: Info; + icon?: string; + icon_color?: string; + tap_action?: ActionConfig; + hold_action?: ActionConfig; + double_tap_action?: ActionConfig; +}; + +/** + * Back Chip Config + * + * @property {"back"} type Type of the chip. + * @property {string} [icon] Custom icon. + */ +export type BackChipConfig = { + type: "back"; + icon?: string; +}; + +/** + * Entity Chip Config + * + * @property {"entity"} type Type of the chip. + * @property {string} [entity] Entity. + * @property {string} [name] Custom name. + * @property {string} [content_info] Custom content. + * @property {string} [icon] Custom icon. + * @property {string} [icon_color] Custom color for icon. + * @property {boolean} [use_entity_picture] + * @property {ActionConfig} [tap_action] Home assistant action to perform on tap. + * @property {ActionConfig} [hold_action] Home assistant action to perform on hold. + * @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap. + */ +export type EntityChipConfig = { + type: "entity"; + entity?: string; + name?: string; + content_info?: Info; + icon?: string; + icon_color?: string; + use_entity_picture?: boolean; + tap_action?: ActionConfig; + hold_action?: ActionConfig; + double_tap_action?: ActionConfig; +}; + +/** + * Menu Chip Config + * + * @property {"menu"} type Type of the chip. + * @property {string} [icon] Custom icon. + */ +export type MenuChipConfig = { + type: "menu"; + icon?: string; +}; + +/** + * Weather Chip Config + * + * @property {"weather"} type Type of the chip. + * @property {string} [entity] Entity. + * @property {ActionConfig} [tap_action] Home assistant action to perform on tap. + * @property {ActionConfig} [hold_action] Home assistant action to perform on hold. + * @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap. + * @property {boolean} [show_temperature] Show the temperature. + * @property {boolean} [show_conditions] Show the conditions. + */ +export type WeatherChipConfig = { + type: "weather"; + entity?: string; + tap_action?: ActionConfig; + hold_action?: ActionConfig; + double_tap_action?: ActionConfig; + show_temperature?: boolean; + show_conditions?: boolean; +}; + +/** + * Template Chip Config + * + * @property {"template"} type Type of the chip. + * @property {string} [entity] Entity. + * @property {ActionConfig} [tap_action] Home assistant action to perform on tap. + * @property {ActionConfig} [hold_action] Home assistant action to perform on hold. + * @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap. + * @property {string} [content] + * @property {string} [icon] Custom icon. + * @property {string} [icon_color] Custom color for icon. + * @property {string} [picture] + * @property {string | string[]} [entity_id] + */ +export type TemplateChipConfig = { + type: "template"; + entity?: string; + hold_action?: ActionConfig; + tap_action?: ActionConfig; + double_tap_action?: ActionConfig; + content?: string; + icon?: string; + icon_color?: string; + picture?: string; + entity_id?: string | string[]; +}; + +/** + * Conditional Chip Config + * + * @property {"conditional"} type Type of the chip. + * @property {LovelaceChipConfig} [chip] A chip configuration. + * @property {[]} conditions + */ +export interface ConditionalChipConfig { + type: "conditional"; + chip?: LovelaceChipConfig; + conditions: any[]; +} + +/** + * Light Chip Config + * + * @property {"light"} type Type of the chip. + * @property {string} [entity] Entity. + * @property {string} [name] + * @property {Info} [content_info] + * @property {string} [icon] Custom icon. + * @property {boolean} [use_light_color] Colorize the icon and slider according light temperature or color. + * @property {ActionConfig} [tap_action] Home assistant action to perform on tap. + * @property {ActionConfig} [hold_action] Home assistant action to perform on hold. + * @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap. + */ +export type LightChipConfig = { + type: "light"; + entity?: string; + name?: string; + content_info?: Info; + icon?: string; + use_light_color?: boolean; + hold_action?: ActionConfig; + tap_action?: ActionConfig; + double_tap_action?: ActionConfig; +}; + +/** + * Spacer Chip Config + * + * @property {"spacer"} type Type of the chip. + */ +export type SpacerChipConfig = { + type: "spacer"; +}; + +export type LovelaceChipConfig = + | ActionChipConfig + | AlarmControlPanelChipConfig + | BackChipConfig + | EntityChipConfig + | MenuChipConfig + | WeatherChipConfig + | TemplateChipConfig + | ConditionalChipConfig + | LightChipConfig + | SpacerChipConfig; diff --git a/src/types/strategy/cards.ts b/src/types/strategy/cards.ts new file mode 100644 index 0000000..b8582a2 --- /dev/null +++ b/src/types/strategy/cards.ts @@ -0,0 +1,70 @@ +import {LovelaceCardConfig} from "../homeassistant/data/lovelace"; +import {TitleCardConfig} from "../lovelace-mushroom/cards/title-card-config"; +import {EntitySharedConfig} from "../lovelace-mushroom/shared/config/entity-config"; +import {AppearanceSharedConfig} from "../lovelace-mushroom/shared/config/appearance-config"; +import {ActionsSharedConfig} from "../lovelace-mushroom/shared/config/actions-config"; +import {TemplateCardConfig} from "../lovelace-mushroom/cards/template-card-config"; +import {EntityCardConfig} from "../lovelace-mushroom/cards/entity-card-config"; +import {PictureEntityCardConfig} from "../homeassistant/panels/lovelave/cards/types"; +import {ClimateCardConfig} from "../lovelace-mushroom/cards/climate-card-config"; +import {CoverCardConfig} from "../lovelace-mushroom/cards/cover-card-config"; +import {FanCardConfig} from "../lovelace-mushroom/cards/fan-card-config"; +import {AreaCardConfig} from "../homeassistant/lovelace/cards/types"; +import {LightCardConfig} from "../lovelace-mushroom/cards/light-card-config"; +import {LockCardConfig} from "../lovelace-mushroom/cards/lock-card-config"; +import {MediaPlayerCardConfig} from "../lovelace-mushroom/cards/media-player-card-config"; +import {NumberCardConfig} from "../lovelace-mushroom/cards/number-card-config"; +import {PersonCardConfig} from "../lovelace-mushroom/cards/person-card-config"; +import {VacuumCardConfig} from "../lovelace-mushroom/cards/vacuum-card-config"; + +export namespace cards { + /** + * Abstract Card Config. + */ + export type AbstractCardConfig = LovelaceCardConfig & + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig; + + /** + * Controller Card Config. + * + * @property {boolean} [showControls=true] False to hide controls. + * @property {string} [iconOn] Icon to show for switching entities from off state. + * @property {string} [iconOff] Icon to show for switching entities to off state. + * @property {string} [onService=none] Service to call for switching entities from off state. + * @property {string} [offService=none] Service to call for switching entities to off state. + */ + export interface ControllerCardConfig extends TitleCardConfig { + type: "mushroom-title-card", + showControls?: boolean; + iconOn?: string; + iconOff?: string; + onService?: string; + offService?: string; + } + + export type AreaCardOptions = Omit; + export type ClimateCardOptions = Omit; + export type ControllerCardOptions = Omit; + export type CoverCardOptions = Omit; + export type EntityCardOptions = Omit; + export type FanCardOptions = Omit; + export type LightCardOptions = Omit; + export type LockCardOptions = Omit; + export type MediaPlayerCardOptions = Omit; + export type NumberCardOptions = Omit; + export type PersonCardOptions = Omit; + export type PictureEntityCardOptions = Omit; + export type TemplateCardOptions = Omit; + export type VacuumCardOptions = Omit; +} + + + + + + + + + diff --git a/src/types/strategy/chips.ts b/src/types/strategy/chips.ts new file mode 100644 index 0000000..d9eace7 --- /dev/null +++ b/src/types/strategy/chips.ts @@ -0,0 +1,6 @@ +import {TemplateChipConfig, WeatherChipConfig} from "../lovelace-mushroom/utils/lovelace/chip/types"; + +export namespace chips { + export type TemplateChipOptions = Omit; + export type WeatherChipOptions = Omit; +} diff --git a/src/types/strategy/generic.ts b/src/types/strategy/generic.ts new file mode 100644 index 0000000..a4532c2 --- /dev/null +++ b/src/types/strategy/generic.ts @@ -0,0 +1,214 @@ +import { + CallServiceActionConfig, + LovelaceCardConfig, + LovelaceConfig, + LovelaceViewConfig +} from "../homeassistant/data/lovelace"; +import {HomeAssistant} from "../homeassistant/types"; +import {AreaRegistryEntry} from "../homeassistant/data/area_registry"; +import {cards} from "./cards"; +import {EntityRegistryEntry} from "../homeassistant/data/entity_registry"; +import {LovelaceChipConfig} from "../lovelace-mushroom/utils/lovelace/chip/types"; +import {HassServiceTarget} from "home-assistant-js-websocket"; + +export namespace generic { + /** + * An entry out of a Home Assistant Register. + */ + export type RegistryEntry = + | AreaRegistryEntry + | DataTransfer + | EntityRegistryEntry + + /** + * View Entity. + * + * @property {number} [order] Ordering position of the entity in the list of available views. + * @property {boolean} [hidden] True if the entity should be hidden from the dashboard. + */ + export interface ViewConfig extends LovelaceViewConfig { + hidden?: boolean; + order?: number; + } + + /** + * Domain Configuration. + * + * @property {number} [order] Ordering position of the entity in the list of available views. + * @property {boolean} [hidden] True if the entity should be hidden from the dashboard. + */ + export interface DomainConfig extends Partial { + hidden?: boolean; + order?: number; + } + + /** + * Dashboard Information Object. + * + * Home Assistant passes this object to the Dashboard Generator method. + * + * @property {LovelaceConfig} config Dashboard configuration. + * @property {HomeAssistant} hass The Home Assistant object. + * + * @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#dashboard-strategies + */ + export interface DashBoardInfo { + config?: LovelaceConfig & { + strategy: { + options?: StrategyConfig + } + }; + hass: HomeAssistant; + } + + /** + * View Information Object. + * + * Home Assistant passes this object to the View Generator method. + * + * @property {LovelaceViewConfig} view View configuration. + * @property {LovelaceConfig} config Dashboard configuration. + * @property {HomeAssistant} hass The Home Assistant object. + * + * @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#view-strategies + */ + export interface ViewInfo { + view: LovelaceViewConfig & { + strategy?: { + options?: StrategyConfig & { area: StrategyArea } + } + }; + config: LovelaceConfig + hass: HomeAssistant; + } + + /** + * Strategy Configuration. + * + * @property {Object.} areas List of areas. + * @property {Object.} [card_options] Card options for entities. + * @property {chips} [chips] List of chips to show in the Home view. + * @property {boolean} [debug] Set to true for more verbose debugging info. + * @property {Object.} domains List of domains. + * @property {object[]} [extra_cards] List of cards to show below room cards. + * @property {object[]} [extra_views] List of views to add to the dashboard. + * @property {object[]} [quick_access_cards] List of cards to show between welcome card and rooms cards. + * @property {Object.} views List of views. + */ + export interface StrategyConfig { + areas: { [k: string]: StrategyArea }; + card_options?: { [k: string]: CustomCardConfig }; + chips?: Chips; + debug: boolean; + domains: { [k: string]: DomainConfig }; + extra_cards?: LovelaceCardConfig[]; + extra_views?: ViewConfig[]; + home_view: { + hidden: HiddenSectionType[] + } + quick_access_cards?: LovelaceCardConfig[]; + views: { [k: string]: ViewConfig }; + } + + const hiddenSectionList = ["chips", "persons", "greeting", "areas", "areasTitle"] as const; + export type HiddenSectionType = typeof hiddenSectionList[number]; + + /** + * Represents the default configuration for a strategy. + */ + export interface StrategyDefaults extends StrategyConfig { + areas: { + undisclosed: StrategyArea & { + area_id: "undisclosed", + }, + [k: string]: StrategyArea, + }, + domains: { + default: DomainConfig, + [k: string]: DomainConfig, + } + } + + /** + * Strategy Area. + * + * @property {number} [order] Ordering position of the area in the list of available areas. + * @property {boolean} [hidden] True if the entity should be hidden from the dashboard. + * @property {object[]} [extra_cards] An array of card configurations. + * The configured cards are added to the dashboard. + * @property {string} [type=default] The type of area card. + */ + export interface StrategyArea extends AreaRegistryEntry { + order?: number; + hidden?: boolean; + extra_cards?: LovelaceCardConfig[]; + type?: string; + } + + /** + * A list of chips to show in the Home view. + * + * @property {boolean} light_count Chip to display the number of lights on. + * @property {boolean} fan_count Chip to display the number of fans on. + * @property {boolean} cover_count Chip to display the number of unclosed covers. + * @property {boolean} switch_count Chip to display the number of switches on. + * @property {boolean} climate_count Chip to display the number of climates which are not off. + * @property {string} weather_entity Entity ID for the weather chip to use, accepts `weather.` only. + * @property {object[]} extra_chips List of extra chips. + */ + export interface Chips { + extra_chips: LovelaceChipConfig[]; + + light_count: boolean; + fan_count: boolean; + cover_count: boolean; + switch_count: boolean; + climate_count: boolean; + weather_entity: string; + + [key: string]: any; + } + + /** + * Custom Card Configuration for an entity. + * + * @property {boolean} hidden True if the entity should be hidden from the dashboard. + */ + export interface CustomCardConfig extends LovelaceCardConfig { + hidden?: boolean; + } + + /** + * Area Filter Context. + * + * @property {AreaRegistryEntry} area Area Entity. + * @property {string[]} areaDeviceIds The id of devices which are linked to the area entity. + * @property {string} domain Domain of the entity. + * Example: `light`. + */ + export interface AreaFilterContext { + area: AreaRegistryEntry; + areaDeviceIds: string[]; + domain: string; + } + + /** + * Checks if the given object is an instance of CallServiceActionConfig. + * + * @param {any} obj - The object to be checked. + * @return {boolean} - Returns true if the object is an instance of CallServiceActionConfig, otherwise false. + */ + export function isCallServiceActionConfig(obj: any): obj is CallServiceActionConfig { + return obj && obj.action === "call-service" && ["action", "service"].every(key => key in obj); + } + + /** + * Checks if the given object is an instance of HassServiceTarget. + * + * @param {any} obj - The object to check. + * @return {boolean} - True if the object is an instance of HassServiceTarget, false otherwise. + */ + export function isCallServiceActionTarget(obj: any): obj is HassServiceTarget { + return obj && ["entity_id", "device_id", "area_id"].some(key => key in obj); + } +} diff --git a/src/types/strategy/views.ts b/src/types/strategy/views.ts new file mode 100644 index 0000000..99190a4 --- /dev/null +++ b/src/types/strategy/views.ts @@ -0,0 +1,17 @@ +import {cards} from "./cards"; +import {LovelaceViewConfig} from "../homeassistant/data/lovelace"; + +export namespace views { + /** + * Options for the extended View class. + * + * @property {cards.ControllerCardConfig} [controllerCardOptions] Options for the Controller card. + */ + export interface ViewConfig extends LovelaceViewConfig { + controllerCardOptions?: cards.ControllerCardOptions; + } +} + + + + diff --git a/src/views/AbstractView.js b/src/views/AbstractView.js deleted file mode 100644 index 4ae2996..0000000 --- a/src/views/AbstractView.js +++ /dev/null @@ -1,131 +0,0 @@ -import {Helper} from "../Helper"; -import {TitleCard} from "../cards/TitleCard"; - -/** - * Abstract View Class. - * - * To create a new view, extend the new class with this one. - * - * @class - * @abstract - */ -class AbstractView { - /** - * Options for creating a view. - * - * @type {abstractOptions} - */ - options = { - title: null, - path: null, - icon: "mdi:view-dashboard", - subview: false, - }; - - /** - * A card to switch all entities in the view. - * - * @type {Object} - */ - viewTitleCard; - - /** - * Class constructor. - * - * @throws {Error} If trying to instantiate this class. - * @throws {Error} If the Helper module isn't initialized. - */ - constructor() { - if (this.constructor === AbstractView) { - throw new Error("Abstract classes can't be instantiated."); - } - - if (!Helper.isInitialized()) { - throw new Error("The Helper module must be initialized before using this one."); - } - } - - /** - * Merge the default options of this class and the custom options into the options of the parent class. - * - * @param {Object} [defaultOptions={}] Default options for the card. - * @param {Object} [customOptions={}] Custom Options for the card. - */ - mergeOptions(defaultOptions, customOptions) { - this.options = { - ...defaultOptions, - ...customOptions, - }; - } - - /** - * Create the cards to include in the view. - * - * @return {Object[] | Promise} An array of card objects. - */ - async createViewCards() { - /** @type Object[] */ - const viewCards = []; - - // Create cards for each area. - for (const area of Helper.areas) { - const areaCards = []; - const entities = Helper.getDeviceEntities(area, this["domain"]); - const className = Helper.sanitizeClassName(this["domain"] + "Card"); - const cardModule = await import(`../cards/${className}`); - - // Create a card for each domain-entity of the current area. - for (const entity of entities) { - let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id] ?? {}; - let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id] ?? {}; - - if (cardOptions.hidden || deviceOptions.hidden) { - continue; - } - - areaCards.push(new cardModule[className](entity, cardOptions).getCard()); - } - - if (areaCards.length) { - // Create a Title card for the current area if it has entities. - areaCards.unshift(new TitleCard( - [area], - { - title: area.name, - ...this.options["titleCard"], - }, - this["domain"], - ).createCard()); - - viewCards.push({ - type: "vertical-stack", - cards: areaCards, - }); - } - } - - viewCards.unshift(viewCards.length ? this.viewTitleCard : { - type: "custom:mushroom-title-card", - title: "No Entities Available", - subtitle: "They're either hidden by the configuration or by Home Assistant.", - }); - - return viewCards; - } - - /** - * Get a view object. - * - * The view includes the cards which are created by method createViewCards(). - * - * @returns {viewOptions & {cards: Object[]}} The view object. - */ - async getView() { - return { - ...this.options, - cards: await this.createViewCards(), - }; - } -} - -export {AbstractView}; diff --git a/src/views/AbstractView.ts b/src/views/AbstractView.ts new file mode 100644 index 0000000..a492feb --- /dev/null +++ b/src/views/AbstractView.ts @@ -0,0 +1,158 @@ +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {StackCardConfig} from "../types/homeassistant/lovelace/cards/types"; +import {LovelaceCardConfig, LovelaceViewConfig} from "../types/homeassistant/data/lovelace"; +import {cards} from "../types/strategy/cards"; +import {TitleCardConfig} from "../types/lovelace-mushroom/cards/title-card-config"; +import {HassServiceTarget} from "home-assistant-js-websocket"; +import abstractCardConfig = cards.AbstractCardConfig; + +/** + * Abstract View Class. + * + * To create a new view, extend the new class with this one. + * + * @class + * @abstract + */ +abstract class AbstractView { + /** + * Configuration of the view. + * + * @type {LovelaceViewConfig} + */ + config: LovelaceViewConfig = { + icon: "mdi:view-dashboard", + subview: false, + }; + + /** + * A card to switch all entities in the view. + * + * @type {StackCardConfig} + */ + viewControllerCard: StackCardConfig = { + cards: [], + type: "", + }; + + /** + * The domain of which we operate the devices. + * + * @private + * @readonly + */ + readonly #domain?: string; + + /** + * Class constructor. + * + * @param {string} [domain] The domain which the view is representing. + * + * @throws {Error} If trying to instantiate this class. + * @throws {Error} If the Helper module isn't initialized. + */ + protected constructor(domain: string = "") { + if (!Helper.isInitialized()) { + throw new Error("The Helper module must be initialized before using this one."); + } + + if (domain) { + this.#domain = domain; + } + } + + /** + * Create the cards to include in the view. + * + * @return {Promise<(StackCardConfig | TitleCardConfig)[]>} An array of card objects. + */ + async createViewCards(): Promise<(StackCardConfig | TitleCardConfig)[]> { + const viewCards: LovelaceCardConfig[] = []; + + // Create cards for each area. + for (const area of Helper.areas) { + const areaCards: abstractCardConfig[] = []; + const entities = Helper.getDeviceEntities(area, this.#domain ?? ""); + const className = Helper.sanitizeClassName(this.#domain + "Card"); + const cardModule = await import(`../cards/${className}`); + + // Set the target for controller cards to the current area. + let target: HassServiceTarget = { + area_id: [area.area_id], + }; + + // Set the target for controller cards to entities without an area. + if (area.area_id === "undisclosed") { + target = { + entity_id: entities.map(entity => entity.entity_id), + } + } + + // Create a card for each domain-entity of the current area. + for (const entity of entities) { + let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id]; + let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id ?? "null"]; + + if (cardOptions?.hidden || deviceOptions?.hidden) { + continue; + } + + areaCards.push(new cardModule[className](entity, cardOptions).getCard()); + } + + // Vertical stack the area cards if it has entities. + if (areaCards.length) { + const titleCardOptions = ("controllerCardOptions" in this.config) ? this.config.controllerCardOptions : {}; + + // Create and insert a Controller card. + areaCards.unshift(new ControllerCard(target, Object.assign({title: area.name}, titleCardOptions)).createCard()); + + viewCards.push({ + type: "vertical-stack", + cards: areaCards, + } as StackCardConfig); + } + } + + // Add a Controller Card for all the entities in the view. + if (viewCards.length) { + viewCards.unshift(this.viewControllerCard); + } + + return viewCards; + } + + /** + * Get a view object. + * + * The view includes the cards which are created by method createViewCards(). + * + * @returns {Promise} The view object. + */ + async getView(): Promise { + return { + ...this.config, + cards: await this.createViewCards(), + }; + } + + /** + * Get a target of entity IDs for the given domain. + * + * @param {string} domain - The target domain to retrieve entity IDs from. + * @return {HassServiceTarget} - A target for a service call. + */ + targetDomain(domain: string): HassServiceTarget { + return { + entity_id: Helper.entities.filter( + entity => + entity.entity_id.startsWith(domain + ".") + && !entity.hidden_by + && !Helper.strategyOptions.card_options?.entity_id.hidden + ).map(entity => entity.entity_id), + }; + } +} + +export {AbstractView}; diff --git a/src/views/CameraView.js b/src/views/CameraView.js deleted file mode 100644 index bbb8cf8..0000000 --- a/src/views/CameraView.js +++ /dev/null @@ -1,70 +0,0 @@ -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}; diff --git a/src/views/CameraView.ts b/src/views/CameraView.ts new file mode 100644 index 0000000..949052f --- /dev/null +++ b/src/views/CameraView.ts @@ -0,0 +1,73 @@ +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; +import {Helper} from "../Helper"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * 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} + * @static + * @private + */ + static #domain: string = "camera"; + + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: "Cameras", + path: "cameras", + icon: "mdi:cctv", + subview: false, + controllerCardOptions: { + showControls: false, + }, + }; + + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: "All Cameras", + subtitle: Helper.getCountTemplate(CameraView.#domain, "ne", "off") + " cameras on", + }; + + /** + * Class constructor. + * + * @param {views.ViewConfig} [options={}] Options for the view. + */ + constructor(options: views.ViewConfig = {}) { + super(CameraView.#domain); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + {}, + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); + } +} + +export {CameraView}; diff --git a/src/views/ClimateView.js b/src/views/ClimateView.js deleted file mode 100644 index 16c164b..0000000 --- a/src/views/ClimateView.js +++ /dev/null @@ -1,71 +0,0 @@ -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}; diff --git a/src/views/ClimateView.ts b/src/views/ClimateView.ts new file mode 100644 index 0000000..1e0a476 --- /dev/null +++ b/src/views/ClimateView.ts @@ -0,0 +1,73 @@ +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * 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} + * @static + * @private + */ + static #domain: string = "climate"; + + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: "Climates", + path: "climates", + icon: "mdi:thermostat", + subview: false, + controllerCardOptions: { + showControls: false, + }, + }; + + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: "All Climates", + subtitle: Helper.getCountTemplate(ClimateView.#domain, "ne", "off") + " climates on", + }; + + /** + * Class constructor. + * + * @param {views.ViewConfig} [options={}] Options for the view. + */ + constructor(options: views.ViewConfig = {}) { + super(ClimateView.#domain); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + this.targetDomain(ClimateView.#domain), + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); + } +} + +export {ClimateView}; diff --git a/src/views/CoverView.js b/src/views/CoverView.js deleted file mode 100644 index 8abfade..0000000 --- a/src/views/CoverView.js +++ /dev/null @@ -1,73 +0,0 @@ -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}; diff --git a/src/views/CoverView.ts b/src/views/CoverView.ts new file mode 100644 index 0000000..23c1f1a --- /dev/null +++ b/src/views/CoverView.ts @@ -0,0 +1,76 @@ +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * 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} + * @static + * @private + */ + static #domain: string = "cover"; + + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: "Covers", + path: "covers", + icon: "mdi:window-open", + subview: false, + controllerCardOptions: { + iconOn: "mdi:arrow-up", + iconOff: "mdi:arrow-down", + onService: "cover.open_cover", + offService: "cover.close_cover", + }, + }; + + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: "All Covers", + subtitle: Helper.getCountTemplate(CoverView.#domain, "eq", "open") + " covers open", + }; + + /** + * Class constructor. + * + * @param {views.ViewConfig} [options={}] Options for the view. + */ + constructor(options: views.ViewConfig = {}) { + super(CoverView.#domain); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + this.targetDomain(CoverView.#domain), + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); + } +} + +export {CoverView}; diff --git a/src/views/FanView.js b/src/views/FanView.js deleted file mode 100644 index a0f42a6..0000000 --- a/src/views/FanView.js +++ /dev/null @@ -1,73 +0,0 @@ -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}; diff --git a/src/views/FanView.ts b/src/views/FanView.ts new file mode 100644 index 0000000..e1699b0 --- /dev/null +++ b/src/views/FanView.ts @@ -0,0 +1,76 @@ +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * 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} + * @static + * @private + */ + static #domain: string = "fan"; + + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: "Fans", + path: "fans", + icon: "mdi:fan", + subview: false, + controllerCardOptions: { + iconOn: "mdi:fan", + iconOff: "mdi:fan-off", + onService: "fan.turn_on", + offService: "fan.turn_off", + }, + }; + + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: "All Fans", + subtitle: Helper.getCountTemplate(FanView.#domain, "eq", "on") + " fans on", + }; + + /** + * Class constructor. + * + * @param {views.ViewConfig} [options={}] Options for the view. + */ + constructor(options: views.ViewConfig = {}) { + super(FanView.#domain); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + this.targetDomain(FanView.#domain), + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); + } +} + +export {FanView}; diff --git a/src/views/HomeView.js b/src/views/HomeView.ts similarity index 51% rename from src/views/HomeView.js rename to src/views/HomeView.ts index 5edd199..54b53f9 100644 --- a/src/views/HomeView.js +++ b/src/views/HomeView.ts @@ -1,6 +1,16 @@ import {Helper} from "../Helper"; import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {LovelaceChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types"; +import {ChipsCardConfig} from "../types/lovelace-mushroom/cards/chips-card"; +import {AreaCardConfig, StackCardConfig} from "../types/homeassistant/lovelace/cards/types"; +import {TemplateCardConfig} from "../types/lovelace-mushroom/cards/template-card-config"; +import {ActionConfig} from "../types/homeassistant/data/lovelace"; +import {TitleCardConfig} from "../types/lovelace-mushroom/cards/title-card-config"; +import {PersonCardConfig} from "../types/lovelace-mushroom/cards/person-card-config"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. /** * Home View Class. * @@ -11,13 +21,14 @@ import {AbstractView} from "./AbstractView"; */ class HomeView extends AbstractView { /** - * Default options for the view. + * Default configuration of the view. * - * @type {viewOptions} + * @type {views.ViewConfig} * @private */ - #defaultOptions = { + #defaultConfig: views.ViewConfig = { title: "Home", + icon: "mdi:home-assistant", path: "home", subview: false, }; @@ -25,73 +36,65 @@ class HomeView extends AbstractView { /** * Class constructor. * - * @param {viewOptions} [options={}] Options for the view. + * @param {views.ViewConfig} [options={}] Options for the view. */ - constructor(options = {}) { + constructor(options: views.ViewConfig = {}) { super(); - this.mergeOptions( - this.#defaultOptions, - options, - ); + + this.config = Object.assign(this.config, this.#defaultConfig, options); } /** * Create the cards to include in the view. * - * @return {Promise} A promise of a card object array. + * @return {Promise<(StackCardConfig | TemplateCardConfig | ChipsCardConfig)[]>} Promise a View Card array. * @override */ - async createViewCards() { + async createViewCards(): Promise<(StackCardConfig | TemplateCardConfig | ChipsCardConfig)[]> { return await Promise.all([ this.#createChips(), this.#createPersonCards(), - this.#createAreaCards(), + this.#createAreaSection(), ]).then(([chips, personCards, areaCards]) => { const options = Helper.strategyOptions; const homeViewCards = []; - // Add chips to the view. if (chips.length) { + // TODO: Create the Chip card at this.#createChips() homeViewCards.push({ type: "custom:mushroom-chips-card", alignment: "center", chips: chips, - }); + } as ChipsCardConfig) } - // Add persons to the view if (personCards.length) { + // TODO: Create the stack at this.#createPersonCards() homeViewCards.push({ type: "horizontal-stack", cards: personCards, - }); + } as StackCardConfig); } - // Add greeting to the view if not hidden. - if (!Helper.strategyOptions.homeView?.hidden?.includes("greeting")) { + if (!Helper.strategyOptions.home_view.hidden.includes("greeting")) { homeViewCards.push({ 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 %}", + 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", - }, + } as ActionConfig, double_tap_action: { action: "none", - }, + } as ActionConfig, hold_action: { action: "none", - }, - }); + } as ActionConfig, + } as TemplateCardConfig); } + // Add quick access cards. if (options.quick_access_cards) { homeViewCards.push(...options.quick_access_cards); @@ -101,7 +104,7 @@ class HomeView extends AbstractView { homeViewCards.push({ type: "vertical-stack", cards: areaCards, - }); + } as StackCardConfig); // Add custom cards. if (options.extra_cards) { @@ -115,49 +118,53 @@ class HomeView extends AbstractView { /** * Create the chips to include in the view. * - * @return {Object[]} A chip object array. + * @return {Promise} Promise a chip array. */ - async #createChips() { - if (Helper.strategyOptions.homeView?.hidden?.includes("chips")) { - // Chips section is hidden; Return 0 chips. + async #createChips(): Promise { + if (Helper.strategyOptions.home_view.hidden.includes("chips")) { + // Chips section is hidden. + return []; } - const chips = []; + const chips: LovelaceChipConfig[] = []; const chipOptions = Helper.strategyOptions.chips; // TODO: Get domains from config. - const exposed_chips = ["light", "fan", "cover", "switch", "climate"]; + const exposedChips = ["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); + 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) => 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!"); + Helper.logError("An error occurred while creating the weather chip!", e); } } // Numeric chips. - for (let chipType of exposed_chips) { - if (chipOptions?.[`${chipType}_count`] ?? true) { + for (let chipType of exposedChips) { + if (chipOptions?.[`${chipType}_count` as string] ?? true) { const className = Helper.sanitizeClassName(chipType + "Chip"); try { chipModule = await import((`../chips/${className}`)); - const chip = new chipModule[className](areaIds); + const chip = new chipModule[className](); + + chip.setTapActionTarget({area_id: areaIds}); chips.push(chip.getChip()); } catch (e) { - console.error(Helper.debug ? e : `An error occurred while creating the ${chipType} chip!`); + Helper.logError(`An error occurred while creating the ${chipType} chip!`, e); } } } @@ -173,18 +180,19 @@ class HomeView extends AbstractView { /** * Create the person cards to include in the view. * - * @return {Object[]} A card object array. + * @return {PersonCardConfig[]} A Person Card array. */ - #createPersonCards() { - if (Helper.strategyOptions.homeView?.hidden?.includes("persons")) { - // Person section is hidden; Return 0 cards. + #createPersonCards(): PersonCardConfig[] { + if (Helper.strategyOptions.home_view.hidden.includes("persons")) { + // Person section is hidden. + return []; } - const cards = []; + const cards: PersonCardConfig[] = []; import("../cards/PersonCard").then(personModule => { - for (const person of Helper.entities.filter(entity => { + for (const person of Helper.entities.filter((entity) => { return entity.entity_id.startsWith("person.") && entity.hidden_by == null && entity.disabled_by == null; @@ -201,40 +209,35 @@ class HomeView extends AbstractView { * * Area cards are grouped into two areas per row. * - * @return {Object[]} A card object array. + * @return {Promise<(TitleCardConfig | StackCardConfig)[]>} Promise an Area Card Section. */ - async #createAreaCards() { - if (Helper.strategyOptions.homeView?.hidden?.includes("areas")) { - // Area section is hidden; Return 0 cards. + async #createAreaSection(): Promise<(TitleCardConfig | StackCardConfig)[]> { + if (Helper.strategyOptions.home_view.hidden.includes("areas")) { + // Areas section is hidden. + return []; } - /** - * Cards to be stacked vertically. - * - * Contains a Title card and horizontal stacks of Area cards. - * - * @type {[{}]} - */ - const groupedCards = []; + const groupedCards: (TitleCardConfig | StackCardConfig)[] = []; - let areaCards = []; + let areaCards: (TemplateCardConfig | AreaCardConfig)[] = []; - // Add title to the area section. - if (!Helper.strategyOptions.homeView?.hidden?.includes("areasTitle")) { + if (!Helper.strategyOptions.home_view.hidden.includes("areasTitle")) { groupedCards.push({ - type: "custom:mushroom-title-card", - title: "Areas", - }); + type: "custom:mushroom-title-card", + title: "Areas", + }, + ); } - // Add cards to the area section. for (const [i, area] of Helper.areas.entries()) { - let module; + type ModuleType = typeof import("../cards/AreaCard"); + + let module: ModuleType; let moduleName = - Helper.strategyOptions.areas[area.area_id ?? "undisclosed"]?.type ?? - Helper.strategyOptions.areas["_"]?.type ?? - "default"; + Helper.strategyOptions.areas[area.area_id]?.type ?? + Helper.strategyOptions.areas["_"]?.type ?? + "default"; // Load module by type in strategy options. try { @@ -249,10 +252,10 @@ class HomeView extends AbstractView { } // Get a card for the area. - if (!Helper.strategyOptions.areas[area.area_id]?.hidden) { + if (!Helper.strategyOptions.areas[area.area_id as string]?.hidden) { let options = { ...Helper.strategyOptions.areas["_"], - ...Helper.strategyOptions.areas[area.area_id ?? "undisclosed"], + ...Helper.strategyOptions.areas[area.area_id], }; areaCards.push(new module.AreaCard(area, options).getCard()); @@ -264,7 +267,7 @@ class HomeView extends AbstractView { groupedCards.push({ type: "horizontal-stack", cards: areaCards.slice(i, i + 2), - }); + } as StackCardConfig); } } } diff --git a/src/views/LightView.js b/src/views/LightView.js deleted file mode 100644 index 1a985a1..0000000 --- a/src/views/LightView.js +++ /dev/null @@ -1,73 +0,0 @@ -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}; diff --git a/src/views/LightView.ts b/src/views/LightView.ts new file mode 100644 index 0000000..75a2b15 --- /dev/null +++ b/src/views/LightView.ts @@ -0,0 +1,76 @@ +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * 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} + * @static + * @private + */ + static #domain: string = "light"; + + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: "Lights", + path: "lights", + icon: "mdi:lightbulb-group", + subview: false, + controllerCardOptions: { + iconOn: "mdi:lightbulb", + iconOff: "mdi:lightbulb-off", + onService: "light.turn_on", + offService: "light.turn_off", + }, + }; + + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: "All Lights", + subtitle: Helper.getCountTemplate(LightView.#domain, "eq", "on") + " lights on", + }; + + /** + * Class constructor. + * + * @param {views.ViewConfig} [options={}] Options for the view. + */ + constructor(options: views.ViewConfig = {}) { + super(LightView.#domain); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + this.targetDomain(LightView.#domain), + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); + } +} + +export {LightView}; diff --git a/src/views/SwitchView.js b/src/views/SwitchView.js deleted file mode 100644 index df4dcbb..0000000 --- a/src/views/SwitchView.js +++ /dev/null @@ -1,73 +0,0 @@ -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}; diff --git a/src/views/SwitchView.ts b/src/views/SwitchView.ts new file mode 100644 index 0000000..8244dab --- /dev/null +++ b/src/views/SwitchView.ts @@ -0,0 +1,76 @@ +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * 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} + * @static + * @private + */ + static #domain: string = "switch"; + + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: "Switches", + path: "switches", + icon: "mdi:dip-switch", + subview: false, + controllerCardOptions: { + iconOn: "mdi:power-plug", + iconOff: "mdi:power-plug-off", + onService: "switch.turn_on", + offService: "switch.turn_off", + }, + }; + + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: "All Switches", + subtitle: Helper.getCountTemplate(SwitchView.#domain, "eq", "on") + " switches on", + }; + + /** + * Class constructor. + * + * @param {views.ViewConfig} [options={}] Options for the view. + */ + constructor(options: views.ViewConfig = {}) { + super(SwitchView.#domain); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + this.targetDomain(SwitchView.#domain), + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); + } +} + +export {SwitchView}; diff --git a/src/views/VacuumView.ts b/src/views/VacuumView.ts new file mode 100644 index 0000000..55b4ae1 --- /dev/null +++ b/src/views/VacuumView.ts @@ -0,0 +1,76 @@ +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; + +// noinspection JSUnusedGlobalSymbols Class is dynamically imported. +/** + * Vacuum View Class. + * + * Used to create a view for entities of the vacuum domain. + * + * @class VacuumView + * @extends AbstractView + */ +class VacuumView extends AbstractView { + /** + * Domain of the view's entities. + * + * @type {string} + * @static + * @private + */ + static #domain: string = "vacuum"; + + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: "Vacuums", + path: "vacuums", + icon: "mdi:robot-vacuum", + subview: false, + controllerCardOptions: { + iconOn: "mdi:robot-vacuum", + iconOff: "mdi:robot-vacuum-off", + onService: "vacuum.start", + offService: "vacuum.stop", + }, + }; + + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: "All Vacuums", + subtitle: Helper.getCountTemplate(VacuumView.#domain, "ne", "off") + " vacuums on", + }; + + /** + * Class constructor. + * + * @param {views.ViewConfig} [options={}] Options for the view. + */ + constructor(options: views.ViewConfig = {}) { + super(VacuumView.#domain); + + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + this.targetDomain(VacuumView.#domain), + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); + } +} + +export {VacuumView}; diff --git a/src/views/typedefs.js b/src/views/typedefs.js deleted file mode 100644 index 8466730..0000000 --- a/src/views/typedefs.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @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 {}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..45286fb --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,39 @@ +{ + "include": [ + "src/**/*" + ], + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Language and Environment */ + "target": "ES2020", + "jsx": "react", + + /* Modules */ + "module": "ES2020", + "moduleResolution": "Node", + + /* JavaScript Support */ + "allowJs": true, + "sourceMap": true, + "outDir": "./dist", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + + /* Type Checking */ + "strict": true, + "noImplicitAny": true, + "noImplicitThis": true, + "noImplicitReturns": true, + "strictNullChecks": true, + + /* Completeness */ + "skipLibCheck": true + }, + "ts-node": { + "compilerOptions": { + "module": "CommonJS" + } + } +} diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index ecc70a9..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,17 +0,0 @@ -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, - }), - ], -}; diff --git a/webpack.config.ts b/webpack.config.ts new file mode 100644 index 0000000..1ee2c25 --- /dev/null +++ b/webpack.config.ts @@ -0,0 +1,33 @@ +// noinspection JSUnusedGlobalSymbols + +import path from 'path'; +import webpack from 'webpack'; + +const config: webpack.Configuration = { + entry: "./src/mushroom-strategy.ts", + mode: "production", + output: { + filename: "mushroom-strategy.js", + path: path.resolve(__dirname, "dist"), + clean: true, + }, + resolve: { + extensions: [".ts", ".tsx", ".js"], + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules/, + }, + ], + }, + plugins: [ + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + ], +}; + +export default config; diff --git a/webpack.dev.config.js b/webpack.dev.config.js deleted file mode 100644 index 0696846..0000000 --- a/webpack.dev.config.js +++ /dev/null @@ -1,20 +0,0 @@ -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, - }), - ], -}; diff --git a/webpack.dev.config.ts b/webpack.dev.config.ts new file mode 100644 index 0000000..3a00697 --- /dev/null +++ b/webpack.dev.config.ts @@ -0,0 +1,36 @@ +// noinspection JSUnusedGlobalSymbols + +import path from 'path'; +import webpack from 'webpack'; + +const config: webpack.Configuration = { + entry: "./src/mushroom-strategy.ts", + mode: "development", + devtool: "source-map", + output: { + filename: "mushroom-strategy.js", + path: path.resolve(__dirname, "dist"), + }, + resolve: { + extensions: [".ts", ".tsx", ".js"], + }, + optimization: { + minimize: false, + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules/, + }, + ], + }, + plugins: [ + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + ], +}; + +export default config;