From 45053c35e79af68282bdc35636b00ef63c990c02 Mon Sep 17 00:00:00 2001 From: Tasshack Date: Sun, 23 Oct 2022 23:09:29 +0300 Subject: [PATCH 001/100] Add Tasshack/dreame-vacuum platform --- README.md | 3 +- docs/templates/setup.md | 1 + docs/templates/tasshackDreameVacuum.md | 211 ++++++++++++++++++ src/model/generators/platform-generator.ts | 4 + .../Tasshack_dreame-vacuum.json | 138 ++++++++++++ src/types/types.ts | 3 + src/xiaomi-vacuum-map-card.ts | 10 +- 7 files changed, 364 insertions(+), 6 deletions(-) create mode 100644 docs/templates/tasshackDreameVacuum.md create mode 100644 src/model/generators/platform_templates/Tasshack_dreame-vacuum.json diff --git a/README.md b/README.md index 27aef481..1beacc66 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,8 @@ Following vacuum platforms are supported out of the box at this moment: - [`send_command`](/docs/templates/sendCommand.md) - [`al-one/Xiaomi MIoT`](/docs/templates/alOneHassXiaomiMiot.md) (additional manual configuration required) - [`Neato`](/docs/templates/neato.md) -- [`Roomba`](/docs/templates/roomba.md) +- [`Roomba`](/docs/templates/roomba.md) +- [`Tasshack/dreame-vacuum`](/docs/templates/tasshackDreameVacuum.md) [Create a request for a new built-in platform](https://github.com/PiotrMachowski/lovelace-xiaomi-vacuum-map-card/issues/new?assignees=PiotrMachowski&labels=new+platform&template=new_platform_request.yml) diff --git a/docs/templates/setup.md b/docs/templates/setup.md index 0c6d0c1a..afaaf506 100644 --- a/docs/templates/setup.md +++ b/docs/templates/setup.md @@ -28,6 +28,7 @@ To get coordinates of zone, point or outline you have to:
  • send_command
  • Neato
  • Roomba
  • +
  • Tasshack/dreame-vacuum
  • diff --git a/docs/templates/tasshackDreameVacuum.md b/docs/templates/tasshackDreameVacuum.md new file mode 100644 index 00000000..5f240c8b --- /dev/null +++ b/docs/templates/tasshackDreameVacuum.md @@ -0,0 +1,211 @@ +# Dreame Vacuum by [@Tasshack](https://github.com/Tasshack) + +[Integration's documentation](https://github.com/Tasshack/dreame-vacuum) + +This platform can be used to control vacuums connected to Home Assistant using Dreame Vacuum integration created by [@Tasshack](https://github.com/Tasshack). + +## Available templates + +* ### Room cleaning (`vacuum_clean_segment`) + + Uses IDs to clean specific rooms. Requires `predefined_selections` to be provided. + + [Configuration generator](https://github.com/PiotrMachowski/lovelace-xiaomi-vacuum-map-card/discussions/317) + + [Getting coordinates](/docs/templates/setup.md#getting-coordinates) + + Used service: `dreame_vacuum.vacuum_clean_segment` + +
    + Example configuration + + ```yaml + map_modes: + - template: vacuum_clean_segment + predefined_selections: + - id: '1' + outline: + - - 850 + - -5400 + - - 3950 + - -5400 + - - 3950 + - -500 + - - 850 + - -500 + - id: '2' + outline: + - - -2650 + - -5250 + - - 850 + - -5250 + - - 850 + - 850 + - - -2650 + - 850 + - id: '3' + outline: + - - 2050 + - -8550 + - - 3950 + - -8550 + - - 3950 + - -5550 + - - 2050 + - -5550 + ``` + +
    +
    + Example video + + https://user-images.githubusercontent.com/6118709/141666925-34b01cde-82ff-447b-aecc-e9ced402b1ed.mp4 + +
    + +* ### Zone cleaning (`vacuum_clean_zone`) + + Uses 4 coordinates to clean rectangular zones. + + Used service: `dreame_vacuum.vacuum_clean_zone` + +
    + Example configuration + + ```yaml + map_modes: + - template: vacuum_clean_zone + ``` + +
    +
    + Example video + + https://user-images.githubusercontent.com/6118709/141666913-d95f082d-f5bf-4ab5-a478-ba44effe6f34.mp4 + +
    + +* ### Predefined zone cleaning (`vacuum_clean_zone_predefined`) + + Uses 4 coordinates to clean rectangular zones that have been defined in the configuration. Requires `predefined_selections` to be provided. + + [Getting coordinates](/docs/templates/setup.md#getting-coordinates) + + Used service: `dreame_vacuum.vacuum_clean_zone` + +
    + Example configuration + + ```yaml + map_modes: + - template: vacuum_clean_zone_predefined + predefined_selections: + - zones: + - - -350 + - -8200 + - 2050 + - -8200 + - - 5250 + - -350 + - 5250 + - -8200 + label: + text: "Bedroom" + x: 2292 + y: 3039 + offset_y: 35 + icon: + name: "mdi:bed" + x: 2292 + y: 3039 + - zones: + - - -2650 + - 5250 + - -850 + - -5250 + label: + text: "Kitchen" + x: -900 + y: -2200 + offset_y: 35 + icon: + name: "mdi:chef-hat" + x: -900 + y: -2200 + ``` + +
    +
    + Example video + + https://user-images.githubusercontent.com/6118709/141666920-492a000c-9a78-4c20-b4f5-9343928140c7.mp4 + +
    + + +* ### Cleaning a specified point (`vacuum_clean_point`) + + Uses a pair of coordinates for vacuum to clean a user-specified point. + + Used service: `dreame_vacuum.vacuum_clean_spot` + +
    + Example configuration + + ```yaml + map_modes: + - template: vacuum_clean_point + ``` + +
    +
    + Example video + + https://user-images.githubusercontent.com/6118709/141666921-2f3d66da-6ffc-492a-8439-625da97651bd.mp4 + +
    + +* ### Cleaning a predefined point (`vacuum_clean_point_predefined`) + + Uses a pair of coordinates for vacuum to clean a point that has been defined in the configuration. Requires `predefined_selections` to be provided. + + [Getting coordinates](/docs/templates/setup.md#getting-coordinates) + + Used service: `dreame_vacuum.vacuum_clean_spot` + +
    + Example configuration + + ```yaml + map_modes: + - template: vacuum_clean_point_predefined + predefined_selections: + - position: [ 2806, 2836 ] + label: + text: "Emptying" + x: 2806 + y: 2803 + offset_y: 35 + icon: + name: "mdi:broom" + x: 2800 + y: 2803 + - position: [ 3.2143, 2.6284 ] + label: + text: "Sofa" + x: 3214 + y: 2628 + offset_y: 35 + icon: + name: "mdi:sofa" + x: 3214 + y: 2628 + ``` + +
    +
    + Example video + + https://user-images.githubusercontent.com/6118709/141666923-965679e9-25fb-44cd-be08-fc63e5c85ce0.mp4 + +
    diff --git a/src/model/generators/platform-generator.ts b/src/model/generators/platform-generator.ts index fb895561..2203064b 100644 --- a/src/model/generators/platform-generator.ts +++ b/src/model/generators/platform-generator.ts @@ -8,6 +8,7 @@ import * as tykarolViomiVacuumV8Template from "./platform_templates/tykarol_viom import * as hypferValetudoTemplate from "./platform_templates/hypfer_valetudo.json"; import * as neatoTemplate from "./platform_templates/neato.json"; import * as roombaTemplate from "./platform_templates/roomba.json"; +import * as tasshackDreameVacuumTemplate from "./platform_templates/Tasshack_dreame-vacuum.json"; import * as setupDecimalTemplate from "./platform_templates/setup_decimal.json"; import * as setupIntegerTemplate from "./platform_templates/setup_integer.json"; import { MapModeConfig, PlatformTemplate, TileFromAttributeTemplate, TileFromSensorTemplate } from "../../types/types"; @@ -26,6 +27,7 @@ export class PlatformGenerator { public static HYPFER_VALETUDO_PLATFORM = "Hypfer/Valetudo"; public static NEATO_PLATFORM = "Neato"; public static ROOMBA_PLATFORM = "Roomba"; + public static TASSHACK_DREAME_VACUUM_PLATFORM = "Tasshack/dreame-vacuum"; public static SETUP_INTEGER_PLATFORM = "Setup integer"; public static SETUP_DECIMAL_PLATFORM = "Setup decimal"; @@ -43,6 +45,7 @@ export class PlatformGenerator { [PlatformGenerator.ALONE_XIAOMI_MIOT_PLATFORM, alOneHassXiaomiMiotTemplate], [PlatformGenerator.NEATO_PLATFORM, neatoTemplate], [PlatformGenerator.ROOMBA_PLATFORM, roombaTemplate], + [PlatformGenerator.TASSHACK_DREAME_VACUUM_PLATFORM, tasshackDreameVacuumTemplate as PlatformTemplate], [PlatformGenerator.SETUP_INTEGER_PLATFORM, setupIntegerTemplate], [PlatformGenerator.SETUP_DECIMAL_PLATFORM, setupDecimalTemplate], ]); @@ -56,6 +59,7 @@ export class PlatformGenerator { [PlatformGenerator.ALONE_XIAOMI_MIOT_PLATFORM, "alOneHassXiaomiMiot"], [PlatformGenerator.TYKAROL_VIOMI_VACUUM_V8_PLATFORM, "tykarolViomiVacuumV8"], [PlatformGenerator.HYPFER_VALETUDO_PLATFORM, "hypferValetudo"], + [PlatformGenerator.SEND_COMMAND_PLATFORM, "tasshackDreameVacuum"], [PlatformGenerator.NEATO_PLATFORM, "neato"], [PlatformGenerator.ROOMBA_PLATFORM, "roomba"], [PlatformGenerator.SETUP_INTEGER_PLATFORM, "setup"], diff --git a/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json b/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json new file mode 100644 index 00000000..e2f27c1b --- /dev/null +++ b/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json @@ -0,0 +1,138 @@ +{ + "map_modes": { + "defaultTemplates": [ + "vacuum_clean_zone", + "vacuum_clean_point" + ], + "templates": { + "vacuum_clean_segment": { + "name": "map_mode.vacuum_clean_segment", + "icon": "mdi:floor-plan", + "selection_type": "ROOM", + "repeats_type": "EXTERNAL", + "max_repeats": 3, + "max_selections": 60, + "service_call_schema": { + "service": "dreame_vacuum.vacuum_clean_segment", + "service_data": { + "segments": "[[selection]]", + "repeats": "[[repeats]]", + "entity_id": "[[entity_id]]" + } + } + }, + "vacuum_clean_zone": { + "name": "map_mode.vacuum_clean_zone", + "icon": "mdi:select-drag", + "selection_type": "MANUAL_RECTANGLE", + "coordinates_rounding": true, + "max_selections": 20, + "repeats_type": "EXTERNAL", + "max_repeats": 3, + "service_call_schema": { + "service": "dreame_vacuum.vacuum_clean_zone", + "service_data": { + "zone": "[[selection]]", + "repeats": "[[repeats]]", + "entity_id": "[[entity_id]]" + } + } + }, + "vacuum_clean_zone_predefined": { + "name": "map_mode.vacuum_clean_zone_predefined", + "icon": "mdi:vector-selection", + "selection_type": "PREDEFINED_RECTANGLE", + "max_selections": 20, + "coordinates_rounding": true, + "repeats_type": "EXTERNAL", + "max_repeats": 3, + "service_call_schema": { + "service": "dreame_vacuum.vacuum_clean_zone", + "service_data": { + "zone": "[[selection]]", + "repeats": "[[repeats]]", + "entity_id": "[[entity_id]]" + } + } + }, + "vacuum_clean_point": { + "name": "map_mode.vacuum_clean_point", + "icon": "mdi:map-marker-plus", + "selection_type": "MANUAL_POINT", + "coordinates_rounding": true, + "repeats_type": "EXTERNAL", + "max_repeats": 3, + "service_call_schema": { + "service": "dreame_vacuum.vacuum_clean_spot", + "service_data": { + "points": "[[selection]]", + "repeats": "[[repeats]]", + "entity_id": "[[entity_id]]" + } + } + }, + "vacuum_clean_point_predefined": { + "name": "map_mode.vacuum_clean_point_predefined", + "icon": "mdi:map-marker", + "selection_type": "PREDEFINED_POINT", + "coordinates_rounding": true, + "repeats_type": "EXTERNAL", + "max_selections": 20, + "max_repeats": 3, + "service_call_schema": { + "service": "dreame_vacuum.vacuum_clean_spot", + "service_data": { + "points": "[[selection]]", + "repeats": "[[repeats]]", + "entity_id": "[[entity_id]]" + } + } + } + } + }, + "tiles": { + "from_attributes": [ + { + "tile_id": "water_volume", + "attribute": "water_volume", + "label": "Water volume", + "icon": "mdi:water" + }, + { + "tile_id": "mop_pad_humidity", + "attribute": "mop_pad_humidity", + "label": "Mop pad", + "icon": "mdi:water-percent" + }, + { + "tile_id": "cleaned_area", + "attribute": "cleaned_area", + "label": "tile.cleaned_area.label", + "icon": "mdi:ruler-square", + "unit": "unit.meter_squared_shortcut" + }, + { + "tile_id": "cleaning_time", + "attribute": "cleaning_time", + "label": "tile.cleaning_time.label", + "icon": "mdi:timer-sand", + "unit": "unit.minute_shortcut" + }, + { + "tile_id": "cleaning_count", + "attribute": "cleaning_count", + "label": "tile.cleaning_count.label", + "icon": "mdi:counter", + "unit": "x" + }, + { + "tile_id": "total_cleaned_area", + "attribute": "total_cleaned_area", + "label": "Total cleaned area", + "icon": "mdi:set-square", + "unit": "unit.meter_squared_shortcut" + } + ], + "from_sensors": [] + } +} \ No newline at end of file diff --git a/src/types/types.ts b/src/types/types.ts index bf03b3d2..7daaa3a4 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -214,6 +214,9 @@ export interface MapExtractorRoom { readonly x1: number; readonly y1: number; readonly name: string | undefined; + readonly icon: string | undefined; + readonly x: number | undefined; + readonly y: number | undefined; } export interface RoomConfigEventData { diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index 5d8fab57..fb357227 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -917,14 +917,14 @@ export class XiaomiVacuumMapCard extends LitElement { const roomConfig = { id: room_id, icon: { - name: "mdi:broom", - x: formatCoord(room.x0 + room.x1, 2), - y: formatCoord(room.y0 + room.y1, 2), + name: room.icon ?? "mdi:broom", + x: room.x ?? formatCoord(room.x0 + room.x1, 2), + y: room.y ?? formatCoord(room.y0 + room.y1, 2), }, label: { text: room.name ?? `Room ${room_id}`, - x: formatCoord(room.x0 + room.x1, 2), - y: formatCoord(room.y0 + room.y1, 2), + x: room.x ?? formatCoord(room.x0 + room.x1, 2), + y: room.y ?? formatCoord(room.y0 + room.y1, 2), offset_y: 35, }, outline: [ From 13c70ce76c0a11d561770795cc894b1510c26187 Mon Sep 17 00:00:00 2001 From: rogodra Date: Fri, 11 Nov 2022 13:04:51 +0100 Subject: [PATCH 002/100] Create ca.json Catalan language --- src/localize/languages/ca.json | 253 +++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 src/localize/languages/ca.json diff --git a/src/localize/languages/ca.json b/src/localize/languages/ca.json new file mode 100644 index 00000000..5892fce6 --- /dev/null +++ b/src/localize/languages/ca.json @@ -0,0 +1,253 @@ +{ + "common": { + "version": "Versió", + "invalid_configuration": "Configuració no vàlida {0}", + "description": "Una targeta que et permet controlar l'aspiradora", + "old_configuration": "S'ha detectat una configuració antiga. Ajusta la teva configuració a l'últim esquema o crea una nova targeta des de zero.", + "old_configuration_migration_link": "Guia de migrat" + }, + "map_mode": { + "invalid": "Plantilla no vàlida!", + "vacuum_goto": "Marcar i anar", + "vacuum_goto_predefined": "Punts", + "vacuum_clean_segment": "Habitacions", + "vacuum_clean_point": "Netejar punt", + "vacuum_clean_point_predefined": "Punts", + "vacuum_clean_zone": "Netejar zona", + "vacuum_clean_zone_predefined": "Llista de zones", + "vacuum_follow_path": "Camí" + }, + "validation": { + "preset": { + "entity": { + "missing": "Propietat no trobada: entity" + }, + "preset_name": { + "missing": "Propietat no trobada: preset_name" + }, + "platform": { + "invalid": "Plataforma d'aspiradora no vàlida: {0}" + }, + "map_source": { + "missing": "Propietat no trobada: map_source", + "none_provided": "Cap càmera ni imatge proporcionada", + "ambiguous": "Només es permet una font de mapa" + }, + "calibration_source": { + "missing": "Propietat no trobada: calibration_source", + "ambiguous": "Només es permet una font de calibratge", + "none_provided": "No s'ha proporcionat cap font de calibratge", + "calibration_points": { + "invalid_number": "Es requereixen exactament 3 o 4 punts de calibratge", + "missing_map": "Cada punt de calibratge ha de contenir coordenades del mapa", + "missing_vacuum": "Cada punt de calibratge ha de contenir les coordenades de l'aspiradora", + "missing_coordinate": "Els punts de calibratge de l'aspiradora i del mapa han de contenir les coordenades x i y" + } + }, + "icons": { + "invalid": "Error a la configuració: icons", + "icon": { + "missing": "Cada entrada de la llista d'icones ha de contenir la propietat de la icona" + } + }, + "tiles": { + "invalid": "Error a la configuració: tiles", + "entity": { + "missing": "Cada entrada de la llista de mosaics ha de contenir l'entitat o la variable interna" + }, + "label": { + "missing": "Cada entrada de la llista de mosaics ha de contenir una etiqueta" + } + }, + "map_modes": { + "invalid": "Error a la configuració: map_modes", + "icon": { + "missing": "Falta la icona del mode de mapa" + }, + "name": { + "missing": "Manca el nom del mode de mapa" + }, + "template": { + "invalid": "Plantilla no vàlida: {0}" + }, + "predefined_selections": { + "not_applicable": "El mode {0} no admet seleccions predefinides", + "zones": { + "missing": "Manquen configuracions de zones", + "invalid_parameters_number": "Cada zona ha de tenir 4 paràmetres" + }, + "points": { + "position": { + "missing": "Manquen configuracions de punts", + "invalid_parameters_number": "Cada punt ha de tenir 2 paràmetres" + } + }, + "rooms": { + "id": { + "missing": "Falta l'identificador de l'habitació", + "invalid_format": "Identificador de l'habitació no vàlid: {0}" + }, + "outline": { + "invalid_parameters_number": "Cada punt del contorn de l'habitació ha de tenir 2 paràmetres" + } + }, + "label": { + "x": { + "missing": "L'etiqueta ha de tenir la propietat x" + }, + "y": { + "missing": "L'etiqueta ha de tenir la propietat y" + }, + "text": { + "missing": "L'etiqueta ha de tenir propietat text" + } + }, + "icon": { + "x": { + "missing": "La icona ha de tenir la propietat x" + }, + "y": { + "missing": "La icona ha de tenir la propietat y" + }, + "name": { + "missing": "La icona ha de tenir una propietat name" + } + } + }, + "service_call_schema": { + "missing": "Falta l'esquema de trucada de servei", + "service": { + "missing": "L'esquema de trucada de servei ha de contenir service", + "invalid": "Servei no vàlid: {0}" + } + } + } + }, + "invalid_entities": "Entitats no vàlides:", + "invalid_calibration": "Calibració no vàlida, comproveu la vostra configuració" + }, + "tile": { + "status": { + "label": "Estat", + "value": { + "starting": "Començant", + "charger disconnected": "Carregador desconnectat", + "idle": "Inactiu", + "remote control active": "Comandament a distància actiu", + "cleaning": "Netejant", + "returning home": "Tornant a casa", + "manual mode": "Mode manual", + "charging": "Carregant", + "charging problem": "Problema de càrrega", + "paused": "En pausa", + "spot cleaning": "Neteja per punts", + "error": "Error", + "shutting down": "Apagant", + "updating": "Actualitzant", + "docking": "Acoblament", + "going to target": "Anant a l'objectiu", + "zoned cleaning": "Neteja per zones", + "segment cleaning": "Neteja per segments", + "emptying the bin": "Buidant el dipòsit", + "charging complete": "Càrrega completa", + "device offline": "Dispositiu desconnectat" + } + }, + "battery_level": { + "label": "Bateria" + }, + "fan_speed": { + "label": "Velocitat del ventilador", + "value": { + "silent": "Silenciós", + "standard": "Normal", + "medium": "Mitjà", + "turbo": "Turbo", + "auto": "Automàtic", + "gentle": "Suau" + } + }, + "sensor_dirty_left": { + "label": "Sensors" + }, + "filter_left": { + "label": "Filtre" + }, + "main_brush_left": { + "label": "Raspall principal" + }, + "side_brush_left": { + "label": "Raspall lateral" + }, + "cleaning_count": { + "label": "Recompte de neteja" + }, + "cleaned_area": { + "label": "Zona netejada" + }, + "cleaning_time": { + "label": "Temps de neteja" + }, + "mop_left": { + "label": "Fregona" + }, + "bin_full": { + "label": "Dipòsit ple", + "value": { + "true": "Sí", + "false": "No" + } + }, + "bin_present": { + "label": "Dipòsit introduït", + "value": { + "true": "Sí", + "false": "No" + } + } + }, + "icon": { + "vacuum_start": "Començar", + "vacuum_pause": "Pausa", + "vacuum_stop": "Atura", + "vacuum_return_to_base": "Tornar a la base", + "vacuum_clean_spot": "Netejar punt", + "vacuum_locate": "Localitzar", + "vacuum_set_fan_speed": "Canvia la velocitat del ventilador" + }, + "unit": { + "hour_shortcut": "h", + "meter_shortcut": "m", + "meter_squared_shortcut": "m²", + "minute_shortcut": "min" + }, + "popups": { + "success": "Fet!", + "no_selection": "No s'ha proporcionat cap selecció", + "failed": "No s'ha pogut trucar al servei" + }, + "editor": { + "description": { + "before_link": "Aquest editor visual només admet una configuració bàsica amb una entitat de càmera creada amb ", + "link_text": "Xiaomi Cloud Map Extractor", + "after_link": ". Per a una configuració més avançada, utilitzeu el mode YAML." + }, + "label": { + "name": "Títol (opcional)", + "entity": "Entitat de l'aspiradora (obligatori)", + "camera": "Entitat de la càmera (obligatori)", + "vacuum_platform": "Plataforma de la aspiradora (obligatori)", + "map_locked": "Mapa bloquejat(opcional)", + "two_finger_pan": "Moure amb dos dits (opcional)", + "platforms_documentation": "Documentació de la plataforma seleccionadan ({0})", + "selection": "Selecció:", + "copy": "Copiar", + "copied": "Copiat!", + "set_static_config": "Genera una configuració estàtica", + "config_set": "Configuració realitzada!\nObriu l'editor de configuració per ajustar-lo.", + "config_set_failed": "No s'ha pogut actualitzar la configuració.", + "generate_rooms_config": "Genera la configuració de les habitacions", + "copy_service_call": "Trucada de servei de còpia" + } + } +} From 1253079fc630cda1bd983b3297319ac2771d1b0f Mon Sep 17 00:00:00 2001 From: rogodra Date: Fri, 11 Nov 2022 13:11:20 +0100 Subject: [PATCH 003/100] Update localize.ts --- src/localize/localize.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/localize/localize.ts b/src/localize/localize.ts index 1152577a..876a7a04 100644 --- a/src/localize/localize.ts +++ b/src/localize/localize.ts @@ -1,3 +1,4 @@ +import * as cs from "./languages/ca.json"; import * as cs from "./languages/cs.json"; import * as da from "./languages/da.json"; import * as de from "./languages/de.json"; @@ -24,6 +25,7 @@ import { Language, TranslatableString, XiaomiVacuumMapCardConfig } from "../type import { HomeAssistant } from "custom-card-helpers"; const languages: Record = { + ca: ca, cs: cs, da: da, de: de, From 5b158cb1993bdf8e35efe3f05e6b44ec7365795e Mon Sep 17 00:00:00 2001 From: rogodra Date: Fri, 11 Nov 2022 13:14:49 +0100 Subject: [PATCH 004/100] Update README.md Catalan language --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 27aef481..32305e81 100644 --- a/README.md +++ b/README.md @@ -633,6 +633,7 @@ map_modes: ## Translations Currently, this card contains translations for following languages: +* `ca` - Catalan (Català) * `cs` - Czech (Čeština) * `da` - Danish (Dansk) * `de` - German (Deutsch) @@ -668,4 +669,4 @@ I'd like to give special thanks to people who helped me with card's design and d Buy Me a Coffee at ko-fi.com -PayPal Logo \ No newline at end of file +PayPal Logo From 7b94b4b9fdb83908b3e48e5260560029c04d3689 Mon Sep 17 00:00:00 2001 From: Piotr Machowski <6118709+PiotrMachowski@users.noreply.github.com> Date: Fri, 11 Nov 2022 14:10:15 +0100 Subject: [PATCH 005/100] Fix import --- src/localize/localize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/localize/localize.ts b/src/localize/localize.ts index 876a7a04..18463a21 100644 --- a/src/localize/localize.ts +++ b/src/localize/localize.ts @@ -1,4 +1,4 @@ -import * as cs from "./languages/ca.json"; +import * as ca from "./languages/ca.json"; import * as cs from "./languages/cs.json"; import * as da from "./languages/da.json"; import * as de from "./languages/de.json"; From 2995f5be47119ea869fecb027b1f98ba04c522b3 Mon Sep 17 00:00:00 2001 From: dsimop Date: Wed, 14 Dec 2022 13:19:15 +0200 Subject: [PATCH 006/100] Update el.json --- src/localize/languages/el.json | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/localize/languages/el.json b/src/localize/languages/el.json index 38d7fc57..7a7b4494 100644 --- a/src/localize/languages/el.json +++ b/src/localize/languages/el.json @@ -190,6 +190,20 @@ }, "mop_left": { "label": "Συντήρηση σφουγγαρίστρας" + }, + "bin_full": { + "label": "Κάδος γεμάτος", + "value": { + "true": "Ναι", + "false": "Όχι" + } + }, + "bin_present": { + "label": "Κάδος παρών", + "value": { + "true": "Ναι", + "false": "Όχι" + } } }, "icon": { @@ -224,7 +238,16 @@ "camera": "Οντότητα κάμερας (απαραίτητο)", "vacuum_platform": "Πλατφόρμα σκούπας (απαραίτητο)", "map_locked": "Κλείδωμα χάρτη (προεραιτικό)", - "two_finger_pan": "Μετακίνηση με δύο δάχτυλα (προεραιτικό)" + "two_finger_pan": "Μετακίνηση με δύο δάχτυλα (προεραιτικό)", + "platforms_documentation": "Τεκμηρίωση της επιλεγμένης πλατφόρμας ({0})", + "selection": "Επιλογή:", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε!", + "set_static_config": "Δημιουργία στατικών ρυθμίσεων", + "config_set": "Ρύθμιση παραμέτρων!\nΑνοίξτε τον επεξεργαστή παραμέτρων για να τον προσαρμόσετε.", + "config_set_failed": "Απέτυχε η ενημέρωση των ρυθμίσεων.", + "generate_rooms_config": "Δημιουργία παραμέτρων δωματίων", + "copy_service_call": "Αντιγραφή κλήσης υπηρεσίας" } } } From 4da19da1ec9b943b8407fd451091f5a5248aa60e Mon Sep 17 00:00:00 2001 From: Piotr Machowski <6118709+PiotrMachowski@users.noreply.github.com> Date: Fri, 27 Jan 2023 04:28:39 +0100 Subject: [PATCH 007/100] Update platform-generator.ts --- src/model/generators/platform-generator.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/model/generators/platform-generator.ts b/src/model/generators/platform-generator.ts index 2203064b..1e4780d2 100644 --- a/src/model/generators/platform-generator.ts +++ b/src/model/generators/platform-generator.ts @@ -41,11 +41,11 @@ export class PlatformGenerator { [PlatformGenerator.TYKAROL_VIOMI_VACUUM_V8_PLATFORM, tykarolViomiVacuumV8Template], [PlatformGenerator.HYPFER_VALETUDO_PLATFORM, hypferValetudoTemplate], [PlatformGenerator.RAND256_VALETUDO_RE_PLATFORM, rand256ValetudoReTemplate as PlatformTemplate], + [PlatformGenerator.TASSHACK_DREAME_VACUUM_PLATFORM, tasshackDreameVacuumTemplate as PlatformTemplate], [PlatformGenerator.SEND_COMMAND_PLATFORM, sendCommandTemplate], [PlatformGenerator.ALONE_XIAOMI_MIOT_PLATFORM, alOneHassXiaomiMiotTemplate], [PlatformGenerator.NEATO_PLATFORM, neatoTemplate], [PlatformGenerator.ROOMBA_PLATFORM, roombaTemplate], - [PlatformGenerator.TASSHACK_DREAME_VACUUM_PLATFORM, tasshackDreameVacuumTemplate as PlatformTemplate], [PlatformGenerator.SETUP_INTEGER_PLATFORM, setupIntegerTemplate], [PlatformGenerator.SETUP_DECIMAL_PLATFORM, setupDecimalTemplate], ]); @@ -54,12 +54,12 @@ export class PlatformGenerator { [PlatformGenerator.XIAOMI_MIIO_PLATFORM, "xiaomiMiio"], [PlatformGenerator.KRZYSZTOFHAJDAMOWICZ_MIIO2_PLATFORM, "krzysztofHajdamowiczMiio2"], [PlatformGenerator.MAROTOWEB_VIOMISE_PLATFORM, "marotowebViomise"], + [PlatformGenerator.TYKAROL_VIOMI_VACUUM_V8_PLATFORM, "tykarolViomiVacuumV8"], + [PlatformGenerator.HYPFER_VALETUDO_PLATFORM, "hypferValetudo"], [PlatformGenerator.RAND256_VALETUDO_RE_PLATFORM, "rand256ValetudoRe"], + [PlatformGenerator.TASSHACK_DREAME_VACUUM_PLATFORM, "tasshackDreameVacuum"], [PlatformGenerator.SEND_COMMAND_PLATFORM, "sendCommand"], [PlatformGenerator.ALONE_XIAOMI_MIOT_PLATFORM, "alOneHassXiaomiMiot"], - [PlatformGenerator.TYKAROL_VIOMI_VACUUM_V8_PLATFORM, "tykarolViomiVacuumV8"], - [PlatformGenerator.HYPFER_VALETUDO_PLATFORM, "hypferValetudo"], - [PlatformGenerator.SEND_COMMAND_PLATFORM, "tasshackDreameVacuum"], [PlatformGenerator.NEATO_PLATFORM, "neato"], [PlatformGenerator.ROOMBA_PLATFORM, "roomba"], [PlatformGenerator.SETUP_INTEGER_PLATFORM, "setup"], From 8fa90a88d83a5c50e3955acc68266b5b536e6219 Mon Sep 17 00:00:00 2001 From: Piotr Machowski <6118709+PiotrMachowski@users.noreply.github.com> Date: Fri, 27 Jan 2023 04:49:32 +0100 Subject: [PATCH 008/100] Add HACS action --- .github/workflows/hacs.yaml | 17 +++++++++++++++++ README.md | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/hacs.yaml diff --git a/.github/workflows/hacs.yaml b/.github/workflows/hacs.yaml new file mode 100644 index 00000000..3269b581 --- /dev/null +++ b/.github/workflows/hacs.yaml @@ -0,0 +1,17 @@ +name: Validate HACS + +on: + push: + pull_request: + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Validate HACS + uses: "hacs/action@main" + with: + category: "plugin" \ No newline at end of file diff --git a/README.md b/README.md index 059f30be..64b0fb52 100644 --- a/README.md +++ b/README.md @@ -249,11 +249,11 @@ Following vacuum platforms are supported out of the box at this moment: - [`tykarol/ViomiVacuumV8`](/docs/templates/tykarolViomiVacuumV8.md) - [`rand256/ValetudoRE`](/docs/templates/rand256ValetudoRe.md) - [`Hypfer/Valetudo`](/docs/templates/hypferValetudo.md) +- [`Tasshack/dreame-vacuum`](/docs/templates/tasshackDreameVacuum.md) - [`send_command`](/docs/templates/sendCommand.md) - [`al-one/Xiaomi MIoT`](/docs/templates/alOneHassXiaomiMiot.md) (additional manual configuration required) - [`Neato`](/docs/templates/neato.md) -- [`Roomba`](/docs/templates/roomba.md) -- [`Tasshack/dreame-vacuum`](/docs/templates/tasshackDreameVacuum.md) +- [`Roomba`](/docs/templates/roomba.md) [Create a request for a new built-in platform](https://github.com/PiotrMachowski/lovelace-xiaomi-vacuum-map-card/issues/new?assignees=PiotrMachowski&labels=new+platform&template=new_platform_request.yml) From 4ceeb50a8f671ec99fa496435731aff65f5aeb23 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 7 Mar 2023 04:19:47 +0100 Subject: [PATCH 009/100] Add String.replaceAll polyfill --- src/polyfills/stringReplaceAll.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/polyfills/stringReplaceAll.ts diff --git a/src/polyfills/stringReplaceAll.ts b/src/polyfills/stringReplaceAll.ts new file mode 100644 index 00000000..d45d3c55 --- /dev/null +++ b/src/polyfills/stringReplaceAll.ts @@ -0,0 +1,14 @@ +if (!String.prototype.replaceAll) { + if (!RegExp) { + String.prototype.replaceAll = function(str, newStr) { + return this.split(str).join(newStr); + }; + } else { + String.prototype.replaceAll = function(str, newStr) { + if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") { + return this.replace(str, newStr); + } + return this.replace(new RegExp(str, "g"), newStr); + }; + } +} From 4824b11c993cb08403decfcb08ccac50220fe24c Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 7 Mar 2023 05:06:06 +0100 Subject: [PATCH 010/100] Improve Tasshack/dreame-vacuum template --- src/localize/languages/en.json | 6 ++++++ src/localize/languages/pl.json | 6 ++++++ .../platform_templates/Tasshack_dreame-vacuum.json | 5 ++--- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index dcb905f2..59568b51 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -185,9 +185,15 @@ "cleaned_area": { "label": "Cleaned area" }, + "total_cleaned_area": { + "label": "Total cleaned area" + }, "cleaning_time": { "label": "Cleaning time" }, + "total_cleaning_time": { + "label": "Total cleaning time" + }, "mop_left": { "label": "Mop left" }, diff --git a/src/localize/languages/pl.json b/src/localize/languages/pl.json index 9917f7dd..84d8f0ac 100644 --- a/src/localize/languages/pl.json +++ b/src/localize/languages/pl.json @@ -185,9 +185,15 @@ "cleaned_area": { "label": "Powierzchnia" }, + "total_cleaned_area": { + "label": "Całkowita powierzchnia" + }, "cleaning_time": { "label": "Czas sprzątania" }, + "total_cleaning_time": { + "label": "Całkowity czas sprzątania" + }, "mop_left": { "label": "Mop" }, diff --git a/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json b/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json index e2f27c1b..9085fdfa 100644 --- a/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json +++ b/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json @@ -128,11 +128,10 @@ { "tile_id": "total_cleaned_area", "attribute": "total_cleaned_area", - "label": "Total cleaned area", + "label": "tile.total_cleaned_area.label", "icon": "mdi:set-square", "unit": "unit.meter_squared_shortcut" } - ], - "from_sensors": [] + ] } } \ No newline at end of file From 5c7eade6dcd685e7daba730d7dc7a47e4bfd077c Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 7 Mar 2023 05:11:14 +0100 Subject: [PATCH 011/100] Add DeebotUniverse/Deebot-4-Home-Assistant platform --- src/model/generators/platform-generator.ts | 4 + ...eebotUniverse_Deebot-4-Home-Assistant.json | 117 ++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json diff --git a/src/model/generators/platform-generator.ts b/src/model/generators/platform-generator.ts index 1e4780d2..0f63410b 100644 --- a/src/model/generators/platform-generator.ts +++ b/src/model/generators/platform-generator.ts @@ -8,6 +8,7 @@ import * as tykarolViomiVacuumV8Template from "./platform_templates/tykarol_viom import * as hypferValetudoTemplate from "./platform_templates/hypfer_valetudo.json"; import * as neatoTemplate from "./platform_templates/neato.json"; import * as roombaTemplate from "./platform_templates/roomba.json"; +import * as deebotTemplate from "./platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json"; import * as tasshackDreameVacuumTemplate from "./platform_templates/Tasshack_dreame-vacuum.json"; import * as setupDecimalTemplate from "./platform_templates/setup_decimal.json"; import * as setupIntegerTemplate from "./platform_templates/setup_integer.json"; @@ -28,6 +29,7 @@ export class PlatformGenerator { public static NEATO_PLATFORM = "Neato"; public static ROOMBA_PLATFORM = "Roomba"; public static TASSHACK_DREAME_VACUUM_PLATFORM = "Tasshack/dreame-vacuum"; + public static DEEBOTUNIVERSE_DEEBOT_4_HOME_ASSISTANT_PLATFORM = "DeebotUniverse/Deebot-4-Home-Assistant"; public static SETUP_INTEGER_PLATFORM = "Setup integer"; public static SETUP_DECIMAL_PLATFORM = "Setup decimal"; @@ -46,6 +48,7 @@ export class PlatformGenerator { [PlatformGenerator.ALONE_XIAOMI_MIOT_PLATFORM, alOneHassXiaomiMiotTemplate], [PlatformGenerator.NEATO_PLATFORM, neatoTemplate], [PlatformGenerator.ROOMBA_PLATFORM, roombaTemplate], + [PlatformGenerator.DEEBOTUNIVERSE_DEEBOT_4_HOME_ASSISTANT_PLATFORM, deebotTemplate], [PlatformGenerator.SETUP_INTEGER_PLATFORM, setupIntegerTemplate], [PlatformGenerator.SETUP_DECIMAL_PLATFORM, setupDecimalTemplate], ]); @@ -62,6 +65,7 @@ export class PlatformGenerator { [PlatformGenerator.ALONE_XIAOMI_MIOT_PLATFORM, "alOneHassXiaomiMiot"], [PlatformGenerator.NEATO_PLATFORM, "neato"], [PlatformGenerator.ROOMBA_PLATFORM, "roomba"], + [PlatformGenerator.DEEBOTUNIVERSE_DEEBOT_4_HOME_ASSISTANT_PLATFORM, "DeebotUniverseDeebot4homeAssistant"], [PlatformGenerator.SETUP_INTEGER_PLATFORM, "setup"], [PlatformGenerator.SETUP_DECIMAL_PLATFORM, "setup"], ]); diff --git a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json new file mode 100644 index 00000000..c4b2fb25 --- /dev/null +++ b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json @@ -0,0 +1,117 @@ +{ + "map_modes": { + "defaultTemplates": [], + "templates": { + "vacuum_clean_segment": { + "name": "map_mode.vacuum_clean_segment", + "icon": "mdi:floor-plan", + "selection_type": "ROOM", + "repeats_type": "EXTERNAL", + "max_repeats": 3, + "service_call_schema": { + "service": "vacuum.send_command", + "service_data": { + "entity_id": "[[entity_id]]", + "command": "spot_area", + "params": { + "rooms": "[[selection_unwrapped]]", + "cleanings": "[[repeats]]" + } + } + } + }, + "vacuum_clean_zone": { + "name": "map_mode.vacuum_clean_zone", + "icon": "mdi:select-drag", + "selection_type": "MANUAL_RECTANGLE", + "coordinates_rounding": true, + "repeats_type": "NONE", + "max_selections": 1, + "service_call_schema": { + "service": "vacuum.send_command", + "service_data": { + "entity_id": "[[entity_id]]", + "command": "custom_area", + "params": { + "coordinates": "[[selection_unwrapped]]" + } + } + } + }, + "vacuum_clean_zone_predefined": { + "name": "map_mode.vacuum_clean_zone_predefined", + "icon": "mdi:floor-plan", + "selection_type": "PREDEFINED_RECTANGLE", + "max_selections": 1, + "coordinates_rounding": true, + "repeats_type": "NONE", + "service_call_schema": { + "service": "xiaomi_miio.vacuum_clean_zone", + "service_data": { + "entity_id": "[[entity_id]]", + "command": "custom_area", + "params": { + "coordinates": "[[selection_unwrapped]]" + } + } + } + } + } + }, + "tiles": { + "from_sensors": [ + { + "tile_id": "water_amount", + "unique_id_regex": "_water_amount", + "label": "tile.water_amount.label", + "icon": "mdi:water" + }, + { + "tile_id": "cleaning_time", + "unique_id_regex": "_stats_time", + "label": "tile.cleaning_time.label", + "icon": "mdi:timer-sand", + "unit": "unit.minute_shortcut" + }, + { + "tile_id": "cleaned_area", + "unique_id_regex": "_stats_area", + "label": "tile.cleaned_area.label", + "icon": "mdi:texture-box", + "unit": "unit.meter_squared_shortcut" + }, + { + "tile_id": "mop_attached", + "unique_id_regex": "_mop_attached", + "label": "tile.mop_attached.label", + "icon": "mdi:water" + }, + { + "tile_id": "cleaning_time", + "unique_id_regex": "_stats_time", + "label": "tile.cleaning_time.label", + "icon": "mdi:timer-sand", + "unit": "unit.minute_shortcut" + }, + { + "tile_id": "total_cleaned_area", + "unique_id_regex": "_stats_total_area", + "label": "tile.total_cleaned_area.label", + "icon": "mdi:texture-box", + "unit": "unit.meter_squared_shortcut" + }, + { + "tile_id": "cleaning_time", + "unique_id_regex": "_stats_total_time", + "label": "tile.total_cleaning_time.label", + "icon": "mdi:timer-sand", + "unit": "unit.hour_shortcut" + }, + { + "tile_id": "cleaning_count", + "unique_id_regex": "_stats_total_cleanings", + "label": "tile.cleaning_count.label" + } + ] + } +} From e8344eeef4b8d610e265596f4db07ac6e75cbb99 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 7 Mar 2023 05:29:06 +0100 Subject: [PATCH 012/100] Add humbertogontijo/homeassistant-roborock platform --- src/model/generators/platform-generator.ts | 6 +- ...umbertogontijo_homeassistant-roborock.json | 256 ++++++++++++++++++ 2 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json diff --git a/src/model/generators/platform-generator.ts b/src/model/generators/platform-generator.ts index 0f63410b..d4ea4aca 100644 --- a/src/model/generators/platform-generator.ts +++ b/src/model/generators/platform-generator.ts @@ -10,6 +10,7 @@ import * as neatoTemplate from "./platform_templates/neato.json"; import * as roombaTemplate from "./platform_templates/roomba.json"; import * as deebotTemplate from "./platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json"; import * as tasshackDreameVacuumTemplate from "./platform_templates/Tasshack_dreame-vacuum.json"; +import * as roborockTemplate from "./platform_templates/humbertogontijo_homeassistant-roborock.json"; import * as setupDecimalTemplate from "./platform_templates/setup_decimal.json"; import * as setupIntegerTemplate from "./platform_templates/setup_integer.json"; import { MapModeConfig, PlatformTemplate, TileFromAttributeTemplate, TileFromSensorTemplate } from "../../types/types"; @@ -30,6 +31,7 @@ export class PlatformGenerator { public static ROOMBA_PLATFORM = "Roomba"; public static TASSHACK_DREAME_VACUUM_PLATFORM = "Tasshack/dreame-vacuum"; public static DEEBOTUNIVERSE_DEEBOT_4_HOME_ASSISTANT_PLATFORM = "DeebotUniverse/Deebot-4-Home-Assistant"; + public static HUMBERTOGONTIJO_ROBOROCK_PLATFORM = "humbertogontijo/homeassistant-roborock"; public static SETUP_INTEGER_PLATFORM = "Setup integer"; public static SETUP_DECIMAL_PLATFORM = "Setup decimal"; @@ -43,7 +45,8 @@ export class PlatformGenerator { [PlatformGenerator.TYKAROL_VIOMI_VACUUM_V8_PLATFORM, tykarolViomiVacuumV8Template], [PlatformGenerator.HYPFER_VALETUDO_PLATFORM, hypferValetudoTemplate], [PlatformGenerator.RAND256_VALETUDO_RE_PLATFORM, rand256ValetudoReTemplate as PlatformTemplate], - [PlatformGenerator.TASSHACK_DREAME_VACUUM_PLATFORM, tasshackDreameVacuumTemplate as PlatformTemplate], + [PlatformGenerator.TASSHACK_DREAME_VACUUM_PLATFORM, tasshackDreameVacuumTemplate], + [PlatformGenerator.HUMBERTOGONTIJO_ROBOROCK_PLATFORM, roborockTemplate as PlatformTemplate], [PlatformGenerator.SEND_COMMAND_PLATFORM, sendCommandTemplate], [PlatformGenerator.ALONE_XIAOMI_MIOT_PLATFORM, alOneHassXiaomiMiotTemplate], [PlatformGenerator.NEATO_PLATFORM, neatoTemplate], @@ -61,6 +64,7 @@ export class PlatformGenerator { [PlatformGenerator.HYPFER_VALETUDO_PLATFORM, "hypferValetudo"], [PlatformGenerator.RAND256_VALETUDO_RE_PLATFORM, "rand256ValetudoRe"], [PlatformGenerator.TASSHACK_DREAME_VACUUM_PLATFORM, "tasshackDreameVacuum"], + [PlatformGenerator.HUMBERTOGONTIJO_ROBOROCK_PLATFORM, "humbertogontijoHomeassistantRoborock"], [PlatformGenerator.SEND_COMMAND_PLATFORM, "sendCommand"], [PlatformGenerator.ALONE_XIAOMI_MIOT_PLATFORM, "alOneHassXiaomiMiot"], [PlatformGenerator.NEATO_PLATFORM, "neato"], diff --git a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json new file mode 100644 index 00000000..0a92d158 --- /dev/null +++ b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json @@ -0,0 +1,256 @@ +{ + "map_modes": { + "defaultTemplates": ["vacuum_clean_zone", "vacuum_goto"], + "templates": { + "vacuum_clean_segment": { + "name": "map_mode.vacuum_clean_segment", + "icon": "mdi:floor-plan", + "selection_type": "ROOM", + "repeats_type": "REPEAT", + "max_repeats": 3, + "service_call_schema": { + "service": "roborock.vacuum_clean_segment", + "service_data": { + "segments": "[[selection]]", + "entity_id": "[[entity_id]]" + } + } + }, + "vacuum_clean_zone": { + "name": "map_mode.vacuum_clean_zone", + "icon": "mdi:select-drag", + "selection_type": "MANUAL_RECTANGLE", + "coordinates_rounding": true, + "max_selections": 5, + "repeats_type": "EXTERNAL", + "max_repeats": 3, + "service_call_schema": { + "service": "roborock.vacuum_clean_zone", + "service_data": { + "zone": "[[selection]]", + "repeats": "[[repeats]]", + "entity_id": "[[entity_id]]" + } + } + }, + "vacuum_clean_zone_predefined": { + "name": "map_mode.vacuum_clean_zone_predefined", + "icon": "mdi:floor-plan", + "selection_type": "PREDEFINED_RECTANGLE", + "max_selections": 5, + "coordinates_rounding": true, + "repeats_type": "EXTERNAL", + "max_repeats": 3, + "service_call_schema": { + "service": "roborock.vacuum_clean_zone", + "service_data": { + "zone": "[[selection]]", + "repeats": "[[repeats]]", + "entity_id": "[[entity_id]]" + } + } + }, + "vacuum_goto": { + "name": "map_mode.vacuum_goto", + "icon": "mdi:map-marker-plus", + "selection_type": "MANUAL_POINT", + "coordinates_rounding": true, + "repeats_type": "NONE", + "service_call_schema": { + "service": "roborock.vacuum_goto", + "service_data": { + "x_coord": "[[point_x]]", + "y_coord": "[[point_y]]", + "entity_id": "[[entity_id]]" + } + } + }, + "vacuum_goto_predefined": { + "name": "map_mode.vacuum_goto_predefined", + "icon": "mdi:map-marker", + "selection_type": "PREDEFINED_POINT", + "coordinates_rounding": true, + "repeats_type": "NONE", + "service_call_schema": { + "service": "xiaomi_miio.vacuum_goto", + "service_data": { + "x_coord": "[[point_x]]", + "y_coord": "[[point_y]]", + "entity_id": "[[entity_id]]" + } + } + }, + "vacuum_follow_path": { + "name": "map_mode.vacuum_follow_path", + "icon": "mdi:map-marker-path", + "selection_type": "MANUAL_PATH", + "coordinates_rounding": true, + "repeats_type": "NONE", + "service_call_schema": { + "service": "script.vacuum_follow_path", + "service_data": { + "service": "xiaomi_miio.vacuum_goto", + "mode": "individual", + "path": "[[selection]]", + "entity_id": "[[entity_id]]" + } + } + } + } + }, + "sensors_from": "2021.11.0", + "tiles": { + "from_attributes": [ + { + "tile_id": "sensor_dirty_left", + "attribute": "sensor_dirty_left", + "label": "tile.sensor_dirty_left.label", + "icon": "mdi:eye-outline", + "unit": "unit.hour_shortcut", + "multiplier": 0.0002777777777777778, + "hold_action": { + "action": "call-service", + "service": "vacuum.send_command", + "confirmation": true, + "service_data": { + "command": "reset_consumable", + "params": "sensor_dirty_time", + "entity_id": "[[vacuum_entity]]" + } + } + }, + { + "tile_id": "filter_left", + "attribute": "filter_left", + "label": "tile.filter_left.label", + "icon": "mdi:air-filter", + "unit": "unit.hour_shortcut", + "hold_action": { + "action": "call-service", + "service": "vacuum.send_command", + "confirmation": true, + "service_data": { + "command": "reset_consumable", + "params": "filter_work_time", + "entity_id": "[[vacuum_entity]]" + } + } + }, + { + "tile_id": "main_brush_left", + "attribute": "main_brush_left", + "label": "tile.main_brush_left.label", + "icon": "mdi:brush", + "unit": "unit.hour_shortcut", + "hold_action": { + "action": "call-service", + "service": "vacuum.send_command", + "confirmation": true, + "service_data": { + "command": "reset_consumable", + "params": "main_brush_work_time", + "entity_id": "[[vacuum_entity]]" + } + } + }, + { + "tile_id": "side_brush_left", + "attribute": "side_brush_left", + "label": "tile.side_brush_left.label", + "icon": "mdi:brush", + "unit": "unit.hour_shortcut", + "hold_action": { + "action": "call-service", + "service": "vacuum.send_command", + "confirmation": true, + "service_data": { + "command": "reset_consumable", + "params": "side_brush_work_time", + "entity_id": "[[vacuum_entity]]" + } + } + }, + { + "tile_id": "cleaning_count", + "attribute": "cleaning_count", + "label": "tile.cleaning_count.label", + "icon": "mdi:counter" + } + ], + "from_sensors": [ + { + "tile_id": "sensor_dirty_left", + "unique_id_regex": "consumable_sensor_dirty_left_", + "label": "tile.sensor_dirty_left.label", + "unit": "unit.hour_shortcut", + "multiplier": 0.0002777777777777778, + "hold_action": { + "action": "call-service", + "service": "vacuum.send_command", + "confirmation": true, + "service_data": { + "command": "reset_consumable", + "params": "sensor_dirty_time", + "entity_id": "[[vacuum_entity]]" + } + } + }, + { + "tile_id": "filter_left", + "unique_id_regex": "consumable_filter_left_", + "label": "tile.filter_left.label", + "unit": "unit.hour_shortcut", + "multiplier": 0.0002777777777777778, + "hold_action": { + "action": "call-service", + "service": "vacuum.send_command", + "confirmation": true, + "service_data": { + "command": "reset_consumable", + "params": "filter_work_time", + "entity_id": "[[vacuum_entity]]" + } + } + }, + { + "tile_id": "main_brush_left", + "unique_id_regex": "consumable_main_brush_left_", + "label": "tile.main_brush_left.label", + "unit": "unit.hour_shortcut", + "multiplier": 0.0002777777777777778, + "hold_action": { + "action": "call-service", + "service": "vacuum.send_command", + "confirmation": true, + "service_data": { + "command": "reset_consumable", + "params": "main_brush_work_time", + "entity_id": "[[vacuum_entity]]" + } + } + }, + { + "tile_id": "side_brush_left", + "unique_id_regex": "consumable_side_brush_left_", + "label": "tile.side_brush_left.label", + "unit": "unit.hour_shortcut", + "multiplier": 0.0002777777777777778, + "hold_action": { + "action": "call-service", + "service": "vacuum.send_command", + "confirmation": true, + "service_data": { + "command": "reset_consumable", + "params": "side_brush_work_time", + "entity_id": "[[vacuum_entity]]" + } + } + }, + { + "tile_id": "cleaning_count", + "unique_id_regex": "clean_history_count_", + "label": "tile.cleaning_count.label" + } + ] + } +} From 77f8fa41804fea2b8ec8f257c0a1066887e75fd1 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 7 Mar 2023 05:44:11 +0100 Subject: [PATCH 013/100] Add romedtino/simple-wyze-vac platform --- src/model/generators/platform-generator.ts | 4 ++ .../romedtino_simple-wyze-vac.json | 49 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/model/generators/platform_templates/romedtino_simple-wyze-vac.json diff --git a/src/model/generators/platform-generator.ts b/src/model/generators/platform-generator.ts index d4ea4aca..5577d68c 100644 --- a/src/model/generators/platform-generator.ts +++ b/src/model/generators/platform-generator.ts @@ -11,6 +11,7 @@ import * as roombaTemplate from "./platform_templates/roomba.json"; import * as deebotTemplate from "./platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json"; import * as tasshackDreameVacuumTemplate from "./platform_templates/Tasshack_dreame-vacuum.json"; import * as roborockTemplate from "./platform_templates/humbertogontijo_homeassistant-roborock.json"; +import * as simpleWyzeTemplate from "./platform_templates/romedtino_simple-wyze-vac.json"; import * as setupDecimalTemplate from "./platform_templates/setup_decimal.json"; import * as setupIntegerTemplate from "./platform_templates/setup_integer.json"; import { MapModeConfig, PlatformTemplate, TileFromAttributeTemplate, TileFromSensorTemplate } from "../../types/types"; @@ -32,6 +33,7 @@ export class PlatformGenerator { public static TASSHACK_DREAME_VACUUM_PLATFORM = "Tasshack/dreame-vacuum"; public static DEEBOTUNIVERSE_DEEBOT_4_HOME_ASSISTANT_PLATFORM = "DeebotUniverse/Deebot-4-Home-Assistant"; public static HUMBERTOGONTIJO_ROBOROCK_PLATFORM = "humbertogontijo/homeassistant-roborock"; + public static ROMEDTINO_SIMPLE_WAZE_PLATFORM = "romedtino/simple-wyze-vac"; public static SETUP_INTEGER_PLATFORM = "Setup integer"; public static SETUP_DECIMAL_PLATFORM = "Setup decimal"; @@ -52,6 +54,7 @@ export class PlatformGenerator { [PlatformGenerator.NEATO_PLATFORM, neatoTemplate], [PlatformGenerator.ROOMBA_PLATFORM, roombaTemplate], [PlatformGenerator.DEEBOTUNIVERSE_DEEBOT_4_HOME_ASSISTANT_PLATFORM, deebotTemplate], + [PlatformGenerator.ROMEDTINO_SIMPLE_WAZE_PLATFORM, simpleWyzeTemplate], [PlatformGenerator.SETUP_INTEGER_PLATFORM, setupIntegerTemplate], [PlatformGenerator.SETUP_DECIMAL_PLATFORM, setupDecimalTemplate], ]); @@ -70,6 +73,7 @@ export class PlatformGenerator { [PlatformGenerator.NEATO_PLATFORM, "neato"], [PlatformGenerator.ROOMBA_PLATFORM, "roomba"], [PlatformGenerator.DEEBOTUNIVERSE_DEEBOT_4_HOME_ASSISTANT_PLATFORM, "DeebotUniverseDeebot4homeAssistant"], + [PlatformGenerator.ROMEDTINO_SIMPLE_WAZE_PLATFORM, "romedtinoSimpleWyze"], [PlatformGenerator.SETUP_INTEGER_PLATFORM, "setup"], [PlatformGenerator.SETUP_DECIMAL_PLATFORM, "setup"], ]); diff --git a/src/model/generators/platform_templates/romedtino_simple-wyze-vac.json b/src/model/generators/platform_templates/romedtino_simple-wyze-vac.json new file mode 100644 index 00000000..a4c31622 --- /dev/null +++ b/src/model/generators/platform_templates/romedtino_simple-wyze-vac.json @@ -0,0 +1,49 @@ +{ + "map_modes": { + "defaultTemplates": [], + "templates": { + "vacuum_clean_segment": { + "name": "map_mode.vacuum_clean_segment", + "icon": "mdi:floor-plan", + "selection_type": "ROOM", + "repeats_type": "NONE", + "max_selection": "20", + "service_call_schema": { + "service": "vacuum.send_command", + "service_data": { + "entity_id": "[[entity_id]]", + "command": "sweep_rooms", + "params": { + "rooms": "[[selection]]" + } + } + } + } + } + }, + "tiles": { + "from_attributes": [ + { + "tile_id": "filter_left", + "attribute": "filter", + "label": "tile.filter_left.label", + "icon": "mdi:air-filter", + "unit": "unit.hour_shortcut" + }, + { + "tile_id": "main_brush_left", + "attribute": "main_brush", + "label": "tile.main_brush_left.label", + "icon": "mdi:brush", + "unit": "unit.hour_shortcut" + }, + { + "tile_id": "side_brush_left", + "attribute": "side_brush", + "label": "tile.side_brush_left.label", + "icon": "mdi:brush", + "unit": "unit.hour_shortcut" + } + ] + } +} From f12c9bc6c03ef11b240b4bd2f49e6512d1435dd3 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 7 Mar 2023 05:50:53 +0100 Subject: [PATCH 014/100] Remove package-lock.json --- package-lock.json | 3280 --------------------------------------------- 1 file changed, 3280 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 31edad31..00000000 --- a/package-lock.json +++ /dev/null @@ -1,3280 +0,0 @@ -{ - "name": "xiaomi-vacuum-map-card", - "version": "v2.1.2", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", - "dev": true - }, - "@babel/core": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.0.tgz", - "integrity": "sha512-tXtmTminrze5HEUPn/a0JtOzzfp0nk+UEXQ/tqIJo3WDGypl/2OFQEMll/zSFU8f/lfmfLXvTaORHF3cfXIQMw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-compilation-targets": "^7.15.0", - "@babel/helper-module-transforms": "^7.15.0", - "@babel/helpers": "^7.14.8", - "@babel/parser": "^7.15.0", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "@babel/generator": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", - "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", - "dev": true, - "requires": { - "@babel/types": "^7.15.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true - }, - "@babel/parser": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", - "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", - "dev": true - }, - "@babel/traverse": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", - "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.15.0", - "@babel/types": "^7.15.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", - "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz", - "integrity": "sha512-h+/9t0ncd4jfZ8wsdAsoIxSa61qhBYlycXiHWqJaQBCXAhDCMbPRSMTGnZIkkmt1u4ag+UQmuqcILwqKzZ4N2A==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.6.tgz", - "integrity": "sha512-Z6gsfGofTxH/+LQXqYEK45kxmcensbzmk/oi8DmaQytlQCgqNZt9XQF8iqlI/SeXWVjaMNxvYvzaYw+kh42mDg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-member-expression-to-functions": "^7.14.5", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5" - } - }, - "@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", - "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", - "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-module-imports": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", - "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-module-transforms": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.0.tgz", - "integrity": "sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-replace-supers": "^7.15.0", - "@babel/helper-simple-access": "^7.14.8", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.9", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" - }, - "dependencies": { - "@babel/generator": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", - "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", - "dev": true, - "requires": { - "@babel/types": "^7.15.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz", - "integrity": "sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg==", - "dev": true, - "requires": { - "@babel/types": "^7.15.0" - } - }, - "@babel/helper-replace-supers": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz", - "integrity": "sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.15.0", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true - }, - "@babel/parser": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", - "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", - "dev": true - }, - "@babel/traverse": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", - "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.15.0", - "@babel/types": "^7.15.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", - "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - }, - "@babel/helper-replace-supers": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", - "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.14.5", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-simple-access": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz", - "integrity": "sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==", - "dev": true, - "requires": { - "@babel/types": "^7.14.8" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true - }, - "@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true - }, - "@babel/helpers": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.3.tgz", - "integrity": "sha512-HwJiz52XaS96lX+28Tnbu31VeFSQJGOeKHJeaEPQlTl7PnlhFElWPj8tUXtqFIzeN86XxXoBr+WFAyK2PPVz6g==", - "dev": true, - "requires": { - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" - }, - "dependencies": { - "@babel/generator": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", - "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", - "dev": true, - "requires": { - "@babel/types": "^7.15.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true - }, - "@babel/parser": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", - "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", - "dev": true - }, - "@babel/traverse": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", - "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.15.0", - "@babel/types": "^7.15.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", - "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", - "dev": true - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz", - "integrity": "sha512-q/PLpv5Ko4dVc1LYMpCY7RVAAO4uk55qPwrIuJ5QJ8c6cVuAmhu7I/49JOppXL6gXf7ZHzpRVEUZdYoPLM04Gg==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-proposal-decorators": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.14.5.tgz", - "integrity": "sha512-LYz5nvQcvYeRVjui1Ykn28i+3aUiXwQ/3MGoEy0InTaz1pJo/lAzmIDXX+BQny/oufgHzJ6vnEEiXQ8KZjEVFg==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-decorators": "^7.14.5" - } - }, - "@babel/plugin-syntax-decorators": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.14.5.tgz", - "integrity": "sha512-c4sZMRWL4GSvP1EXy0woIP7m4jkVcEuG8R1TOZxPBPtp4FSM/kiPZub9UIs/Jrb5ZAOzvTUSGYrWsrSu1JvoPw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/traverse": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", - "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.7", - "@babel/types": "^7.14.5", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "to-fast-properties": "^2.0.0" - } - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - } - } - }, - "@formatjs/ecma402-abstract": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz", - "integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==", - "requires": { - "@formatjs/intl-localematcher": "0.2.25", - "tslib": "^2.1.0" - } - }, - "@formatjs/fast-memoize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz", - "integrity": "sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==", - "requires": { - "tslib": "^2.1.0" - } - }, - "@formatjs/icu-messageformat-parser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz", - "integrity": "sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==", - "requires": { - "@formatjs/ecma402-abstract": "1.11.4", - "@formatjs/icu-skeleton-parser": "1.3.6", - "tslib": "^2.1.0" - } - }, - "@formatjs/icu-skeleton-parser": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz", - "integrity": "sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==", - "requires": { - "@formatjs/ecma402-abstract": "1.11.4", - "tslib": "^2.1.0" - } - }, - "@formatjs/intl-localematcher": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz", - "integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==", - "requires": { - "tslib": "^2.1.0" - } - }, - "@formatjs/intl-utils": { - "version": "3.8.4", - "resolved": "https://registry.npmjs.org/@formatjs/intl-utils/-/intl-utils-3.8.4.tgz", - "integrity": "sha512-j5C6NyfKevIxsfLK8KwO1C0vvP7k1+h4A9cFpc+cr6mEwCc1sPkr17dzh0Ke6k9U5pQccAQoXdcNBl3IYa4+ZQ==", - "requires": { - "emojis-list": "^3.0.0" - } - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@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 - }, - "@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 - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@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 - }, - "@jridgewell/trace-mapping": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", - "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@lit/reactive-element": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.3.2.tgz", - "integrity": "sha512-A2e18XzPMrIh35nhIdE4uoqRzoIpEU5vZYuQN4S3Ee1zkGdYC27DP12pewbw/RLgPHzaE4kx/YqxMzebOpm0dA==" - }, - "@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, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@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 - }, - "@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, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@rollup/plugin-json": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", - "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8" - } - }, - "@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "requires": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - } - }, - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "@types/node": { - "version": "16.3.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.2.tgz", - "integrity": "sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==", - "dev": true - }, - "@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/trusted-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", - "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" - }, - "@typescript-eslint/eslint-plugin": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", - "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", - "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "4.33.0", - "@typescript-eslint/scope-manager": "4.33.0", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.1.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/experimental-utils": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", - "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, - "@typescript-eslint/parser": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", - "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "debug": "^4.3.1" - } - }, - "@typescript-eslint/scope-manager": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", - "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0" - } - }, - "@typescript-eslint/types": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", - "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", - "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", - "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.33.0", - "eslint-visitor-keys": "^2.0.0" - } - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "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 - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-includes": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", - "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.5" - } - }, - "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 - }, - "array.prototype.flat": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", - "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - } - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.16.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.7.tgz", - "integrity": "sha512-7I4qVwqZltJ7j37wObBe3SoTz+nS8APaNcrBOlgoirb6/HbEU2XxW/LpUDTCngM6iauwFqmRTuOMfyKnFGY5JA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001248", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.793", - "escalade": "^3.1.1", - "node-releases": "^1.1.73" - } - }, - "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 - }, - "builtin-modules": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", - "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001251", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001251.tgz", - "integrity": "sha512-HOe1r+9VkU4TFmnU70z+r7OLmtR+/chB1rdcJUeQlAinjEeb0cKL20tlAtOagNZhbrtLnCvV19B4FmF1rgzl6A==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "change-perspective": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/change-perspective/-/change-perspective-1.0.1.tgz", - "integrity": "sha512-x2w4Zv6lCAv85RXVSVFsL5MpOe1SveXyVmIcoT6RN+Kij22V9U8oNzzd4hxAmoZpvyK+/mUS0/2upGxu/FV09g==" - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "colorette": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.3.0.tgz", - "integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==", - "dev": true - }, - "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 - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "compare-versions": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-4.0.1.tgz", - "integrity": "sha512-CWRFliMK+3xX42rdenZuutpAA02Egtxb5HbSSb5WJRHhkdGC6VAHigssQ7DoyNq7g+6VamJhCQ5wpoApZkFxGw==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "confusing-browser-globals": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", - "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", - "dev": true - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "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, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "custom-card-helpers": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/custom-card-helpers/-/custom-card-helpers-1.9.0.tgz", - "integrity": "sha512-5IW4OXq3MiiCqDvqeu+MYsM1NmntKW1WfJhyJFsdP2tbzqEI4BOnqRz2qzdp08lE4QLVhYfRLwe0WAqgQVNeFg==", - "requires": { - "@formatjs/intl-utils": "^3.8.4", - "home-assistant-js-websocket": "^6.0.1", - "intl-messageformat": "^9.11.1", - "lit": "^2.1.1", - "rollup": "^2.63.0", - "superstruct": "^0.15.3", - "typescript": "^4.5.4" - }, - "dependencies": { - "home-assistant-js-websocket": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/home-assistant-js-websocket/-/home-assistant-js-websocket-6.1.1.tgz", - "integrity": "sha512-TnZFzF4mn5F/v0XKUTK2GMQXrn/+eQpgaSDSELl6U0HSwSbFwRhGWLz330YT+hiKMspDflamsye//RPL+zwhDw==" - }, - "rollup": { - "version": "2.72.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.72.1.tgz", - "integrity": "sha512-NTc5UGy/NWFGpSqF1lFY8z9Adri6uhyMLI6LvPAXdBKoPRFhIIiBUpt+Qg2awixqO3xvzSijjhnb4+QEZwJmxA==", - "requires": { - "fsevents": "~2.3.2" - } - }, - "typescript": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", - "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==" - } - } - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "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, - "requires": { - "path-type": "^4.0.0" - }, - "dependencies": { - "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 - } - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "electron-to-chromium": { - "version": "1.3.807", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.807.tgz", - "integrity": "sha512-p8uxxg2a23zRsvQ2uwA/OOI+O4BQxzaR7YKMIGGGQCpYmkFX2CVF5f0/hxLMV7yCr7nnJViCwHLhPfs52rIYCA==", - "dev": true - }, - "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 - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", - "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", - "object-inspect": "^1.10.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "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, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "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 - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "eslint-config-airbnb-base": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz", - "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==", - "dev": true, - "requires": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.2" - } - }, - "eslint-config-prettier": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", - "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true - }, - "eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.2.tgz", - "integrity": "sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "pkg-dir": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-import": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.24.0.tgz", - "integrity": "sha512-Kc6xqT9hiYi2cgybOc0I2vC9OgAYga5o/rAFinam/yF/t5uBqxQbauNPMC6fgb640T/89P0gFoO27FOilJ/Cqg==", - "dev": true, - "requires": { - "array-includes": "^3.1.3", - "array.prototype.flat": "^1.2.4", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.5", - "eslint-module-utils": "^2.6.2", - "find-up": "^2.0.0", - "has": "^1.0.3", - "is-core-module": "^2.4.0", - "minimatch": "^3.0.4", - "object.values": "^1.1.3", - "pkg-up": "^2.0.0", - "read-pkg-up": "^3.0.0", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.9.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-plugin-prettier": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", - "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "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, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "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, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "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 - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dev": true, - "requires": { - "@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" - } - }, - "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 - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "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, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "dependencies": { - "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, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "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, - "requires": { - "p-locate": "^4.1.0" - } - }, - "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, - "requires": { - "p-try": "^2.0.0" - } - }, - "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, - "requires": { - "p-limit": "^2.2.0" - } - }, - "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 - }, - "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 - }, - "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, - "requires": { - "find-up": "^4.0.0" - } - } - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", - "dev": true - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "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, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "home-assistant-js-websocket": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/home-assistant-js-websocket/-/home-assistant-js-websocket-5.11.2.tgz", - "integrity": "sha512-dRvOG0qNDwZuQvTveHmFpU+OJ5ytBzNChW0vH2mocDPeNhhcDQoKiUao3DEZgH+oAiz/9U3A0xm06y7hxs5MRw==" - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "ignore": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", - "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "intl-messageformat": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.13.0.tgz", - "integrity": "sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==", - "requires": { - "@formatjs/ecma402-abstract": "1.11.4", - "@formatjs/fast-memoize": "1.2.1", - "@formatjs/icu-messageformat-parser": "2.1.0", - "tslib": "^2.1.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-bigint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", - "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", - "dev": true - }, - "is-boolean-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", - "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", - "dev": true - }, - "is-core-module": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", - "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "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 - }, - "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, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - }, - "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 - }, - "is-number-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", - "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", - "dev": true - }, - "is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, - "is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" - } - }, - "is-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", - "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", - "dev": true - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "jest-worker": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", - "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", - "dev": true, - "requires": { - "merge-stream": "^2.0.0", - "supports-color": "^6.1.0" - }, - "dependencies": { - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "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 - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lit": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.2.3.tgz", - "integrity": "sha512-5/v+r9dH3Pw/o0rhp/qYk3ERvOUclNF31bWb0FiW6MPgwdQIr+/KCt/p3zcd8aPl8lIGnxdGrVcZA+gWS6oFOQ==", - "requires": { - "@lit/reactive-element": "^1.3.0", - "lit-element": "^3.2.0", - "lit-html": "^2.2.0" - } - }, - "lit-element": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.2.0.tgz", - "integrity": "sha512-HbE7yt2SnUtg5DCrWt028oaU4D5F4k/1cntAFHTkzY8ZIa8N0Wmu92PxSxucsQSOXlODFrICkQ5x/tEshKi13g==", - "requires": { - "@lit/reactive-element": "^1.3.0", - "lit-html": "^2.2.0" - } - }, - "lit-html": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.2.3.tgz", - "integrity": "sha512-vI4j3eWwtQaR8q/O63juZVliBIFMio716X719/lSsGH4UWPy2/7Qf377jsNs4cx3gCHgIbx8yxFgXFQ/igZyXQ==", - "requires": { - "@types/trusted-types": "^2.0.2" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "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, - "requires": { - "yallist": "^4.0.0" - } - }, - "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.4" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "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 - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node-releases": { - "version": "1.1.74", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.74.tgz", - "integrity": "sha512-caJBVempXZPepZoZAPCWRTNxYQ+xtG/KAi4ozTA5A+nJ7IU+kLQCbqaUjb5Rwy14M9upBWiQ4NutcmW04LJSRw==", - "dev": true - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "object.entries": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.4.tgz", - "integrity": "sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2" - } - }, - "object.values": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", - "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "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 - }, - "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 - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "pointer-tracker": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/pointer-tracker/-/pointer-tracker-2.4.0.tgz", - "integrity": "sha512-pWI2tpaM/XNtc9mUTv42Rmjf6mkHvE8LT5DDEq0G7baPNhxNM9E3CepubPplSoSLk9E5bwQrAMyDcPVmJyTW4g==" - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", - "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", - "dev": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "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 - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rollup": { - "version": "2.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.59.0.tgz", - "integrity": "sha512-l7s90JQhCQ6JyZjKgo7Lq1dKh2RxatOM+Jr6a9F7WbS9WgKbocyUSeLmZl8evAse7y96Ae98L2k1cBOwWD8nHw==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "rollup-plugin-babel": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz", - "integrity": "sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "rollup-pluginutils": "^2.8.1" - } - }, - "rollup-plugin-commonjs": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", - "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0", - "rollup-pluginutils": "^2.8.1" - }, - "dependencies": { - "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - } - } - }, - "rollup-plugin-node-resolve": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", - "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==", - "dev": true, - "requires": { - "@types/resolve": "0.0.8", - "builtin-modules": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.11.1", - "rollup-pluginutils": "^2.8.1" - } - }, - "rollup-plugin-serve": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-serve/-/rollup-plugin-serve-1.1.0.tgz", - "integrity": "sha512-pYkSsuA0/psKqhhictkJw1c2klya5b+LlCvipWqI9OE1aG2M97mRumZCbBlry5CMEOzYBBgSDgd1694sNbmyIw==", - "dev": true, - "requires": { - "mime": ">=2.4.6", - "opener": "1" - } - }, - "rollup-plugin-terser": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", - "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "jest-worker": "^26.2.1", - "serialize-javascript": "^4.0.0", - "terser": "^5.0.0" - }, - "dependencies": { - "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 - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - } - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "rollup-plugin-typescript2": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.30.0.tgz", - "integrity": "sha512-NUFszIQyhgDdhRS9ya/VEmsnpTe+GERDMmFo0Y+kf8ds51Xy57nPNGglJY+W6x1vcouA7Au7nsTgsLFj2I0PxQ==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^4.1.0", - "find-cache-dir": "^3.3.1", - "fs-extra": "8.1.0", - "resolve": "1.20.0", - "tslib": "2.1.0" - }, - "dependencies": { - "@rollup/pluginutils": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.1.tgz", - "integrity": "sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ==", - "dev": true, - "requires": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" - } - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", - "dev": true - } - } - }, - "rollup-plugin-uglify": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/rollup-plugin-uglify/-/rollup-plugin-uglify-6.0.4.tgz", - "integrity": "sha512-ddgqkH02klveu34TF0JqygPwZnsbhHVI6t8+hGTcYHngPkQb5MIHI0XiztXIN/d6V9j+efwHAqEL7LspSxQXGw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "jest-worker": "^24.0.0", - "serialize-javascript": "^2.1.2", - "uglify-js": "^3.4.9" - }, - "dependencies": { - "serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", - "dev": true - } - } - }, - "rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1" - }, - "dependencies": { - "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - } - } - }, - "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, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "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, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "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 - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "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, - "requires": { - "color-convert": "^2.0.1" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - } - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "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 - } - } - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "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, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "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, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "superstruct": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.15.4.tgz", - "integrity": "sha512-eOoMeSbP9ZJChNOm/9RYjE+F36rYR966AAqeG3xhQB02j2sfAUXDp4EQ/7bAOqnlJnuFDB8yvOu50SocvKpUEw==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "table": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.3.tgz", - "integrity": "sha512-5DkIxeA7XERBqMwJq0aHZOdMadBx4e6eDoFRuyT5VR82J0Ycg2DwM6GfA/EQAhJ+toRTaS1lIdSQCqgrmhPnlw==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ajv": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.7.1.tgz", - "integrity": "sha512-gPpOObTO1QjbnN1sVMjJcp1TF9nggMfO4MBR5uQl6ZVTOaEPq5i4oq/6R9q2alMMPB3eg53wFv1RuJBLuxf3Hw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", - "dev": true - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "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, - "requires": { - "is-number": "^7.0.0" - } - }, - "transformation-matrix": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-2.8.0.tgz", - "integrity": "sha512-ciqdbEofLC3RFCj1bo06fH/IXf/83DOyuXi50RKdUx2iWFI02+mvsiSqB7pKKypBJgAxzCf7IvA+FMD70HXWQQ==" - }, - "tsconfig-paths": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.10.1.tgz", - "integrity": "sha512-rETidPDgCpltxF7MjBZlAFPUHv5aHH2MymyPvh+vEyWAED4Eb/WeMbsnD/JDr4OKPOA1TssDHgIcpTN5Kh0p6Q==", - "dev": true, - "requires": { - "json5": "^2.2.0", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - } - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", - "dev": true - }, - "uglify-js": { - "version": "3.13.10", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.10.tgz", - "integrity": "sha512-57H3ACYFXeo1IaZ1w02sfA71wI60MGco/IQFjOqK+WtKoprh7Go2/yvd2HPtoJILO2Or84ncLccI4xoHMTSbGg==", - "dev": true - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } -} From 24bb46712bab418c58c81a1059ebe78781167307 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 7 Mar 2023 05:57:49 +0100 Subject: [PATCH 015/100] Remove uglify --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 5ac207ae..77743c56 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "rollup-plugin-serve": "^1.1.0", "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.30.0", - "rollup-plugin-uglify": "^6.0.4", "typescript": "^4.4.3" }, "scripts": { From 8ac10c8af480426dd03f8dc0a3e6bf301deef25b Mon Sep 17 00:00:00 2001 From: Piotr Machowski <6118709+PiotrMachowski@users.noreply.github.com> Date: Tue, 7 Mar 2023 15:19:42 +0100 Subject: [PATCH 016/100] Adjust build action --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 65a02234..97d2d653 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,10 +12,13 @@ on: jobs: build: - name: Test build + name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 + - uses: actions/setup-node@v3 + with: + node-version: 14 - name: Build run: | npm install From 517a7c2b6f6b4c96854abda199100698105d0d44 Mon Sep 17 00:00:00 2001 From: Piotr Machowski <6118709+PiotrMachowski@users.noreply.github.com> Date: Tue, 7 Mar 2023 17:15:17 +0100 Subject: [PATCH 017/100] Adjust typing problems --- .../humbertogontijo_homeassistant-roborock.json | 16 ++++++++-------- .../platform_templates/rand256_valetudo_re.json | 6 +++--- .../platform_templates/xiaomiMiio.json | 16 ++++++++-------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json index 0a92d158..701cbe6e 100644 --- a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json +++ b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json @@ -111,7 +111,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "sensor_dirty_time", @@ -128,7 +128,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "filter_work_time", @@ -145,7 +145,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "main_brush_work_time", @@ -162,7 +162,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "side_brush_work_time", @@ -187,7 +187,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "sensor_dirty_time", @@ -204,7 +204,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "filter_work_time", @@ -221,7 +221,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "main_brush_work_time", @@ -238,7 +238,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "side_brush_work_time", diff --git a/src/model/generators/platform_templates/rand256_valetudo_re.json b/src/model/generators/platform_templates/rand256_valetudo_re.json index 25245615..66dd2fc5 100644 --- a/src/model/generators/platform_templates/rand256_valetudo_re.json +++ b/src/model/generators/platform_templates/rand256_valetudo_re.json @@ -90,7 +90,7 @@ "hold_action": { "action": "call-service", "service": "mqtt.publish", - "confirmation": true, + "confirmation": {}, "service_data": { "topic": "[[topic]]/custom_command", "payload": "{\"command\": \"reset_consumable\",\n\"consumable\": \"filter_work_time\"}" @@ -106,7 +106,7 @@ "hold_action": { "action": "call-service", "service": "mqtt.publish", - "confirmation": true, + "confirmation": {}, "service_data": { "topic": "[[topic]]/custom_command", "payload": "{\"command\": \"reset_consumable\",\n\"consumable\": \"main_brush_work_time\"}" @@ -122,7 +122,7 @@ "hold_action": { "action": "call-service", "service": "mqtt.publish", - "confirmation": true, + "confirmation": {}, "service_data": { "topic": "[[topic]]/custom_command", "payload": "{\"command\": \"reset_consumable\",\n\"consumable\": \"side_brush_work_time\"}" diff --git a/src/model/generators/platform_templates/xiaomiMiio.json b/src/model/generators/platform_templates/xiaomiMiio.json index 4e89d22c..98a71305 100644 --- a/src/model/generators/platform_templates/xiaomiMiio.json +++ b/src/model/generators/platform_templates/xiaomiMiio.json @@ -110,7 +110,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "sensor_dirty_time", @@ -127,7 +127,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "filter_work_time", @@ -144,7 +144,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "main_brush_work_time", @@ -161,7 +161,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "side_brush_work_time", @@ -186,7 +186,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "sensor_dirty_time", @@ -203,7 +203,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "filter_work_time", @@ -220,7 +220,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "main_brush_work_time", @@ -237,7 +237,7 @@ "hold_action": { "action": "call-service", "service": "vacuum.send_command", - "confirmation": true, + "confirmation": {}, "service_data": { "command": "reset_consumable", "params": "side_brush_work_time", From 0918842847f9e41aa9533ca07a97acff0d0d616c Mon Sep 17 00:00:00 2001 From: Piotr Machowski <6118709+PiotrMachowski@users.noreply.github.com> Date: Tue, 7 Mar 2023 17:43:18 +0100 Subject: [PATCH 018/100] Improve humbertogontijo/homeassistant-roborock template --- src/model/generators/platform-generator.ts | 6 +- ...umbertogontijo_homeassistant-roborock.json | 96 ++++--------------- 2 files changed, 19 insertions(+), 83 deletions(-) diff --git a/src/model/generators/platform-generator.ts b/src/model/generators/platform-generator.ts index 5577d68c..944ff6f6 100644 --- a/src/model/generators/platform-generator.ts +++ b/src/model/generators/platform-generator.ts @@ -41,14 +41,14 @@ export class PlatformGenerator { "https://github.com/PiotrMachowski/lovelace-xiaomi-vacuum-map-card/tree/master/docs/templates/{0}.md"; private static TEMPLATES = new Map([ - [PlatformGenerator.XIAOMI_MIIO_PLATFORM, xiaomiMiioTemplate as PlatformTemplate], + [PlatformGenerator.XIAOMI_MIIO_PLATFORM, xiaomiMiioTemplate], [PlatformGenerator.KRZYSZTOFHAJDAMOWICZ_MIIO2_PLATFORM, krzysztofHajdamowiczMiio2Template], [PlatformGenerator.MAROTOWEB_VIOMISE_PLATFORM, marotowebViomiseTemplate], [PlatformGenerator.TYKAROL_VIOMI_VACUUM_V8_PLATFORM, tykarolViomiVacuumV8Template], [PlatformGenerator.HYPFER_VALETUDO_PLATFORM, hypferValetudoTemplate], - [PlatformGenerator.RAND256_VALETUDO_RE_PLATFORM, rand256ValetudoReTemplate as PlatformTemplate], + [PlatformGenerator.RAND256_VALETUDO_RE_PLATFORM, rand256ValetudoReTemplate], [PlatformGenerator.TASSHACK_DREAME_VACUUM_PLATFORM, tasshackDreameVacuumTemplate], - [PlatformGenerator.HUMBERTOGONTIJO_ROBOROCK_PLATFORM, roborockTemplate as PlatformTemplate], + [PlatformGenerator.HUMBERTOGONTIJO_ROBOROCK_PLATFORM, roborockTemplate], [PlatformGenerator.SEND_COMMAND_PLATFORM, sendCommandTemplate], [PlatformGenerator.ALONE_XIAOMI_MIOT_PLATFORM, alOneHassXiaomiMiotTemplate], [PlatformGenerator.NEATO_PLATFORM, neatoTemplate], diff --git a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json index 701cbe6e..e41357d5 100644 --- a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json +++ b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json @@ -72,7 +72,7 @@ "coordinates_rounding": true, "repeats_type": "NONE", "service_call_schema": { - "service": "xiaomi_miio.vacuum_goto", + "service": "roborock.vacuum_goto", "service_data": { "x_coord": "[[point_x]]", "y_coord": "[[point_y]]", @@ -89,7 +89,7 @@ "service_call_schema": { "service": "script.vacuum_follow_path", "service_data": { - "service": "xiaomi_miio.vacuum_goto", + "service": "roborock.vacuum_goto", "mode": "individual", "path": "[[selection]]", "entity_id": "[[entity_id]]" @@ -98,85 +98,7 @@ } } }, - "sensors_from": "2021.11.0", "tiles": { - "from_attributes": [ - { - "tile_id": "sensor_dirty_left", - "attribute": "sensor_dirty_left", - "label": "tile.sensor_dirty_left.label", - "icon": "mdi:eye-outline", - "unit": "unit.hour_shortcut", - "multiplier": 0.0002777777777777778, - "hold_action": { - "action": "call-service", - "service": "vacuum.send_command", - "confirmation": {}, - "service_data": { - "command": "reset_consumable", - "params": "sensor_dirty_time", - "entity_id": "[[vacuum_entity]]" - } - } - }, - { - "tile_id": "filter_left", - "attribute": "filter_left", - "label": "tile.filter_left.label", - "icon": "mdi:air-filter", - "unit": "unit.hour_shortcut", - "hold_action": { - "action": "call-service", - "service": "vacuum.send_command", - "confirmation": {}, - "service_data": { - "command": "reset_consumable", - "params": "filter_work_time", - "entity_id": "[[vacuum_entity]]" - } - } - }, - { - "tile_id": "main_brush_left", - "attribute": "main_brush_left", - "label": "tile.main_brush_left.label", - "icon": "mdi:brush", - "unit": "unit.hour_shortcut", - "hold_action": { - "action": "call-service", - "service": "vacuum.send_command", - "confirmation": {}, - "service_data": { - "command": "reset_consumable", - "params": "main_brush_work_time", - "entity_id": "[[vacuum_entity]]" - } - } - }, - { - "tile_id": "side_brush_left", - "attribute": "side_brush_left", - "label": "tile.side_brush_left.label", - "icon": "mdi:brush", - "unit": "unit.hour_shortcut", - "hold_action": { - "action": "call-service", - "service": "vacuum.send_command", - "confirmation": {}, - "service_data": { - "command": "reset_consumable", - "params": "side_brush_work_time", - "entity_id": "[[vacuum_entity]]" - } - } - }, - { - "tile_id": "cleaning_count", - "attribute": "cleaning_count", - "label": "tile.cleaning_count.label", - "icon": "mdi:counter" - } - ], "from_sensors": [ { "tile_id": "sensor_dirty_left", @@ -250,6 +172,20 @@ "tile_id": "cleaning_count", "unique_id_regex": "clean_history_count_", "label": "tile.cleaning_count.label" + }, + { + "tile_id": "cleaned_area", + "unique_id_regex": "current_clean_area_", + "label": "tile.cleaned_area.label", + "unit": "unit.meter_squared_shortcut", + "precision": 2 + }, + { + "tile_id": "cleaning_time", + "unique_id_regex": "current_clean_time_", + "label": "tile.cleaning_time.label", + "unit": "unit.minute_shortcut", + "multiplier": 0.016667 } ] } From b365a730148b4ffceb16429b8659709c1b014912 Mon Sep 17 00:00:00 2001 From: Piotr Machowski <6118709+PiotrMachowski@users.noreply.github.com> Date: Tue, 7 Mar 2023 17:45:04 +0100 Subject: [PATCH 019/100] Adjust typing --- src/model/generators/platform-generator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/model/generators/platform-generator.ts b/src/model/generators/platform-generator.ts index 944ff6f6..5577d68c 100644 --- a/src/model/generators/platform-generator.ts +++ b/src/model/generators/platform-generator.ts @@ -41,14 +41,14 @@ export class PlatformGenerator { "https://github.com/PiotrMachowski/lovelace-xiaomi-vacuum-map-card/tree/master/docs/templates/{0}.md"; private static TEMPLATES = new Map([ - [PlatformGenerator.XIAOMI_MIIO_PLATFORM, xiaomiMiioTemplate], + [PlatformGenerator.XIAOMI_MIIO_PLATFORM, xiaomiMiioTemplate as PlatformTemplate], [PlatformGenerator.KRZYSZTOFHAJDAMOWICZ_MIIO2_PLATFORM, krzysztofHajdamowiczMiio2Template], [PlatformGenerator.MAROTOWEB_VIOMISE_PLATFORM, marotowebViomiseTemplate], [PlatformGenerator.TYKAROL_VIOMI_VACUUM_V8_PLATFORM, tykarolViomiVacuumV8Template], [PlatformGenerator.HYPFER_VALETUDO_PLATFORM, hypferValetudoTemplate], - [PlatformGenerator.RAND256_VALETUDO_RE_PLATFORM, rand256ValetudoReTemplate], + [PlatformGenerator.RAND256_VALETUDO_RE_PLATFORM, rand256ValetudoReTemplate as PlatformTemplate], [PlatformGenerator.TASSHACK_DREAME_VACUUM_PLATFORM, tasshackDreameVacuumTemplate], - [PlatformGenerator.HUMBERTOGONTIJO_ROBOROCK_PLATFORM, roborockTemplate], + [PlatformGenerator.HUMBERTOGONTIJO_ROBOROCK_PLATFORM, roborockTemplate as PlatformTemplate], [PlatformGenerator.SEND_COMMAND_PLATFORM, sendCommandTemplate], [PlatformGenerator.ALONE_XIAOMI_MIOT_PLATFORM, alOneHassXiaomiMiotTemplate], [PlatformGenerator.NEATO_PLATFORM, neatoTemplate], From a7a1ef4c1790024bcacbf66743db49047b4d565e Mon Sep 17 00:00:00 2001 From: Piotr Machowski <6118709+PiotrMachowski@users.noreply.github.com> Date: Tue, 7 Mar 2023 17:48:37 +0100 Subject: [PATCH 020/100] Set version to v2.1.3-beta --- .vscode/tasks.json | 2 +- package.json | 2 +- src/const.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index eb5e155a..42556a6d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,5 +1,5 @@ { - "version": "v2.1.2", + "version": "v2.1.3-beta", "tasks": [ { "type": "npm", diff --git a/package.json b/package.json index 77743c56..81c44fc4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiaomi-vacuum-map-card", - "version": "v2.1.2", + "version": "v2.1.3-beta", "description": "Xiaomi Vacuum Map Card", "keywords": [ "home-assistant", diff --git a/src/const.ts b/src/const.ts index a9511772..af09f068 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,4 +1,4 @@ -export const CARD_VERSION = "v2.1.2"; +export const CARD_VERSION = "v2.1.3-beta"; export const CARD_CUSTOM_ELEMENT_NAME = "xiaomi-vacuum-map-card"; export const EDITOR_CUSTOM_ELEMENT_NAME = "xiaomi-vacuum-map-card-editor"; export const ACTION_HANDLER_CUSTOM_ELEMENT_NAME = "action-handler-xiaomi-vacuum-map-card"; From ac0999bb3928068e68f8a55dd3a61d96c08fa464 Mon Sep 17 00:00:00 2001 From: Piotr Machowski <6118709+PiotrMachowski@users.noreply.github.com> Date: Tue, 7 Mar 2023 17:51:19 +0100 Subject: [PATCH 021/100] Update release action --- .github/workflows/release.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c7226883..1fe357ba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,15 +10,17 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 + + - uses: actions/setup-node@v3 + with: + node-version: 14 - # Build - name: Build the file run: | cd /home/runner/work/lovelace-xiaomi-vacuum-map-card/lovelace-xiaomi-vacuum-map-card npm install npm run build - # Upload build file to the release as an asset. - name: Upload zip to release uses: svenstaro/upload-release-action@v1-release From dfe231d9dc257da7fbd936d7e342d0efde3f7218 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 7 Mar 2023 21:25:25 +0100 Subject: [PATCH 022/100] Fix generating tiles --- .../DeebotUniverse_Deebot-4-Home-Assistant.json | 1 + .../humbertogontijo_homeassistant-roborock.json | 1 + src/model/generators/platform_templates/hypfer_valetudo.json | 1 + 3 files changed, 3 insertions(+) diff --git a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json index c4b2fb25..55488de6 100644 --- a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json +++ b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json @@ -58,6 +58,7 @@ } } }, + "sensors_from": "2020.1.0", "tiles": { "from_sensors": [ { diff --git a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json index e41357d5..c4d044d3 100644 --- a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json +++ b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json @@ -98,6 +98,7 @@ } } }, + "sensors_from": "2020.1.0", "tiles": { "from_sensors": [ { diff --git a/src/model/generators/platform_templates/hypfer_valetudo.json b/src/model/generators/platform_templates/hypfer_valetudo.json index 6a8ca8bf..5826b548 100644 --- a/src/model/generators/platform_templates/hypfer_valetudo.json +++ b/src/model/generators/platform_templates/hypfer_valetudo.json @@ -78,6 +78,7 @@ } } }, + "sensors_from": "2020.1.0", "tiles": { "from_sensors": [ { From c7caae358fe28c7d73548a02ec478e34e7ffed37 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Fri, 10 Mar 2023 22:30:44 +0100 Subject: [PATCH 023/100] Prepare code for handling HA translations --- package.json | 2 +- src/config-validators.ts | 5 +- src/editor.ts | 7 +- src/localize/hass/blank_before_percent.ts | 20 ++ src/localize/hass/capitalize_first_letter.ts | 4 + src/localize/hass/check_valid_date.ts | 9 + .../hass/compute_attribute_display.ts | 54 +++++ src/localize/hass/compute_domain.ts | 4 + src/localize/hass/compute_state_display.ts | 198 ++++++++++++++++++ src/localize/hass/duration.ts | 35 ++++ src/localize/hass/entity.ts | 18 ++ src/localize/hass/entity_attributes.ts | 108 ++++++++++ src/localize/hass/format_date.ts | 103 +++++++++ src/localize/hass/format_date_time.ts | 95 +++++++++ src/localize/hass/format_number.ts | 162 ++++++++++++++ src/localize/hass/format_time.ts | 68 ++++++ src/localize/hass/frontend.ts | 30 +++ src/localize/hass/is_date.ts | 13 ++ src/localize/hass/is_timestamp.ts | 14 ++ src/localize/hass/localize.ts | 62 ++++++ src/localize/hass/round.ts | 4 + src/localize/hass/translation.ts | 92 ++++++++ src/localize/hass/use_am_pm.ts | 18 ++ src/localize/localize.ts | 4 +- src/model/generators/icon-list-generator.ts | 4 +- src/model/generators/platform-generator.ts | 4 +- src/model/generators/tiles-generator.ts | 6 +- src/model/map_mode/map-mode.ts | 4 +- .../map_objects/predefined-multi-rectangle.ts | 5 +- src/model/map_objects/predefined-point.ts | 5 +- src/types/fixes.ts | 42 +++- src/types/types.ts | 13 ++ src/utils.ts | 15 +- 33 files changed, 1196 insertions(+), 31 deletions(-) create mode 100644 src/localize/hass/blank_before_percent.ts create mode 100644 src/localize/hass/capitalize_first_letter.ts create mode 100644 src/localize/hass/check_valid_date.ts create mode 100644 src/localize/hass/compute_attribute_display.ts create mode 100644 src/localize/hass/compute_domain.ts create mode 100644 src/localize/hass/compute_state_display.ts create mode 100644 src/localize/hass/duration.ts create mode 100644 src/localize/hass/entity.ts create mode 100644 src/localize/hass/entity_attributes.ts create mode 100644 src/localize/hass/format_date.ts create mode 100644 src/localize/hass/format_date_time.ts create mode 100644 src/localize/hass/format_number.ts create mode 100644 src/localize/hass/format_time.ts create mode 100644 src/localize/hass/frontend.ts create mode 100644 src/localize/hass/is_date.ts create mode 100644 src/localize/hass/is_timestamp.ts create mode 100644 src/localize/hass/localize.ts create mode 100644 src/localize/hass/round.ts create mode 100644 src/localize/hass/translation.ts create mode 100644 src/localize/hass/use_am_pm.ts diff --git a/package.json b/package.json index 81c44fc4..feb2a6e3 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "license": "MIT", "dependencies": { "custom-card-helpers": "^1.9.0", - "home-assistant-js-websocket": "^5.11.1", + "home-assistant-js-websocket": "^8.0.1", "lit": "^2.0.0", "pointer-tracker": "^2.4.0", "transformation-matrix": "^2.8.0", diff --git a/src/config-validators.ts b/src/config-validators.ts index 25c9c1d9..ce85e120 100644 --- a/src/config-validators.ts +++ b/src/config-validators.ts @@ -1,5 +1,3 @@ -import { HomeAssistant } from "custom-card-helpers"; - import { CalibrationPoint, CalibrationSourceConfig, @@ -23,6 +21,7 @@ import { localize } from "./localize/localize"; import { MapMode } from "./model/map_mode/map-mode"; import { SelectionType } from "./model/map_mode/selection-type"; import { PlatformGenerator } from "./model/generators/platform-generator"; +import { HomeAssistantFixed } from "./types/fixes"; function validateMapSource(mapSource: MapSourceConfig): TranslatableString[] { if (!mapSource.camera && !mapSource.image) { @@ -276,7 +275,7 @@ export function isOldConfig(config: XiaomiVacuumMapCardConfig): boolean { return config.map_image || config.map_camera; } -export function areAllEntitiesDefined(usedEntities: string[], hass: HomeAssistant): string[] { +export function areAllEntitiesDefined(usedEntities: string[], hass: HomeAssistantFixed): string[] { const availableEntities = Object.keys(hass.states); return usedEntities.filter(e => !availableEntities.includes(e)); } diff --git a/src/editor.ts b/src/editor.ts index 01eeb95c..1d0ee62b 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { fireEvent, HomeAssistant, LovelaceCardEditor } from "custom-card-helpers"; +import { fireEvent, LovelaceCardEditor } from "custom-card-helpers"; import { RoomConfigEventData, TranslatableString, XiaomiVacuumMapCardConfig } from "./types/types"; import { localizeWithHass } from "./localize/localize"; @@ -18,10 +18,11 @@ import { } from "./const"; import { copyMessage } from "./utils"; import { ToastRenderer } from "./renderers/toast-renderer"; +import { HomeAssistantFixed } from "./types/fixes"; @customElement(EDITOR_CUSTOM_ELEMENT_NAME) -export class XiaomiVacuumMapCardEditor extends LitElement implements LovelaceCardEditor { - @property({ attribute: false }) public hass?: HomeAssistant; +export class XiaomiVacuumMapCardEditor extends LitElement implements Omit { + @property({ attribute: false }) public hass?: HomeAssistantFixed; @state() private _config?: XiaomiVacuumMapCardConfig; @state() private _helpers?: any; @state() private _lastSelection?: any; diff --git a/src/localize/hass/blank_before_percent.ts b/src/localize/hass/blank_before_percent.ts new file mode 100644 index 00000000..15881bea --- /dev/null +++ b/src/localize/hass/blank_before_percent.ts @@ -0,0 +1,20 @@ +// home-assistant/frontend/src/common/translations/blank_before_percent.ts + +import { FrontendLocaleData } from "./translation"; + +// Logic based on https://en.wikipedia.org/wiki/Percent_sign#Form_and_spacing +export const blankBeforePercent = ( + localeOptions: FrontendLocaleData +): string => { + switch (localeOptions.language) { + case "cz": + case "de": + case "fi": + case "fr": + case "sk": + case "sv": + return " "; + default: + return ""; + } +}; diff --git a/src/localize/hass/capitalize_first_letter.ts b/src/localize/hass/capitalize_first_letter.ts new file mode 100644 index 00000000..068f45af --- /dev/null +++ b/src/localize/hass/capitalize_first_letter.ts @@ -0,0 +1,4 @@ +// home-assistant/frontend/src/common/string/capitalize-first-letter.ts + +export const capitalizeFirstLetter = (str: string) => + str.charAt(0).toUpperCase() + str.slice(1); diff --git a/src/localize/hass/check_valid_date.ts b/src/localize/hass/check_valid_date.ts new file mode 100644 index 00000000..7e959b76 --- /dev/null +++ b/src/localize/hass/check_valid_date.ts @@ -0,0 +1,9 @@ +// home-assistant/frontend/src/common/datetime/check_valid_date.ts + +export default function checkValidDate(date?: Date): boolean { + if (!date) { + return false; + } + + return date instanceof Date && !isNaN(date.valueOf()); +} diff --git a/src/localize/hass/compute_attribute_display.ts b/src/localize/hass/compute_attribute_display.ts new file mode 100644 index 00000000..8614b7b7 --- /dev/null +++ b/src/localize/hass/compute_attribute_display.ts @@ -0,0 +1,54 @@ +// home-assistant/frontend/src/common/entity/compute_attribute_display.ts + +import { HassEntity } from "home-assistant-js-websocket"; +import { LocalizeFunc } from "./localize"; +import { computeDomain } from "./compute_domain"; +import { EntityRegistryDisplayEntry, HomeAssistantFixed } from "../../types/fixes"; +import { formatAttributeName } from "./entity_attributes"; + +export const computeAttributeValueDisplay = ( + localize: LocalizeFunc, + stateObj: HassEntity, + entities: HomeAssistantFixed["entities"], + attribute: string, + value?: any +): string => { + const entityId = stateObj.entity_id; + const attributeValue = + value !== undefined ? value : stateObj.attributes[attribute]; + const domain = computeDomain(entityId); + const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined; + const translationKey = entity?.translation_key; + + return ( + (translationKey && + localize( + `component.${entity.platform}.entity.${domain}.${translationKey}.state_attributes.${attribute}.state.${attributeValue}` + )) || + localize( + `component.${domain}.state_attributes._.${attribute}.state.${attributeValue}` + ) || + attributeValue + ); +}; + +export const computeAttributeNameDisplay = ( + localize: LocalizeFunc, + stateObj: HassEntity, + entities: HomeAssistantFixed["entities"], + attribute: string +): string => { + const entityId = stateObj.entity_id; + const domain = computeDomain(entityId); + const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined; + const translationKey = entity?.translation_key; + + return ( + (translationKey && + localize( + `component.${entity.platform}.entity.${domain}.${translationKey}.state_attributes.${attribute}.name` + )) || + localize(`component.${domain}.state_attributes._.${attribute}.name`) || + formatAttributeName(attribute) + ); +}; diff --git a/src/localize/hass/compute_domain.ts b/src/localize/hass/compute_domain.ts new file mode 100644 index 00000000..93970673 --- /dev/null +++ b/src/localize/hass/compute_domain.ts @@ -0,0 +1,4 @@ +// home-assistant/frontend/src/common/entity/compute_domain.ts + +export const computeDomain = (entityId: string): string => + entityId.substr(0, entityId.indexOf(".")); diff --git a/src/localize/hass/compute_state_display.ts b/src/localize/hass/compute_state_display.ts new file mode 100644 index 00000000..2443b0fa --- /dev/null +++ b/src/localize/hass/compute_state_display.ts @@ -0,0 +1,198 @@ +// home-assistant/frontend/src/common/entity/compute_state_display.ts + +import { HassEntity } from "home-assistant-js-websocket"; +import { UNAVAILABLE, UNKNOWN } from "./entity"; +import { formatDuration, UNIT_TO_SECOND_CONVERT } from "./duration"; +import { formatDate } from "./format_date"; +import { formatDateTime } from "./format_date_time"; +import { formatTime } from "./format_time"; +import { + formatNumber, + getNumberFormatOptions, + isNumericFromAttributes, +} from "./format_number"; +import { blankBeforePercent } from "./blank_before_percent"; +import { LocalizeFunc } from "./localize"; //type +import { computeDomain } from "./compute_domain"; +import { EntityRegistryDisplayEntry, FrontendLocaleDataFixed, HomeAssistantFixed } from "../../types/fixes"; + +export const computeStateDisplay = ( + localize: LocalizeFunc, + stateObj: HassEntity, + locale: FrontendLocaleDataFixed, + entities: HomeAssistantFixed["entities"], + state?: string +): string => + computeStateDisplayFromEntityAttributes( + localize, + locale, + entities, + stateObj.entity_id, + stateObj.attributes, + state !== undefined ? state : stateObj.state + ); + +export const computeStateDisplayFromEntityAttributes = ( + localize: LocalizeFunc, + locale: FrontendLocaleDataFixed, + entities: HomeAssistantFixed["entities"], + entityId: string, + attributes: any, + state: string +): string => { + if (state === UNKNOWN || state === UNAVAILABLE) { + return localize(`state.default.${state}`); + } + + const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined; + + // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber` + if (isNumericFromAttributes(attributes)) { + // state is duration + if ( + attributes.device_class === "duration" && + attributes.unit_of_measurement && + UNIT_TO_SECOND_CONVERT[attributes.unit_of_measurement] + ) { + try { + return formatDuration(state, attributes.unit_of_measurement); + } catch (_err) { + // fallback to default + } + } + if (attributes.device_class === "monetary") { + try { + return formatNumber(state, locale, { + style: "currency", + currency: attributes.unit_of_measurement, + minimumFractionDigits: 2, + // Override monetary options with number format + ...getNumberFormatOptions( + { state, attributes } as HassEntity, + entity + ), + }); + } catch (_err) { + // fallback to default + } + } + const unit = !attributes.unit_of_measurement + ? "" + : attributes.unit_of_measurement === "%" + ? blankBeforePercent(locale) + "%" + : ` ${attributes.unit_of_measurement}`; + return `${formatNumber( + state, + locale, + getNumberFormatOptions({ state, attributes } as HassEntity, entity) + )}${unit}`; + } + + const domain = computeDomain(entityId); + + if (domain === "input_datetime") { + if (state !== undefined) { + // If trying to display an explicit state, need to parse the explicit state to `Date` then format. + // Attributes aren't available, we have to use `state`. + try { + const components = state.split(" "); + if (components.length === 2) { + // Date and time. + return formatDateTime(new Date(components.join("T")), locale); + } + if (components.length === 1) { + if (state.includes("-")) { + // Date only. + return formatDate(new Date(`${state}T00:00`), locale); + } + if (state.includes(":")) { + // Time only. + const now = new Date(); + return formatTime( + new Date(`${now.toISOString().split("T")[0]}T${state}`), + locale + ); + } + } + return state; + } catch (_e) { + // Formatting methods may throw error if date parsing doesn't go well, + // just return the state string in that case. + return state; + } + } else { + // If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format. + let date: Date; + if (attributes.has_date && attributes.has_time) { + date = new Date( + attributes.year, + attributes.month - 1, + attributes.day, + attributes.hour, + attributes.minute + ); + return formatDateTime(date, locale); + } + if (attributes.has_date) { + date = new Date(attributes.year, attributes.month - 1, attributes.day); + return formatDate(date, locale); + } + if (attributes.has_time) { + date = new Date(); + date.setHours(attributes.hour, attributes.minute); + return formatTime(date, locale); + } + return state; + } + } + + if (domain === "humidifier") { + if (state === "on" && attributes.humidity) { + return `${attributes.humidity} %`; + } + } + + // `counter` `number` and `input_number` domains do not have a unit of measurement but should still use `formatNumber` + if ( + domain === "counter" || + domain === "number" || + domain === "input_number" + ) { + // Format as an integer if the value and step are integers + return formatNumber( + state, + locale, + getNumberFormatOptions({ state, attributes } as HassEntity, entity) + ); + } + + // state of button is a timestamp + if ( + domain === "button" || + domain === "input_button" || + domain === "scene" || + (domain === "sensor" && attributes.device_class === "timestamp") + ) { + try { + return formatDateTime(new Date(state), locale); + } catch (_err) { + return state; + } + } + + return ( + (entity?.translation_key && + localize( + `component.${entity.platform}.entity.${domain}.${entity.translation_key}.state.${state}` + )) || + // Return device class translation + (attributes.device_class && + localize( + `component.${domain}.state.${attributes.device_class}.${state}` + )) || + // Return default translation + localize(`component.${domain}.state._.${state}`) || + // We don't know! Return the raw state. + state + ); +}; diff --git a/src/localize/hass/duration.ts b/src/localize/hass/duration.ts new file mode 100644 index 00000000..002fd57a --- /dev/null +++ b/src/localize/hass/duration.ts @@ -0,0 +1,35 @@ +// home-assistant/frontend/src/common/datetime/duration.ts + +const DAY_IN_SECONDS = 86400; +const HOUR_IN_SECONDS = 3600; +const MINUTE_IN_SECONDS = 60; + +export const UNIT_TO_SECOND_CONVERT = { + s: 1, + min: MINUTE_IN_SECONDS, + h: HOUR_IN_SECONDS, + d: DAY_IN_SECONDS, +}; + +const leftPad = (num: number) => (num < 10 ? `0${num}` : num); + +export function secondsToDuration(d: number) { + const h = Math.floor(d / 3600); + const m = Math.floor((d % 3600) / 60); + const s = Math.floor((d % 3600) % 60); + + if (h > 0) { + return `${h}:${leftPad(m)}:${leftPad(s)}`; + } + if (m > 0) { + return `${m}:${leftPad(s)}`; + } + if (s > 0) { + return "" + s; + } + return null; +} + +export const formatDuration = (duration: string, units: string): string => + secondsToDuration(parseFloat(duration) * UNIT_TO_SECOND_CONVERT[units]) || + "0"; diff --git a/src/localize/hass/entity.ts b/src/localize/hass/entity.ts new file mode 100644 index 00000000..7c387e5e --- /dev/null +++ b/src/localize/hass/entity.ts @@ -0,0 +1,18 @@ +// home-assistant/frontend/src/data/entity.ts + +// Creates a type predicate function for determining if an array literal includes a given value +export const arrayLiteralIncludes = + (array: T) => + (searchElement: unknown, fromIndex?: number): searchElement is T[number] => + array.includes(searchElement as T[number], fromIndex); + +export const UNAVAILABLE = "unavailable"; +export const UNKNOWN = "unknown"; +export const ON = "on"; +export const OFF = "off"; + +export const UNAVAILABLE_STATES = [UNAVAILABLE, UNKNOWN] as const; +export const OFF_STATES = [UNAVAILABLE, UNKNOWN, OFF] as const; + +export const isUnavailableState = arrayLiteralIncludes(UNAVAILABLE_STATES); +export const isOffState = arrayLiteralIncludes(OFF_STATES); diff --git a/src/localize/hass/entity_attributes.ts b/src/localize/hass/entity_attributes.ts new file mode 100644 index 00000000..ac8c4daa --- /dev/null +++ b/src/localize/hass/entity_attributes.ts @@ -0,0 +1,108 @@ +// home-assistant/frontend/src/data/entity_attributes.ts + +import { html, TemplateResult } from "lit"; +import checkValidDate from "./check_valid_date"; +import { formatDate } from "./format_date"; +import { formatDateTimeWithSeconds } from "./format_date_time"; +import { formatNumber } from "./format_number"; +import { capitalizeFirstLetter } from "./capitalize_first_letter"; +import { isDate } from "./is_date"; +import { isTimestamp } from "./is_timestamp"; +import { HomeAssistantFixed } from "../../types/fixes"; +import { computeAttributeValueDisplay } from "./compute_attribute_display"; +import { HassEntity } from "home-assistant-js-websocket/dist/types"; + + +export const STATE_ATTRIBUTES = [ + "assumed_state", + "attribution", + "custom_ui_more_info", + "custom_ui_state_card", + "device_class", + "editable", + "emulated_hue_name", + "emulated_hue", + "entity_picture", + "friendly_name", + "haaska_hidden", + "haaska_name", + "icon", + "initial_state", + "last_reset", + "restored", + "state_class", + "supported_features", + "unit_of_measurement", +]; + +// Convert from internal snake_case format to user-friendly format +export function formatAttributeName(value: string): string { + value = value + .replace(/_/g, " ") + .replace(/\bid\b/g, "ID") + .replace(/\bip\b/g, "IP") + .replace(/\bmac\b/g, "MAC") + .replace(/\bgps\b/g, "GPS"); + return capitalizeFirstLetter(value); +} + +export function formatAttributeValue( + hass: HomeAssistantFixed, + stateObj: HassEntity, + attribute: string +): string | TemplateResult { + const value = stateObj.attributes[attribute]; + + if (value === null) { + return "—"; + } + + // YAML handling + if ( + (Array.isArray(value) && value.some((val) => val instanceof Object)) || + (!Array.isArray(value) && value instanceof Object) + ) { + return html`
    ${value}
    `; + } + + if (typeof value === "number") { + return formatNumber(value, hass.locale); + } + + if (typeof value === "string") { + // URL handling + if (value.startsWith("http")) { + try { + // If invalid URL, exception will be raised + const url = new URL(value); + if (url.protocol === "http:" || url.protocol === "https:") + return html`${value}`; + } catch (_) { + // Nothing to do here + } + } + + // Date handling + if (isDate(value, true)) { + // Timestamp handling + if (isTimestamp(value)) { + const date = new Date(value); + if (checkValidDate(date)) { + return formatDateTimeWithSeconds(date, hass.locale); + } + } + + // Value was not a timestamp, so only do date formatting + const date = new Date(value); + if (checkValidDate(date)) { + return formatDate(date, hass.locale); + } + } + } + + return Array.isArray(value) + ? value.join(", ") + : computeAttributeValueDisplay(hass.localize, stateObj, hass.entities, attribute); +} diff --git a/src/localize/hass/format_date.ts b/src/localize/hass/format_date.ts new file mode 100644 index 00000000..51128711 --- /dev/null +++ b/src/localize/hass/format_date.ts @@ -0,0 +1,103 @@ +// home-assistant/frontend/src/common/datetime/format_date.ts + +import { FrontendLocaleDataFixed } from "../../types/fixes"; + +// Tuesday, August 10 +export const formatDateWeekdayDay = ( + dateObj: Date, + locale: FrontendLocaleDataFixed +): string => formatDateWeekdayDayMem(locale).format(dateObj); + +const formatDateWeekdayDayMem = ( + (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat(locale.language, { + weekday: "long", + month: "long", + day: "numeric", + }) +); + +// August 10, 2021 +export const formatDate = (dateObj: Date, locale: FrontendLocaleDataFixed): string => + formatDateMem(locale).format(dateObj); + +const formatDateMem = ( + (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat(locale.language, { + year: "numeric", + month: "long", + day: "numeric", + }) +); + +// 10/08/2021 +export const formatDateNumeric = (dateObj: Date, locale: FrontendLocaleDataFixed): string => + formatDateNumericMem(locale).format(dateObj); + +const formatDateNumericMem = ( + (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat(locale.language, { + year: "numeric", + month: "numeric", + day: "numeric", + }) +); + +// Aug 10 +export const formatDateShort = (dateObj: Date, locale: FrontendLocaleDataFixed): string => + formatDateShortMem(locale).format(dateObj); + +const formatDateShortMem = ( + (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat(locale.language, { + day: "numeric", + month: "short", + }) +); + +// August 2021 +export const formatDateMonthYear = ( + dateObj: Date, + locale: FrontendLocaleDataFixed +): string => formatDateMonthYearMem(locale).format(dateObj); + +const formatDateMonthYearMem = ( + (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat(locale.language, { + month: "long", + year: "numeric", + }) +); + +// August +export const formatDateMonth = (dateObj: Date, locale: FrontendLocaleDataFixed): string => + formatDateMonthMem(locale).format(dateObj); + +const formatDateMonthMem = ( + (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat(locale.language, { + month: "long", + }) +); + +// 2021 +export const formatDateYear = (dateObj: Date, locale: FrontendLocaleDataFixed): string => + formatDateYearMem(locale).format(dateObj); + +const formatDateYearMem = ( + (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat(locale.language, { + year: "numeric", + }) +); + +// Monday +export const formatDateWeekday = (dateObj: Date, locale: FrontendLocaleDataFixed): string => + formatDateWeekdayMem(locale).format(dateObj); + +const formatDateWeekdayMem = ( + (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat(locale.language, { + weekday: "long", + }) +); diff --git a/src/localize/hass/format_date_time.ts b/src/localize/hass/format_date_time.ts new file mode 100644 index 00000000..bd941b72 --- /dev/null +++ b/src/localize/hass/format_date_time.ts @@ -0,0 +1,95 @@ +// home-assistant/frontend/src/common/datetime/format_date_time.ts + +import { useAmPm } from "./use_am_pm"; +import { FrontendLocaleDataFixed } from "../../types/fixes"; + + +// August 9, 2021, 8:23 AM +export const formatDateTime = (dateObj: Date, locale: FrontendLocaleDataFixed): string => + formatDateTimeMem(locale).format(dateObj); + +const formatDateTimeMem = ( + (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat( + locale.language === "en" && !useAmPm(locale) + ? "en-u-hc-h23" + : locale.language, + { + year: "numeric", + month: "long", + day: "numeric", + hour: useAmPm(locale) ? "numeric" : "2-digit", + minute: "2-digit", + hour12: useAmPm(locale), + } + ) +); + +// Aug 9, 8:23 AM +export const formatShortDateTime = ( + dateObj: Date, + locale: FrontendLocaleDataFixed +): string => formatShortDateTimeMem(locale).format(dateObj); + +const formatShortDateTimeMem = ( + (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat( + locale.language === "en" && !useAmPm(locale) + ? "en-u-hc-h23" + : locale.language, + { + month: "short", + day: "numeric", + hour: useAmPm(locale) ? "numeric" : "2-digit", + minute: "2-digit", + hour12: useAmPm(locale), + } + ) +); + +// August 9, 2021, 8:23:15 AM +export const formatDateTimeWithSeconds = ( + dateObj: Date, + locale: FrontendLocaleDataFixed +): string => formatDateTimeWithSecondsMem(locale).format(dateObj); + +const formatDateTimeWithSecondsMem = ( + (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat( + locale.language === "en" && !useAmPm(locale) + ? "en-u-hc-h23" + : locale.language, + { + year: "numeric", + month: "long", + day: "numeric", + hour: useAmPm(locale) ? "numeric" : "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: useAmPm(locale), + } + ) +); + +// 9/8/2021, 8:23 AM +export const formatDateTimeNumeric = ( + dateObj: Date, + locale: FrontendLocaleDataFixed +): string => formatDateTimeNumericMem(locale).format(dateObj); + +const formatDateTimeNumericMem = ( + (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat( + locale.language === "en" && !useAmPm(locale) + ? "en-u-hc-h23" + : locale.language, + { + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "2-digit", + hour12: useAmPm(locale), + } + ) +); diff --git a/src/localize/hass/format_number.ts b/src/localize/hass/format_number.ts new file mode 100644 index 00000000..a833d932 --- /dev/null +++ b/src/localize/hass/format_number.ts @@ -0,0 +1,162 @@ +// home-assistant/frontend/src/common/number/format_number.ts + +import { + HassEntity, + HassEntityAttributeBase, +} from "home-assistant-js-websocket"; +import { FrontendLocaleData, NumberFormat } from "./translation"; +import { round } from "./round"; +import { EntityRegistryDisplayEntry } from "../../types/fixes"; + +/** + * Returns true if the entity is considered numeric based on the attributes it has + * @param stateObj The entity state object + */ +export const isNumericState = (stateObj: HassEntity): boolean => + isNumericFromAttributes(stateObj.attributes); + +export const isNumericFromAttributes = ( + attributes: HassEntityAttributeBase +): boolean => !!attributes.unit_of_measurement || !!attributes.state_class; + +export const numberFormatToLocale = ( + localeOptions: FrontendLocaleData +): string | string[] | undefined => { + switch (localeOptions.number_format) { + case NumberFormat.comma_decimal: + return ["en-US", "en"]; // Use United States with fallback to English formatting 1,234,567.89 + case NumberFormat.decimal_comma: + return ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89 + case NumberFormat.space_comma: + return ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89 + case NumberFormat.system: + return undefined; + default: + return localeOptions.language; + } +}; + +/** + * Formats a number based on the user's preference with thousands separator(s) and decimal character for better legibility. + * + * @param num The number to format + * @param localeOptions The user-selected language and formatting, from `hass.locale` + * @param options Intl.NumberFormatOptions to use + */ +export const formatNumber = ( + num: string | number, + localeOptions?: FrontendLocaleData, + options?: Intl.NumberFormatOptions +): string => { + const locale = localeOptions + ? numberFormatToLocale(localeOptions) + : undefined; + + // Polyfill for Number.isNaN, which is more reliable than the global isNaN() + Number.isNaN = + Number.isNaN || + function isNaN(input) { + return typeof input === "number" && isNaN(input); + }; + + if ( + localeOptions?.number_format !== NumberFormat.none && + !Number.isNaN(Number(num)) && + Intl + ) { + try { + return new Intl.NumberFormat( + locale, + getDefaultFormatOptions(num, options) + ).format(Number(num)); + } catch (err: any) { + // Don't fail when using "TEST" language + // eslint-disable-next-line no-console + console.error(err); + return new Intl.NumberFormat( + undefined, + getDefaultFormatOptions(num, options) + ).format(Number(num)); + } + } + + if ( + !Number.isNaN(Number(num)) && + num !== "" && + localeOptions?.number_format === NumberFormat.none && + Intl + ) { + // If NumberFormat is none, use en-US format without grouping. + return new Intl.NumberFormat( + "en-US", + getDefaultFormatOptions(num, { + ...options, + useGrouping: false, + }) + ).format(Number(num)); + } + + if (typeof num === "string") { + return num; + } + return `${round(num, options?.maximumFractionDigits).toString()}${ + options?.style === "currency" ? ` ${options.currency}` : "" + }`; +}; + +/** + * Checks if the current entity state should be formatted as an integer based on the `state` and `step` attribute and returns the appropriate `Intl.NumberFormatOptions` object with `maximumFractionDigits` set + * @param entityState The state object of the entity + * @returns An `Intl.NumberFormatOptions` object with `maximumFractionDigits` set to 0, or `undefined` + */ +export const getNumberFormatOptions = ( + entityState: HassEntity, + entity?: EntityRegistryDisplayEntry +): Intl.NumberFormatOptions | undefined => { + const precision = entity?.display_precision; + if (precision != null) { + return { + maximumFractionDigits: precision, + minimumFractionDigits: precision, + }; + } + if ( + Number.isInteger(Number(entityState.attributes?.step)) && + Number.isInteger(Number(entityState.state)) + ) { + return { maximumFractionDigits: 0 }; + } + return undefined; +}; + +/** + * Generates default options for Intl.NumberFormat + * @param num The number to be formatted + * @param options The Intl.NumberFormatOptions that should be included in the returned options + */ +export const getDefaultFormatOptions = ( + num: string | number, + options?: Intl.NumberFormatOptions +): Intl.NumberFormatOptions => { + const defaultOptions: Intl.NumberFormatOptions = { + maximumFractionDigits: 2, + ...options, + }; + + if (typeof num !== "string") { + return defaultOptions; + } + + // Keep decimal trailing zeros if they are present in a string numeric value + if ( + !options || + (options.minimumFractionDigits === undefined && + options.maximumFractionDigits === undefined) + ) { + const digits = num.indexOf(".") > -1 ? num.split(".")[1].length : 0; + defaultOptions.minimumFractionDigits = digits; + defaultOptions.maximumFractionDigits = digits; + } + + return defaultOptions; +}; diff --git a/src/localize/hass/format_time.ts b/src/localize/hass/format_time.ts new file mode 100644 index 00000000..dc9b7b8e --- /dev/null +++ b/src/localize/hass/format_time.ts @@ -0,0 +1,68 @@ +// home-assistant/frontend/src/common/datetime/format_time.ts + +import { useAmPm } from "./use_am_pm"; +import { FrontendLocaleDataFixed } from "../../types/fixes"; + +// 9:15 PM || 21:15 +export const formatTime = (dateObj: Date, locale: FrontendLocaleDataFixed): string => + formatTimeMem(locale).format(dateObj); + +const formatTimeMem = (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat( + locale.language === "en" && !useAmPm(locale) + ? "en-u-hc-h23" + : locale.language, + { + hour: "numeric", + minute: "2-digit", + hour12: useAmPm(locale), + }, + ); + +// 9:15:24 PM || 21:15:24 +export const formatTimeWithSeconds = ( + dateObj: Date, + locale: FrontendLocaleDataFixed, +): string => formatTimeWithSecondsMem(locale).format(dateObj); + +const formatTimeWithSecondsMem = (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat( + locale.language === "en" && !useAmPm(locale) + ? "en-u-hc-h23" + : locale.language, + { + hour: useAmPm(locale) ? "numeric" : "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: useAmPm(locale), + }, + ); + +// Tuesday 7:00 PM || Tuesday 19:00 +export const formatTimeWeekday = (dateObj: Date, locale: FrontendLocaleDataFixed): string => + formatTimeWeekdayMem(locale).format(dateObj); + +const formatTimeWeekdayMem = (locale: FrontendLocaleDataFixed) => + new Intl.DateTimeFormat( + locale.language === "en" && !useAmPm(locale) + ? "en-u-hc-h23" + : locale.language, + { + weekday: "long", + hour: useAmPm(locale) ? "numeric" : "2-digit", + minute: "2-digit", + hour12: useAmPm(locale), + }, + ); + +// 21:15 +export const formatTime24h = (dateObj: Date): string => + formatTime24hMem().format(dateObj); + +const formatTime24hMem = () => + // en-GB to fix Chrome 24:59 to 0:59 https://stackoverflow.com/a/60898146 + new Intl.DateTimeFormat("en-GB", { + hour: "numeric", + minute: "2-digit", + hour12: false, + }); diff --git a/src/localize/hass/frontend.ts b/src/localize/hass/frontend.ts new file mode 100644 index 00000000..670d62e3 --- /dev/null +++ b/src/localize/hass/frontend.ts @@ -0,0 +1,30 @@ +// home-assistant/frontend/src/data/frontend.ts + +import { Connection } from "home-assistant-js-websocket"; + +export interface CoreFrontendUserData { + showAdvanced?: boolean; +} + +declare global { + interface FrontendUserData { + core: CoreFrontendUserData; + } +} + +export type ValidUserDataKey = keyof FrontendUserData; + +export const fetchFrontendUserData = async < + UserDataKey extends ValidUserDataKey +>( + conn: Connection, + key: UserDataKey +): Promise => { + const result = await conn.sendMessagePromise<{ + value: FrontendUserData[UserDataKey] | null; + }>({ + type: "frontend/get_user_data", + key, + }); + return result.value; +}; \ No newline at end of file diff --git a/src/localize/hass/is_date.ts b/src/localize/hass/is_date.ts new file mode 100644 index 00000000..17bead57 --- /dev/null +++ b/src/localize/hass/is_date.ts @@ -0,0 +1,13 @@ +// home-assistant/frontend/src/common/string/is_date.ts + +// https://regex101.com/r/kc5C14/2 +const regExpString = "^\\d{4}-(0[1-9]|1[0-2])-([12]\\d|0[1-9]|3[01])"; + +const regExp = new RegExp(regExpString + "$"); +// 2nd expression without the "end of string" enforced, so it can be used +// to just verify the start of a string and then based on that result e.g. +// check for a full timestamp string efficiently. +const regExpNoStringEnd = new RegExp(regExpString); + +export const isDate = (input: string, allowCharsAfterDate = false): boolean => + allowCharsAfterDate ? regExpNoStringEnd.test(input) : regExp.test(input); diff --git a/src/localize/hass/is_timestamp.ts b/src/localize/hass/is_timestamp.ts new file mode 100644 index 00000000..7efebf89 --- /dev/null +++ b/src/localize/hass/is_timestamp.ts @@ -0,0 +1,14 @@ +// home-assistant/frontend/src/common/string/is_timestamp.ts + +// https://stackoverflow.com/a/14322189/1947205 +// Changes: +// 1. Do not allow a plus or minus at the start. +// 2. Enforce that we have a "T" or a blank after the date portion +// to ensure we have a timestamp and not only a date. +// 3. Disallow dates based on week number. +// 4. Disallow dates only consisting of a year. +// https://regex101.com/r/kc5C14/3 +const regexp = + /^\d{4}-(0[1-9]|1[0-2])-([12]\d|0[1-9]|3[01])[T| ](((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)(\8[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)$/; + +export const isTimestamp = (input: string): boolean => regexp.test(input); diff --git a/src/localize/hass/localize.ts b/src/localize/hass/localize.ts new file mode 100644 index 00000000..3e31d2b7 --- /dev/null +++ b/src/localize/hass/localize.ts @@ -0,0 +1,62 @@ +// home-assistant/frontend/src/common/translations/localize.ts + +// Exclude some patterns from key type checking for now +// These are intended to be removed as errors are fixed +// Fixing component category will require tighter definition of types from backend and/or web socket +export type LocalizeKeys = + // | FlattenObjectKeys> + | `state.default.unavailable` + | `state.default.unknown` + | `panel.${string}` + | `ui.card.alarm_control_panel.${string}` + | `ui.card.weather.attributes.${string}` + | `ui.card.weather.cardinal_direction.${string}` + | `ui.components.calendar.event.rrule.${string}` + | `ui.components.logbook.${string}` + | `ui.components.selectors.file.${string}` + | `ui.dialogs.entity_registry.editor.${string}` + | `ui.dialogs.more_info_control.vacuum.${string}` + | `ui.dialogs.quick-bar.commands.${string}` + | `ui.dialogs.unhealthy.reason.${string}` + | `ui.dialogs.unsupported.reason.${string}` + | `ui.panel.config.${string}.${"caption" | "description"}` + | `ui.panel.config.automation.${string}` + | `ui.panel.config.dashboard.${string}` + | `ui.panel.config.devices.${string}` + | `ui.panel.config.energy.${string}` + | `ui.panel.config.info.${string}` + | `ui.panel.config.lovelace.${string}` + | `ui.panel.config.network.${string}` + | `ui.panel.config.scene.${string}` + | `ui.panel.config.zha.${string}` + | `ui.panel.config.zwave_js.${string}` + | `ui.panel.lovelace.card.${string}` + | `ui.panel.lovelace.editor.${string}` + | `ui.panel.page-authorize.form.${string}` + | `component.${string}`; + +export type LocalizeFunc = ( + key: Keys, + ...args: any[] +) => string; + +// Tweaked from https://www.raygesualdo.com/posts/flattening-object-keys-with-typescript-types +export type FlattenObjectKeys< + T extends Record, + Key extends keyof T = keyof T +> = Key extends string + ? T[Key] extends Record + ? `${Key}.${FlattenObjectKeys}` + : `${Key}` + : never; + + + +interface FormatType { + [format: string]: any; +} +export interface FormatsType { + number: FormatType; + date: FormatType; + time: FormatType; +} diff --git a/src/localize/hass/round.ts b/src/localize/hass/round.ts new file mode 100644 index 00000000..26a5b66c --- /dev/null +++ b/src/localize/hass/round.ts @@ -0,0 +1,4 @@ +// home-assistant/frontend/src/common/number/round.ts + +export const round = (value: number, precision = 2): number => + Math.round(value * 10 ** precision) / 10 ** precision; diff --git a/src/localize/hass/translation.ts b/src/localize/hass/translation.ts new file mode 100644 index 00000000..a630b0a1 --- /dev/null +++ b/src/localize/hass/translation.ts @@ -0,0 +1,92 @@ +// home-assistant/frontend/src/data/translation.ts + +import { fetchFrontendUserData } from "./frontend"; +import { HomeAssistantFixed } from "../../types/fixes"; + +export enum NumberFormat { + language = "language", + system = "system", + comma_decimal = "comma_decimal", + decimal_comma = "decimal_comma", + space_comma = "space_comma", + none = "none", +} + +export enum TimeFormat { + language = "language", + system = "system", + am_pm = "12", + twenty_four = "24", +} + +export enum FirstWeekday { + language = "language", + monday = "monday", + tuesday = "tuesday", + wednesday = "wednesday", + thursday = "thursday", + friday = "friday", + saturday = "saturday", + sunday = "sunday", +} + +export interface FrontendLocaleData { + language: string; + number_format: NumberFormat; + time_format: TimeFormat; + first_weekday: FirstWeekday; +} + +declare global { + interface FrontendUserData { + language: FrontendLocaleData; + } +} + +export type TranslationCategory = + | "title" + | "state" + | "state_attributes" + | "entity" + | "config" + | "config_panel" + | "options" + | "device_automation" + | "mfa_setup" + | "system_health" + | "device_class" + | "application_credentials" + | "issues" + | "selector"; + +export const fetchTranslationPreferences = (hass: HomeAssistantFixed) => + fetchFrontendUserData(hass.connection, "language"); + + +export const getHassTranslations = async ( + hass: HomeAssistantFixed, + language: string, + category: TranslationCategory, + integration?: string | string[], + config_flow?: boolean +): Promise> => { + const result = await hass.callWS<{ resources: Record }>({ + type: "frontend/get_translations", + language, + category, + integration, + config_flow, + }); + return result.resources; +}; + +export const getHassTranslationsPre109 = async ( + hass: HomeAssistantFixed, + language: string +): Promise> => { + const result = await hass.callWS<{ resources: Record }>({ + type: "frontend/get_translations", + language, + }); + return result.resources; +}; diff --git a/src/localize/hass/use_am_pm.ts b/src/localize/hass/use_am_pm.ts new file mode 100644 index 00000000..31dd59a8 --- /dev/null +++ b/src/localize/hass/use_am_pm.ts @@ -0,0 +1,18 @@ +// home-assistant/frontend/src/common/datetime/use_am_pm.ts + +import { TimeFormat } from "custom-card-helpers"; +import { FrontendLocaleDataFixed } from "../../types/fixes"; + +export const useAmPm = (locale: FrontendLocaleDataFixed): boolean => { + if ( + locale.time_format === TimeFormat.language || + locale.time_format === TimeFormat.system + ) { + const testLanguage = + locale.time_format === TimeFormat.language ? locale.language : undefined; + const test = new Date().toLocaleString(testLanguage); + return test.includes("AM") || test.includes("PM"); + } + + return locale.time_format === TimeFormat.am_pm; +}; diff --git a/src/localize/localize.ts b/src/localize/localize.ts index 18463a21..ebc2a1cc 100644 --- a/src/localize/localize.ts +++ b/src/localize/localize.ts @@ -22,7 +22,7 @@ import * as uk from "./languages/uk.json"; import * as zh from "./languages/zh.json"; import * as zhHant from "./languages/zh-Hant.json"; import { Language, TranslatableString, XiaomiVacuumMapCardConfig } from "../types/types"; -import { HomeAssistant } from "custom-card-helpers"; +import { HomeAssistantFixed } from "../types/fixes"; const languages: Record = { ca: ca, @@ -95,7 +95,7 @@ export function localize(ts: TranslatableString, lang?: Language, fallback?: str export function localizeWithHass( ts: TranslatableString, - hass?: HomeAssistant, + hass?: HomeAssistantFixed, config?: XiaomiVacuumMapCardConfig, fallback?: string, ): string { diff --git a/src/model/generators/icon-list-generator.ts b/src/model/generators/icon-list-generator.ts index acd1deba..c71e317e 100644 --- a/src/model/generators/icon-list-generator.ts +++ b/src/model/generators/icon-list-generator.ts @@ -1,8 +1,8 @@ import { HassEntity } from "home-assistant-js-websocket/dist/types"; -import { HomeAssistant } from "custom-card-helpers"; import { IconActionConfig, Language } from "../../types/types"; import { localize } from "../../localize/localize"; +import { HomeAssistantFixed } from "../../types/fixes"; export class IconListGenerator { private static _ICON_MAPPING = { @@ -17,7 +17,7 @@ export class IconListGenerator { Gentle: "mdi:waves", }; - public static generate(hass: HomeAssistant, vacuumEntity: string, language: Language): IconActionConfig[] { + public static generate(hass: HomeAssistantFixed, vacuumEntity: string, language: Language): IconActionConfig[] { if (!hass) return []; const state = hass.states[vacuumEntity]; const state_available = state && state.attributes; diff --git a/src/model/generators/platform-generator.ts b/src/model/generators/platform-generator.ts index 5577d68c..423dee3a 100644 --- a/src/model/generators/platform-generator.ts +++ b/src/model/generators/platform-generator.ts @@ -15,9 +15,9 @@ import * as simpleWyzeTemplate from "./platform_templates/romedtino_simple-wyze- import * as setupDecimalTemplate from "./platform_templates/setup_decimal.json"; import * as setupIntegerTemplate from "./platform_templates/setup_integer.json"; import { MapModeConfig, PlatformTemplate, TileFromAttributeTemplate, TileFromSensorTemplate } from "../../types/types"; -import { HomeAssistant } from "custom-card-helpers"; import { compare } from "compare-versions"; import { SelectionType } from "../map_mode/selection-type"; +import { HomeAssistantFixed } from "../../types/fixes"; export class PlatformGenerator { public static XIAOMI_MIIO_PLATFORM = "default"; @@ -113,7 +113,7 @@ export class PlatformGenerator { return this.getPlatformTemplate(platform).tiles.from_sensors ?? []; } - public static usesSensors(hass: HomeAssistant, platform: string): boolean { + public static usesSensors(hass: HomeAssistantFixed, platform: string): boolean { const sensorsFrom = this.getPlatformTemplate(platform).sensors_from; if (sensorsFrom) { return compare(hass.config.version.replace(/\.*[a-z].*/, ""), sensorsFrom, ">="); diff --git a/src/model/generators/tiles-generator.ts b/src/model/generators/tiles-generator.ts index 893ffd7f..c8083fc6 100644 --- a/src/model/generators/tiles-generator.ts +++ b/src/model/generators/tiles-generator.ts @@ -1,4 +1,3 @@ -import { HomeAssistant } from "custom-card-helpers"; import { HassEntity } from "home-assistant-js-websocket"; import { @@ -14,10 +13,11 @@ import { localize } from "../../localize/localize"; import { getAllEntitiesFromTheSameDevice, getFilledTemplate } from "../../utils"; import { PlatformGenerator } from "./platform-generator"; import { TemplatableTileValue } from "../map_mode/templatable-value"; +import { HomeAssistantFixed } from "../../types/fixes"; export class TilesGenerator { public static generate( - hass: HomeAssistant, + hass: HomeAssistantFixed, vacuumEntity: string, platform: string, language: Language, @@ -140,7 +140,7 @@ export class TilesGenerator { } private static async addTilesFromSensors( - hass: HomeAssistant, + hass: HomeAssistantFixed, vacuumEntityId: string, platform: string, tiles: TileConfig[], diff --git a/src/model/map_mode/map-mode.ts b/src/model/map_mode/map-mode.ts index bc6676b7..ade02840 100644 --- a/src/model/map_mode/map-mode.ts +++ b/src/model/map_mode/map-mode.ts @@ -11,9 +11,9 @@ import { import { localize } from "../../localize/localize"; import { ServiceCall } from "./service-call"; import { PlatformGenerator } from "../generators/platform-generator"; -import { HomeAssistant } from "custom-card-helpers"; import { evaluateJinjaTemplate, replaceInTarget } from "../../utils"; import { Modifier } from "./modifier"; +import { HomeAssistantFixed } from "../../types/fixes"; export class MapMode { private static readonly PREDEFINED_SELECTION_TYPES = [ @@ -55,7 +55,7 @@ export class MapMode { } public async getServiceCall( - hass: HomeAssistant, + hass: HomeAssistantFixed, entityId: string, selection: unknown[], repeats: number, diff --git a/src/model/map_objects/predefined-multi-rectangle.ts b/src/model/map_objects/predefined-multi-rectangle.ts index c126c3f3..9429c613 100644 --- a/src/model/map_objects/predefined-multi-rectangle.ts +++ b/src/model/map_objects/predefined-multi-rectangle.ts @@ -1,13 +1,14 @@ // noinspection CssUnresolvedCustomProperty import { css, CSSResultGroup, svg, SVGTemplateResult } from "lit"; -import { forwardHaptic, HomeAssistant } from "custom-card-helpers"; +import { forwardHaptic } from "custom-card-helpers"; import { Context } from "./context"; import { MapObject } from "./map-object"; import { PredefinedZoneConfig, VariablesStorage, ZoneType, ZoneWithRepeatsType } from "../../types/types"; import { deleteFromArray } from "../../utils"; import { MapMode } from "../map_mode/map-mode"; +import { HomeAssistantFixed } from "../../types/fixes"; export class PredefinedMultiRectangle extends MapObject { private readonly _config: PredefinedZoneConfig; @@ -25,7 +26,7 @@ export class PredefinedMultiRectangle extends MapObject { public static getFromEntities( newMode: MapMode, - hass: HomeAssistant, + hass: HomeAssistantFixed, contextCreator: () => Context, ): PredefinedMultiRectangle[] { return newMode.predefinedSelections diff --git a/src/model/map_objects/predefined-point.ts b/src/model/map_objects/predefined-point.ts index 41c598af..501c3490 100644 --- a/src/model/map_objects/predefined-point.ts +++ b/src/model/map_objects/predefined-point.ts @@ -1,6 +1,6 @@ // noinspection CssUnresolvedCustomProperty import { css, CSSResultGroup, svg, SVGTemplateResult } from "lit"; -import { forwardHaptic, HomeAssistant } from "custom-card-helpers"; +import { forwardHaptic } from "custom-card-helpers"; import { Context } from "./context"; import { @@ -13,6 +13,7 @@ import { import { MapObject } from "./map-object"; import { deleteFromArray } from "../../utils"; import { MapMode } from "../map_mode/map-mode"; +import { HomeAssistantFixed } from "../../types/fixes"; export class PredefinedPoint extends MapObject { private readonly _config: PredefinedPointConfig; @@ -38,7 +39,7 @@ export class PredefinedPoint extends MapObject { public static getFromEntities( newMode: MapMode, - hass: HomeAssistant, + hass: HomeAssistantFixed, contextCreator: () => Context, ): PredefinedPoint[] { return newMode.predefinedSelections diff --git a/src/types/fixes.ts b/src/types/fixes.ts index 4a0edf5b..85019fd0 100644 --- a/src/types/fixes.ts +++ b/src/types/fixes.ts @@ -1,5 +1,43 @@ -import { HomeAssistant } from "custom-card-helpers"; +import { HomeAssistant, NumberFormat, TimeFormat } from "custom-card-helpers"; +import { Connection } from "home-assistant-js-websocket"; -export interface HomeAssistantFixed extends HomeAssistant { +export interface HomeAssistantFixed extends Omit { hassUrl(path?): string; + + entities: { [id: string]: EntityRegistryDisplayEntry }; + + connection: Connection; + locale: FrontendLocaleDataFixed; +} + +export enum FirstWeekday { + language = "language", + monday = "monday", + tuesday = "tuesday", + wednesday = "wednesday", + thursday = "thursday", + friday = "friday", + saturday = "saturday", + sunday = "sunday", } + +export interface FrontendLocaleDataFixed { + language: string; + number_format: NumberFormat; + time_format: TimeFormat; + first_weekday: FirstWeekday; +} + +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; +} \ No newline at end of file diff --git a/src/types/types.ts b/src/types/types.ts index 7daaa3a4..aef558b8 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -223,3 +223,16 @@ export interface RoomConfigEventData { readonly modeIndex: number; readonly rooms: Array; } + +export interface Translation { + nativeName: string; + isRTL: boolean; + hash: string; +} + +export interface TranslationMetadata { + fragments: string[]; + translations: { + [lang: string]: Translation; + }; +} diff --git a/src/utils.ts b/src/utils.ts index dd7ee79b..18788248 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -20,6 +20,7 @@ import { SelectionType } from "./model/map_mode/selection-type"; import { MousePosition } from "./model/map_objects/mouse-position"; import { XiaomiVacuumMapCard } from "./xiaomi-vacuum-map-card"; import { Modifier } from "./model/map_mode/modifier"; +import { HomeAssistantFixed } from "./types/fixes"; export function stopEvent(event: MouseEvent | TouchEvent): void { event.preventDefault(); @@ -104,7 +105,7 @@ export function getWatchedEntities(config: XiaomiVacuumMapCardConfig): string[] export function isConditionMet( condition: ConditionConfig, internalVariables: VariablesStorage, - hass: HomeAssistant, + hass: HomeAssistantFixed, ): boolean { let currentValue: ReplacedKey = ""; if (condition.internal_variable && condition.internal_variable in internalVariables) { @@ -126,7 +127,7 @@ export function isConditionMet( export function areConditionsMet( config: ConditionalObjectConfig, internalVariables: VariablesStorage, - hass: HomeAssistant, + hass: HomeAssistantFixed, ): boolean { return (config.conditions ?? []).every(condition => isConditionMet(condition, internalVariables, hass)); } @@ -135,12 +136,12 @@ export function hasConfigOrAnyEntityChanged( watchedEntities: string[], changedProps: PropertyValues, forceUpdate: boolean, - hass?: HomeAssistant, + hass?: HomeAssistantFixed, ): boolean { if (changedProps.has("config") || forceUpdate) { return true; } - const oldHass = changedProps.get("hass") as HomeAssistant | undefined; + const oldHass = changedProps.get("hass") as HomeAssistantFixed | undefined; return !oldHass || watchedEntities.some(entity => oldHass.states[entity] !== hass?.states[entity]); } @@ -154,7 +155,7 @@ export function handleActionWithConfig( ): (ev: ActionHandlerEvent) => void { return (ev: ActionHandlerEvent): void => { if (node.hass && config && ev.detail.action) { - handleAction(node, node.hass, config, ev.detail.action); + handleAction(node, node.hass as unknown as HomeAssistant, config, ev.detail.action); } }; } @@ -177,7 +178,7 @@ export function getMousePosition( } export async function getAllEntitiesFromTheSameDevice( - hass: HomeAssistant, + hass: HomeAssistantFixed, entity: string, ): Promise { const vacuumDeviceId = ( @@ -220,7 +221,7 @@ export function copyMessage(val: string): void { } export async function evaluateJinjaTemplate( - hass: HomeAssistant, + hass: HomeAssistantFixed, template: string, ): Promise> { return new Promise(resolve => { From 9ea89c776207229ad7dadac211c943bcd921e8b1 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Sat, 11 Mar 2023 04:52:56 +0100 Subject: [PATCH 024/100] Apply HA translations on tiles --- src/config-validators.ts | 2 +- src/localize/hass/compute_state_display.ts | 12 ++- src/localize/hass/duration.ts | 9 +- src/localize/hass/entity_attributes.ts | 41 +++----- src/localize/localize.ts | 23 ++++- src/model/generators/tiles-generator.ts | 27 ++++- src/renderers/tile-renderer.ts | 112 ++++++++++++++++----- src/types/types.ts | 8 +- src/xiaomi-vacuum-map-card.ts | 1 + 9 files changed, 166 insertions(+), 69 deletions(-) diff --git a/src/config-validators.ts b/src/config-validators.ts index ce85e120..b8b806cd 100644 --- a/src/config-validators.ts +++ b/src/config-validators.ts @@ -81,7 +81,7 @@ function validateTileConfig(config: TileConfig): TranslatableString[] { if (!config.entity && !config.internal_variable) { errors.push("validation.preset.tiles.entity.missing"); } - if (!config.label) { + if (!config.label && !config.entity) { errors.push("validation.preset.tiles.label.missing"); } return errors; diff --git a/src/localize/hass/compute_state_display.ts b/src/localize/hass/compute_state_display.ts index 2443b0fa..ef6aa6d6 100644 --- a/src/localize/hass/compute_state_display.ts +++ b/src/localize/hass/compute_state_display.ts @@ -21,7 +21,7 @@ export const computeStateDisplay = ( stateObj: HassEntity, locale: FrontendLocaleDataFixed, entities: HomeAssistantFixed["entities"], - state?: string + skipUnit = false, ): string => computeStateDisplayFromEntityAttributes( localize, @@ -29,7 +29,8 @@ export const computeStateDisplay = ( entities, stateObj.entity_id, stateObj.attributes, - state !== undefined ? state : stateObj.state + stateObj.state, + skipUnit ); export const computeStateDisplayFromEntityAttributes = ( @@ -38,7 +39,8 @@ export const computeStateDisplayFromEntityAttributes = ( entities: HomeAssistantFixed["entities"], entityId: string, attributes: any, - state: string + state: string, + skipUnit = false, ): string => { if (state === UNKNOWN || state === UNAVAILABLE) { return localize(`state.default.${state}`); @@ -63,7 +65,7 @@ export const computeStateDisplayFromEntityAttributes = ( if (attributes.device_class === "monetary") { try { return formatNumber(state, locale, { - style: "currency", + style: skipUnit ? undefined: "currency", currency: attributes.unit_of_measurement, minimumFractionDigits: 2, // Override monetary options with number format @@ -76,7 +78,7 @@ export const computeStateDisplayFromEntityAttributes = ( // fallback to default } } - const unit = !attributes.unit_of_measurement + const unit = !attributes.unit_of_measurement || skipUnit ? "" : attributes.unit_of_measurement === "%" ? blankBeforePercent(locale) + "%" diff --git a/src/localize/hass/duration.ts b/src/localize/hass/duration.ts index 002fd57a..0b2d601d 100644 --- a/src/localize/hass/duration.ts +++ b/src/localize/hass/duration.ts @@ -13,7 +13,7 @@ export const UNIT_TO_SECOND_CONVERT = { const leftPad = (num: number) => (num < 10 ? `0${num}` : num); -export function secondsToDuration(d: number) { +export function secondsToDuration(d: number): string | null { const h = Math.floor(d / 3600); const m = Math.floor((d % 3600) / 60); const s = Math.floor((d % 3600) % 60); @@ -30,6 +30,7 @@ export function secondsToDuration(d: number) { return null; } -export const formatDuration = (duration: string, units: string): string => - secondsToDuration(parseFloat(duration) * UNIT_TO_SECOND_CONVERT[units]) || - "0"; +export const formatDuration = (duration: string, units: string): string => { + const seconds = parseFloat(duration) * UNIT_TO_SECOND_CONVERT[units]; + return (seconds >= 0 ? secondsToDuration(seconds) : `-${secondsToDuration(-seconds)}`) ?? "0"; +}; diff --git a/src/localize/hass/entity_attributes.ts b/src/localize/hass/entity_attributes.ts index ac8c4daa..53688ee6 100644 --- a/src/localize/hass/entity_attributes.ts +++ b/src/localize/hass/entity_attributes.ts @@ -50,7 +50,7 @@ export function formatAttributeValue( hass: HomeAssistantFixed, stateObj: HassEntity, attribute: string -): string | TemplateResult { +): string { const value = stateObj.attributes[attribute]; if (value === null) { @@ -62,44 +62,27 @@ export function formatAttributeValue( (Array.isArray(value) && value.some((val) => val instanceof Object)) || (!Array.isArray(value) && value instanceof Object) ) { - return html`
    ${value}
    `; + return JSON.stringify(value); } if (typeof value === "number") { return formatNumber(value, hass.locale); } - if (typeof value === "string") { - // URL handling - if (value.startsWith("http")) { - try { - // If invalid URL, exception will be raised - const url = new URL(value); - if (url.protocol === "http:" || url.protocol === "https:") - return html`${value}`; - } catch (_) { - // Nothing to do here - } - } - - // Date handling - if (isDate(value, true)) { - // Timestamp handling - if (isTimestamp(value)) { - const date = new Date(value); - if (checkValidDate(date)) { - return formatDateTimeWithSeconds(date, hass.locale); - } - } - - // Value was not a timestamp, so only do date formatting + if (typeof value === "string" && isDate(value, true)) { + // Timestamp handling + if (isTimestamp(value)) { const date = new Date(value); if (checkValidDate(date)) { - return formatDate(date, hass.locale); + return formatDateTimeWithSeconds(date, hass.locale); } } + + // Value was not a timestamp, so only do date formatting + const date = new Date(value); + if (checkValidDate(date)) { + return formatDate(date, hass.locale); + } } return Array.isArray(value) diff --git a/src/localize/localize.ts b/src/localize/localize.ts index ebc2a1cc..c60211c8 100644 --- a/src/localize/localize.ts +++ b/src/localize/localize.ts @@ -21,8 +21,11 @@ import * as tr from "./languages/tr.json"; import * as uk from "./languages/uk.json"; import * as zh from "./languages/zh.json"; import * as zhHant from "./languages/zh-Hant.json"; -import { Language, TranslatableString, XiaomiVacuumMapCardConfig } from "../types/types"; +import { EntityConfig, Language, TranslatableString, XiaomiVacuumMapCardConfig } from "../types/types"; import { HomeAssistantFixed } from "../types/fixes"; +import { HassEntity } from "home-assistant-js-websocket/dist/types"; +import { formatAttributeValue } from "./hass/entity_attributes"; +import { computeStateDisplay } from "./hass/compute_state_display"; const languages: Record = { ca: ca, @@ -101,3 +104,21 @@ export function localizeWithHass( ): string { return localize(ts, config?.language ?? hass?.locale?.language, fallback); } + +export function localizeEntity(hass: HomeAssistantFixed, config: EntityConfig, entity: HassEntity): string { + return "attribute" in config && config.attribute !== undefined + ? entity.attributes[config.attribute] !== undefined + ? formatAttributeValue( + hass, + entity, + config.attribute, + ) + : hass.localize("state.default.unknown") + : computeStateDisplay( + hass.localize, + entity, + hass.locale, + hass.entities, + config.unit !== undefined, + ); +} \ No newline at end of file diff --git a/src/model/generators/tiles-generator.ts b/src/model/generators/tiles-generator.ts index c8083fc6..30507b07 100644 --- a/src/model/generators/tiles-generator.ts +++ b/src/model/generators/tiles-generator.ts @@ -14,6 +14,7 @@ import { getAllEntitiesFromTheSameDevice, getFilledTemplate } from "../../utils" import { PlatformGenerator } from "./platform-generator"; import { TemplatableTileValue } from "../map_mode/templatable-value"; import { HomeAssistantFixed } from "../../types/fixes"; +import { computeAttributeNameDisplay } from "../../localize/hass/compute_attribute_display"; export class TilesGenerator { public static generate( @@ -40,6 +41,7 @@ export class TilesGenerator { return new Promise(resolve => resolve( this.addTilesFromAttributes( + hass, state, vacuumEntity, platform, @@ -125,6 +127,7 @@ export class TilesGenerator { } private static addTilesFromAttributes( + hass: HomeAssistantFixed, state: HassEntity, vacuumEntity: string, platform: string, @@ -135,7 +138,7 @@ export class TilesGenerator { ): TileConfig[] { PlatformGenerator.getTilesFromAttributesTemplates(platform) .filter(t => t.attribute in state.attributes) - .forEach(t => tiles.push(this.mapAttributeToTile(vacuumEntity, t, language, variables))); + .forEach(t => tiles.push(this.mapAttributeToTile(hass, vacuumEntity, t, language, variables))); return this.replaceDuplicates(tiles, tilesToOverride); } @@ -162,13 +165,14 @@ export class TilesGenerator { tile: t, entity: entityRegistryEntries.filter(e => e.unique_id.match(t.unique_id_regex)), })) - .flatMap(v => v.entity.map(e => this.mapEntryToTile(vacuumEntityId, e, v.tile, language, variables))) + .flatMap(v => v.entity.map(e => this.mapEntryToTile(hass, vacuumEntityId, e, v.tile, language, variables))) .forEach(t => tiles.push(t)); } return new Promise(resolve => resolve(this.replaceDuplicates(tiles, tilesToOverride))); } private static mapEntryToTile( + hass: HomeAssistantFixed, vacuum_entity_id: string, entry: EntityRegistryEntry, tile_template: TileFromSensorTemplate, @@ -176,6 +180,7 @@ export class TilesGenerator { variables: VariablesStorage, ): TileConfig { return this.mapToTile( + hass, tile_template, vacuum_entity_id, entry.entity_id, @@ -187,12 +192,14 @@ export class TilesGenerator { } private static mapAttributeToTile( + hass: HomeAssistantFixed, entity_id: string, tile_template: TileFromAttributeTemplate, language: Language, variables: VariablesStorage, ): TileConfig { return this.mapToTile( + hass, tile_template, entity_id, entity_id, @@ -204,6 +211,7 @@ export class TilesGenerator { } private static mapToTile( + hass: HomeAssistantFixed, tileTemplate: TileTemplate, vacuum_entity_id: string, entity_id: string, @@ -215,7 +223,7 @@ export class TilesGenerator { const tileConfig: TileConfig = { ...tileTemplate, entity: entity_id, - label: localize(tileTemplate.label, language), + label: this.getTileLabel(hass, tileTemplate, language, entity_id, attribute), attribute: attribute, icon: icon, unit: tileTemplate.unit ? localize(tileTemplate.unit, language) : undefined, @@ -234,6 +242,19 @@ export class TilesGenerator { ) as unknown as TileConfig; } + private static getTileLabel( + hass: HomeAssistantFixed, + tile: TileConfig, + language: Language, + entity_id: string, + attribute: string | undefined) { + if (tile.label !== undefined) + return localize(tile.label, language); + if (attribute !== undefined) + return computeAttributeNameDisplay(hass.localize, hass.states[entity_id], hass.entities, attribute); + return hass.states[entity_id].attributes.friendly_name ?? entity_id; + } + private static generateTranslationKeys( keys: Array, tile_id: string | undefined, diff --git a/src/renderers/tile-renderer.ts b/src/renderers/tile-renderer.ts index 022706d0..f9d14358 100644 --- a/src/renderers/tile-renderer.ts +++ b/src/renderers/tile-renderer.ts @@ -1,10 +1,16 @@ import { css, CSSResultGroup, html, TemplateResult } from "lit"; -import { hasAction } from "custom-card-helpers"; +import { ifDefined } from "lit/directives/if-defined"; +import { computeStateDomain, hasAction } from "custom-card-helpers"; +import { HassEntity } from "home-assistant-js-websocket/dist/types"; import { actionHandler } from "../action-handler-directive"; import { conditional, handleActionWithConfig } from "../utils"; -import { ReplacedKey, TileConfig, VariablesStorage } from "../types/types"; +import { EntityConfig, ReplacedKey, TileConfig, VariablesStorage } from "../types/types"; import { XiaomiVacuumMapCard } from "../xiaomi-vacuum-map-card"; +import { HomeAssistantFixed } from "../types/fixes"; +import { localizeEntity } from "../localize/localize"; +import { computeAttributeNameDisplay } from "../localize/hass/compute_attribute_display"; +import { blankBeforePercent } from "../localize/hass/blank_before_percent"; export class TileRenderer { public static render( @@ -12,24 +18,12 @@ export class TileRenderer { internalVariables: VariablesStorage, card: XiaomiVacuumMapCard, ): TemplateResult { - let value: ReplacedKey = ""; - if (config.entity) { - value = config.attribute - ? card.hass.states[config.entity].attributes[config.attribute] - : card.hass.states[config.entity].state; - } else if (config.internal_variable && config.internal_variable in internalVariables) { - value = internalVariables[config.internal_variable]; - } - if (value !== null && (typeof value === "number" || !isNaN(+value))) { - value = parseFloat(value.toString()) * (config.multiplier ?? 1); - if (config.precision != undefined) { - value = value.toFixed(config.precision); - } - } - const translations = config.translations ?? {}; - if (`${value}`.toLowerCase() in translations) { - value = translations[`${value}`.toLowerCase()]; - } + const stateObj = config.entity ? card.hass.states[config.entity] : undefined; + const title = this.getTileLabel(card.hass, config, stateObj); + const value = this.getTileValue(card.hass, config, internalVariables, stateObj); + const icon = this.getIcon(config, stateObj); + const domain = stateObj ? computeStateDomain(stateObj) : undefined; + return html`
    -
    ${config.label}
    +
    ${title}
    ${conditional( - !!config.icon, - () => html`
    - + icon !== "", + () => html`
    + +
    `, )} -
    ${value}${config.unit ?? ""}
    +
    ${value}
    `; } + private static getTileLabel( + hass: HomeAssistantFixed, + tile: TileConfig, + stateObject?: HassEntity, + ) { + if (tile.label !== undefined) + return tile.label; + if (stateObject !== undefined) { + if (tile.attribute !== undefined) + return computeAttributeNameDisplay(hass.localize, stateObject, hass.entities, tile.attribute); + return stateObject.attributes?.friendly_name ?? tile.entity; + } + return tile.tile_id ?? "tile"; + } + + private static getTileValue( + hass: HomeAssistantFixed, + config: TileConfig, + internalVariables: VariablesStorage, + stateObject?: HassEntity, + ) { + let value: ReplacedKey = ""; + const processNumber = config.multiplier !== undefined || config.precision !== undefined; + if (config.entity && stateObject) { + if (processNumber) { + value = config.attribute + ? stateObject.attributes[config.attribute] + : stateObject.state; + } else { + value = localizeEntity(hass, config as EntityConfig, hass.states[config.entity]); + } + } else if (config.internal_variable && config.internal_variable in internalVariables) { + value = internalVariables[config.internal_variable]; + } + if (processNumber && value !== null && (typeof value === "number" || !isNaN(+value))) { + value = parseFloat(value.toString()) * (config.multiplier ?? 1); + if (config.precision !== undefined) { + value = value.toFixed(config.precision); + } + } + const translations = config.translations ?? {}; + if (`${value}`.toLowerCase() in translations) { + value = translations[`${value}`.toLowerCase()]; + } + const unit = this.getUnit(hass, config); + return `${value}${unit}`; + } + + private static getIcon(config: TileConfig, stateObject?: HassEntity) { + if (config.icon === undefined && stateObject) { + return stateObject.attributes.icon ?? null; + } + return config.icon; + } + + private static getUnit(hass: HomeAssistantFixed, config: TileConfig) { + return !config.unit + ? "" : + config.unit === "%" + ? blankBeforePercent(hass.locale) + "%" + : ` ${config.unit}`; + } + public static get styles(): CSSResultGroup { return css` .tile-wrapper { diff --git a/src/types/types.ts b/src/types/types.ts index aef558b8..ef850aec 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -121,7 +121,7 @@ export interface IconActionConfig extends ActionableObjectConfig, ConditionalObj export interface TileConfig extends ActionableObjectConfig, ConditionalObjectConfig { readonly tile_id?: string; - readonly label: string; + readonly label?: string; readonly tooltip?: string; readonly icon?: string; readonly internal_variable?: string; @@ -236,3 +236,9 @@ export interface TranslationMetadata { [lang: string]: Translation; }; } + +export interface EntityConfig { + entity: string; + attribute?: string; + unit?: string; +} \ No newline at end of file diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index fb357227..a07ff2d9 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -596,6 +596,7 @@ export class XiaomiVacuumMapCard extends LitElement { private _setPreset(config: CardPresetConfig): void { this.currentPreset = config; + this.watchedEntities = getWatchedEntities({type: "", ...config}); } private _updateCalibration(config: CardPresetConfig): void { From 5dadcdf6c98c6968fdcd76d7ba24d3790c4f71cb Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Mon, 13 Mar 2023 04:44:51 +0100 Subject: [PATCH 025/100] Add displaying card version in editor --- package.json | 5 ++++- src/const.ts | 2 +- src/editor.ts | 10 ++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index feb2a6e3..86c30b3f 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,14 @@ "@rollup/plugin-json": "^4.1.0", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", + "cross-var": "^1.1.0", "eslint": "^7.32.0", "eslint-config-airbnb-base": "^14.2.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.24.0", "eslint-plugin-prettier": "^4.0.0", "prettier": "^2.4.1", + "replace-in-file": "^6.3.5", "rollup": "^2.58.0", "rollup-plugin-babel": "^4.4.0", "rollup-plugin-commonjs": "^10.1.0", @@ -47,7 +49,8 @@ }, "scripts": { "start": "rollup -c rollup.config.dev.js --watch", - "build": "npm run lint && npm run rollup", + "build": "npm run lint && npm run rollup && npm run add-version", + "add-version": "cross-var replace-in-file \"@VACUUM_MAP_CARD_VERSION_PLACEHOLDER@\" \"$npm_package_version\" \"dist/xiaomi-vacuum-map-card.js\"", "lint": "eslint src/*.ts", "rollup": "rollup -c" } diff --git a/src/const.ts b/src/const.ts index af09f068..d5b6a82e 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,4 +1,4 @@ -export const CARD_VERSION = "v2.1.3-beta"; +export const CARD_VERSION = "@VACUUM_MAP_CARD_VERSION_PLACEHOLDER@"; export const CARD_CUSTOM_ELEMENT_NAME = "xiaomi-vacuum-map-card"; export const EDITOR_CUSTOM_ELEMENT_NAME = "xiaomi-vacuum-map-card-editor"; export const ACTION_HANDLER_CUSTOM_ELEMENT_NAME = "action-handler-xiaomi-vacuum-map-card"; diff --git a/src/editor.ts b/src/editor.ts index 1d0ee62b..37210849 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -7,6 +7,7 @@ import { RoomConfigEventData, TranslatableString, XiaomiVacuumMapCardConfig } fr import { localizeWithHass } from "./localize/localize"; import { PlatformGenerator } from "./model/generators/platform-generator"; import { + CARD_VERSION, EDITOR_CUSTOM_ELEMENT_NAME, EVENT_AUTOGENERATED_CONFIG, EVENT_AUTOGENERATED_CONFIG_GET, @@ -220,6 +221,7 @@ export class XiaomiVacuumMapCardEditor extends LitElement implements Omit
    +
    ${this._localize("common.version")} ${CARD_VERSION} ${ToastRenderer.render("editor")}
    `; @@ -364,6 +366,7 @@ export class XiaomiVacuumMapCardEditor extends LitElement implements Omit Date: Mon, 13 Mar 2023 04:45:40 +0100 Subject: [PATCH 026/100] Improve DeebotUniverse/Deebot-4-Home-Assistant template --- .../DeebotUniverse_Deebot-4-Home-Assistant.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json index 55488de6..d28f05b9 100644 --- a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json +++ b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json @@ -1,13 +1,13 @@ { "map_modes": { - "defaultTemplates": [], + "defaultTemplates": ["vacuum_clean_zone"], "templates": { "vacuum_clean_segment": { "name": "map_mode.vacuum_clean_segment", "icon": "mdi:floor-plan", "selection_type": "ROOM", "repeats_type": "EXTERNAL", - "max_repeats": 3, + "max_repeats": 2, "service_call_schema": { "service": "vacuum.send_command", "service_data": { From 17eed4fa5c5811e9315fde9478b51f36297303b5 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Mon, 13 Mar 2023 04:56:40 +0100 Subject: [PATCH 027/100] Fix retrieving version --- src/const.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/const.ts b/src/const.ts index d5b6a82e..42cd27bd 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,4 +1,4 @@ -export const CARD_VERSION = "@VACUUM_MAP_CARD_VERSION_PLACEHOLDER@"; +export const CARD_VERSION = "v@VACUUM_MAP_CARD_VERSION_PLACEHOLDER@"; export const CARD_CUSTOM_ELEMENT_NAME = "xiaomi-vacuum-map-card"; export const EDITOR_CUSTOM_ELEMENT_NAME = "xiaomi-vacuum-map-card-editor"; export const ACTION_HANDLER_CUSTOM_ELEMENT_NAME = "action-handler-xiaomi-vacuum-map-card"; From 1460d08a948825ce87c9edff37168f641260e5c2 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 14 Mar 2023 02:00:30 +0100 Subject: [PATCH 028/100] Add support for generating outline --- src/types/types.ts | 12 +++++++----- src/xiaomi-vacuum-map-card.ts | 28 +++++++++++++++++----------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/types/types.ts b/src/types/types.ts index ef850aec..d2a30f67 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -16,6 +16,7 @@ export type RectangleType = [PointType, PointType, PointType, PointType]; export type ZoneType = [number, number, number, number]; export type ZoneWithRepeatsType = [number, number, number, number, number]; export type PointType = [number, number]; +export type OutlineType = PointType[]; export type PointWithRepeatsType = [number, number, number]; export type PredefinedSelectionConfig = PredefinedZoneConfig | PredefinedPointConfig | RoomConfig; export type TranslatableString = string | [string, string, string]; @@ -177,7 +178,7 @@ export interface PredefinedPointConfig extends PredefinedSelectionCommonConfig { export interface RoomConfig extends PredefinedSelectionCommonConfig { readonly id: number | string; - readonly outline?: [number, number][]; + readonly outline?: OutlineType; } export interface LabelConfig { @@ -209,10 +210,11 @@ export interface MapCroppingConfig { } export interface MapExtractorRoom { - readonly x0: number; - readonly y0: number; - readonly x1: number; - readonly y1: number; + readonly x0: number | undefined; + readonly y0: number | undefined; + readonly x1: number | undefined; + readonly y1: number | undefined; + readonly outline: OutlineType | undefined; readonly name: string | undefined; readonly icon: string | undefined; readonly x: number | undefined; diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index a07ff2d9..7a43f75a 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -912,28 +912,34 @@ export class XiaomiVacuumMapCard extends LitElement { for (const room_id in rooms) { if (!rooms.hasOwnProperty(room_id)) continue; const room = rooms[room_id]; - const keepFloat = room.x0.toString().includes("."); + if(!room.outline && !room.x0 && !room.y0 && !room.x1 && !room.y1) + continue; + const outline = room.outline ?? [ + [room.x0, room.y0], + [room.x1, room.y0], + [room.x1, room.y1], + [room.x0, room.y1], + ]; + const keepFloat = outline.toString().includes("."); const formatCoord = (v: number, divide = 1): number => keepFloat ? v / divide : Math.round(v / divide); + const x = outline.reduce((a, v) => a + (v[0] ?? 0), 0); + const y = outline.reduce((a, v) => a + (v[1] ?? 0), 0); + const roomConfig = { id: room_id, icon: { name: room.icon ?? "mdi:broom", - x: room.x ?? formatCoord(room.x0 + room.x1, 2), - y: room.y ?? formatCoord(room.y0 + room.y1, 2), + x: room.x ?? formatCoord(x, outline.length), + y: room.y ?? formatCoord(y, outline.length), }, label: { text: room.name ?? `Room ${room_id}`, - x: room.x ?? formatCoord(room.x0 + room.x1, 2), - y: room.y ?? formatCoord(room.y0 + room.y1, 2), + x: room.x ?? formatCoord(x, outline.length), + y: room.y ?? formatCoord(y, outline.length), offset_y: 35, }, - outline: [ - [room.x0, room.y0], - [room.x1, room.y0], - [room.x1, room.y1], - [room.x0, room.y1], - ], + outline: outline, } as RoomConfig; roomsConfig.push(roomConfig); } From 7f8af915843ebe222c8d6d73d8b0157a1ee153ca Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Wed, 15 Mar 2023 03:10:54 +0100 Subject: [PATCH 029/100] Improve generating tiles --- package.json | 3 +- src/model/generators/platform-generator.ts | 10 -- ...eebotUniverse_Deebot-4-Home-Assistant.json | 1 - ...umbertogontijo_homeassistant-roborock.json | 1 - .../platform_templates/hypfer_valetudo.json | 1 - .../platform_templates/xiaomiMiio.json | 1 - src/model/generators/tiles-generator.ts | 93 +++++++++---------- src/types/types.ts | 1 - 8 files changed, 45 insertions(+), 66 deletions(-) diff --git a/package.json b/package.json index 86c30b3f..e025ec88 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,7 @@ "lit": "^2.0.0", "pointer-tracker": "^2.4.0", "transformation-matrix": "^2.8.0", - "change-perspective": "^1.0.1", - "compare-versions": "^4.0.1" + "change-perspective": "^1.0.1" }, "devDependencies": { "@babel/core": "^7.15.0", diff --git a/src/model/generators/platform-generator.ts b/src/model/generators/platform-generator.ts index 423dee3a..a4ed5c28 100644 --- a/src/model/generators/platform-generator.ts +++ b/src/model/generators/platform-generator.ts @@ -15,9 +15,7 @@ import * as simpleWyzeTemplate from "./platform_templates/romedtino_simple-wyze- import * as setupDecimalTemplate from "./platform_templates/setup_decimal.json"; import * as setupIntegerTemplate from "./platform_templates/setup_integer.json"; import { MapModeConfig, PlatformTemplate, TileFromAttributeTemplate, TileFromSensorTemplate } from "../../types/types"; -import { compare } from "compare-versions"; import { SelectionType } from "../map_mode/selection-type"; -import { HomeAssistantFixed } from "../../types/fixes"; export class PlatformGenerator { public static XIAOMI_MIIO_PLATFORM = "default"; @@ -113,14 +111,6 @@ export class PlatformGenerator { return this.getPlatformTemplate(platform).tiles.from_sensors ?? []; } - public static usesSensors(hass: HomeAssistantFixed, platform: string): boolean { - const sensorsFrom = this.getPlatformTemplate(platform).sensors_from; - if (sensorsFrom) { - return compare(hass.config.version.replace(/\.*[a-z].*/, ""), sensorsFrom, ">="); - } - return false; - } - public static getRoomsTemplate(platform: string): string | undefined { const platformTemplate = this.getPlatformTemplate(platform); for (const templateName in platformTemplate.map_modes.templates) { diff --git a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json index d28f05b9..804099c1 100644 --- a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json +++ b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json @@ -58,7 +58,6 @@ } } }, - "sensors_from": "2020.1.0", "tiles": { "from_sensors": [ { diff --git a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json index c4d044d3..e41357d5 100644 --- a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json +++ b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json @@ -98,7 +98,6 @@ } } }, - "sensors_from": "2020.1.0", "tiles": { "from_sensors": [ { diff --git a/src/model/generators/platform_templates/hypfer_valetudo.json b/src/model/generators/platform_templates/hypfer_valetudo.json index 5826b548..6a8ca8bf 100644 --- a/src/model/generators/platform_templates/hypfer_valetudo.json +++ b/src/model/generators/platform_templates/hypfer_valetudo.json @@ -78,7 +78,6 @@ } } }, - "sensors_from": "2020.1.0", "tiles": { "from_sensors": [ { diff --git a/src/model/generators/platform_templates/xiaomiMiio.json b/src/model/generators/platform_templates/xiaomiMiio.json index 98a71305..14cd6e21 100644 --- a/src/model/generators/platform_templates/xiaomiMiio.json +++ b/src/model/generators/platform_templates/xiaomiMiio.json @@ -98,7 +98,6 @@ } } }, - "sensors_from": "2021.11.0", "tiles": { "from_attributes": [ { diff --git a/src/model/generators/tiles-generator.ts b/src/model/generators/tiles-generator.ts index 30507b07..f89c9c85 100644 --- a/src/model/generators/tiles-generator.ts +++ b/src/model/generators/tiles-generator.ts @@ -16,8 +16,39 @@ import { TemplatableTileValue } from "../map_mode/templatable-value"; import { HomeAssistantFixed } from "../../types/fixes"; import { computeAttributeNameDisplay } from "../../localize/hass/compute_attribute_display"; + +class TilesGeneratorContext { + private readonly _tiles: TileConfig[]; + private readonly _userDefinedTiles: TileConfig[]; + + constructor(userDefinedTiles: TileConfig[]) { + this._userDefinedTiles = userDefinedTiles; + this._tiles = []; + } + + public addTiles(tiles: TileConfig[]): void { + tiles.forEach(t => this.addTile(t)); + } + + public addTile(tile: TileConfig): void { + if (tile.tile_id && this._tiles.map(t => t.tile_id).includes(tile.tile_id)) { + return; + } + if (tile.tile_id && this._userDefinedTiles.some(t => t.tile_id === tile.tile_id)) { + this._userDefinedTiles.filter(t => t.tile_id === tile.tile_id).forEach(t => this._tiles.push(t)); + return; + } + this._tiles.push(tile); + } + + public get tiles(): TileConfig[] { + return this._tiles; + } +} + + export class TilesGenerator { - public static generate( + public static async generate( hass: HomeAssistantFixed, vacuumEntity: string, platform: string, @@ -26,33 +57,16 @@ export class TilesGenerator { variables: VariablesStorage, ): Promise { if (!hass) return new Promise(resolve => resolve([])); - const useNewGenerator = PlatformGenerator.usesSensors(hass, platform); - const state = hass.states[vacuumEntity]; const tiles: TileConfig[] = []; if (!state) { - return new Promise(resolve => resolve(tiles)); - } - - tiles.push(...this.getCommonTiles(state, vacuumEntity, language)); - if (useNewGenerator) { - return this.addTilesFromSensors(hass, vacuumEntity, platform, tiles, language, tilesToOverride, variables); - } else { - return new Promise(resolve => - resolve( - this.addTilesFromAttributes( - hass, - state, - vacuumEntity, - platform, - tiles, - language, - tilesToOverride, - variables, - ), - ), - ); + return tiles; } + const context = new TilesGeneratorContext(tilesToOverride); + context.addTiles(this.getCommonTiles(state, vacuumEntity, language)); + context.addTiles(await this.getTilesFromSensors(hass, vacuumEntity, platform, language, variables)); + context.addTiles(this.getTilesFromAttributes(hass, state, vacuumEntity, platform, language, variables)); + return context.tiles; } private static getCommonTiles(state: HassEntity, vacuumEntity: string, language: Language): TileConfig[] { @@ -126,29 +140,24 @@ export class TilesGenerator { return tiles; } - private static addTilesFromAttributes( + private static getTilesFromAttributes( hass: HomeAssistantFixed, state: HassEntity, vacuumEntity: string, platform: string, - tiles: TileConfig[], language: Language, - tilesToOverride: Array, variables: VariablesStorage, ): TileConfig[] { - PlatformGenerator.getTilesFromAttributesTemplates(platform) + return PlatformGenerator.getTilesFromAttributesTemplates(platform) .filter(t => t.attribute in state.attributes) - .forEach(t => tiles.push(this.mapAttributeToTile(hass, vacuumEntity, t, language, variables))); - return this.replaceDuplicates(tiles, tilesToOverride); + .map(t => this.mapAttributeToTile(hass, vacuumEntity, t, language, variables)); } - private static async addTilesFromSensors( + private static async getTilesFromSensors( hass: HomeAssistantFixed, vacuumEntityId: string, platform: string, - tiles: TileConfig[], language: Language, - tilesToOverride: Array, variables: VariablesStorage, ): Promise { let entityRegistryEntries; @@ -160,15 +169,14 @@ export class TilesGenerator { entityRegistryEntries = []; } if (entityRegistryEntries.length > 0) { - PlatformGenerator.getTilesFromSensorsTemplates(platform) + return PlatformGenerator.getTilesFromSensorsTemplates(platform) .map(t => ({ tile: t, entity: entityRegistryEntries.filter(e => e.unique_id.match(t.unique_id_regex)), })) .flatMap(v => v.entity.map(e => this.mapEntryToTile(hass, vacuumEntityId, e, v.tile, language, variables))) - .forEach(t => tiles.push(t)); } - return new Promise(resolve => resolve(this.replaceDuplicates(tiles, tilesToOverride))); + return []; } private static mapEntryToTile( @@ -293,17 +301,4 @@ export class TilesGenerator { return defaultVariables; } - private static replaceDuplicates( - autogeneratedTiles: Array, - tilesToOverride: Array, - ): Array { - const overriddenTileIds = tilesToOverride.map(t => t.tile_id ?? ""); - for (let i = 0; i < autogeneratedTiles.length; i++) { - const tileId = autogeneratedTiles[i].tile_id ?? ""; - if (overriddenTileIds.includes(tileId)) { - autogeneratedTiles[i] = tilesToOverride[overriddenTileIds.indexOf(tileId)]; - } - } - return autogeneratedTiles; - } } diff --git a/src/types/types.ts b/src/types/types.ts index d2a30f67..11b9833d 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -95,7 +95,6 @@ export interface PlatformTemplate { readonly defaultTemplates: string[]; readonly templates: { [templateName: string]: MapModeConfig }; }; - readonly sensors_from?: string; readonly tiles: { readonly from_attributes?: TileFromAttributeTemplate[]; readonly from_sensors?: TileFromSensorTemplate[]; From 9d23a948f2d31825725fa15796b47ef2a09e55cc Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Wed, 15 Mar 2023 03:35:25 +0100 Subject: [PATCH 030/100] Improve overriding tiles --- src/config-validators.ts | 3 +++ src/model/generators/tiles-generator.ts | 8 +++++++- src/types/types.ts | 1 + src/xiaomi-vacuum-map-card.ts | 3 ++- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/config-validators.ts b/src/config-validators.ts index b8b806cd..5f9ae216 100644 --- a/src/config-validators.ts +++ b/src/config-validators.ts @@ -78,6 +78,9 @@ function validateTileConfig(config: TileConfig): TranslatableString[] { return ["validation.preset.tiles.invalid"]; } const errors: TranslatableString[] = []; + if (config.replace_config) { + return errors; + } if (!config.entity && !config.internal_variable) { errors.push("validation.preset.tiles.entity.missing"); } diff --git a/src/model/generators/tiles-generator.ts b/src/model/generators/tiles-generator.ts index f89c9c85..9a148542 100644 --- a/src/model/generators/tiles-generator.ts +++ b/src/model/generators/tiles-generator.ts @@ -35,7 +35,13 @@ class TilesGeneratorContext { return; } if (tile.tile_id && this._userDefinedTiles.some(t => t.tile_id === tile.tile_id)) { - this._userDefinedTiles.filter(t => t.tile_id === tile.tile_id).forEach(t => this._tiles.push(t)); + this._userDefinedTiles.filter(t => t.tile_id === tile.tile_id).forEach(t => { + if (t.replace_config) { + this._tiles.push({ ...tile, ...t }); + } else { + this._tiles.push(t); + } + }); return; } this._tiles.push(tile); diff --git a/src/types/types.ts b/src/types/types.ts index 11b9833d..2078a32f 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -131,6 +131,7 @@ export interface TileConfig extends ActionableObjectConfig, ConditionalObjectCon readonly multiplier?: number; readonly precision?: number; readonly translations?: Record; + readonly replace_config?: boolean; } export interface ActionableObjectConfig { diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index 7a43f75a..380cf31c 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -568,7 +568,8 @@ export class XiaomiVacuumMapCard extends LitElement { const tilesIds = tiles.map(t => t.tile_id ?? ""); return [ ...tiles, - ...(config.tiles ?? []).filter(t => t.tile_id === undefined || !tilesIds.includes(t.tile_id)), + ...(config.tiles ?? []).filter(t => !t.replace_config && + (t.tile_id === undefined || !tilesIds.includes(t.tile_id))), ]; }) : new Promise(resolve => resolve(config.tiles ?? [])); From d167011dd5ed7e7b88d62e70ec57412f9da5a20f Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Wed, 15 Mar 2023 03:46:49 +0100 Subject: [PATCH 031/100] Add possibility to sort tiles --- src/model/generators/tiles-generator.ts | 10 ++++++++++ src/types/types.ts | 1 + src/xiaomi-vacuum-map-card.ts | 1 + 3 files changed, 12 insertions(+) diff --git a/src/model/generators/tiles-generator.ts b/src/model/generators/tiles-generator.ts index 9a148542..4a47c92f 100644 --- a/src/model/generators/tiles-generator.ts +++ b/src/model/generators/tiles-generator.ts @@ -308,3 +308,13 @@ export class TilesGenerator { } } + +export function sortTiles(t1: TileConfig, t2: TileConfig): number { + if (t1.order === undefined && t2.order === undefined) + return 0; + if (t1.order === undefined) + return 1; + if (t2.order === undefined) + return -1; + return t1.order - t2.order; +} \ No newline at end of file diff --git a/src/types/types.ts b/src/types/types.ts index 2078a32f..eda5d58f 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -132,6 +132,7 @@ export interface TileConfig extends ActionableObjectConfig, ConditionalObjectCon readonly precision?: number; readonly translations?: Record; readonly replace_config?: boolean; + readonly order?: number; } export interface ActionableObjectConfig { diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index 380cf31c..4ff74fb5 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -574,6 +574,7 @@ export class XiaomiVacuumMapCard extends LitElement { }) : new Promise(resolve => resolve(config.tiles ?? [])); tilesGenerated + .then(t => t.sort(sortTiles)) .then(tiles => this._setPreset({ ...config, tiles: tiles, icons: icons })) .then(() => setTimeout(() => this.requestUpdate(), 100)) .then(() => this._setCurrentMode(0, false)); From 08385b662e383b693074293ea16c8bddf615dd9d Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Wed, 15 Mar 2023 03:52:21 +0100 Subject: [PATCH 032/100] Fix imports --- src/xiaomi-vacuum-map-card.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index 4ff74fb5..c7feb625 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -62,7 +62,7 @@ import { MapMode } from "./model/map_mode/map-mode"; import { SelectionType } from "./model/map_mode/selection-type"; import { RepeatsType } from "./model/map_mode/repeats-type"; import { PlatformGenerator } from "./model/generators/platform-generator"; -import { TilesGenerator } from "./model/generators/tiles-generator"; +import { sortTiles, TilesGenerator } from "./model/generators/tiles-generator"; import { IconListGenerator } from "./model/generators/icon-list-generator"; import { TileRenderer } from "./renderers/tile-renderer"; import { IconRenderer } from "./renderers/icon-renderer"; From 05e3abbe03f6e31a7a598810a6c18c44dd0dfe67 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Wed, 15 Mar 2023 04:05:33 +0100 Subject: [PATCH 033/100] Improve detecting editor --- src/xiaomi-vacuum-map-card.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index c7feb625..5b980fc7 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -840,7 +840,12 @@ export class XiaomiVacuumMapCard extends LitElement { } private _isInEditor(): boolean { - return this.parentElement?.tagName?.toLowerCase() === "hui-card-preview"; + function isInEditor(e: HTMLElement): boolean { + return e.parentElement?.tagName?.toLowerCase() === "hui-card-preview" || ( + e.parentElement != null && isInEditor(e.parentElement) + ); + } + return isInEditor(this); } private _handleAutogeneratedConfigGet(): void { From df73350008e3c9d1368317d9b804ddc214f0b7f9 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Wed, 15 Mar 2023 04:26:06 +0100 Subject: [PATCH 034/100] Improve marotoweb/viomise template --- .../generators/platform_templates/marotoweb_viomise.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/model/generators/platform_templates/marotoweb_viomise.json b/src/model/generators/platform_templates/marotoweb_viomise.json index 66ea7f77..436b312d 100644 --- a/src/model/generators/platform_templates/marotoweb_viomise.json +++ b/src/model/generators/platform_templates/marotoweb_viomise.json @@ -68,15 +68,16 @@ "name": "map_mode.vacuum_clean_segment", "icon": "mdi:floor-plan", "selection_type": "ROOM", - "max_selections": 1, + "max_selections": 5, "repeats_type": "EXTERNAL", "max_repeats": 3, "service_call_schema": { "service": "vacuum.send_command", + "evaluate_data_as_template": true, "service_data": { "entity_id": "[[entity_id]]", "command": "set_mode_withroom", - "params": "[ 0, [[repeats]], 1, [[selection_unwrapped]] ]|[[jsonify]]" + "params": "[ \"{{state_attr('[[entity_id]]', 'is_mop')}}\", [[repeats]], [[selection_size]], [[selection_unwrapped]] ]|[[jsonify]]" } } } From 835b38fe6ce4cd2267d67b82c29ffa6bd2cc1b58 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Wed, 15 Mar 2023 04:30:18 +0100 Subject: [PATCH 035/100] Improve marotoweb/viomise template --- .../generators/platform_templates/marotoweb_viomise.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/model/generators/platform_templates/marotoweb_viomise.json b/src/model/generators/platform_templates/marotoweb_viomise.json index 436b312d..d68f193b 100644 --- a/src/model/generators/platform_templates/marotoweb_viomise.json +++ b/src/model/generators/platform_templates/marotoweb_viomise.json @@ -69,15 +69,14 @@ "icon": "mdi:floor-plan", "selection_type": "ROOM", "max_selections": 5, - "repeats_type": "EXTERNAL", - "max_repeats": 3, + "repeats_type": "NONE", "service_call_schema": { "service": "vacuum.send_command", "evaluate_data_as_template": true, "service_data": { "entity_id": "[[entity_id]]", "command": "set_mode_withroom", - "params": "[ \"{{state_attr('[[entity_id]]', 'is_mop')}}\", [[repeats]], [[selection_size]], [[selection_unwrapped]] ]|[[jsonify]]" + "params": "[ \"{{state_attr('[[entity_id]]', 'is_mop')}}\", 1, [[selection_size]], [[selection_unwrapped]] ]|[[jsonify]]" } } } From 117c7d59b4eb837d368d17b86969dc8cd33949f5 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 28 Mar 2023 04:31:49 +0200 Subject: [PATCH 036/100] Extend handling templates in icons and tiles --- .../humbertogontijo_homeassistant-roborock.json | 8 ++++---- .../platform_templates/xiaomiMiio.json | 16 ++++++++-------- src/model/generators/tiles-generator.ts | 2 +- src/model/map_mode/service-call-schema.ts | 2 +- src/model/map_mode/templatable-value.ts | 2 +- src/utils.ts | 16 +++++++++++++++- src/xiaomi-vacuum-map-card.ts | 10 +++++----- 7 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json index e41357d5..480087ed 100644 --- a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json +++ b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json @@ -113,7 +113,7 @@ "service_data": { "command": "reset_consumable", "params": "sensor_dirty_time", - "entity_id": "[[vacuum_entity]]" + "entity_id": "[[vacuum_entity_id]]" } } }, @@ -130,7 +130,7 @@ "service_data": { "command": "reset_consumable", "params": "filter_work_time", - "entity_id": "[[vacuum_entity]]" + "entity_id": "[[vacuum_entity_id]]" } } }, @@ -147,7 +147,7 @@ "service_data": { "command": "reset_consumable", "params": "main_brush_work_time", - "entity_id": "[[vacuum_entity]]" + "entity_id": "[[vacuum_entity_id]]" } } }, @@ -164,7 +164,7 @@ "service_data": { "command": "reset_consumable", "params": "side_brush_work_time", - "entity_id": "[[vacuum_entity]]" + "entity_id": "[[vacuum_entity_id]]" } } }, diff --git a/src/model/generators/platform_templates/xiaomiMiio.json b/src/model/generators/platform_templates/xiaomiMiio.json index 14cd6e21..795d917e 100644 --- a/src/model/generators/platform_templates/xiaomiMiio.json +++ b/src/model/generators/platform_templates/xiaomiMiio.json @@ -113,7 +113,7 @@ "service_data": { "command": "reset_consumable", "params": "sensor_dirty_time", - "entity_id": "[[vacuum_entity]]" + "entity_id": "[[vacuum_entity_id]]" } } }, @@ -130,7 +130,7 @@ "service_data": { "command": "reset_consumable", "params": "filter_work_time", - "entity_id": "[[vacuum_entity]]" + "entity_id": "[[vacuum_entity_id]]" } } }, @@ -147,7 +147,7 @@ "service_data": { "command": "reset_consumable", "params": "main_brush_work_time", - "entity_id": "[[vacuum_entity]]" + "entity_id": "[[vacuum_entity_id]]" } } }, @@ -164,7 +164,7 @@ "service_data": { "command": "reset_consumable", "params": "side_brush_work_time", - "entity_id": "[[vacuum_entity]]" + "entity_id": "[[vacuum_entity_id]]" } } }, @@ -189,7 +189,7 @@ "service_data": { "command": "reset_consumable", "params": "sensor_dirty_time", - "entity_id": "[[vacuum_entity]]" + "entity_id": "[[vacuum_entity_id]]" } } }, @@ -206,7 +206,7 @@ "service_data": { "command": "reset_consumable", "params": "filter_work_time", - "entity_id": "[[vacuum_entity]]" + "entity_id": "[[vacuum_entity_id]]" } } }, @@ -223,7 +223,7 @@ "service_data": { "command": "reset_consumable", "params": "main_brush_work_time", - "entity_id": "[[vacuum_entity]]" + "entity_id": "[[vacuum_entity_id]]" } } }, @@ -240,7 +240,7 @@ "service_data": { "command": "reset_consumable", "params": "side_brush_work_time", - "entity_id": "[[vacuum_entity]]" + "entity_id": "[[vacuum_entity_id]]" } } }, diff --git a/src/model/generators/tiles-generator.ts b/src/model/generators/tiles-generator.ts index 4a47c92f..3502adc9 100644 --- a/src/model/generators/tiles-generator.ts +++ b/src/model/generators/tiles-generator.ts @@ -302,7 +302,7 @@ export class TilesGenerator { ): VariablesStorage { const defaultVariables: VariablesStorage = {}; defaultVariables[TemplatableTileValue.ENTITY_ID] = entityId ?? vacuumEntity; - defaultVariables[TemplatableTileValue.VACUUM_ENTITY] = vacuumEntity; + defaultVariables[TemplatableTileValue.VACUUM_ENTITY_ID] = vacuumEntity; defaultVariables[TemplatableTileValue.ATTRIBUTE] = attribute ?? ""; return defaultVariables; } diff --git a/src/model/map_mode/service-call-schema.ts b/src/model/map_mode/service-call-schema.ts index 95456f67..7e0d2b1a 100644 --- a/src/model/map_mode/service-call-schema.ts +++ b/src/model/map_mode/service-call-schema.ts @@ -35,7 +35,7 @@ export class ServiceCallSchema { ); } - private static getDefaultVariables(entityId: string, selection: unknown[], repeats: number): VariablesStorage { + public static getDefaultVariables(entityId: string, selection: unknown[], repeats: number): VariablesStorage { const variables: VariablesStorage = {}; variables[TemplatableValue.ENTITY_ID] = entityId; variables[TemplatableValue.SELECTION] = selection; diff --git a/src/model/map_mode/templatable-value.ts b/src/model/map_mode/templatable-value.ts index f64b8f3f..be63c126 100644 --- a/src/model/map_mode/templatable-value.ts +++ b/src/model/map_mode/templatable-value.ts @@ -10,6 +10,6 @@ export enum TemplatableValue { export enum TemplatableTileValue { ENTITY_ID = "entity_id", - VACUUM_ENTITY = "vacuum_entity", + VACUUM_ENTITY_ID = "vacuum_entity_id", ATTRIBUTE = "attribute", } diff --git a/src/utils.ts b/src/utils.ts index 18788248..a7a9a111 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -21,6 +21,8 @@ import { MousePosition } from "./model/map_objects/mouse-position"; import { XiaomiVacuumMapCard } from "./xiaomi-vacuum-map-card"; import { Modifier } from "./model/map_mode/modifier"; import { HomeAssistantFixed } from "./types/fixes"; +import { ServiceCallSchema } from "./model/map_mode/service-call-schema"; +import { TemplatableTileValue } from "./model/map_mode/templatable-value"; export function stopEvent(event: MouseEvent | TouchEvent): void { event.preventDefault(); @@ -155,7 +157,18 @@ export function handleActionWithConfig( ): (ev: ActionHandlerEvent) => void { return (ev: ActionHandlerEvent): void => { if (node.hass && config && ev.detail.action) { - handleAction(node, node.hass as unknown as HomeAssistant, config, ev.detail.action); + const currentPreset = node._getCurrentPreset(); + const currentMode = node._getCurrentMode(); + const tileVariables = {}; + tileVariables[TemplatableTileValue.VACUUM_ENTITY_ID] = currentPreset.entity; + if (config.hasOwnProperty("attribute")) { + tileVariables[TemplatableTileValue.ATTRIBUTE] = config["attribute"]; + } + const entity_id = config.hasOwnProperty("entity") ? config["entity"]: currentPreset.entity; + const { selection, variables } = node._getSelection(currentMode); + const defaultVariables = ServiceCallSchema.getDefaultVariables(entity_id, selection, node.repeats); + const filled = getFilledTemplate(config as Record, defaultVariables, tileVariables, node.internalVariables, variables); + handleAction(node, node.hass as unknown as HomeAssistant, filled as ActionableObjectConfig, ev.detail.action); } }; } @@ -276,6 +289,7 @@ export function getFilledTemplate( for (const variablesStorage of variablesStorages) { variables = { ...variablesStorage, ...variables }; } + console.log("VARIABLES", variables); const keyReplacer = v => getReplacedValue(v, variables); replaceInTarget(target, keyReplacer); return target; diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index 5b980fc7..b38e27d0 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -107,12 +107,12 @@ export class XiaomiVacuumMapCard extends LitElement { @state() private mapScale!: number; @state() private mapX!: number; @state() private mapY!: number; - @state() private repeats = 1; + @state() public repeats = 1; @state() private selectedMode = 0; @state() private mapLocked = false; @state() private configErrors: string[] = []; @state() private connected = false; - @state() private internalVariables = {}; + @state() public internalVariables = {}; private currentPreset!: CardPresetConfig; private watchedEntities: string[] = []; private selectedManualRectangles: ManualRectangle[] = []; @@ -420,7 +420,7 @@ export class XiaomiVacuumMapCard extends LitElement { this._updateElements(); } - private _getCurrentPreset(): CardPresetConfig { + public _getCurrentPreset(): CardPresetConfig { return this.currentPreset; } @@ -763,11 +763,11 @@ export class XiaomiVacuumMapCard extends LitElement { this._selectionChanged(); } - private _getCurrentMode(): MapMode { + public _getCurrentMode(): MapMode { return this.modes[this.selectedMode]; } - private _getSelection(mode: MapMode): { selection: unknown[]; variables: VariablesStorage } { + public _getSelection(mode: MapMode): { selection: unknown[]; variables: VariablesStorage } { if (!mode) { return { selection: [], variables: {} }; } From f484ee8764740f2f8654a1f28ad325af0727efec Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 28 Mar 2023 04:34:58 +0200 Subject: [PATCH 037/100] Cleanup --- src/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index a7a9a111..7d0fcc0a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -289,7 +289,6 @@ export function getFilledTemplate( for (const variablesStorage of variablesStorages) { variables = { ...variablesStorage, ...variables }; } - console.log("VARIABLES", variables); const keyReplacer = v => getReplacedValue(v, variables); replaceInTarget(target, keyReplacer); return target; From dae7b397b371ba1f268f235a0b1594405c3650da Mon Sep 17 00:00:00 2001 From: Gabriel Colceriu <33186056+gabrielcolceriu@users.noreply.github.com> Date: Tue, 28 Mar 2023 05:44:57 +0300 Subject: [PATCH 038/100] Add Romanian translation (#558) * Create ro.json * Update ro.json * Update localize.ts * Update ro.json * Update ro.json * Update ro.json * Update README.md --- README.md | 1 + src/localize/languages/ro.json | 253 +++++++++++++++++++++++++++++++++ src/localize/localize.ts | 2 + 3 files changed, 256 insertions(+) create mode 100644 src/localize/languages/ro.json diff --git a/README.md b/README.md index 64b0fb52..18965e1a 100644 --- a/README.md +++ b/README.md @@ -650,6 +650,7 @@ Currently, this card contains translations for following languages: * `nl` - Dutch (Nederlands) * `pl` - Polish (Polski) * `pt-BR` - Brazilian Portuguese (Português Brasileiro) +* `ro` - Romanian (Română) * `ru` - Russian (Русский) * `sk` - Slovak (Slovenčina) * `sv` - Swedish (Svenska) diff --git a/src/localize/languages/ro.json b/src/localize/languages/ro.json new file mode 100644 index 00000000..481c8c1a --- /dev/null +++ b/src/localize/languages/ro.json @@ -0,0 +1,253 @@ +{ + "common": { + "version": "Versiune", + "invalid_configuration": "Configurație invalidă {0}", + "description": "Un card care vă lasă să vă configurați aspiratorul", + "old_configuration": "Configurație veche detectată. Modificați fisierul de configurare la ultima versiune sau creați un nou card de la inceput.", + "old_configuration_migration_link": "Ghid de migrare" + }, + "map_mode": { + "invalid": "Template invalid!", + "vacuum_goto": "Punct și Mergi", + "vacuum_goto_predefined": "Puncte", + "vacuum_clean_segment": "Camere", + "vacuum_clean_point": "Punct de curățare", + "vacuum_clean_point_predefined": "Puncte predefinite", + "vacuum_clean_zone": "Zonă de curățare", + "vacuum_clean_zone_predefined": "Listă zone", + "vacuum_follow_path": "Cale" + }, + "validation": { + "preset": { + "entity": { + "missing": "Proprietate lipsă: entity" + }, + "preset_name": { + "missing": "Proprietate lipsă: preset_name" + }, + "platform": { + "invalid": "Invalid vacuum platform: {0}" + }, + "map_source": { + "missing": "Missing property: map_source", + "none_provided": "No camera neither image provided", + "ambiguous": "Only one map source allowed" + }, + "calibration_source": { + "missing": "Missing property: calibration_source", + "ambiguous": "Only one calibration source allowed", + "none_provided": "No calibration source provided", + "calibration_points": { + "invalid_number": "Exactly 3 or 4 calibration points required", + "missing_map": "Each calibration point must contain map coordinates", + "missing_vacuum": "Each calibration point must contain vacuum coordinates", + "missing_coordinate": "Map and vacuum calibration points must contain both x and y coordinate" + } + }, + "icons": { + "invalid": "Eroare în configurare: icons", + "icon": { + "missing": "Fiecare intrare din lista de pictograme trebuie să conțină proprietatea pictogramei" + } + }, + "tiles": { + "invalid": "Eroare în configurare: tiles", + "entity": { + "missing": "Each entry of tiles list must contain entity or internal variable" + }, + "label": { + "missing": "Fiecare intrare a listei de plăci trebuie să conțină o etichetă" + } + }, + "map_modes": { + "invalid": "Eroare în configurare: map_modes", + "icon": { + "missing": "Lipsește pictograma modului hartă" + }, + "name": { + "missing": "Lipsește numele modului hartă" + }, + "template": { + "invalid": "Template invalid: {0}" + }, + "predefined_selections": { + "not_applicable": "Modul {0} nu suportă selecțiile predefinite", + "zones": { + "missing": "Lipsă zone în configurație", + "invalid_parameters_number": "Fiecare zonă trebuie să aibă 4 parametri" + }, + "points": { + "position": { + "missing": "Lipsă puncte în configurație", + "invalid_parameters_number": "Fiecare punct trebuie să aibă 2 parametri" + } + }, + "rooms": { + "id": { + "missing": "Lipsă id cameră", + "invalid_format": "Id cameră invalid: {0}" + }, + "outline": { + "invalid_parameters_number": "Fiecare punct al conturului camerei trebuie să aibă 2 parametri" + } + }, + "label": { + "x": { + "missing": "Eticheta trebuie să aibă proprietatea x" + }, + "y": { + "missing": "Eticheta trebuie să aibă proprietatea y" + }, + "text": { + "missing": "Eticheta trebuie să aibă proprietatea text" + } + }, + "icon": { + "x": { + "missing": "Pictograma trebuie să aibă proprietatea x" + }, + "y": { + "missing": "Pictograma trebuie să aibă proprietatea y" + }, + "name": { + "missing": "Pictograma trebuie să aibă proprietatea nume" + } + } + }, + "service_call_schema": { + "missing": "Lipsește schema de apel de service", + "service": { + "missing": "Schema de apel de service trebuie să conțină serviciu", + "invalid": "Serviciu invalid: {0}" + } + } + } + }, + "invalid_entities": "Entități invalide:", + "invalid_calibration": "Calibrare nevalidă, vă rugăm să vă verificați configurația" + }, + "tile": { + "status": { + "label": "Stare", + "value": { + "starting": "Pornire", + "charger disconnected": "Încărcătorul a fost deconectat", + "idle": "Inactiv", + "remote control active": "Telecomanda activa", + "cleaning": "Curățare", + "returning home": "Întoarcere acasă", + "manual mode": "Mod manual", + "charging": "Se încarcă", + "charging problem": "Probleme de încărcare", + "paused": "În pauză", + "spot cleaning": "Curățarea petelor", + "error": "Eroare", + "shutting down": "Se închide", + "updating": "Se updatează", + "docking": "Andocare", + "going to target": "Mergând la țintă", + "zoned cleaning": "Curățare zonă", + "segment cleaning": "Curățare segment", + "emptying the bin": "Golirea coșului de gunoi", + "charging complete": "Încarcare completă", + "device offline": "Dispozitiv offline" + } + }, + "battery_level": { + "label": "Baterie" + }, + "fan_speed": { + "label": "Viteza ventilatorului", + "value": { + "silent": "Tăcut", + "standard": "Standard", + "medium": "Mediu", + "turbo": "Turbo", + "auto": "Auto", + "gentle": "Blând" + } + }, + "sensor_dirty_left": { + "label": "Timp rămas senzori" + }, + "filter_left": { + "label": "Timp rămas filtru" + }, + "main_brush_left": { + "label": "Timp rămas peria principală" + }, + "side_brush_left": { + "label": "Timp rămas peria laterală" + }, + "cleaning_count": { + "label": "Număr de curățări" + }, + "cleaned_area": { + "label": "Zonă curățată" + }, + "cleaning_time": { + "label": "Timp de curățare" + }, + "mop_left": { + "label": "Timp rămas mop" + }, + "bin_full": { + "label": "Coș de gunoi plin", + "value": { + "true": "Da", + "false": "Nu" + } + }, + "bin_present": { + "label": "Coș de gunoi prezent", + "value": { + "true": "Da", + "false": "Nu" + } + } + }, + "icon": { + "vacuum_start": "Start", + "vacuum_pause": "Pauză", + "vacuum_stop": "Stop", + "vacuum_return_to_base": "Întoarceți-vă la bază", + "vacuum_clean_spot": "Curățare pată", + "vacuum_locate": "Localizați", + "vacuum_set_fan_speed": "Schimbă viteza ventilatorului" + }, + "unit": { + "hour_shortcut": "h", + "meter_shortcut": "m", + "meter_squared_shortcut": "m²", + "minute_shortcut": "min" + }, + "popups": { + "success": "Succes!", + "no_selection": "Nu este oferită nicio selecție", + "failed": "Nu s-a putut apela serviciul" + }, + "editor": { + "description": { + "before_link": "Acest editor vizual acceptă doar o configurație de bază cu o entitate de cameră creată folosind ", + "link_text": "Xiaomi Cloud Map Extractor", + "after_link": ". Pentru o configurare mai avansată, utilizați modul YAML." + }, + "label": { + "name": "Titlu (opțional)", + "entity": "Entitate aspirator (necesar)", + "camera": "Entitate camera (necesar)", + "vacuum_platform": "Platformă aspirator (necesar)", + "map_locked": "Hartă blocată (opțional)", + "two_finger_pan": "Mișcare hartă cu două degete (opțional)", + "platforms_documentation": "Documentația platformei alese ({0})", + "selection": "Selecție:", + "copy": "Copiază", + "copied": "Copiat!", + "set_static_config": "Generează config static", + "config_set": "Configurare setată!\nDeschide editorul de configurare pentru a-l ajusta.", + "config_set_failed": "Nu s-a putut actualiza configurația.", + "generate_rooms_config": "Generați configurația camerelor", + "copy_service_call": "Copiere apel de serviciu" + } + } +} diff --git a/src/localize/localize.ts b/src/localize/localize.ts index c60211c8..50df4372 100644 --- a/src/localize/localize.ts +++ b/src/localize/localize.ts @@ -14,6 +14,7 @@ import * as nbNo from "./languages/nb-NO.json"; import * as nl from "./languages/nl.json"; import * as pl from "./languages/pl.json"; import * as ptBr from "./languages/pt-BR.json"; +import * as ro from "./languages/ro.json"; import * as ru from "./languages/ru.json"; import * as sk from "./languages/sk.json"; import * as sv from "./languages/sv.json"; @@ -44,6 +45,7 @@ const languages: Record = { nl: nl, pl: pl, "pt-BR": ptBr, + ro: ro, ru: ru, sk: sk, sv: sv, From eecb3003362644969de1729759945b6eb4c15814 Mon Sep 17 00:00:00 2001 From: tzagim <2285958+tzagim@users.noreply.github.com> Date: Wed, 29 Mar 2023 21:55:28 +0300 Subject: [PATCH 039/100] Update he translation (#565) * Update he translation * Update he.json Ooops --- src/localize/languages/he.json | 62 +++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/src/localize/languages/he.json b/src/localize/languages/he.json index f47568f4..8a25de3b 100644 --- a/src/localize/languages/he.json +++ b/src/localize/languages/he.json @@ -53,7 +53,7 @@ "tiles": { "invalid": "שגיאה בתצורה: אריחים", "entity": { - "missing_outdated_translation": "כל ערך של רשימת אריחים חייב להכיל ישות" + "missing": "כל ערך של רשימת אריחים חייב להכיל ישות" }, "label": { "missing": "כל כניסה של רשימת אריחים חייבת להכיל תווית" @@ -128,13 +128,44 @@ }, "tile": { "status": { - "label": "סטטוס" + "label": "סטטוס", + "value": { + "starting": "מתחיל", + "charger disconnected": "המטען מנותק", + "idle": "ממתין", + "remote control active": "שליטה מרוחק פעילה", + "cleaning": "מנקה", + "returning home": "חוזר הביתה", + "manual mode": "מצב ידני", + "charging": "טעינה", + "charging problem": "בעיית טעינה", + "paused": "מושהה", + "spot cleaning": "ניקוי נקודתי", + "error": "שגיאה", + "shutting down": "מתכבה", + "updating": "מתעדכן", + "docking": "בעגינה", + "going to target": "בדרך אל היעד", + "zoned cleaning": "ניקוי אזור", + "segment cleaning": "ניקוי מקטע", + "emptying the bin": "ריקון האשפה", + "charging complete": "טעינה הושלמה", + "device offline": "התקן לא מקוון" + } }, "battery_level": { "label": "סוללה" }, "fan_speed": { - "label": "מהירות מאוורר" + "label": "מהירות מאוורר", + "value": { + "silent": "שקט", + "standard": "סטנדרט", + "medium": "בינוני", + "turbo": "טורבו", + "auto": "אוטומט", + "gentle": "עדין" + } }, "sensor_dirty_left": { "label": "נותר לחיישנים" @@ -159,6 +190,20 @@ }, "mop_left": { "label": "נותר למטלית" + }, + "bin_full": { + "label": "פח מלא", + "value": { + "true": "כן", + "false": "לא" + } + }, + "bin_present": { + "label": "מיכל קיים", + "value": { + "true": "כן", + "false": "לא" + } } }, "icon": { @@ -193,7 +238,16 @@ "camera": "יישות מצלמה (נדרש)", "vacuum_platform": "פלטפורמת שואב (נדרש)", "map_locked": "נעילת מפה (אופציונלי)", - "two_finger_pan": "צביטת שתי אצבעות (אופציונלי)" + "two_finger_pan": "צביטת שתי אצבעות (אופציונלי)", + "platforms_documentation": "תיעוד הפלטפורמה שנבחרה ({0})", + "selection": "בחירה:", + "copy": "העתקה", + "copied": "הועתק!", + "set_static_config": "יצירת תצורה סטטית", + "config_set": "תצורה הוגדרה!\nיש לפתוח את עורך התצורה כדי להתאים אותו.", + "config_set_failed": "עדכון התצורה נכשל.", + "generate_rooms_config": "יצירת תצורת חדרים", + "copy_service_call": "העתקת קריאת שירות" } } } From c05d9e0270dfef0dcfca1671f95a4a4abc5ea906 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Thu, 30 Mar 2023 18:00:18 +0200 Subject: [PATCH 040/100] Include missing variables --- package.json | 2 +- src/utils.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e025ec88..1b5e9af2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiaomi-vacuum-map-card", - "version": "v2.1.3-beta", + "version": "v2.2.0-beta", "description": "Xiaomi Vacuum Map Card", "keywords": [ "home-assistant", diff --git a/src/utils.ts b/src/utils.ts index 7d0fcc0a..b00e74c5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -167,7 +167,8 @@ export function handleActionWithConfig( const entity_id = config.hasOwnProperty("entity") ? config["entity"]: currentPreset.entity; const { selection, variables } = node._getSelection(currentMode); const defaultVariables = ServiceCallSchema.getDefaultVariables(entity_id, selection, node.repeats); - const filled = getFilledTemplate(config as Record, defaultVariables, tileVariables, node.internalVariables, variables); + const filled = getFilledTemplate(config as Record, defaultVariables, tileVariables, + node.internalVariables, currentMode.variables, variables); handleAction(node, node.hass as unknown as HomeAssistant, filled as ActionableObjectConfig, ev.detail.action); } }; From 7bdb02eeb05f885e6bdffa462b704fac3fda3ccc Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Fri, 31 Mar 2023 00:55:38 +0200 Subject: [PATCH 041/100] Fix menu rendering issues --- src/renderers/modes-menu-renderer.ts | 34 +++++++++++++++++++++++++--- src/xiaomi-vacuum-map-card.ts | 8 +++---- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/renderers/modes-menu-renderer.ts b/src/renderers/modes-menu-renderer.ts index 0f063926..5d56b7cb 100644 --- a/src/renderers/modes-menu-renderer.ts +++ b/src/renderers/modes-menu-renderer.ts @@ -3,10 +3,11 @@ import { css, CSSResultGroup, html, TemplateResult } from "lit"; import { MapMode } from "../model/map_mode/map-mode"; export class ModesMenuRenderer { - public static render(modes: MapMode[], getMode: number, setMode: (number) => void): TemplateResult { + public static render(modes: MapMode[], getMode: number, setMode: (number) => void, menu?: HTMLElement): TemplateResult { const getCurrentMode = (): MapMode => modes[getMode]; return html` - +
    @@ -14,7 +15,7 @@ export class ModesMenuRenderer {
    ${getCurrentMode().name}
    ${modes.map( - (mode, index) => html` html`
    @@ -37,6 +38,29 @@ export class ModesMenuRenderer { `; } + private static updateStyles(menu: HTMLElement | undefined, items: number): void { + const div = menu?.shadowRoot?.querySelector("div") as HTMLElement; + if (menu && div) { + const height = 50; + const minDiff = (items - 1) * height + 32; + if (window.innerHeight - div.getBoundingClientRect().bottom >= minDiff) { + div.style.marginBottom = `-${height}px`; + menu.style.marginBottom = `${height}px`; + } else { + div.style.marginBottom = "0px"; + menu.style.marginBottom = "0px"; + } + const mwcMenu = menu.shadowRoot?.querySelector("mwc-menu") as HTMLElement; + if (mwcMenu) { + mwcMenu.style.zIndex = "1"; + mwcMenu.style.position = "fixed"; + } + menu.querySelectorAll("mwc-list-item").forEach((item)=> + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + item.shadowRoot!.querySelector("span")!.style.flexGrow = "1"); + } + } + public static get styles(): CSSResultGroup { return css` .modes-dropdown-menu { @@ -51,6 +75,10 @@ export class ModesMenuRenderer { .modes-dropdown-menu-button { display: inline-flex; } + + .list-item:host:host { + flex-grow: 1; + } .modes-dropdown-menu-button-button { width: 50px; diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index b38e27d0..b14b81f2 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { css, CSSResultGroup, html, LitElement, PropertyValues, svg, SVGTemplateResult, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { customElement, property, state, query } from "lit/decorators"; import { ActionHandlerEvent, forwardHaptic, LovelaceCard, LovelaceCardEditor } from "custom-card-helpers"; import "./editor"; @@ -113,6 +113,7 @@ export class XiaomiVacuumMapCard extends LitElement { @state() private configErrors: string[] = []; @state() private connected = false; @state() public internalVariables = {}; + @query(".modes-dropdown-menu") private _modesDropdownMenu?: HTMLElement; private currentPreset!: CardPresetConfig; private watchedEntities: string[] = []; private selectedManualRectangles: ManualRectangle[] = []; @@ -381,6 +382,7 @@ export class XiaomiVacuumMapCard extends LitElement { ${conditional(modes.length > 1, () => ModesMenuRenderer.render(modes, this.selectedMode, selected => this._setCurrentMode(selected), + this._modesDropdownMenu ), )} ${conditional( @@ -996,9 +998,7 @@ export class XiaomiVacuumMapCard extends LitElement { } private _updateElements(): void { - const s = this.shadowRoot - ?.querySelector(".modes-dropdown-menu") - ?.shadowRoot?.querySelector(".dropdown-content") as HTMLElement; + const s = this._modesDropdownMenu?.shadowRoot?.querySelector(".dropdown-content") as HTMLElement; if (s) { s.style.borderRadius = this._getCssProperty("--map-card-internal-big-radius"); } From a3e9f484f30bbef596e6829d80c5646f4f330a09 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Fri, 31 Mar 2023 01:01:49 +0200 Subject: [PATCH 042/100] Mitigate room cleaning issues for Xiaomi platform --- .../generators/platform_templates/xiaomiMiio.json | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/model/generators/platform_templates/xiaomiMiio.json b/src/model/generators/platform_templates/xiaomiMiio.json index 795d917e..cb4a6c03 100644 --- a/src/model/generators/platform_templates/xiaomiMiio.json +++ b/src/model/generators/platform_templates/xiaomiMiio.json @@ -6,13 +6,19 @@ "name": "map_mode.vacuum_clean_segment", "icon": "mdi:floor-plan", "selection_type": "ROOM", - "repeats_type": "REPEAT", + "repeats_type": "EXTERNAL", "max_repeats": 3, "service_call_schema": { - "service": "xiaomi_miio.vacuum_clean_segment", + "service": "vacuum.send_command", "service_data": { - "segments": "[[selection]]", - "entity_id": "[[entity_id]]" + "command": "app_segment_clean", + "entity_id": "[[entity_id]]", + "params": [ + { + "segments": "[[selection]]", + "repeat": "[[repeats]]" + } + ] } } }, From d652d1e50d934bb476ab22be9636fbc9a1806c18 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Fri, 31 Mar 2023 01:26:34 +0200 Subject: [PATCH 043/100] Fix toast in editor --- src/editor.ts | 10 ++++++---- src/localize/languages/en.json | 3 +++ src/localize/languages/pl.json | 3 +++ src/xiaomi-vacuum-map-card.ts | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/editor.ts b/src/editor.ts index 37210849..d43cf48a 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -65,8 +65,10 @@ export class XiaomiVacuumMapCardEditor extends LitElement implements Omit
    - + ${this._localize("editor.label.set_static_config")}
    -
    ${this._localize("common.version")} ${CARD_VERSION} +
    ${this._localize("common.version")} ${CARD_VERSION}
    ${ToastRenderer.render("editor")}
    `; diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 59568b51..e4caf0f2 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -254,6 +254,9 @@ "config_set_failed": "Failed to update config.", "generate_rooms_config": "Generate rooms config", "copy_service_call": "Copy service call" + }, + "alerts": { + "set_static_config": "You should use this functionality only if you want to manually adjust automatically generated configuration.\nContinue?" } } } diff --git a/src/localize/languages/pl.json b/src/localize/languages/pl.json index 84d8f0ac..dfa2553d 100644 --- a/src/localize/languages/pl.json +++ b/src/localize/languages/pl.json @@ -254,6 +254,9 @@ "config_set_failed": "Błąd aktualizacji konfiguracji.", "generate_rooms_config": "Wygeneruj konfigurację pokoi", "copy_service_call": "Skopiuj wywołanie usługi" + }, + "alerts": { + "set_static_config": "Ten przycisk powinien zostać użyty tylko wtedy, jeśli chcesz ręcznie dostosować automatycznie wygenerowaną konfigurację.\nKontynuować?" } } } diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index b14b81f2..1ab43761 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -576,7 +576,7 @@ export class XiaomiVacuumMapCard extends LitElement { }) : new Promise(resolve => resolve(config.tiles ?? [])); tilesGenerated - .then(t => t.sort(sortTiles)) + .then(t => [...t].sort(sortTiles)) .then(tiles => this._setPreset({ ...config, tiles: tiles, icons: icons })) .then(() => setTimeout(() => this.requestUpdate(), 100)) .then(() => this._setCurrentMode(0, false)); From 575f9c8caab99b834251b6de27a727c2fd946313 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Fri, 31 Mar 2023 01:49:32 +0200 Subject: [PATCH 044/100] Improve hiding modes selector --- src/const.ts | 10 +++++ src/xiaomi-vacuum-map-card.ts | 83 +++++++++++++++++++---------------- 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/src/const.ts b/src/const.ts index 42cd27bd..531534f8 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,3 +1,7 @@ +import { MapModeConfig } from "./types/types"; +import { SelectionType } from "./model/map_mode/selection-type"; +import { RepeatsType } from "./model/map_mode/repeats-type"; + export const CARD_VERSION = "v@VACUUM_MAP_CARD_VERSION_PLACEHOLDER@"; export const CARD_CUSTOM_ELEMENT_NAME = "xiaomi-vacuum-map-card"; export const EDITOR_CUSTOM_ELEMENT_NAME = "xiaomi-vacuum-map-card-editor"; @@ -14,3 +18,9 @@ export const EVENT_SERVICE_CALL_GET = "map-card-service-call-get"; export const EVENT_SERVICE_CALL = "map-card-service-call"; export const EVENT_LOVELACE_DOM = "ll-custom"; export const EVENT_LOVELACE_DOM_DETAIL = "xiaomi_vacuum_map_card"; + +export const EMPTY_MAP_MODE: MapModeConfig = { + run_immediately: true, + selection_type: SelectionType[SelectionType.ROOM], + repeats_type: RepeatsType[RepeatsType.NONE] +} \ No newline at end of file diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index 1ab43761..f244bf61 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -28,6 +28,7 @@ import { DISCONNECTED_IMAGE, DISCONNECTION_TIME, EDITOR_CUSTOM_ELEMENT_NAME, + EMPTY_MAP_MODE, EVENT_AUTOGENERATED_CONFIG, EVENT_AUTOGENERATED_CONFIG_GET, EVENT_LOVELACE_DOM, @@ -373,45 +374,49 @@ export class XiaomiVacuumMapCard extends LitElement { ${conditional(!validCalibration, () => this._showInvalidCalibrationWarning())} -
    - ${conditional( - validCalibration && (modes.length > 1 || mapControls.length > 0), - () => html` -
    -
    - ${conditional(modes.length > 1, () => - ModesMenuRenderer.render(modes, this.selectedMode, selected => - this._setCurrentMode(selected), - this._modesDropdownMenu - ), - )} - ${conditional( - mapControls.length > 0, - () => html`
    ${mapControls}
    `, - )} + ${conditional( + modes.length > 1 || mapControls.length > 0 || (icons?.length??0) !== 0 || (tiles?.length ?? 0) !== 0, + () => html` +
    + ${conditional( + validCalibration && (modes.length > 1 || mapControls.length > 0), + () => html` +
    +
    + ${conditional(modes.length > 1, () => + ModesMenuRenderer.render(modes, this.selectedMode, selected => + this._setCurrentMode(selected), + this._modesDropdownMenu + ), + )} + ${conditional( + mapControls.length > 0, + () => html`
    ${mapControls}
    `, + )} +
    -
    - `, - )} - ${conditional( - (icons?.length ?? 0) !== 0, - () => html` -
    -
    - ${icons?.map(icon => IconRenderer.render(icon, this))} + `, + )} + ${conditional( + (icons?.length ?? 0) !== 0, + () => html` +
    +
    + ${icons?.map(icon => IconRenderer.render(icon, this))} +
    -
    - `, - )} - ${conditional( - (tiles?.length ?? 0) !== 0, - () => html` -
    - ${tiles?.map(sensor => TileRenderer.render(sensor, this.internalVariables, this))} -
    - `, - )} -
    + `, + )} + ${conditional( + (tiles?.length ?? 0) !== 0, + () => html` +
    + ${tiles?.map(sensor => TileRenderer.render(sensor, this.internalVariables, this))} +
    + `, + )} +
    ` + )} ${ToastRenderer.render("map-card")} `; @@ -531,9 +536,9 @@ export class XiaomiVacuumMapCard extends LitElement { if (this.hass) this._updateCalibration(config); const vacuumPlatform = config.vacuum_platform ?? "default"; this.modes = ( - (config.map_modes?.length ?? 0) === 0 + (config.map_modes?.length ?? -1) === -1 ? PlatformGenerator.generateDefaultModes(vacuumPlatform) - : config.map_modes ?? [] + : config.map_modes ?? [EMPTY_MAP_MODE] ).map(m => new MapMode(vacuumPlatform, m, this.config.language)); this.presetIndex = index; From 7665e67820a9b51d6c87f0dfd8aab403cadcdd41 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Fri, 31 Mar 2023 02:18:07 +0200 Subject: [PATCH 045/100] Added possibility to use dynamic tile icon --- src/model/generators/tiles-generator.ts | 2 +- src/renderers/tile-renderer.ts | 13 +++++++++++-- src/types/types.ts | 1 + src/utils.ts | 3 +++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/model/generators/tiles-generator.ts b/src/model/generators/tiles-generator.ts index 3502adc9..353df039 100644 --- a/src/model/generators/tiles-generator.ts +++ b/src/model/generators/tiles-generator.ts @@ -118,7 +118,7 @@ export class TilesGenerator { entity: vacuumEntity, label: localize("tile.battery_level.label", language), attribute: "battery_level", - icon: state.attributes["battery_icon"], + icon_source: `${vacuumEntity}.attributes.battery_icon`, unit: "%", }); if ("battery_level" in state.attributes && !("battery_icon" in state.attributes)) diff --git a/src/renderers/tile-renderer.ts b/src/renderers/tile-renderer.ts index f9d14358..6b46842d 100644 --- a/src/renderers/tile-renderer.ts +++ b/src/renderers/tile-renderer.ts @@ -21,7 +21,7 @@ export class TileRenderer { const stateObj = config.entity ? card.hass.states[config.entity] : undefined; const title = this.getTileLabel(card.hass, config, stateObj); const value = this.getTileValue(card.hass, config, internalVariables, stateObj); - const icon = this.getIcon(config, stateObj); + const icon = this.getIcon(card.hass, config, stateObj); const domain = stateObj ? computeStateDomain(stateObj) : undefined; return html` @@ -100,7 +100,16 @@ export class TileRenderer { return `${value}${unit}`; } - private static getIcon(config: TileConfig, stateObject?: HassEntity) { + private static getIcon(hass: HomeAssistantFixed, config: TileConfig, stateObject?: HassEntity) { + if (config.icon_source) { + const split = config.icon_source.split(".attributes."); + const entity = hass.states[split[0]]; + let icon = entity.state; + if (split.length === 2) { + icon = entity.attributes[split[1]]; + } + return icon; + } if (config.icon === undefined && stateObject) { return stateObject.attributes.icon ?? null; } diff --git a/src/types/types.ts b/src/types/types.ts index eda5d58f..e2ca41b2 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -124,6 +124,7 @@ export interface TileConfig extends ActionableObjectConfig, ConditionalObjectCon readonly label?: string; readonly tooltip?: string; readonly icon?: string; + readonly icon_source?: string; readonly internal_variable?: string; readonly entity?: string; readonly attribute?: string; diff --git a/src/utils.ts b/src/utils.ts index b00e74c5..7180d737 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -83,6 +83,9 @@ export function getWatchedEntitiesForPreset(config: CardPresetConfig, language: (config.tiles ?? []).forEach(s => { if (s.entity) watchedEntities.add(s.entity); }); + (config.tiles ?? []).forEach(s => { + if (s.icon_source) watchedEntities.add(s.icon_source.split(".attributes.")[0]); + }); (config.tiles ?? []) .filter(s => s.conditions) .flatMap(s => s.conditions) From d5ebb33c4f084a0e912352363582a3aea873ee62 Mon Sep 17 00:00:00 2001 From: Piotr Machowski <6118709+PiotrMachowski@users.noreply.github.com> Date: Fri, 31 Mar 2023 15:42:08 +0200 Subject: [PATCH 046/100] Adjust labels in Tasshack/dreame-vacuum integration --- src/localize/languages/en.json | 6 ++++++ src/localize/languages/pl.json | 6 ++++++ .../platform_templates/Tasshack_dreame-vacuum.json | 4 ++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index e4caf0f2..07d6a925 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -210,6 +210,12 @@ "true": "Yes", "false": "No" } + }, + "water_volume": { + "label": "Water volume" + }, + "mop_pad_humidity": { + "label": "Mop pad" } }, "icon": { diff --git a/src/localize/languages/pl.json b/src/localize/languages/pl.json index dfa2553d..30de38ce 100644 --- a/src/localize/languages/pl.json +++ b/src/localize/languages/pl.json @@ -210,6 +210,12 @@ "true": "Tak", "false": "Nie" } + }, + "water_volume": { + "label": "Poziom wody" + }, + "mop_pad_humidity": { + "label": "Wilgotność mopa" } }, "icon": { diff --git a/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json b/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json index 9085fdfa..b83c654e 100644 --- a/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json +++ b/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json @@ -95,13 +95,13 @@ { "tile_id": "water_volume", "attribute": "water_volume", - "label": "Water volume", + "label": "tile.water_volume.label", "icon": "mdi:water" }, { "tile_id": "mop_pad_humidity", "attribute": "mop_pad_humidity", - "label": "Mop pad", + "label": "tile.mop_pad_humidity.label", "icon": "mdi:water-percent" }, { From b85ab7a4a84733d80ec742bab7f01171d75ab567 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Fri, 31 Mar 2023 22:41:59 +0200 Subject: [PATCH 047/100] Add events API --- src/types/types.ts | 27 ++++++++++++------------- src/xiaomi-vacuum-map-card.ts | 38 ++++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/types/types.ts b/src/types/types.ts index e2ca41b2..f7c9d904 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -228,21 +228,20 @@ export interface RoomConfigEventData { readonly rooms: Array; } -export interface Translation { - nativeName: string; - isRTL: boolean; - hash: string; -} - -export interface TranslationMetadata { - fragments: string[]; - translations: { - [lang: string]: Translation; - }; -} - export interface EntityConfig { entity: string; attribute?: string; unit?: string; -} \ No newline at end of file +} + +export enum ActionType { + CLEANING_START = "cleaning.start", + INTERNAL_VARIABLE_SET = "internal_variable.set", + MAP_MODE_NEXT = "map_mode.next", + MAP_MODE_PREVIOUS = "map_mode.previous", + MAP_MODE_SET = "map_mode.set", + REPEATS_DECREMENT = "repeats.decrement", + REPEATS_INCREMENT = "repeats.increment", + REPEATS_SET = "repeats.set", + SELECTION_CLEAR = "selection.clear", +} diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index f244bf61..fa682be8 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -14,6 +14,7 @@ import type { XiaomiVacuumMapCardConfig, } from "./types/types"; import { + ActionType, CalibrationPoint, CardPresetConfig, MapExtractorRoom, @@ -892,7 +893,7 @@ export class XiaomiVacuumMapCard extends LitElement { } } - private _handleLovelaceDomEvent(e: Event): void { + private async _handleLovelaceDomEvent(e: Event): Promise { const lovelaceEvent = e as LovelaceDomEvent; if ( EVENT_LOVELACE_DOM_DETAIL in lovelaceEvent.detail && @@ -900,8 +901,39 @@ export class XiaomiVacuumMapCard extends LitElement { lovelaceEvent.detail[EVENT_LOVELACE_DOM_DETAIL]["action_handler_id"] === this.config.action_handler_id ) { const details = lovelaceEvent.detail[EVENT_LOVELACE_DOM_DETAIL]; - if (details["action"] === "set_internal_variable") { - this._setInternalVariable(details["variable"], details["value"]); + if (details["action"] === undefined) + return; + const action = details["action"] as ActionType; + const data = details["data"]; + const currentMode = this._getCurrentMode(); + switch (action) { + case ActionType.CLEANING_START: + await this._run(false); + break; + case ActionType.INTERNAL_VARIABLE_SET: + this._setInternalVariable(data["variable"], data["value"]); + break; + case ActionType.MAP_MODE_NEXT: + this._setCurrentMode((this.selectedMode + 1) % this.modes.length, false); + break; + case ActionType.MAP_MODE_PREVIOUS: + this._setCurrentMode((this.selectedMode - 1 + this.modes.length) % this.modes.length, false); + break; + case ActionType.MAP_MODE_SET: + this._setCurrentMode(data["index"] % this.modes.length, false); + break; + case ActionType.REPEATS_DECREMENT: + this.repeats = ((this.repeats + currentMode.maxRepeats - 2) % currentMode.maxRepeats) + 1; + break; + case ActionType.REPEATS_INCREMENT: + this.repeats = (this.repeats % currentMode.maxRepeats) + 1; + break; + case ActionType.REPEATS_SET: + this.repeats = ((data["value"] + currentMode.maxRepeats - 1) % currentMode.maxRepeats) + 1; + break; + case ActionType.SELECTION_CLEAR: + this._setCurrentMode(this.selectedMode); + break; } } } From aac5c80b966a3038a5647b455bcbaaafbc358c1e Mon Sep 17 00:00:00 2001 From: Mathijs Groothuis Date: Fri, 14 Apr 2023 16:53:04 +0200 Subject: [PATCH 048/100] Update nl.json (#569) * Update nl.json Update some improper Dutch to better wording. Voor de Nederlanders: In het Nederlands is een batterij een niet-oplaadbaar ding. Een oplaadbare variant noemen we hier een accu. In het Nederlands schrijf je alles aaneengesloten, en zonder spaties. Al het andere wordt de Engelse ziekte genoemd (https://nl.wikipedia.org/wiki/Engelse_ziekte_(taal)) * Update nl.json Add additional language improvements * Add new translations * Add new translated Dutch sentences --- src/localize/languages/nl.json | 78 +++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/src/localize/languages/nl.json b/src/localize/languages/nl.json index b3ae17ac..3c7376e9 100644 --- a/src/localize/languages/nl.json +++ b/src/localize/languages/nl.json @@ -11,10 +11,10 @@ "vacuum_goto": "Pin & Go", "vacuum_goto_predefined": "Punten", "vacuum_clean_segment": "Kamers", - "vacuum_clean_point": "Schoonmaak punten", + "vacuum_clean_point": "Schoonmaakpunten", "vacuum_clean_point_predefined": "Punten", "vacuum_clean_zone": "Zone schoonmaak", - "vacuum_clean_zone_predefined": "Zone lijst", + "vacuum_clean_zone_predefined": "Zonelijst", "vacuum_follow_path": "Pad" }, "validation": { @@ -39,9 +39,9 @@ "none_provided": "Geen kalibratiebron opgegeven", "calibration_points": { "invalid_number": "Precies 3 of 4 kalibratiepunten vereist", - "missing_map": "Elk kalibratiepunt moet kaart coördinaten bevatten", + "missing_map": "Elk kalibratiepunt moet kaart-coördinaten bevatten", "missing_vacuum": "Elk kalibratiepunt moet stofzuiger coördinaten bevatten", - "missing_coordinate": "Kaart en stofzuiger kalibratiepunten moeten zowel een x als y coödinaat bevatten" + "missing_coordinate": "Kalibratiepunten van kaart en stofzuiger moeten zowel een X- als Y-coördinaat bevatten" } }, "icons": { @@ -73,22 +73,22 @@ "predefined_selections": { "not_applicable": "Modus {0} ondersteunt geen vooraf gedefinieerde selecties", "zones": { - "missing": "Zone configuratie ontbreekt", + "missing": "Zone-configuratie ontbreekt", "invalid_parameters_number": "Elke zone moet 4 coördinaten hebben" }, "points": { "position": { - "missing": "Punten configuratie ontbreekt", + "missing": "Puntenconfiguratie ontbreekt", "invalid_parameters_number": "Elk punt moet 2 coördinaten hebben" } }, "rooms": { "id": { - "missing": "Kamer id ontbreekt", - "invalid_format": "Ongeldige kamer id: {0}" + "missing": "Kamer-id ontbreekt", + "invalid_format": "Ongeldige kamer-id: {0}" }, "outline": { - "invalid_parameters_number": "Elk punt van de kamer omtrek moet 2 coördinaten hebben" + "invalid_parameters_number": "Elk punt van de kameromtrek moet 2 coördinaten hebben" } }, "label": { @@ -117,7 +117,7 @@ "service_call_schema": { "missing": "Serviceoproep schema", "service": { - "missing": "Serviceoproep schema moet een service bevatten", + "missing": "Serviceoproep-schema moet een service bevatten", "invalid": "Ongeldige service: {0}" } } @@ -154,14 +154,14 @@ } }, "battery_level": { - "label": "Batterij" + "label": "Accupercentage" }, "fan_speed": { - "label": "Fan snelheid", + "label": "Ventilatorsnelheid", "value": { "silent": "Stil", "standard": "Standaard", - "medium": "Medium", + "medium": "Gemiddeld", "turbo": "Turbo", "auto": "Automatisch", "gentle": "Zacht" @@ -185,11 +185,37 @@ "cleaned_area": { "label": "Oppervlakte" }, + "total_cleaned_area": { + "label": "Totale schoongemaakte oppervlakte" + }, "cleaning_time": { "label": "Schoonmaaktijd" }, + "total_cleaning_time": { + "label": "Totale schoonmaaktijd" + }, "mop_left": { "label": "Dweil" + }, + "bin_full": { + "label": "Afvalcontainer vol", + "value": { + "true": "Yes", + "false": "No" + } + }, + "bin_present": { + "label": "Afvalcontainer aanwezig", + "value": { + "true": "Yes", + "false": "No" + } + }, + "water_volume": { + "label": "Watervolume" + }, + "mop_pad_humidity": { + "label": "Dweildoek" } }, "icon": { @@ -199,7 +225,7 @@ "vacuum_return_to_base": "Terug naar basisstation", "vacuum_clean_spot": "Spot schoonmaak", "vacuum_locate": "Lokaliseren", - "vacuum_set_fan_speed": "Fan snelheid aanpassen" + "vacuum_set_fan_speed": "Ventilatorsnelheid aanpassen" }, "unit": { "hour_shortcut": "u", @@ -214,17 +240,29 @@ }, "editor": { "description": { - "before_link": "Deze grafische editor ondersteunt slechts een basis configuratie met een camera entiteit welke gemaakt is met ", + "before_link": "Deze grafische editor ondersteunt slechts een basis-configuratie met een camera-entiteit die gemaakt is met ", "link_text": "Xiaomi Cloud Map Extractor", - "after_link": ". Gebruik de YAML modus voor een geavanceerde configuratie." + "after_link": ". Gebruik de YAML-modus voor een meer uitgebreide configuratie." }, "label": { "name": "Titel (optioneel)", - "entity": "Stofzuiger entiteit (verplicht)", - "camera": "Camera entiteit (verplicht)", - "vacuum_platform": "stofzuigerplatform (verplicht)", + "entity": "Stofzuigerentiteit (verplicht)", + "camera": "Camera-entiteit (verplicht)", + "vacuum_platform": "Stofzuigerplatform (verplicht)", "map_locked": "Kaart vergrendelen (optioneel)", - "two_finger_pan": "Kaart verplaatsen met twee vingers (optioneel)" + "two_finger_pan": "Kaart verplaatsen met twee vingers (optioneel)", + "platforms_documentation": "Documentatie van gekozen stofzuigerplatform ({0})", + "selection": "Selectie:", + "copy": "Kopiëren", + "copied": "Gekopieerd!", + "set_static_config": "Statische configuratie aanmaken", + "config_set": "Configuratie ingesteld!\nOpen de configuratie-editor om deze aan te passen.", + "config_set_failed": "Bijwerken van de configuratie mislukt", + "generate_rooms_config": "Kamer-configuratie aanmaken", + "copy_service_call": "Service Call kopiëren" + }, + "alerts": { + "set_static_config": "Gebruik deze functionaliteit alleen als je de gegenereerde configuratie nog handmatig wil aanpassen.\nDoorgaan?" } } } From 34b71b9db7899f6d58a57d906f55e501f9316107 Mon Sep 17 00:00:00 2001 From: tzagim <2285958+tzagim@users.noreply.github.com> Date: Thu, 20 Apr 2023 09:37:03 +0300 Subject: [PATCH 049/100] Update he.json --- src/localize/languages/he.json | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/localize/languages/he.json b/src/localize/languages/he.json index 8a25de3b..a80686de 100644 --- a/src/localize/languages/he.json +++ b/src/localize/languages/he.json @@ -3,7 +3,7 @@ "version": "גירסה", "invalid_configuration": "תצורה לא חוקית {0}", "description": "כרטיס המאפשר לך לשלוט בשואב שלך", - "old_configuration": "זוהתה תצורה ישנה. התאם את התצורה שלך לסכמה העדכנית ביותר או צור כרטיס חדש מההתחלה.", + "old_configuration": "זוהתה תצורה ישנה. יש להתאים את התצורה שלך לסכמה העדכנית ביותר או ליצור כרטיס חדש מההתחלה.", "old_configuration_migration_link": "מדריך להגירה" }, "map_mode": { @@ -31,7 +31,7 @@ "map_source": { "missing": "נכס חסר: map_source", "none_provided": "לא סופקה אף תמונה", - "ambiguous": "מותר רק מקור מפה אחד" + "ambiguous": "ניתן להשתמש במקור מפה אחד בלבד" }, "calibration_source": { "missing": "חסר מאפיין: calibration_source", @@ -185,9 +185,15 @@ "cleaned_area": { "label": "שטח שנוקה" }, + "total_cleaned_area": { + "label": "סך השטח שנוקה" + }, "cleaning_time": { "label": "זמן ניקיון" }, + "total_cleaning_time": { + "label": "סך זמן הניקיון" + }, "mop_left": { "label": "נותר למטלית" }, @@ -204,6 +210,12 @@ "true": "כן", "false": "לא" } + }, + "water_volume": { + "label": "רמת מים" + }, + "mop_pad_humidity": { + "label": "משטח ניגוב" } }, "icon": { @@ -248,6 +260,9 @@ "config_set_failed": "עדכון התצורה נכשל.", "generate_rooms_config": "יצירת תצורת חדרים", "copy_service_call": "העתקת קריאת שירות" + }, + "alerts": { + "set_static_config": "עליך להשתמש בפונקציונליות זו רק אם ברצונך להתאים באופן ידני את התצורה שנוצרה באופן אוטומטי.\nלהמשיך?" } } } From 899c2c279dacadd68f64c6ed8a1bae03bf003549 Mon Sep 17 00:00:00 2001 From: Dis90 Date: Wed, 26 Apr 2023 18:44:53 +0300 Subject: [PATCH 050/100] Finnish translation --- README.md | 1 + src/localize/languages/fi.json | 268 +++++++++++++++++++++++++++++++++ src/localize/localize.ts | 2 + 3 files changed, 271 insertions(+) create mode 100644 src/localize/languages/fi.json diff --git a/README.md b/README.md index 18965e1a..d01fc457 100644 --- a/README.md +++ b/README.md @@ -641,6 +641,7 @@ Currently, this card contains translations for following languages: * `el` - Greek (Ελληνικά) * `en` - English * `es` - Spanish (Español) +* `fi` - Finnish (Suomi) * `fr` - French (Français) * `he` - Hebrew (עברית) * `hu` - Hungarian (Magyar) diff --git a/src/localize/languages/fi.json b/src/localize/languages/fi.json new file mode 100644 index 00000000..db558173 --- /dev/null +++ b/src/localize/languages/fi.json @@ -0,0 +1,268 @@ +{ + "common": { + "version": "Versio", + "invalid_configuration": "Virheellinen määritys {0}", + "description": "Kortti, jolla voit hallita imuriasi", + "old_configuration": "Vanha rakenne havaittu. Muokkaa rakenne viimeisimmän skeeman mukaiseksi tai luo uusi kortti.", + "old_configuration_migration_link": "Migraatio-ohje" + }, + "map_mode": { + "invalid": "Virheellinen malli!", + "vacuum_goto": "Pin & Go", + "vacuum_goto_predefined": "Pisteet", + "vacuum_clean_segment": "Huoneet", + "vacuum_clean_point": "Puhdista piste", + "vacuum_clean_point_predefined": "Pisteet", + "vacuum_clean_zone": "Aluepuhdistus", + "vacuum_clean_zone_predefined": "Alueet", + "vacuum_follow_path": "Reitti" + }, + "validation": { + "preset": { + "entity": { + "missing": "Puuttuva ominaisuus: entity" + }, + "preset_name": { + "missing": "Puuttuva ominaisuus: preset_name" + }, + "platform": { + "invalid": "Virheellinen imurin alusta: {0}" + }, + "map_source": { + "missing": "Puuttuva ominaisuus: map_source", + "none_provided": "Ei kameraa eikä kuvaa", + "ambiguous": "Vain yksi karttalähde sallittu" + }, + "calibration_source": { + "missing": "Puuttuva ominaisuus: calibration_source", + "ambiguous": "Vain yksi kalibrointilähde on sallittu", + "none_provided": "Kalibrointilähde puuttuu", + "calibration_points": { + "invalid_number": "Tarvitaan täsmälleen 3 tai 4 kalibrointipistettä", + "missing_map": "Jokaisen kalibrointipisteen tulee sisältää karttakoordinaatit", + "missing_vacuum": "Jokaisen kalibrointipisteen tulee sisältää imurikoordinaatit", + "missing_coordinate": "Kartta- ja imurikalibrointipisteiden tulee sisältää sekä x- että y-koordinaatit" + } + }, + "icons": { + "invalid": "Virhe määrityksessä: icons", + "icon": { + "missing": "Jokaisen kuvakeluettelon merkinnän tulee sisältää kuvakeominaisuus" + } + }, + "tiles": { + "invalid": "Virhe määrityksessä: tiles", + "entity": { + "missing": "Jokaisen ruutuluettelon merkinnän on sisällettävä entiteetti tai sisäinen muuttuja" + }, + "label": { + "missing": "Jokaisen ruutuluettelon merkinnän on sisällettävä tunniste" + } + }, + "map_modes": { + "invalid": "Virhe määrityksessä: map_modes", + "icon": { + "missing": "Karttatilan kuvake puuttuu" + }, + "name": { + "missing": "Karttatilan nimi puuttuu" + }, + "template": { + "invalid": "Virheellinen malli: {0}" + }, + "predefined_selections": { + "not_applicable": "Tila {0} ei tue ennalta määritettyjä valintoja", + "zones": { + "missing": "Alueiden määritys puuttuu", + "invalid_parameters_number": "Jokaisella alueella on oltava 4 parametria" + }, + "points": { + "position": { + "missing": "Pisteiden määritys puuttuu", + "invalid_parameters_number": "Jokaisella pisteellä on oltava 2 parametria" + } + }, + "rooms": { + "id": { + "missing": "Huoneen id puuttuu", + "invalid_format": "Virheellinen huoneen id: {0}" + }, + "outline": { + "invalid_parameters_number": "Jokaisella huoneen ääriviivan pisteellä on oltava 2 parametria" + } + }, + "label": { + "x": { + "missing": "Tunnisteella on oltava x-ominaisuus" + }, + "y": { + "missing": "Tunnisteella on oltava y-ominaisuus" + }, + "text": { + "missing": "Tunnisteella on oltava tekstiominaisuus" + } + }, + "icon": { + "x": { + "missing": "Kuvakkeella on oltava x-ominaisuus" + }, + "y": { + "missing": "Kuvakkeella on oltava y-ominaisuus" + }, + "name": { + "missing": "Kuvakkeella on oltava nimiominaisuus" + } + } + }, + "service_call_schema": { + "missing": "Puuttuva palvelukutsuskeema", + "service": { + "missing": "Palvelukutsuskeeman tulee sisältää palvelu", + "invalid": "Virheellinen palvelu: {0}" + } + } + } + }, + "invalid_entities": "Virheelliset entiteetit:", + "invalid_calibration": "Virheellinen kalibrointi, tarkista asetukset" + }, + "tile": { + "status": { + "label": "Tila", + "value": { + "starting": "Käynnistetään", + "charger disconnected": "Laturi irroitettu", + "idle": "Lepotila", + "remote control active": "Kaukosäädin aktiivinen", + "cleaning": "Puhdistetaan", + "returning home": "Palataan telakkaan", + "manual mode": "Manuaalinen tila", + "charging": "Ladataan", + "charging problem": "Latausvirhe", + "paused": "Tauotettu", + "spot cleaning": "Kohdan puhdistus", + "error": "Virhe", + "shutting down": "Sammutetaan", + "updating": "Päivitetään", + "docking": "Telakoidutaan", + "going to target": "Mennään kohteeseen", + "zoned cleaning": "Aluepuhdistus", + "segment cleaning": "Segmentin puhdistus", + "emptying the bin": "Tyhjennetään säiliötä", + "charging complete": "Lataus valmis", + "device offline": "Laite poissa päältä" + } + }, + "battery_level": { + "label": "Akku" + }, + "fan_speed": { + "label": "Tuulettimen nopeus", + "value": { + "silent": "Hiljainen", + "standard": "Vakio", + "medium": "Keskinopeus", + "turbo": "Turbo", + "auto": "Auto", + "gentle": "Kevyt" + } + }, + "sensor_dirty_left": { + "label": "Anturit jäljellä" + }, + "filter_left": { + "label": "Suodatin jäljellä" + }, + "main_brush_left": { + "label": "Pääharja jäljellä" + }, + "side_brush_left": { + "label": "Sivuharja jäljellä" + }, + "cleaning_count": { + "label": "Puhdistusmäärä" + }, + "cleaned_area": { + "label": "Puhdistettu alue" + }, + "total_cleaned_area": { + "label": "Puhdistettu alue yhteensä" + }, + "cleaning_time": { + "label": "Puhdistusaika" + }, + "total_cleaning_time": { + "label": "Kokonaispuhdistusaika" + }, + "mop_left": { + "label": "Moppi jäljellä" + }, + "bin_full": { + "label": "Säiliö täynnä", + "value": { + "true": "Kyllä", + "false": "Ei" + } + }, + "bin_present": { + "label": "Säiliö löytyy", + "value": { + "true": "Kyllä", + "false": "Ei" + } + }, + "water_volume": { + "label": "Veden määrä" + }, + "mop_pad_humidity": { + "label": "Moppi tyyny" + } + }, + "icon": { + "vacuum_start": "Käynnistä", + "vacuum_pause": "Tauko", + "vacuum_stop": "Pysäytä", + "vacuum_return_to_base": "Palaa telakkaan", + "vacuum_clean_spot": "Siivoa kohta", + "vacuum_locate": "Paikanna", + "vacuum_set_fan_speed": "Vaihda tuulettimen nopeutta" + }, + "unit": { + "hour_shortcut": "t", + "meter_shortcut": "m", + "meter_squared_shortcut": "m²", + "minute_shortcut": "min" + }, + "popups": { + "success": "Onnistui!", + "no_selection": "Valintoja ei annettu", + "failed": "Virhe kutsuessa palvelua" + }, + "editor": { + "description": { + "before_link": "Visuaalinen editori tukee vain peruskokoonpanoa, jossa kamerakokonaisuus on luotu käyttämällä ", + "link_text": "Xiaomi Cloud Map Extractor", + "after_link": ". Käytä YAML-tilaa edistyneempään asennukseen." + }, + "label": { + "name": "Otsikko (valinnainen)", + "entity": "Imuri entiteetti (vaadittu)", + "camera": "Kamera entiteetti (vaadittu)", + "vacuum_platform": "Imurin alusta (vaadittu)", + "map_locked": "Kartta lukittu (valinnainen)", + "two_finger_pan": "Liikuta karttaa kahdella sormella (valinnainen)", + "platforms_documentation": "Valitun alustan dokumentaatio ({0})", + "selection": "Valinta:", + "copy": "Kopioi", + "copied": "Kopioitu!", + "set_static_config": "Luo staattiset asetukset", + "config_set": "Määritykset asetettu!\nMuokkaa sitä avaamalla asetuseditori.", + "config_set_failed": "Konfiguroinnin päivitys epäonnistui.", + "generate_rooms_config": "Luo huoneiden asetukset", + "copy_service_call": "Kopioi palvelukutsu" + }, + "alerts": { + "set_static_config": "Käytä tätä toimintoa vain, jos haluat säätää automaattisesti luotuja määrityksiä manuaalisesti.\nJatketaanko?" + } + } +} diff --git a/src/localize/localize.ts b/src/localize/localize.ts index 50df4372..dfac8e05 100644 --- a/src/localize/localize.ts +++ b/src/localize/localize.ts @@ -5,6 +5,7 @@ import * as de from "./languages/de.json"; import * as el from "./languages/el.json"; import * as en from "./languages/en.json"; import * as es from "./languages/es.json"; +import * as fi from "./languages/fi.json"; import * as fr from "./languages/fr.json"; import * as he from "./languages/he.json"; import * as hu from "./languages/hu.json"; @@ -36,6 +37,7 @@ const languages: Record = { el: el, en: en, es: es, + fi: fi, fr: fr, he: he, hu: hu, From 072bb2e459775c2e9875ed931b2f8e83208758a2 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 23 May 2023 01:50:52 +0200 Subject: [PATCH 051/100] Improve modes menu dropdown --- src/renderers/modes-menu-renderer.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/renderers/modes-menu-renderer.ts b/src/renderers/modes-menu-renderer.ts index 5d56b7cb..3a9216fd 100644 --- a/src/renderers/modes-menu-renderer.ts +++ b/src/renderers/modes-menu-renderer.ts @@ -6,7 +6,7 @@ export class ModesMenuRenderer { public static render(modes: MapMode[], getMode: number, setMode: (number) => void, menu?: HTMLElement): TemplateResult { const getCurrentMode = (): MapMode => modes[getMode]; return html` -
    @@ -44,9 +44,13 @@ export class ModesMenuRenderer { const height = 50; const minDiff = (items - 1) * height + 32; if (window.innerHeight - div.getBoundingClientRect().bottom >= minDiff) { + div.style.marginTop = `0px`; + menu.style.marginTop = `0px`; div.style.marginBottom = `-${height}px`; menu.style.marginBottom = `${height}px`; } else { + div.style.marginTop = `-${height}px`; + menu.style.marginTop = `${height}px`; div.style.marginBottom = "0px"; menu.style.marginBottom = "0px"; } @@ -55,9 +59,10 @@ export class ModesMenuRenderer { mwcMenu.style.zIndex = "1"; mwcMenu.style.position = "fixed"; } - menu.querySelectorAll("mwc-list-item").forEach((item)=> + menu.querySelectorAll("mwc-list-item").forEach((item) => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - item.shadowRoot!.querySelector("span")!.style.flexGrow = "1"); + item.shadowRoot!.querySelector("span")!.style.flexGrow = "1"; + }); } } From dc34396f5957d614655cb59d5ab56a4cc8d9fe9f Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 23 May 2023 01:51:24 +0200 Subject: [PATCH 052/100] Improve editor detection --- src/xiaomi-vacuum-map-card.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index fa682be8..6f400c78 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -131,6 +131,7 @@ export class XiaomiVacuumMapCard extends LitElement { private modes: MapMode[] = []; private shouldHandleMouseUp!: boolean; private lastHassUpdate!: Date; + private isInEditor = false; constructor() { super(); @@ -208,7 +209,8 @@ export class XiaomiVacuumMapCard extends LitElement { if (this._isInEditor()) { window.addEventListener(EVENT_AUTOGENERATED_CONFIG_GET, this._handleAutogeneratedConfigGet); window.addEventListener(EVENT_ROOM_CONFIG_GET, this._handleRoomsConfigGet); - window.addEventListener(EVENT_SERVICE_CALL_GET, this._handleServiceCallGet); + window.addEventListener(EVENT_SERVICE_CALL_GET, this._handleServiceCallGet) + this.isInEditor = true; } if (this.config.action_handler_id) { document.addEventListener(EVENT_LOVELACE_DOM, this._handleLovelaceDomEvent); @@ -840,7 +842,7 @@ export class XiaomiVacuumMapCard extends LitElement { const currentMode = this._getCurrentMode(); const { selection } = this._getSelection(currentMode); - if (this._isInEditor()) { + if (this.isInEditor) { const event = new Event(EVENT_SELECTION_CHANGED); (event as any).selection = selection ?? "[]"; window.dispatchEvent(event); @@ -848,10 +850,10 @@ export class XiaomiVacuumMapCard extends LitElement { } private _isInEditor(): boolean { - function isInEditor(e: HTMLElement): boolean { - return e.parentElement?.tagName?.toLowerCase() === "hui-card-preview" || ( - e.parentElement != null && isInEditor(e.parentElement) - ); + function isInEditor(e: Element): boolean { + return e.parentElement?.tagName?.toLowerCase() === "hui-card-preview" + || e.parentElement != null && isInEditor(e.parentElement) + || e.parentNode?.toString() == "[object ShadowRoot]" && isInEditor((e.getRootNode() as ShadowRoot).host); } return isInEditor(this); } From ca78775bbd6e58da38c8a5fde4e203e4fab6e7ec Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 23 May 2023 02:47:55 +0200 Subject: [PATCH 053/100] Adjust localization --- .../hass/compute_attribute_display.ts | 108 ++++++++++++++++-- src/localize/hass/compute_state_display.ts | 65 +++++++---- src/localize/hass/duration.ts | 49 ++++---- src/localize/hass/entity_attributes.ts | 4 +- src/localize/hass/localize.ts | 3 +- src/localize/hass/translation.ts | 6 +- src/localize/localize.ts | 1 - 7 files changed, 175 insertions(+), 61 deletions(-) diff --git a/src/localize/hass/compute_attribute_display.ts b/src/localize/hass/compute_attribute_display.ts index 8614b7b7..5d6173fb 100644 --- a/src/localize/hass/compute_attribute_display.ts +++ b/src/localize/hass/compute_attribute_display.ts @@ -3,30 +3,106 @@ import { HassEntity } from "home-assistant-js-websocket"; import { LocalizeFunc } from "./localize"; import { computeDomain } from "./compute_domain"; -import { EntityRegistryDisplayEntry, HomeAssistantFixed } from "../../types/fixes"; +import { EntityRegistryDisplayEntry, FrontendLocaleDataFixed, HomeAssistantFixed } from "../../types/fixes"; import { formatAttributeName } from "./entity_attributes"; +import { FrontendLocaleData } from "./translation"; +import { capitalizeFirstLetter } from "./capitalize_first_letter"; +import { isDate } from "./is_date"; +import { isTimestamp } from "./is_timestamp"; +import checkValidDate from "./check_valid_date"; +import { formatDateTimeWithSeconds } from "./format_date_time"; +import { formatDate } from "./format_date"; +import { formatNumber } from "./format_number"; + export const computeAttributeValueDisplay = ( localize: LocalizeFunc, stateObj: HassEntity, + locale: FrontendLocaleDataFixed, entities: HomeAssistantFixed["entities"], attribute: string, value?: any ): string => { - const entityId = stateObj.entity_id; const attributeValue = value !== undefined ? value : stateObj.attributes[attribute]; + + // Null value, the state is unknown + if (attributeValue === null) { + return localize("state.default.unknown"); + } + + // Number value, return formatted number + if (typeof attributeValue === "number") { + return formatNumber(attributeValue, locale); + } + + // Special handling in case this is a string with an known format + if (typeof attributeValue === "string") { + + // Date handling + if (isDate(attributeValue, true)) { + // Timestamp handling + if (isTimestamp(attributeValue)) { + const date = new Date(attributeValue); + if (checkValidDate(date)) { + return formatDateTimeWithSeconds(date, locale); + } + } + + // Value was not a timestamp, so only do date formatting + const date = new Date(attributeValue); + if (checkValidDate(date)) { + return formatDate(date, locale); + } + } + } + + // Values are objects, render object + if ( + (Array.isArray(attributeValue) && + attributeValue.some((val) => val instanceof Object)) || + (!Array.isArray(attributeValue) && attributeValue instanceof Object) + ) { + return JSON.stringify(attributeValue); + } + + // If this is an array, try to determine the display value for each item + if (Array.isArray(attributeValue)) { + return attributeValue + .map((item) => + computeAttributeValueDisplay( + localize, + stateObj, + locale, + entities, + attribute, + item + ) + ) + .join(", "); + } + + // We've explored all known value handling, so now we'll try to find a + // translation for the value. + const entityId = stateObj.entity_id; const domain = computeDomain(entityId); - const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined; - const translationKey = entity?.translation_key; + const deviceClass = stateObj.attributes.device_class; + const registryEntry = entities[entityId] as + | EntityRegistryDisplayEntry + | undefined; + const translationKey = registryEntry?.translation_key; return ( (translationKey && localize( - `component.${entity.platform}.entity.${domain}.${translationKey}.state_attributes.${attribute}.state.${attributeValue}` + `component.${registryEntry.platform}.entity.${domain}.${translationKey}.state_attributes.${attribute}.state.${attributeValue}` + )) || + (deviceClass && + localize( + `component.${domain}.entity_component.${deviceClass}.state_attributes.${attribute}.state.${attributeValue}` )) || localize( - `component.${domain}.state_attributes._.${attribute}.state.${attributeValue}` + `component.${domain}.entity_component._.state_attributes.${attribute}.state.${attributeValue}` ) || attributeValue ); @@ -39,6 +115,7 @@ export const computeAttributeNameDisplay = ( attribute: string ): string => { const entityId = stateObj.entity_id; + const deviceClass = stateObj.attributes.device_class; const domain = computeDomain(entityId); const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined; const translationKey = entity?.translation_key; @@ -48,7 +125,20 @@ export const computeAttributeNameDisplay = ( localize( `component.${entity.platform}.entity.${domain}.${translationKey}.state_attributes.${attribute}.name` )) || - localize(`component.${domain}.state_attributes._.${attribute}.name`) || - formatAttributeName(attribute) + (deviceClass && + localize( + `component.${domain}.entity_component.${deviceClass}.state_attributes.${attribute}.name` + )) || + localize( + `component.${domain}.entity_component._.state_attributes.${attribute}.name` + ) || + capitalizeFirstLetter( + attribute + .replace(/_/g, " ") + .replace(/\bid\b/g, "ID") + .replace(/\bip\b/g, "IP") + .replace(/\bmac\b/g, "MAC") + .replace(/\bgps\b/g, "GPS") + ) ); -}; +}; \ No newline at end of file diff --git a/src/localize/hass/compute_state_display.ts b/src/localize/hass/compute_state_display.ts index ef6aa6d6..f2ff98f5 100644 --- a/src/localize/hass/compute_state_display.ts +++ b/src/localize/hass/compute_state_display.ts @@ -2,7 +2,7 @@ import { HassEntity } from "home-assistant-js-websocket"; import { UNAVAILABLE, UNKNOWN } from "./entity"; -import { formatDuration, UNIT_TO_SECOND_CONVERT } from "./duration"; +import { formatDuration, UNIT_TO_MILLISECOND_CONVERT } from "./duration"; import { formatDate } from "./format_date"; import { formatDateTime } from "./format_date_time"; import { formatTime } from "./format_time"; @@ -15,46 +15,65 @@ import { blankBeforePercent } from "./blank_before_percent"; import { LocalizeFunc } from "./localize"; //type import { computeDomain } from "./compute_domain"; import { EntityRegistryDisplayEntry, FrontendLocaleDataFixed, HomeAssistantFixed } from "../../types/fixes"; +import { FrontendLocaleData } from "./translation"; -export const computeStateDisplay = ( + +export const computeStateDisplaySingleEntity = ( localize: LocalizeFunc, stateObj: HassEntity, locale: FrontendLocaleDataFixed, - entities: HomeAssistantFixed["entities"], - skipUnit = false, + entity: EntityRegistryDisplayEntry | undefined, + state?: string ): string => computeStateDisplayFromEntityAttributes( localize, locale, - entities, + entity, stateObj.entity_id, stateObj.attributes, - stateObj.state, - skipUnit + state !== undefined ? state : stateObj.state ); -export const computeStateDisplayFromEntityAttributes = ( +export const computeStateDisplay = ( localize: LocalizeFunc, - locale: FrontendLocaleDataFixed, + stateObj: HassEntity, + locale: FrontendLocaleData, entities: HomeAssistantFixed["entities"], + state?: string +): string => { + const entity = entities[stateObj.entity_id] as + | EntityRegistryDisplayEntry + | undefined; + + return computeStateDisplayFromEntityAttributes( + localize, + locale, + entity, + stateObj.entity_id, + stateObj.attributes, + state !== undefined ? state : stateObj.state + ); +}; + +export const computeStateDisplayFromEntityAttributes = ( + localize: LocalizeFunc, + locale: FrontendLocaleData, + entity: EntityRegistryDisplayEntry | undefined, entityId: string, attributes: any, - state: string, - skipUnit = false, + state: string ): string => { if (state === UNKNOWN || state === UNAVAILABLE) { return localize(`state.default.${state}`); } - const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined; - // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber` if (isNumericFromAttributes(attributes)) { // state is duration if ( attributes.device_class === "duration" && attributes.unit_of_measurement && - UNIT_TO_SECOND_CONVERT[attributes.unit_of_measurement] + UNIT_TO_MILLISECOND_CONVERT[attributes.unit_of_measurement] ) { try { return formatDuration(state, attributes.unit_of_measurement); @@ -65,7 +84,7 @@ export const computeStateDisplayFromEntityAttributes = ( if (attributes.device_class === "monetary") { try { return formatNumber(state, locale, { - style: skipUnit ? undefined: "currency", + style: "currency", currency: attributes.unit_of_measurement, minimumFractionDigits: 2, // Override monetary options with number format @@ -78,7 +97,7 @@ export const computeStateDisplayFromEntityAttributes = ( // fallback to default } } - const unit = !attributes.unit_of_measurement || skipUnit + const unit = !attributes.unit_of_measurement ? "" : attributes.unit_of_measurement === "%" ? blankBeforePercent(locale) + "%" @@ -92,7 +111,7 @@ export const computeStateDisplayFromEntityAttributes = ( const domain = computeDomain(entityId); - if (domain === "input_datetime") { + if (["date", "input_datetime", "time"].includes(domain)) { if (state !== undefined) { // If trying to display an explicit state, need to parse the explicit state to `Date` then format. // Attributes aren't available, we have to use `state`. @@ -168,11 +187,9 @@ export const computeStateDisplayFromEntityAttributes = ( ); } - // state of button is a timestamp + // state is a timestamp if ( - domain === "button" || - domain === "input_button" || - domain === "scene" || + ["button", "input_button", "scene", "stt", "tts"].includes(domain) || (domain === "sensor" && attributes.device_class === "timestamp") ) { try { @@ -190,11 +207,11 @@ export const computeStateDisplayFromEntityAttributes = ( // Return device class translation (attributes.device_class && localize( - `component.${domain}.state.${attributes.device_class}.${state}` + `component.${domain}.entity_component.${attributes.device_class}.state.${state}` )) || // Return default translation - localize(`component.${domain}.state._.${state}`) || + localize(`component.${domain}.entity_component._.state.${state}`) || // We don't know! Return the raw state. state ); -}; +}; \ No newline at end of file diff --git a/src/localize/hass/duration.ts b/src/localize/hass/duration.ts index 0b2d601d..cbc8f2db 100644 --- a/src/localize/hass/duration.ts +++ b/src/localize/hass/duration.ts @@ -1,22 +1,36 @@ // home-assistant/frontend/src/common/datetime/duration.ts -const DAY_IN_SECONDS = 86400; -const HOUR_IN_SECONDS = 3600; -const MINUTE_IN_SECONDS = 60; +const DAY_IN_MILLISECONDS = 86400000; +const HOUR_IN_MILLISECONDS = 3600000; +const MINUTE_IN_MILLISECONDS = 60000; +const SECOND_IN_MILLISECONDS = 1000; -export const UNIT_TO_SECOND_CONVERT = { - s: 1, - min: MINUTE_IN_SECONDS, - h: HOUR_IN_SECONDS, - d: DAY_IN_SECONDS, +export const UNIT_TO_MILLISECOND_CONVERT = { + ms: 1, + s: SECOND_IN_MILLISECONDS, + min: MINUTE_IN_MILLISECONDS, + h: HOUR_IN_MILLISECONDS, + d: DAY_IN_MILLISECONDS, }; -const leftPad = (num: number) => (num < 10 ? `0${num}` : num); +export const formatDuration = (duration: string, units: string): string => + millisecondsToDuration( + parseFloat(duration) * UNIT_TO_MILLISECOND_CONVERT[units] + ) || "0"; -export function secondsToDuration(d: number): string | null { - const h = Math.floor(d / 3600); - const m = Math.floor((d % 3600) / 60); - const s = Math.floor((d % 3600) % 60); +const leftPad = (num: number, digits = 2) => { + let paddedNum = "" + num; + for (let i = 1; i < digits; i++) { + paddedNum = parseInt(paddedNum) < 10 ** i ? `0${paddedNum}` : paddedNum; + } + return paddedNum; +}; + +export function millisecondsToDuration(d: number): string | null { + const h = Math.floor(d / 1000 / 3600); + const m = Math.floor(((d / 1000) % 3600) / 60); + const s = Math.floor(((d / 1000) % 3600) % 60); + const ms = Math.floor(d % 1000); if (h > 0) { return `${h}:${leftPad(m)}:${leftPad(s)}`; @@ -24,13 +38,8 @@ export function secondsToDuration(d: number): string | null { if (m > 0) { return `${m}:${leftPad(s)}`; } - if (s > 0) { - return "" + s; + if (s > 0 || ms > 0) { + return `${s}${ms > 0 ? `.${leftPad(ms, 3)}` : ``}`; } return null; } - -export const formatDuration = (duration: string, units: string): string => { - const seconds = parseFloat(duration) * UNIT_TO_SECOND_CONVERT[units]; - return (seconds >= 0 ? secondsToDuration(seconds) : `-${secondsToDuration(-seconds)}`) ?? "0"; -}; diff --git a/src/localize/hass/entity_attributes.ts b/src/localize/hass/entity_attributes.ts index 53688ee6..7c346028 100644 --- a/src/localize/hass/entity_attributes.ts +++ b/src/localize/hass/entity_attributes.ts @@ -1,6 +1,5 @@ // home-assistant/frontend/src/data/entity_attributes.ts -import { html, TemplateResult } from "lit"; import checkValidDate from "./check_valid_date"; import { formatDate } from "./format_date"; import { formatDateTimeWithSeconds } from "./format_date_time"; @@ -14,6 +13,7 @@ import { HassEntity } from "home-assistant-js-websocket/dist/types"; export const STATE_ATTRIBUTES = [ + "entity_id", "assumed_state", "attribution", "custom_ui_more_info", @@ -87,5 +87,5 @@ export function formatAttributeValue( return Array.isArray(value) ? value.join(", ") - : computeAttributeValueDisplay(hass.localize, stateObj, hass.entities, attribute); + : computeAttributeValueDisplay(hass.localize, stateObj, hass.locale, hass.entities, attribute); } diff --git a/src/localize/hass/localize.ts b/src/localize/hass/localize.ts index 3e31d2b7..985009b3 100644 --- a/src/localize/hass/localize.ts +++ b/src/localize/hass/localize.ts @@ -50,11 +50,10 @@ export type FlattenObjectKeys< : `${Key}` : never; - - interface FormatType { [format: string]: any; } + export interface FormatsType { number: FormatType; date: FormatType; diff --git a/src/localize/hass/translation.ts b/src/localize/hass/translation.ts index a630b0a1..8119ca8f 100644 --- a/src/localize/hass/translation.ts +++ b/src/localize/hass/translation.ts @@ -3,6 +3,7 @@ import { fetchFrontendUserData } from "./frontend"; import { HomeAssistantFixed } from "../../types/fixes"; + export enum NumberFormat { language = "language", system = "system", @@ -46,8 +47,8 @@ declare global { export type TranslationCategory = | "title" | "state" - | "state_attributes" | "entity" + | "entity_component" | "config" | "config_panel" | "options" @@ -62,7 +63,6 @@ export type TranslationCategory = export const fetchTranslationPreferences = (hass: HomeAssistantFixed) => fetchFrontendUserData(hass.connection, "language"); - export const getHassTranslations = async ( hass: HomeAssistantFixed, language: string, @@ -89,4 +89,4 @@ export const getHassTranslationsPre109 = async ( language, }); return result.resources; -}; +}; \ No newline at end of file diff --git a/src/localize/localize.ts b/src/localize/localize.ts index dfac8e05..45771e88 100644 --- a/src/localize/localize.ts +++ b/src/localize/localize.ts @@ -123,6 +123,5 @@ export function localizeEntity(hass: HomeAssistantFixed, config: EntityConfig, e entity, hass.locale, hass.entities, - config.unit !== undefined, ); } \ No newline at end of file From 97939632bbd92548a62f7e7e9bf6b5719748d2cf Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Tue, 23 May 2023 04:04:07 +0200 Subject: [PATCH 054/100] Extract dropdown renderer --- src/renderers/dropdown-menu-renderer.ts | 84 +++++++++++++++++++++++++ src/renderers/modes-menu-renderer.ts | 70 ++------------------- 2 files changed, 90 insertions(+), 64 deletions(-) create mode 100644 src/renderers/dropdown-menu-renderer.ts diff --git a/src/renderers/dropdown-menu-renderer.ts b/src/renderers/dropdown-menu-renderer.ts new file mode 100644 index 00000000..a8396d44 --- /dev/null +++ b/src/renderers/dropdown-menu-renderer.ts @@ -0,0 +1,84 @@ +import { html, TemplateResult } from "lit"; +import { conditional } from "../utils"; + + +interface DropdownEntry { + icon: string; + name: string; +} + +export abstract class DropdownMenuRenderer { + + protected static _render( + values: T[], + currentIndex: number, + setValue: (_: number) => void, + menu: HTMLElement | undefined, + classPrefix: string, + renderNameCollapsed: boolean, + ): TemplateResult { + const currentValue = values[currentIndex]; + return html` + +
    + + + + ${conditional( + renderNameCollapsed, + () => html`
    ${currentValue.name}
    ` + )} +
    + ${values.map( + (mode, index) => html` +
    +
    + + + +
    +
    ${mode.name}
    +
    +
    `, + )} +
    + `; + } + + private static updateStyles(menu: HTMLElement | undefined, items: number): void { + const div = menu?.shadowRoot?.querySelector("div") as HTMLElement; + if (menu && div) { + const height = 50; + const minDiff = (items - 1) * height + 32; + if (window.innerHeight - div.getBoundingClientRect().bottom >= minDiff) { + div.style.marginTop = `0px`; + menu.style.marginTop = `0px`; + div.style.marginBottom = `-${height}px`; + menu.style.marginBottom = `${height}px`; + } else { + div.style.marginTop = `-${height}px`; + menu.style.marginTop = `${height}px`; + div.style.marginBottom = "0px"; + menu.style.marginBottom = "0px"; + } + const mwcMenu = menu.shadowRoot?.querySelector("mwc-menu") as HTMLElement; + if (mwcMenu) { + mwcMenu.style.zIndex = "1"; + mwcMenu.style.position = "fixed"; + } + menu.querySelectorAll("mwc-list-item").forEach((item) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + item.shadowRoot!.querySelector("span")!.style.flexGrow = "1"; + }); + } + } +} diff --git a/src/renderers/modes-menu-renderer.ts b/src/renderers/modes-menu-renderer.ts index 3a9216fd..d9d61614 100644 --- a/src/renderers/modes-menu-renderer.ts +++ b/src/renderers/modes-menu-renderer.ts @@ -1,69 +1,11 @@ -import { css, CSSResultGroup, html, TemplateResult } from "lit"; +import { css, CSSResultGroup, TemplateResult } from "lit"; import { MapMode } from "../model/map_mode/map-mode"; +import { DropdownMenuRenderer } from "./dropdown-menu-renderer"; -export class ModesMenuRenderer { - public static render(modes: MapMode[], getMode: number, setMode: (number) => void, menu?: HTMLElement): TemplateResult { - const getCurrentMode = (): MapMode => modes[getMode]; - return html` - -
    - - - -
    ${getCurrentMode().name}
    -
    - ${modes.map( - (mode, index) => html` -
    -
    - - - -
    -
    ${mode.name}
    -
    -
    `, - )} -
    - `; - } - - private static updateStyles(menu: HTMLElement | undefined, items: number): void { - const div = menu?.shadowRoot?.querySelector("div") as HTMLElement; - if (menu && div) { - const height = 50; - const minDiff = (items - 1) * height + 32; - if (window.innerHeight - div.getBoundingClientRect().bottom >= minDiff) { - div.style.marginTop = `0px`; - menu.style.marginTop = `0px`; - div.style.marginBottom = `-${height}px`; - menu.style.marginBottom = `${height}px`; - } else { - div.style.marginTop = `-${height}px`; - menu.style.marginTop = `${height}px`; - div.style.marginBottom = "0px"; - menu.style.marginBottom = "0px"; - } - const mwcMenu = menu.shadowRoot?.querySelector("mwc-menu") as HTMLElement; - if (mwcMenu) { - mwcMenu.style.zIndex = "1"; - mwcMenu.style.position = "fixed"; - } - menu.querySelectorAll("mwc-list-item").forEach((item) => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - item.shadowRoot!.querySelector("span")!.style.flexGrow = "1"; - }); - } +export class ModesMenuRenderer extends DropdownMenuRenderer { + public static render(modes: MapMode[], getMode: number, setMode: (number) => void, menu: HTMLElement | undefined): TemplateResult { + return this._render(modes, getMode, setMode, menu, "modes", true); } public static get styles(): CSSResultGroup { @@ -81,7 +23,7 @@ export class ModesMenuRenderer { display: inline-flex; } - .list-item:host:host { + .modes-dropdown-list-item:host:host { flex-grow: 1; } From 44eb3a2c56ace4dca63abd1c572fdfcc99223534 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Fri, 26 May 2023 03:41:04 +0200 Subject: [PATCH 055/100] Add possibility to use dropdown in icons --- src/model/generators/icon-list-generator.ts | 17 +- src/renderers/dropdown-menu-renderer.ts | 3 +- src/renderers/icon-renderer.ts | 166 +++++++++++++++++++- src/renderers/tile-renderer.ts | 2 +- src/types/types.ts | 9 ++ src/xiaomi-vacuum-map-card.ts | 15 +- 6 files changed, 196 insertions(+), 16 deletions(-) diff --git a/src/model/generators/icon-list-generator.ts b/src/model/generators/icon-list-generator.ts index c71e317e..e34d09db 100644 --- a/src/model/generators/icon-list-generator.ts +++ b/src/model/generators/icon-list-generator.ts @@ -176,9 +176,10 @@ export class IconListGenerator { const fanSpeeds = state_available ? state.attributes["fan_speed_list"] ?? [] : []; for (let i = 0; i < fanSpeeds.length; i++) { const fanSpeed = fanSpeeds[i]; - const nextFanSpeed = fanSpeeds[(i + 1) % fanSpeeds.length]; icons.push({ + menu_id: "fan_speed", icon: fanSpeed in this._ICON_MAPPING ? this._ICON_MAPPING[fanSpeed] : "mdi:fan-alert", + label: localize("tile.fan_speed.value."+fanSpeed.toLowerCase(), language, fanSpeed), conditions: [ { entity: vacuumEntity, @@ -192,7 +193,7 @@ export class IconListGenerator { service: "vacuum.set_fan_speed", service_data: { entity_id: vacuumEntity, - fan_speed: nextFanSpeed, + fan_speed: fanSpeed, }, }, }); @@ -212,10 +213,20 @@ export class IconListGenerator { }, }); } - return icons; + return icons.sort(sortTiles); } private static isFeatureSupported(state: HassEntity, features: number) { return state && state.attributes && ((state.attributes["supported_features"] ?? 0) & features) === features; } } + +export function sortTiles(i1: IconActionConfig, i2: IconActionConfig): number { + if (i1.order === undefined && i2.order === undefined) + return 0; + if (i1.order === undefined) + return 1; + if (i2.order === undefined) + return -1; + return i1.order - i2.order; +} diff --git a/src/renderers/dropdown-menu-renderer.ts b/src/renderers/dropdown-menu-renderer.ts index a8396d44..95e300be 100644 --- a/src/renderers/dropdown-menu-renderer.ts +++ b/src/renderers/dropdown-menu-renderer.ts @@ -16,10 +16,11 @@ export abstract class DropdownMenuRenderer { menu: HTMLElement | undefined, classPrefix: string, renderNameCollapsed: boolean, + additionalClasses: string[] = [] ): TemplateResult { const currentValue = values[currentIndex]; return html` -
    diff --git a/src/renderers/icon-renderer.ts b/src/renderers/icon-renderer.ts index 1cc7d8ff..50c6915f 100644 --- a/src/renderers/icon-renderer.ts +++ b/src/renderers/icon-renderer.ts @@ -1,16 +1,30 @@ import { css, CSSResultGroup, html, TemplateResult } from "lit"; -import { hasAction } from "custom-card-helpers"; +import { handleAction, hasAction, HomeAssistant } from "custom-card-helpers"; import { actionHandler } from "../action-handler-directive"; -import { handleActionWithConfig } from "../utils"; -import { IconActionConfig } from "../types/types"; +import { areConditionsMet, handleActionWithConfig } from "../utils"; +import { + DropdownIconActionConfig, + IconActionConfig, + VariablesStorage, +} from "../types/types"; import { XiaomiVacuumMapCard } from "../xiaomi-vacuum-map-card"; +import { DropdownMenuRenderer } from "./dropdown-menu-renderer"; +import { HomeAssistantFixed } from "../types/fixes"; -export class IconRenderer { - public static render(config: IconActionConfig, card: XiaomiVacuumMapCard): TemplateResult { +export class IconRenderer extends DropdownMenuRenderer { + + public static render(config: IconActionConfig | DropdownIconActionConfig, card: XiaomiVacuumMapCard, menus: HTMLElement[]): TemplateResult { + if (Array.isArray(config)) { + return this.renderIconDropdown(config as DropdownIconActionConfig, card, menus); + } + return this.renderSingleIcon(config, card); + } + + public static renderSingleIcon(config: IconActionConfig, card: XiaomiVacuumMapCard): TemplateResult { return html` { + return {icon: i.icon, name: (i.label??"")}; + }); + const selected = config.findIndex(i => i.isSelected); + const itemClass = `icon-menu-${config[0].menu_id}`; + const menu = menus.find(m => m.classList.contains(itemClass)); + return this._render( + items, + selected, + (v) => { + handleAction(card, card.hass as unknown as HomeAssistant, config[v], "tap"); + }, + menu, + "icon", + false, + [itemClass] + ); + } + public static get styles(): CSSResultGroup { return css` - .vacuum-actions-item { + .icon-dropdown-menu-button-button { float: left; width: 50px; height: 50px; @@ -32,7 +66,125 @@ export class IconRenderer { justify-content: center; align-items: center; background-color: transparent; + border-radius: var(--map-card-internal-big-radius); } + + .icon-dropdown-menu { + --mdc-menu-item-height: 50px; + --mdc-theme-primary: transparent; + --mdc-list-vertical-padding: 0px; + --mdc-list-side-padding: 0px; + --mdc-shape-medium: var(--map-card-internal-big-radius); + --mdc-ripple-color: transparent; + } + + .icon-dropdown-menu-button { + display: inline-flex; + } + + .icon-dropdown-list-item:host:host { + flex-grow: 1; + } + + .icon-dropdown-menu-button-text { + display: inline-flex; + line-height: 50px; + background-color: transparent; + padding-left: 10px; + padding-right: 15px; + } + + .icon-dropdown-menu-entry { + display: inline-flex; + width: 100%; + } + + .icon-dropdown-menu-entry.selected { + border-radius: var(--map-card-internal-big-radius); + background-color: var(--map-card-internal-primary-color); + color: var(--map-card-internal-primary-text-color); + } + + .icon-dropdown-menu-entry-button-wrapper.first:not(.selected) { + border-top-left-radius: var(--map-card-internal-big-radius); + border-top-right-radius: var(--map-card-internal-big-radius); + } + + .icon-dropdown-menu-entry-button-wrapper.last:not(.selected) { + border-bottom-left-radius: var(--map-card-internal-big-radius); + border-bottom-right-radius: var(--map-card-internal-big-radius); + } + + .icon-dropdown-menu-entry-button.selected { + border-start-start-radius: var(--map-card-internal-big-radius); + border-end-start-radius: var(--map-card-internal-big-radius); + background-color: var(--map-card-internal-primary-color); + color: var(--map-card-internal-primary-text-color); + } + + .icon-dropdown-menu-entry-button-wrapper { + background-color: var(--map-card-internal-secondary-color); + color: var(--map-card-internal-secondary-text-color); + overflow: hidden; + } + + .icon-dropdown-menu-entry-button { + width: 50px; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + background-color: var(--map-card-internal-secondary-color); + color: var(--map-card-internal-secondary-text-color); + } + + .icon-dropdown-menu-entry-text { + display: inline-flex; + line-height: 50px; + background-color: transparent; + padding-left: 10px; + padding-right: 15px; + } + + .icon-dropdown-menu-listbox { + padding: 0; + background-color: transparent; + } + `; } + + public static preprocessIcons(icons: IconActionConfig[] | undefined, + internalVariables: VariablesStorage, + hass: HomeAssistantFixed, + ): (IconActionConfig | DropdownIconActionConfig)[] { + if (icons === undefined) { + return []; + } + const output: (IconActionConfig | DropdownIconActionConfig)[] = []; + const menuIndexes = new Map(); + icons.forEach(i => { + const isSelected = areConditionsMet(i, internalVariables, hass); + if (i.menu_id === undefined) { + if (isSelected) { + output.push(i); + } + } else { + const icon = {...i, isSelected: isSelected}; + if (menuIndexes.has(i.menu_id) && Array.isArray(output[menuIndexes.get(i.menu_id) as number])) { + (output[menuIndexes.get(i.menu_id) as number] as DropdownIconActionConfig).push(icon); + } else { + menuIndexes.set(i.menu_id, output.length); + output.push([icon]); + } + } + }); + for (let i = output.length - 1; i >= 0; i--) { + if (Array.isArray(output[i]) + && !(output[i] as DropdownIconActionConfig).some(iconEntry => iconEntry.isSelected)) { + output.splice(i, 1); + } + } + return output; + } } diff --git a/src/renderers/tile-renderer.ts b/src/renderers/tile-renderer.ts index 6b46842d..cfb5410b 100644 --- a/src/renderers/tile-renderer.ts +++ b/src/renderers/tile-renderer.ts @@ -27,7 +27,7 @@ export class TileRenderer { return html`
    ; private currentPreset!: CardPresetConfig; private watchedEntities: string[] = []; private selectedManualRectangles: ManualRectangle[] = []; @@ -131,7 +132,7 @@ export class XiaomiVacuumMapCard extends LitElement { private modes: MapMode[] = []; private shouldHandleMouseUp!: boolean; private lastHassUpdate!: Date; - private isInEditor = false; + public isInEditor = false; constructor() { super(); @@ -265,7 +266,9 @@ export class XiaomiVacuumMapCard extends LitElement { this._updateCalibration(preset); const tiles = preset.tiles?.filter(tile => areConditionsMet(tile, this.internalVariables, this.hass)); - const icons = preset.icons?.filter(icon => areConditionsMet(icon, this.internalVariables, this.hass)); + // const icons = preset.icons?.filter(icon => areConditionsMet(icon, this.internalVariables, this.hass)); + + const icons = IconRenderer.preprocessIcons(preset.icons, this.internalVariables, this.hass); const modes = this.modes; const mapSrc = this._getMapSrc(preset); @@ -405,7 +408,7 @@ export class XiaomiVacuumMapCard extends LitElement { () => html`
    - ${icons?.map(icon => IconRenderer.render(icon, this))} + ${icons?.map((icon ) => IconRenderer.render(icon, this, [...this._iconDropdownMenus??[]]))}
    `, @@ -421,6 +424,7 @@ export class XiaomiVacuumMapCard extends LitElement {
    ` )} ${ToastRenderer.render("map-card")} + `; } @@ -1757,6 +1761,9 @@ export class XiaomiVacuumMapCard extends LitElement { overflow: hidden; background-color: var(--map-card-internal-secondary-color); color: var(--map-card-internal-secondary-text-color); + display: flex; + flex-wrap: wrap; + justify-content: center; } .tiles-wrapper { From ae7776de0613a76ad3a2ae5410449f1b1c509289 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Fri, 26 May 2023 06:01:43 +0200 Subject: [PATCH 056/100] Extract tile to a separate component --- src/components/tile.ts | 175 +++++++++++++++++++++++++++++++++ src/renderers/tile-renderer.ts | 158 ----------------------------- src/types/types.ts | 2 + src/xiaomi-vacuum-map-card.ts | 18 +++- 4 files changed, 190 insertions(+), 163 deletions(-) create mode 100644 src/components/tile.ts delete mode 100644 src/renderers/tile-renderer.ts diff --git a/src/components/tile.ts b/src/components/tile.ts new file mode 100644 index 00000000..c1f42b31 --- /dev/null +++ b/src/components/tile.ts @@ -0,0 +1,175 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import { ifDefined } from "lit/directives/if-defined"; +import { ActionHandlerEvent, computeStateDomain, hasAction } from "custom-card-helpers"; +import { HassEntity } from "home-assistant-js-websocket/dist/types"; + +import { actionHandler } from "../action-handler-directive"; +import { conditional } from "../utils"; +import { EntityConfig, ReplacedKey, TileConfig, VariablesStorage } from "../types/types"; +import { HomeAssistantFixed } from "../types/fixes"; +import { localizeEntity } from "../localize/localize"; +import { computeAttributeNameDisplay } from "../localize/hass/compute_attribute_display"; +import { blankBeforePercent } from "../localize/hass/blank_before_percent"; + + +@customElement("xvmc-tile") +export class Tile extends LitElement { + + @property({attribute: false}) + private config!: TileConfig; + + @property({attribute: false}) + private hass!: HomeAssistantFixed; + + @property({attribute: false}) + private isInEditor!: boolean; + + @property({attribute: false}) + private handleActionWithConfig!: (_: TileConfig) => ((ev: ActionHandlerEvent) => void); + + @property({attribute: false}) + private internalVariables!: VariablesStorage; + + protected render(): TemplateResult | void { + if (!this.config || !this.hass || !this.handleActionWithConfig || !this.internalVariables) { + return; + } + this.className = `tile-wrapper clickable ripple ${this.config.tile_id ? `tile-${this.config.tile_id}-wrapper` : ""}`; + const stateObj = this.config.entity ? this.hass.states[this.config.entity] : undefined; + const title = this.getTileLabel(stateObj); + const value = this.getTileValue(stateObj); + const icon = this.getIcon(stateObj); + const domain = stateObj ? computeStateDomain(stateObj) : undefined; + + return html` +
    +
    ${title}
    +
    + ${conditional( + icon !== "", + () => html` +
    + + +
    `, + )} +
    ${value}
    +
    +
    + `; + } + + private getTileLabel( + stateObject?: HassEntity, + ) { + if (this.config.label !== undefined) + return this.config.label; + if (stateObject !== undefined) { + if (this.config.attribute !== undefined) + return computeAttributeNameDisplay(this.hass.localize, stateObject, this.hass.entities, this.config.attribute); + return stateObject.attributes?.friendly_name ?? this.config.entity; + } + return this.config.tile_id ?? "tile"; + } + + private getTileValue( + stateObject?: HassEntity, + ) { + let value: ReplacedKey = ""; + const processNumber = this.config.multiplier !== undefined || this.config.precision !== undefined; + if (this.config.entity && stateObject) { + if (processNumber) { + value = this.config.attribute + ? stateObject.attributes[this.config.attribute] + : stateObject.state; + } else { + value = localizeEntity(this.hass, this.config as EntityConfig, this.hass.states[this.config.entity]); + } + } else if (this.config.internal_variable && this.config.internal_variable in this.internalVariables) { + value = this.internalVariables[this.config.internal_variable]; + } + if (processNumber && value !== null && (typeof value === "number" || !isNaN(+value))) { + value = parseFloat(value.toString()) * (this.config.multiplier ?? 1); + if (this.config.precision !== undefined) { + value = value.toFixed(this.config.precision); + } + } + const translations = this.config.translations ?? {}; + if (`${value}`.toLowerCase() in translations) { + value = translations[`${value}`.toLowerCase()]; + } + const unit = this.getUnit(); + return `${value}${unit}`; + } + + private getIcon(stateObject?: HassEntity) { + if (this.config.icon_source) { + const split = this.config.icon_source.split(".attributes."); + const entity = this.hass.states[split[0]]; + let icon = entity.state; + if (split.length === 2) { + icon = entity.attributes[split[1]]; + } + return icon; + } + if (this.config.icon === undefined && stateObject) { + return stateObject.attributes.icon ?? null; + } + return this.config.icon; + } + + private getUnit() { + return !this.config.unit + ? "" : + this.config.unit === "%" + ? blankBeforePercent(this.hass.locale) + "%" + : ` ${this.config.unit}`; + } + + protected createRenderRoot(): Element | ShadowRoot { + return this; + } + + public static get styles(): CSSResultGroup { + return css` + .tile-wrapper { + min-width: fit-content; + width: 80px; + padding: 10px; + border-radius: var(--map-card-internal-small-radius); + background-color: var(--map-card-internal-tertiary-color); + flex-grow: 1; + overflow: hidden; + color: var(--map-card-internal-tertiary-text-color); + } + + .tile-title { + font-size: smaller; + } + + .tile-value-wrapper { + display: inline-flex; + align-items: flex-end; + padding-top: 5px; + } + + .tile-icon { + padding-right: 5px; + } + + .tile-value { + } + `; + } +} diff --git a/src/renderers/tile-renderer.ts b/src/renderers/tile-renderer.ts deleted file mode 100644 index cfb5410b..00000000 --- a/src/renderers/tile-renderer.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { css, CSSResultGroup, html, TemplateResult } from "lit"; -import { ifDefined } from "lit/directives/if-defined"; -import { computeStateDomain, hasAction } from "custom-card-helpers"; -import { HassEntity } from "home-assistant-js-websocket/dist/types"; - -import { actionHandler } from "../action-handler-directive"; -import { conditional, handleActionWithConfig } from "../utils"; -import { EntityConfig, ReplacedKey, TileConfig, VariablesStorage } from "../types/types"; -import { XiaomiVacuumMapCard } from "../xiaomi-vacuum-map-card"; -import { HomeAssistantFixed } from "../types/fixes"; -import { localizeEntity } from "../localize/localize"; -import { computeAttributeNameDisplay } from "../localize/hass/compute_attribute_display"; -import { blankBeforePercent } from "../localize/hass/blank_before_percent"; - -export class TileRenderer { - public static render( - config: TileConfig, - internalVariables: VariablesStorage, - card: XiaomiVacuumMapCard, - ): TemplateResult { - const stateObj = config.entity ? card.hass.states[config.entity] : undefined; - const title = this.getTileLabel(card.hass, config, stateObj); - const value = this.getTileValue(card.hass, config, internalVariables, stateObj); - const icon = this.getIcon(card.hass, config, stateObj); - const domain = stateObj ? computeStateDomain(stateObj) : undefined; - - return html` -
    -
    ${title}
    -
    - ${conditional( - icon !== "", - () => html`
    - - -
    `, - )} -
    ${value}
    -
    -
    - `; - } - - private static getTileLabel( - hass: HomeAssistantFixed, - tile: TileConfig, - stateObject?: HassEntity, - ) { - if (tile.label !== undefined) - return tile.label; - if (stateObject !== undefined) { - if (tile.attribute !== undefined) - return computeAttributeNameDisplay(hass.localize, stateObject, hass.entities, tile.attribute); - return stateObject.attributes?.friendly_name ?? tile.entity; - } - return tile.tile_id ?? "tile"; - } - - private static getTileValue( - hass: HomeAssistantFixed, - config: TileConfig, - internalVariables: VariablesStorage, - stateObject?: HassEntity, - ) { - let value: ReplacedKey = ""; - const processNumber = config.multiplier !== undefined || config.precision !== undefined; - if (config.entity && stateObject) { - if (processNumber) { - value = config.attribute - ? stateObject.attributes[config.attribute] - : stateObject.state; - } else { - value = localizeEntity(hass, config as EntityConfig, hass.states[config.entity]); - } - } else if (config.internal_variable && config.internal_variable in internalVariables) { - value = internalVariables[config.internal_variable]; - } - if (processNumber && value !== null && (typeof value === "number" || !isNaN(+value))) { - value = parseFloat(value.toString()) * (config.multiplier ?? 1); - if (config.precision !== undefined) { - value = value.toFixed(config.precision); - } - } - const translations = config.translations ?? {}; - if (`${value}`.toLowerCase() in translations) { - value = translations[`${value}`.toLowerCase()]; - } - const unit = this.getUnit(hass, config); - return `${value}${unit}`; - } - - private static getIcon(hass: HomeAssistantFixed, config: TileConfig, stateObject?: HassEntity) { - if (config.icon_source) { - const split = config.icon_source.split(".attributes."); - const entity = hass.states[split[0]]; - let icon = entity.state; - if (split.length === 2) { - icon = entity.attributes[split[1]]; - } - return icon; - } - if (config.icon === undefined && stateObject) { - return stateObject.attributes.icon ?? null; - } - return config.icon; - } - - private static getUnit(hass: HomeAssistantFixed, config: TileConfig) { - return !config.unit - ? "" : - config.unit === "%" - ? blankBeforePercent(hass.locale) + "%" - : ` ${config.unit}`; - } - - public static get styles(): CSSResultGroup { - return css` - .tile-wrapper { - min-width: fit-content; - width: 80px; - padding: 10px; - border-radius: var(--map-card-internal-small-radius); - background-color: var(--map-card-internal-tertiary-color); - flex-grow: 1; - overflow: hidden; - color: var(--map-card-internal-tertiary-text-color); - } - - .tile-title { - font-size: smaller; - } - - .tile-value-wrapper { - display: inline-flex; - align-items: flex-end; - padding-top: 5px; - } - - .tile-icon { - padding-right: 5px; - } - - .tile-value { - } - `; - } -} diff --git a/src/types/types.ts b/src/types/types.ts index f22d8b16..71c14621 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -2,6 +2,7 @@ import { ActionConfig, LovelaceCard, LovelaceCardConfig, LovelaceCardEditor } fr import { ACTION_HANDLER_CUSTOM_ELEMENT_NAME, CARD_CUSTOM_ELEMENT_NAME, EDITOR_CUSTOM_ELEMENT_NAME } from "../const"; import { XiaomiVacuumMapCardActionHandler } from "../action-handler-directive"; import { XiaomiVacuumMapCard } from "../xiaomi-vacuum-map-card"; +import { Tile } from "../components/tile"; declare global { interface HTMLElementTagNameMap { @@ -9,6 +10,7 @@ declare global { [EDITOR_CUSTOM_ELEMENT_NAME]: LovelaceCardEditor; [ACTION_HANDLER_CUSTOM_ELEMENT_NAME]: XiaomiVacuumMapCardActionHandler; "hui-error-card": LovelaceCard; + "xvmc-tile": Tile; } } diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index 12480d09..61f8f227 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -52,7 +52,7 @@ import { conditional, delay, getMousePosition, - getWatchedEntities, + getWatchedEntities, handleActionWithConfig, hasConfigOrAnyEntityChanged, stopEvent, } from "./utils"; @@ -66,7 +66,6 @@ import { RepeatsType } from "./model/map_mode/repeats-type"; import { PlatformGenerator } from "./model/generators/platform-generator"; import { sortTiles, TilesGenerator } from "./model/generators/tiles-generator"; import { IconListGenerator } from "./model/generators/icon-list-generator"; -import { TileRenderer } from "./renderers/tile-renderer"; import { IconRenderer } from "./renderers/icon-renderer"; import { ToastRenderer } from "./renderers/toast-renderer"; import { ModesMenuRenderer } from "./renderers/modes-menu-renderer"; @@ -78,6 +77,8 @@ import { HomeAssistantFixed } from "./types/fixes"; import "./polyfills/objectEntries"; import "./polyfills/objectFromEntries"; +import { Tile } from "./components/tile"; + const line1 = " XIAOMI-VACUUM-MAP-CARD"; const line2 = ` ${localize("common.version")} ${CARD_VERSION}`; const length = Math.max(line1.length, line2.length) + 3; @@ -417,14 +418,21 @@ export class XiaomiVacuumMapCard extends LitElement { (tiles?.length ?? 0) !== 0, () => html`
    - ${tiles?.map(sensor => TileRenderer.render(sensor, this.internalVariables, this))} + ${tiles?.map(tile => html` + handleActionWithConfig(this, c)} + .internalVariables=${this.internalVariables} + > + `)}
    `, )}
    ` )} ${ToastRenderer.render("map-card")} - `; } @@ -1813,7 +1821,7 @@ export class XiaomiVacuumMapCard extends LitElement { ${Room.styles} ${ModesMenuRenderer.styles} ${IconRenderer.styles} - ${TileRenderer.styles} + ${Tile.styles} ${ToastRenderer.styles} `; } From a115c528b715875a8f7427051f451f5c29e1a2d8 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Sat, 27 May 2023 17:15:00 +0200 Subject: [PATCH 057/100] Extract dropdown menu to a separate component --- src/components/dropdown-menu.ts | 201 +++++++++++++++++++++++++++ src/renderers/modes-menu-renderer.ts | 106 -------------- src/xiaomi-vacuum-map-card.ts | 17 ++- 3 files changed, 211 insertions(+), 113 deletions(-) create mode 100644 src/components/dropdown-menu.ts delete mode 100644 src/renderers/modes-menu-renderer.ts diff --git a/src/components/dropdown-menu.ts b/src/components/dropdown-menu.ts new file mode 100644 index 00000000..7a8a9f5c --- /dev/null +++ b/src/components/dropdown-menu.ts @@ -0,0 +1,201 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { conditional } from "../utils"; +import { customElement, property, query } from "lit/decorators"; + + +interface DropdownEntry { + icon: string; + name: string; +} + +@customElement("xvmc-dropdown-menu") +export class DropdownMenu extends LitElement { + + @property({ attribute: false }) + private values!: T[]; + + @property({ attribute: false }) + private currentIndex!: number; + + @property({ attribute: false }) + private setValue!: (_: number) => void; + + @property({ attribute: false }) + private renderNameCollapsed!: boolean; + + @property({ attribute: false }) + private additionalClasses: string[] = []; + + @query(".dropdown-menu") + private menu: HTMLElement | undefined; + + public render(): TemplateResult { + const currentValue = this.values[this.currentIndex]; + return html` + + + ${this.values.map( + (mode, index) => html` + +
    +
    + + + +
    +
    ${mode.name}
    +
    +
    `, + )} +
    + `; + } + + private updateStyles(items: number): void { + const div = this.menu?.shadowRoot?.querySelector("div") as HTMLElement; + if (this.menu && div) { + const height = 50; + const minDiff = (items - 1) * height + 32; + if (window.innerHeight - div.getBoundingClientRect().bottom >= minDiff) { + div.style.marginTop = `0px`; + this.menu.style.marginTop = `0px`; + div.style.marginBottom = `-${height}px`; + this.menu.style.marginBottom = `${height}px`; + } else { + div.style.marginTop = `-${height}px`; + this.menu.style.marginTop = `${height}px`; + div.style.marginBottom = "0px"; + this.menu.style.marginBottom = "0px"; + } + const mwcMenu = this.menu.shadowRoot?.querySelector("mwc-menu") as HTMLElement; + if (mwcMenu) { + mwcMenu.style.zIndex = "1"; + mwcMenu.style.position = "fixed"; + } + this.menu.querySelectorAll("mwc-list-item").forEach((item) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + item.shadowRoot!.querySelector("span")!.style.flexGrow = "1"; + }); + } + } + + protected createRenderRoot(): Element | ShadowRoot { + return this; + } + + public static get styles(): CSSResultGroup { + return css` + .dropdown-menu { + --mdc-menu-item-height: 50px; + --mdc-theme-primary: transparent; + --mdc-list-vertical-padding: 0px; + --mdc-list-side-padding: 0px; + --mdc-shape-medium: var(--map-card-internal-big-radius); + --mdc-ripple-color: transparent; + } + + .dropdown-menu-button { + display: inline-flex; + } + + .dropdown-list-item:host:host { + flex-grow: 1; + } + + .dropdown-menu-button-button { + width: 50px; + height: 50px; + border-radius: var(--map-card-internal-big-radius); + display: flex; + justify-content: center; + background-color: var(--map-card-internal-primary-color); + align-items: center; + } + + .dropdown-menu-button-text { + display: inline-flex; + line-height: 50px; + background-color: transparent; + padding-left: 10px; + padding-right: 15px; + } + + .dropdown-menu-entry { + display: inline-flex; + width: 100%; + } + + .dropdown-menu-entry.selected { + border-radius: var(--map-card-internal-big-radius); + background-color: var(--map-card-internal-primary-color); + color: var(--map-card-internal-primary-text-color); + } + + .dropdown-menu-entry-button-wrapper.first:not(.selected) { + border-top-left-radius: var(--map-card-internal-big-radius); + border-top-right-radius: var(--map-card-internal-big-radius); + } + + .dropdown-menu-entry-button-wrapper.last:not(.selected) { + border-bottom-left-radius: var(--map-card-internal-big-radius); + border-bottom-right-radius: var(--map-card-internal-big-radius); + } + + .dropdown-menu-entry-button.selected { + border-start-start-radius: var(--map-card-internal-big-radius); + border-end-start-radius: var(--map-card-internal-big-radius); + background-color: var(--map-card-internal-primary-color); + color: var(--map-card-internal-primary-text-color); + } + + .dropdown-menu-entry-button-wrapper { + background-color: var(--map-card-internal-secondary-color); + color: var(--map-card-internal-secondary-text-color); + overflow: hidden; + } + + .dropdown-menu-entry-button { + width: 50px; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + background-color: var(--map-card-internal-secondary-color); + color: var(--map-card-internal-secondary-text-color); + } + + .dropdown-menu-entry-text { + display: inline-flex; + line-height: 50px; + background-color: transparent; + padding-left: 10px; + padding-right: 15px; + } + + .dropdown-menu-listbox { + padding: 0; + background-color: transparent; + } + `; + } +} \ No newline at end of file diff --git a/src/renderers/modes-menu-renderer.ts b/src/renderers/modes-menu-renderer.ts deleted file mode 100644 index d9d61614..00000000 --- a/src/renderers/modes-menu-renderer.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { css, CSSResultGroup, TemplateResult } from "lit"; - -import { MapMode } from "../model/map_mode/map-mode"; -import { DropdownMenuRenderer } from "./dropdown-menu-renderer"; - -export class ModesMenuRenderer extends DropdownMenuRenderer { - public static render(modes: MapMode[], getMode: number, setMode: (number) => void, menu: HTMLElement | undefined): TemplateResult { - return this._render(modes, getMode, setMode, menu, "modes", true); - } - - public static get styles(): CSSResultGroup { - return css` - .modes-dropdown-menu { - --mdc-menu-item-height: 50px; - --mdc-theme-primary: transparent; - --mdc-list-vertical-padding: 0px; - --mdc-list-side-padding: 0px; - --mdc-shape-medium: var(--map-card-internal-big-radius); - --mdc-ripple-color: transparent; - } - - .modes-dropdown-menu-button { - display: inline-flex; - } - - .modes-dropdown-list-item:host:host { - flex-grow: 1; - } - - .modes-dropdown-menu-button-button { - width: 50px; - height: 50px; - border-radius: var(--map-card-internal-big-radius); - display: flex; - justify-content: center; - background-color: var(--map-card-internal-primary-color); - align-items: center; - } - - .modes-dropdown-menu-button-text { - display: inline-flex; - line-height: 50px; - background-color: transparent; - padding-left: 10px; - padding-right: 15px; - } - - .modes-dropdown-menu-entry { - display: inline-flex; - width: 100%; - } - - .modes-dropdown-menu-entry.selected { - border-radius: var(--map-card-internal-big-radius); - background-color: var(--map-card-internal-primary-color); - color: var(--map-card-internal-primary-text-color); - } - - .modes-dropdown-menu-entry-button-wrapper.first:not(.selected) { - border-top-left-radius: var(--map-card-internal-big-radius); - border-top-right-radius: var(--map-card-internal-big-radius); - } - - .modes-dropdown-menu-entry-button-wrapper.last:not(.selected) { - border-bottom-left-radius: var(--map-card-internal-big-radius); - border-bottom-right-radius: var(--map-card-internal-big-radius); - } - - .modes-dropdown-menu-entry-button.selected { - border-start-start-radius: var(--map-card-internal-big-radius); - border-end-start-radius: var(--map-card-internal-big-radius); - background-color: var(--map-card-internal-primary-color); - color: var(--map-card-internal-primary-text-color); - } - - .modes-dropdown-menu-entry-button-wrapper { - background-color: var(--map-card-internal-secondary-color); - color: var(--map-card-internal-secondary-text-color); - overflow: hidden; - } - - .modes-dropdown-menu-entry-button { - width: 50px; - height: 50px; - display: flex; - justify-content: center; - align-items: center; - background-color: var(--map-card-internal-secondary-color); - color: var(--map-card-internal-secondary-text-color); - } - - .modes-dropdown-menu-entry-text { - display: inline-flex; - line-height: 50px; - background-color: transparent; - padding-left: 10px; - padding-right: 15px; - } - - .modes-dropdown-menu-listbox { - padding: 0; - background-color: transparent; - } - `; - } -} diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index 61f8f227..a11c57d2 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -68,7 +68,6 @@ import { sortTiles, TilesGenerator } from "./model/generators/tiles-generator"; import { IconListGenerator } from "./model/generators/icon-list-generator"; import { IconRenderer } from "./renderers/icon-renderer"; import { ToastRenderer } from "./renderers/toast-renderer"; -import { ModesMenuRenderer } from "./renderers/modes-menu-renderer"; import { CoordinatesConverter } from "./model/map_objects/coordinates-converter"; import { MapObject } from "./model/map_objects/map-object"; import { MousePosition } from "./model/map_objects/mouse-position"; @@ -78,6 +77,7 @@ import "./polyfills/objectEntries"; import "./polyfills/objectFromEntries"; import { Tile } from "./components/tile"; +import { DropdownMenu } from "./components/dropdown-menu"; const line1 = " XIAOMI-VACUUM-MAP-CARD"; const line2 = ` ${localize("common.version")} ${CARD_VERSION}`; @@ -390,11 +390,14 @@ export class XiaomiVacuumMapCard extends LitElement { () => html`
    - ${conditional(modes.length > 1, () => - ModesMenuRenderer.render(modes, this.selectedMode, selected => - this._setCurrentMode(selected), - this._modesDropdownMenu - ), + ${conditional(modes.length > 1, () => html` + this._setCurrentMode(selected)} + .renderNameCollapsed=${true}> + + `, )} ${conditional( mapControls.length > 0, @@ -1819,9 +1822,9 @@ export class XiaomiVacuumMapCard extends LitElement { ${ManualPoint.styles} ${PredefinedPoint.styles} ${Room.styles} - ${ModesMenuRenderer.styles} ${IconRenderer.styles} ${Tile.styles} + ${DropdownMenu.styles} ${ToastRenderer.styles} `; } From 9aebafc14c23df69b102487583ed570c5416a8e1 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Sun, 28 May 2023 01:42:37 +0200 Subject: [PATCH 058/100] Extract icon to a separate component --- src/components/dropdown-icon.ts | 48 ++++++ src/components/icon.ts | 86 +++++++++++ src/components/single-icon.ts | 53 +++++++ src/components/tile.ts | 16 +- src/renderers/dropdown-menu-renderer.ts | 85 ----------- src/renderers/icon-renderer.ts | 190 ------------------------ src/types/types.ts | 13 +- src/utils.ts | 45 +++--- src/xiaomi-vacuum-map-card.ts | 26 ++-- 9 files changed, 253 insertions(+), 309 deletions(-) create mode 100644 src/components/dropdown-icon.ts create mode 100644 src/components/icon.ts create mode 100644 src/components/single-icon.ts delete mode 100644 src/renderers/dropdown-menu-renderer.ts delete mode 100644 src/renderers/icon-renderer.ts diff --git a/src/components/dropdown-icon.ts b/src/components/dropdown-icon.ts new file mode 100644 index 00000000..5f9ec1f7 --- /dev/null +++ b/src/components/dropdown-icon.ts @@ -0,0 +1,48 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; + +import { + ActionHandlerFunctionCreator, + DropdownIconActionConfig, +} from "../types/types"; +import { customElement, property } from "lit/decorators"; + +@customElement("xvmc-dropdown-icon") +export class DropdownIcon extends LitElement { + + @property({attribute: false}) + private config!: DropdownIconActionConfig; + + @property({attribute: false}) + private onAction!: ActionHandlerFunctionCreator; + + public render(): TemplateResult { + const items = this.config.map(i => { + return { icon: i.icon, name: (i.label ?? "") }; + }); + const currentIndex = this.config.findIndex(i => i.isSelected); + const itemClass = `icon-menu-${this.config[0].menu_id}`; + return html` + { + return this.onAction(this.config[selected])(); + }} + .renderNameCollapsed=${false} + .additionalClasses=${[itemClass, "dropdown-icon"]}> + + ` + } + + protected createRenderRoot(): Element | ShadowRoot { + return this; + } + + public static get styles(): CSSResultGroup { + return css` + .dropdown-icon * .dropdown-menu-button-button { + background-color: transparent; + } + `; + } +} diff --git a/src/components/icon.ts b/src/components/icon.ts new file mode 100644 index 00000000..ddea3717 --- /dev/null +++ b/src/components/icon.ts @@ -0,0 +1,86 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; + +import { areConditionsMet } from "../utils"; +import { + ActionHandlerFunctionCreator, + DropdownIconActionConfig, + IconActionConfig, + VariablesStorage, +} from "../types/types"; +import { HomeAssistantFixed } from "../types/fixes"; +import "./single-icon"; +import { DropdownIcon } from "./dropdown-icon"; +import { customElement, property } from "lit/decorators"; +import { SingleIcon } from "./single-icon"; + +@customElement("xvmc-icon") +export class Icon extends LitElement { + + @property({attribute: false}) + private config!: IconActionConfig | DropdownIconActionConfig; + + @property({attribute: false}) + private onAction!: ActionHandlerFunctionCreator; + + public render(): TemplateResult { + if (Array.isArray(this.config)) { + return html` + + + `; + } + return html` + + + `; + } + + protected createRenderRoot(): Element | ShadowRoot { + return this; + } + + public static get styles(): CSSResultGroup { + return css` + ${SingleIcon.styles} + ${DropdownIcon.styles} + `; + } + + public static preprocessIcons(icons: IconActionConfig[] | undefined, + internalVariables: VariablesStorage, + hass: HomeAssistantFixed, + ): (IconActionConfig | DropdownIconActionConfig)[] { + if (icons === undefined) { + return []; + } + const output: (IconActionConfig | DropdownIconActionConfig)[] = []; + const menuIndexes = new Map(); + icons.forEach(i => { + const isSelected = areConditionsMet(i, internalVariables, hass); + if (i.menu_id === undefined) { + if (isSelected) { + output.push(i); + } + } else { + const icon = {...i, isSelected: isSelected}; + if (menuIndexes.has(i.menu_id) && Array.isArray(output[menuIndexes.get(i.menu_id) as number])) { + (output[menuIndexes.get(i.menu_id) as number] as DropdownIconActionConfig).push(icon); + } else { + menuIndexes.set(i.menu_id, output.length); + output.push([icon]); + } + } + }); + for (let i = output.length - 1; i >= 0; i--) { + if (Array.isArray(output[i]) + && !(output[i] as DropdownIconActionConfig).some(iconEntry => iconEntry.isSelected)) { + output.splice(i, 1); + } + } + return output; + } +} diff --git a/src/components/single-icon.ts b/src/components/single-icon.ts new file mode 100644 index 00000000..8594c5c4 --- /dev/null +++ b/src/components/single-icon.ts @@ -0,0 +1,53 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { hasAction } from "custom-card-helpers"; + +import { actionHandler } from "../action-handler-directive"; +import { + ActionHandlerFunctionCreator, + IconActionConfig, +} from "../types/types"; +import { customElement, property } from "lit/decorators"; + +@customElement("xvmc-single-icon") +export class SingleIcon extends LitElement { + + @property({attribute: false}) + private config!: IconActionConfig; + + @property({attribute: false}) + private onAction!: ActionHandlerFunctionCreator; + + public render(): TemplateResult { + return html` + + + + `; + } + + protected createRenderRoot(): Element | ShadowRoot { + return this; + } + + public static get styles(): CSSResultGroup { + return css` + .single-icon { + float: left; + width: 50px; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + background-color: transparent; + border-radius: var(--map-card-internal-big-radius); + } + `; + } +} diff --git a/src/components/tile.ts b/src/components/tile.ts index c1f42b31..53f7c7fb 100644 --- a/src/components/tile.ts +++ b/src/components/tile.ts @@ -1,12 +1,18 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; -import { ActionHandlerEvent, computeStateDomain, hasAction } from "custom-card-helpers"; +import { computeStateDomain, hasAction } from "custom-card-helpers"; import { HassEntity } from "home-assistant-js-websocket/dist/types"; import { actionHandler } from "../action-handler-directive"; import { conditional } from "../utils"; -import { EntityConfig, ReplacedKey, TileConfig, VariablesStorage } from "../types/types"; +import { + ActionHandlerFunctionCreator, + EntityConfig, + ReplacedKey, + TileConfig, + VariablesStorage, +} from "../types/types"; import { HomeAssistantFixed } from "../types/fixes"; import { localizeEntity } from "../localize/localize"; import { computeAttributeNameDisplay } from "../localize/hass/compute_attribute_display"; @@ -26,13 +32,13 @@ export class Tile extends LitElement { private isInEditor!: boolean; @property({attribute: false}) - private handleActionWithConfig!: (_: TileConfig) => ((ev: ActionHandlerEvent) => void); + private onAction!: ActionHandlerFunctionCreator; @property({attribute: false}) private internalVariables!: VariablesStorage; protected render(): TemplateResult | void { - if (!this.config || !this.hass || !this.handleActionWithConfig || !this.internalVariables) { + if (!this.config || !this.hass || !this.onAction || !this.internalVariables) { return; } this.className = `tile-wrapper clickable ripple ${this.config.tile_id ? `tile-${this.config.tile_id}-wrapper` : ""}`; @@ -45,7 +51,7 @@ export class Tile extends LitElement { return html`
    ( - values: T[], - currentIndex: number, - setValue: (_: number) => void, - menu: HTMLElement | undefined, - classPrefix: string, - renderNameCollapsed: boolean, - additionalClasses: string[] = [] - ): TemplateResult { - const currentValue = values[currentIndex]; - return html` - -
    - - - - ${conditional( - renderNameCollapsed, - () => html`
    ${currentValue.name}
    ` - )} -
    - ${values.map( - (mode, index) => html` -
    -
    - - - -
    -
    ${mode.name}
    -
    -
    `, - )} -
    - `; - } - - private static updateStyles(menu: HTMLElement | undefined, items: number): void { - const div = menu?.shadowRoot?.querySelector("div") as HTMLElement; - if (menu && div) { - const height = 50; - const minDiff = (items - 1) * height + 32; - if (window.innerHeight - div.getBoundingClientRect().bottom >= minDiff) { - div.style.marginTop = `0px`; - menu.style.marginTop = `0px`; - div.style.marginBottom = `-${height}px`; - menu.style.marginBottom = `${height}px`; - } else { - div.style.marginTop = `-${height}px`; - menu.style.marginTop = `${height}px`; - div.style.marginBottom = "0px"; - menu.style.marginBottom = "0px"; - } - const mwcMenu = menu.shadowRoot?.querySelector("mwc-menu") as HTMLElement; - if (mwcMenu) { - mwcMenu.style.zIndex = "1"; - mwcMenu.style.position = "fixed"; - } - menu.querySelectorAll("mwc-list-item").forEach((item) => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - item.shadowRoot!.querySelector("span")!.style.flexGrow = "1"; - }); - } - } -} diff --git a/src/renderers/icon-renderer.ts b/src/renderers/icon-renderer.ts deleted file mode 100644 index 50c6915f..00000000 --- a/src/renderers/icon-renderer.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { css, CSSResultGroup, html, TemplateResult } from "lit"; -import { handleAction, hasAction, HomeAssistant } from "custom-card-helpers"; - -import { actionHandler } from "../action-handler-directive"; -import { areConditionsMet, handleActionWithConfig } from "../utils"; -import { - DropdownIconActionConfig, - IconActionConfig, - VariablesStorage, -} from "../types/types"; -import { XiaomiVacuumMapCard } from "../xiaomi-vacuum-map-card"; -import { DropdownMenuRenderer } from "./dropdown-menu-renderer"; -import { HomeAssistantFixed } from "../types/fixes"; - -export class IconRenderer extends DropdownMenuRenderer { - - public static render(config: IconActionConfig | DropdownIconActionConfig, card: XiaomiVacuumMapCard, menus: HTMLElement[]): TemplateResult { - if (Array.isArray(config)) { - return this.renderIconDropdown(config as DropdownIconActionConfig, card, menus); - } - return this.renderSingleIcon(config, card); - } - - public static renderSingleIcon(config: IconActionConfig, card: XiaomiVacuumMapCard): TemplateResult { - return html` - - - - `; - } - - public static renderIconDropdown(config: DropdownIconActionConfig, card: XiaomiVacuumMapCard, menus: HTMLElement[]): TemplateResult { - const items = config.map(i => { - return {icon: i.icon, name: (i.label??"")}; - }); - const selected = config.findIndex(i => i.isSelected); - const itemClass = `icon-menu-${config[0].menu_id}`; - const menu = menus.find(m => m.classList.contains(itemClass)); - return this._render( - items, - selected, - (v) => { - handleAction(card, card.hass as unknown as HomeAssistant, config[v], "tap"); - }, - menu, - "icon", - false, - [itemClass] - ); - } - - public static get styles(): CSSResultGroup { - return css` - .icon-dropdown-menu-button-button { - float: left; - width: 50px; - height: 50px; - display: flex; - justify-content: center; - align-items: center; - background-color: transparent; - border-radius: var(--map-card-internal-big-radius); - } - - .icon-dropdown-menu { - --mdc-menu-item-height: 50px; - --mdc-theme-primary: transparent; - --mdc-list-vertical-padding: 0px; - --mdc-list-side-padding: 0px; - --mdc-shape-medium: var(--map-card-internal-big-radius); - --mdc-ripple-color: transparent; - } - - .icon-dropdown-menu-button { - display: inline-flex; - } - - .icon-dropdown-list-item:host:host { - flex-grow: 1; - } - - .icon-dropdown-menu-button-text { - display: inline-flex; - line-height: 50px; - background-color: transparent; - padding-left: 10px; - padding-right: 15px; - } - - .icon-dropdown-menu-entry { - display: inline-flex; - width: 100%; - } - - .icon-dropdown-menu-entry.selected { - border-radius: var(--map-card-internal-big-radius); - background-color: var(--map-card-internal-primary-color); - color: var(--map-card-internal-primary-text-color); - } - - .icon-dropdown-menu-entry-button-wrapper.first:not(.selected) { - border-top-left-radius: var(--map-card-internal-big-radius); - border-top-right-radius: var(--map-card-internal-big-radius); - } - - .icon-dropdown-menu-entry-button-wrapper.last:not(.selected) { - border-bottom-left-radius: var(--map-card-internal-big-radius); - border-bottom-right-radius: var(--map-card-internal-big-radius); - } - - .icon-dropdown-menu-entry-button.selected { - border-start-start-radius: var(--map-card-internal-big-radius); - border-end-start-radius: var(--map-card-internal-big-radius); - background-color: var(--map-card-internal-primary-color); - color: var(--map-card-internal-primary-text-color); - } - - .icon-dropdown-menu-entry-button-wrapper { - background-color: var(--map-card-internal-secondary-color); - color: var(--map-card-internal-secondary-text-color); - overflow: hidden; - } - - .icon-dropdown-menu-entry-button { - width: 50px; - height: 50px; - display: flex; - justify-content: center; - align-items: center; - background-color: var(--map-card-internal-secondary-color); - color: var(--map-card-internal-secondary-text-color); - } - - .icon-dropdown-menu-entry-text { - display: inline-flex; - line-height: 50px; - background-color: transparent; - padding-left: 10px; - padding-right: 15px; - } - - .icon-dropdown-menu-listbox { - padding: 0; - background-color: transparent; - } - - `; - } - - public static preprocessIcons(icons: IconActionConfig[] | undefined, - internalVariables: VariablesStorage, - hass: HomeAssistantFixed, - ): (IconActionConfig | DropdownIconActionConfig)[] { - if (icons === undefined) { - return []; - } - const output: (IconActionConfig | DropdownIconActionConfig)[] = []; - const menuIndexes = new Map(); - icons.forEach(i => { - const isSelected = areConditionsMet(i, internalVariables, hass); - if (i.menu_id === undefined) { - if (isSelected) { - output.push(i); - } - } else { - const icon = {...i, isSelected: isSelected}; - if (menuIndexes.has(i.menu_id) && Array.isArray(output[menuIndexes.get(i.menu_id) as number])) { - (output[menuIndexes.get(i.menu_id) as number] as DropdownIconActionConfig).push(icon); - } else { - menuIndexes.set(i.menu_id, output.length); - output.push([icon]); - } - } - }); - for (let i = output.length - 1; i >= 0; i--) { - if (Array.isArray(output[i]) - && !(output[i] as DropdownIconActionConfig).some(iconEntry => iconEntry.isSelected)) { - output.splice(i, 1); - } - } - return output; - } -} diff --git a/src/types/types.ts b/src/types/types.ts index 71c14621..eb572f4c 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,4 +1,10 @@ -import { ActionConfig, LovelaceCard, LovelaceCardConfig, LovelaceCardEditor } from "custom-card-helpers"; +import { + ActionConfig, + ActionHandlerEvent, + LovelaceCard, + LovelaceCardConfig, + LovelaceCardEditor, +} from "custom-card-helpers"; import { ACTION_HANDLER_CUSTOM_ELEMENT_NAME, CARD_CUSTOM_ELEMENT_NAME, EDITOR_CUSTOM_ELEMENT_NAME } from "../const"; import { XiaomiVacuumMapCardActionHandler } from "../action-handler-directive"; import { XiaomiVacuumMapCard } from "../xiaomi-vacuum-map-card"; @@ -27,6 +33,9 @@ export type ReplacedKey = string | Record | number | unknown[]; export type VariablesStorage = Record; export type KeyReplacer = (key: string) => ReplacedKey; export type LovelaceDomEvent = CustomEvent>; +export type DropdownIconActionConfig = DropdownEntryIconActionConfig[]; +export type ActionHandlerFunction = ((_?: ActionHandlerEvent) => void); +export type ActionHandlerFunctionCreator = (_: ActionableObjectConfig) => ActionHandlerFunction; export type EntityRegistryEntry = { entity_id: string; @@ -124,8 +133,6 @@ export interface IconActionConfig extends ActionableObjectConfig, ConditionalObj readonly label?: string; } -export type DropdownIconActionConfig = DropdownEntryIconActionConfig[]; - export interface DropdownEntryIconActionConfig extends IconActionConfig { readonly isSelected: boolean; } diff --git a/src/utils.ts b/src/utils.ts index 7180d737..d24f538e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,6 +3,7 @@ import { PropertyValues } from "lit"; import { ActionableObjectConfig, + ActionHandlerFunction, CardPresetConfig, ConditionalObjectConfig, ConditionConfig, @@ -154,27 +155,37 @@ export function conditional(condition: boolean, content: () => T): T | null { return condition ? content() : null; } +export function createActionWithConfigHandler( + node: XiaomiVacuumMapCard, + config: ActionableObjectConfig | undefined, + action?: string, +): ActionHandlerFunction { + if (action) { + return (): void => handleActionWithConfig(node, config, action); + } + return (ev?: ActionHandlerEvent): void => handleActionWithConfig(node, config, ev?.detail?.action ?? "tap"); +} + export function handleActionWithConfig( node: XiaomiVacuumMapCard, config: ActionableObjectConfig | undefined, -): (ev: ActionHandlerEvent) => void { - return (ev: ActionHandlerEvent): void => { - if (node.hass && config && ev.detail.action) { - const currentPreset = node._getCurrentPreset(); - const currentMode = node._getCurrentMode(); - const tileVariables = {}; - tileVariables[TemplatableTileValue.VACUUM_ENTITY_ID] = currentPreset.entity; - if (config.hasOwnProperty("attribute")) { - tileVariables[TemplatableTileValue.ATTRIBUTE] = config["attribute"]; - } - const entity_id = config.hasOwnProperty("entity") ? config["entity"]: currentPreset.entity; - const { selection, variables } = node._getSelection(currentMode); - const defaultVariables = ServiceCallSchema.getDefaultVariables(entity_id, selection, node.repeats); - const filled = getFilledTemplate(config as Record, defaultVariables, tileVariables, - node.internalVariables, currentMode.variables, variables); - handleAction(node, node.hass as unknown as HomeAssistant, filled as ActionableObjectConfig, ev.detail.action); + action: string, +): void { + if (node.hass && config && action) { + const currentPreset = node._getCurrentPreset(); + const currentMode = node._getCurrentMode(); + const tileVariables = {}; + tileVariables[TemplatableTileValue.VACUUM_ENTITY_ID] = currentPreset.entity; + if (config.hasOwnProperty("attribute")) { + tileVariables[TemplatableTileValue.ATTRIBUTE] = config["attribute"]; } - }; + const entity_id = config.hasOwnProperty("entity") ? config["entity"] : currentPreset.entity; + const { selection, variables } = node._getSelection(currentMode); + const defaultVariables = ServiceCallSchema.getDefaultVariables(entity_id, selection, node.repeats); + const filled = getFilledTemplate(config as Record, defaultVariables, tileVariables, + node.internalVariables, currentMode.variables, variables); + handleAction(node, node.hass as unknown as HomeAssistant, filled as ActionableObjectConfig, action); + } } export function getMousePosition( diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index a11c57d2..0d062272 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -5,6 +5,7 @@ import { ActionHandlerEvent, forwardHaptic, LovelaceCard, LovelaceCardEditor } f import "./editor"; import type { + ActionableObjectConfig, LovelaceDomEvent, PredefinedPointConfig, ReplacedKey, @@ -14,6 +15,7 @@ import type { XiaomiVacuumMapCardConfig, } from "./types/types"; import { + ActionHandlerFunction, ActionType, CalibrationPoint, CardPresetConfig, @@ -52,7 +54,8 @@ import { conditional, delay, getMousePosition, - getWatchedEntities, handleActionWithConfig, + getWatchedEntities, + createActionWithConfigHandler, hasConfigOrAnyEntityChanged, stopEvent, } from "./utils"; @@ -66,7 +69,6 @@ import { RepeatsType } from "./model/map_mode/repeats-type"; import { PlatformGenerator } from "./model/generators/platform-generator"; import { sortTiles, TilesGenerator } from "./model/generators/tiles-generator"; import { IconListGenerator } from "./model/generators/icon-list-generator"; -import { IconRenderer } from "./renderers/icon-renderer"; import { ToastRenderer } from "./renderers/toast-renderer"; import { CoordinatesConverter } from "./model/map_objects/coordinates-converter"; import { MapObject } from "./model/map_objects/map-object"; @@ -78,6 +80,7 @@ import "./polyfills/objectFromEntries"; import { Tile } from "./components/tile"; import { DropdownMenu } from "./components/dropdown-menu"; +import { Icon } from "./components/icon"; const line1 = " XIAOMI-VACUUM-MAP-CARD"; const line2 = ` ${localize("common.version")} ${CARD_VERSION}`; @@ -269,7 +272,7 @@ export class XiaomiVacuumMapCard extends LitElement { const tiles = preset.tiles?.filter(tile => areConditionsMet(tile, this.internalVariables, this.hass)); // const icons = preset.icons?.filter(icon => areConditionsMet(icon, this.internalVariables, this.hass)); - const icons = IconRenderer.preprocessIcons(preset.icons, this.internalVariables, this.hass); + const icons = Icon.preprocessIcons(preset.icons, this.internalVariables, this.hass); const modes = this.modes; const mapSrc = this._getMapSrc(preset); @@ -412,7 +415,12 @@ export class XiaomiVacuumMapCard extends LitElement { () => html`
    - ${icons?.map((icon ) => IconRenderer.render(icon, this, [...this._iconDropdownMenus??[]]))} + ${icons?.map((icon ) => html` + createActionWithConfigHandler(this, c, action)} + > + `)}
    `, @@ -426,7 +434,7 @@ export class XiaomiVacuumMapCard extends LitElement { .hass=${this.hass} .config=${tile} .isInEditor=${this.isInEditor} - .handleActionWithConfig=${(c) => handleActionWithConfig(this, c)} + .onAction=${(c: ActionableObjectConfig, action?: string) => createActionWithConfigHandler(this, c, action)} .internalVariables=${this.internalVariables} > `)} @@ -1147,9 +1155,9 @@ export class XiaomiVacuumMapCard extends LitElement { this.shouldHandleMouseUp = false; } - private _handleRunAction(): (ev: ActionHandlerEvent) => void { - return async (ev: ActionHandlerEvent): Promise => { - if (this.hass && ev.detail.action) { + private _handleRunAction(): ActionHandlerFunction { + return async (ev?: ActionHandlerEvent): Promise => { + if (this.hass && ev?.detail?.action) { switch (ev.detail.action) { case "tap": await this._run(false); @@ -1822,7 +1830,7 @@ export class XiaomiVacuumMapCard extends LitElement { ${ManualPoint.styles} ${PredefinedPoint.styles} ${Room.styles} - ${IconRenderer.styles} + ${Icon.styles} ${Tile.styles} ${DropdownMenu.styles} ${ToastRenderer.styles} From 0d46af6de6f7f10af7d226f7ca64ddcc0a9bdbe2 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Sun, 28 May 2023 03:26:02 +0200 Subject: [PATCH 059/100] Extract wrappers to separate components --- src/components/dropdown-icon.ts | 9 +- src/components/dropdown-menu.ts | 9 +- src/components/icon.ts | 44 +------ src/components/icons-wrapper.ts | 100 ++++++++++++++ src/components/preset-selector.ts | 105 +++++++++++++++ src/components/rootless-lit-element.ts | 9 ++ src/components/single-icon.ts | 9 +- src/components/tile.ts | 9 +- src/components/tiles-wrapper.ts | 61 +++++++++ src/xiaomi-vacuum-map-card.ts | 173 +++++-------------------- 10 files changed, 324 insertions(+), 204 deletions(-) create mode 100644 src/components/icons-wrapper.ts create mode 100644 src/components/preset-selector.ts create mode 100644 src/components/rootless-lit-element.ts create mode 100644 src/components/tiles-wrapper.ts diff --git a/src/components/dropdown-icon.ts b/src/components/dropdown-icon.ts index 5f9ec1f7..8fc8e387 100644 --- a/src/components/dropdown-icon.ts +++ b/src/components/dropdown-icon.ts @@ -1,13 +1,14 @@ -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { css, CSSResultGroup, html, TemplateResult } from "lit"; import { ActionHandlerFunctionCreator, DropdownIconActionConfig, } from "../types/types"; import { customElement, property } from "lit/decorators"; +import { RootlessLitElement } from "./rootless-lit-element"; @customElement("xvmc-dropdown-icon") -export class DropdownIcon extends LitElement { +export class DropdownIcon extends RootlessLitElement { @property({attribute: false}) private config!: DropdownIconActionConfig; @@ -34,10 +35,6 @@ export class DropdownIcon extends LitElement { ` } - protected createRenderRoot(): Element | ShadowRoot { - return this; - } - public static get styles(): CSSResultGroup { return css` .dropdown-icon * .dropdown-menu-button-button { diff --git a/src/components/dropdown-menu.ts b/src/components/dropdown-menu.ts index 7a8a9f5c..74d5b58a 100644 --- a/src/components/dropdown-menu.ts +++ b/src/components/dropdown-menu.ts @@ -1,6 +1,7 @@ -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { css, CSSResultGroup, html, TemplateResult } from "lit"; import { conditional } from "../utils"; import { customElement, property, query } from "lit/decorators"; +import { RootlessLitElement } from "./rootless-lit-element"; interface DropdownEntry { @@ -9,7 +10,7 @@ interface DropdownEntry { } @customElement("xvmc-dropdown-menu") -export class DropdownMenu extends LitElement { +export class DropdownMenu extends RootlessLitElement { @property({ attribute: false }) private values!: T[]; @@ -99,10 +100,6 @@ export class DropdownMenu extends LitElement { } } - protected createRenderRoot(): Element | ShadowRoot { - return this; - } - public static get styles(): CSSResultGroup { return css` .dropdown-menu { diff --git a/src/components/icon.ts b/src/components/icon.ts index ddea3717..2633bf13 100644 --- a/src/components/icon.ts +++ b/src/components/icon.ts @@ -1,6 +1,5 @@ -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { css, CSSResultGroup, html, TemplateResult } from "lit"; -import { areConditionsMet } from "../utils"; import { ActionHandlerFunctionCreator, DropdownIconActionConfig, @@ -12,9 +11,10 @@ import "./single-icon"; import { DropdownIcon } from "./dropdown-icon"; import { customElement, property } from "lit/decorators"; import { SingleIcon } from "./single-icon"; +import { RootlessLitElement } from "./rootless-lit-element"; @customElement("xvmc-icon") -export class Icon extends LitElement { +export class Icon extends RootlessLitElement { @property({attribute: false}) private config!: IconActionConfig | DropdownIconActionConfig; @@ -39,48 +39,10 @@ export class Icon extends LitElement { `; } - protected createRenderRoot(): Element | ShadowRoot { - return this; - } - public static get styles(): CSSResultGroup { return css` ${SingleIcon.styles} ${DropdownIcon.styles} `; } - - public static preprocessIcons(icons: IconActionConfig[] | undefined, - internalVariables: VariablesStorage, - hass: HomeAssistantFixed, - ): (IconActionConfig | DropdownIconActionConfig)[] { - if (icons === undefined) { - return []; - } - const output: (IconActionConfig | DropdownIconActionConfig)[] = []; - const menuIndexes = new Map(); - icons.forEach(i => { - const isSelected = areConditionsMet(i, internalVariables, hass); - if (i.menu_id === undefined) { - if (isSelected) { - output.push(i); - } - } else { - const icon = {...i, isSelected: isSelected}; - if (menuIndexes.has(i.menu_id) && Array.isArray(output[menuIndexes.get(i.menu_id) as number])) { - (output[menuIndexes.get(i.menu_id) as number] as DropdownIconActionConfig).push(icon); - } else { - menuIndexes.set(i.menu_id, output.length); - output.push([icon]); - } - } - }); - for (let i = output.length - 1; i >= 0; i--) { - if (Array.isArray(output[i]) - && !(output[i] as DropdownIconActionConfig).some(iconEntry => iconEntry.isSelected)) { - output.splice(i, 1); - } - } - return output; - } } diff --git a/src/components/icons-wrapper.ts b/src/components/icons-wrapper.ts new file mode 100644 index 00000000..6a623799 --- /dev/null +++ b/src/components/icons-wrapper.ts @@ -0,0 +1,100 @@ +import { css, CSSResultGroup, html, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; + +import { + ActionHandlerFunctionCreator, + DropdownIconActionConfig, + IconActionConfig, VariablesStorage, +} from "../types/types"; +import { DropdownIcon } from "./dropdown-icon"; +import { SingleIcon } from "./single-icon"; +import { RootlessLitElement } from "./rootless-lit-element"; +import { HomeAssistantFixed } from "../types/fixes"; +import { areConditionsMet } from "../utils"; + +@customElement("xvmc-icons-wrapper") +export class IconsWrapper extends RootlessLitElement { + + @property({attribute: false}) + private icons!: (IconActionConfig | DropdownIconActionConfig)[]; + + @property({attribute: false}) + private onAction!: ActionHandlerFunctionCreator; + + public render(): TemplateResult | void { + if ((this.icons?.length ?? 0) === 0){ + return; + } + return html` +
    +
    + ${this.icons?.map((icon ) => html` + + `)} +
    +
    + `; + } + + public static get styles(): CSSResultGroup { + return css` + .icons-wrapper { + display: flex; + justify-content: center; + align-items: center; + } + + .icons-list { + float: right; + border-radius: var(--map-card-internal-big-radius); + overflow: hidden; + background-color: var(--map-card-internal-secondary-color); + color: var(--map-card-internal-secondary-text-color); + display: flex; + flex-wrap: wrap; + justify-content: center; + } + + ${SingleIcon.styles} + ${DropdownIcon.styles} + `; + } + + + public static preprocessIcons(icons: IconActionConfig[] | undefined, + internalVariables: VariablesStorage, + hass: HomeAssistantFixed, + ): (IconActionConfig | DropdownIconActionConfig)[] { + if (icons === undefined) { + return []; + } + const output: (IconActionConfig | DropdownIconActionConfig)[] = []; + const menuIndexes = new Map(); + icons.forEach(i => { + const isSelected = areConditionsMet(i, internalVariables, hass); + if (i.menu_id === undefined) { + if (isSelected) { + output.push(i); + } + } else { + const icon = {...i, isSelected: isSelected}; + if (menuIndexes.has(i.menu_id) && Array.isArray(output[menuIndexes.get(i.menu_id) as number])) { + (output[menuIndexes.get(i.menu_id) as number] as DropdownIconActionConfig).push(icon); + } else { + menuIndexes.set(i.menu_id, output.length); + output.push([icon]); + } + } + }); + for (let i = output.length - 1; i >= 0; i--) { + if (Array.isArray(output[i]) + && !(output[i] as DropdownIconActionConfig).some(iconEntry => iconEntry.isSelected)) { + output.splice(i, 1); + } + } + return output; + } +} diff --git a/src/components/preset-selector.ts b/src/components/preset-selector.ts new file mode 100644 index 00000000..f4c65804 --- /dev/null +++ b/src/components/preset-selector.ts @@ -0,0 +1,105 @@ +import { css, CSSResultGroup, html, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; + +import { CardPresetConfig } from "../types/types"; +import { RootlessLitElement } from "./rootless-lit-element"; + +@customElement("xvmc-preset-selector") +export class PresetSelector extends RootlessLitElement { + + @property({attribute: false}) + private availablePresets!: CardPresetConfig[]; + + @property({attribute: false}) + private availablePresetIndex!: number; + + @property({attribute: false}) + private openPreviousPreset!: () => void; + + @property({attribute: false}) + private previousPresetIndex!: number; + + @property({attribute: false}) + private presetActivable!: boolean; + + @property({attribute: false}) + private presetName!: string; + + @property({attribute: false}) + private executePresetsActivation!: () => void; + + @property({attribute: false}) + private openNextPreset!: () => void; + + @property({attribute: false}) + private nextPresetIndex!: number; + + public render(): TemplateResult | void { + if (this.availablePresets.length < 2){ + return; + } + const rtl = getComputedStyle(this)?.getPropertyValue("direction") === "rtl"; + return html` +
    +
    + + +
    +
    +
    ${this.presetName}
    +
    + ${new Array(this.availablePresets.length).fill(0).map((_, i) => (i === this.availablePresetIndex ? "●" : "○"))} +
    +
    +
    + + +
    +
    + `; + } + + public static get styles(): CSSResultGroup { + return css` + .preset-selector-wrapper { + width: 100%; + display: inline-flex; + align-content: center; + justify-content: space-between; + align-items: center; + } + + .preset-selector-icon-wrapper { + height: 44px; + width: 44px; + display: grid; + place-items: center; + } + + .preset-selector-icon { + cursor: pointer; + } + + .preset-selector-icon.disabled { + color: var(--map-card-internal-disabled-text-color); + cursor: default; + } + + .preset-label-wrapper { + display: flex; + flex-direction: column; + align-items: center; + } + + .preset-indicator { + line-height: 50%; + } + `; + } +} diff --git a/src/components/rootless-lit-element.ts b/src/components/rootless-lit-element.ts new file mode 100644 index 00000000..eb2579d6 --- /dev/null +++ b/src/components/rootless-lit-element.ts @@ -0,0 +1,9 @@ +import { LitElement } from "lit"; + +export abstract class RootlessLitElement extends LitElement { + + protected createRenderRoot(): Element | ShadowRoot { + return this; + } + +} diff --git a/src/components/single-icon.ts b/src/components/single-icon.ts index 8594c5c4..0ef25d72 100644 --- a/src/components/single-icon.ts +++ b/src/components/single-icon.ts @@ -1,4 +1,4 @@ -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { css, CSSResultGroup, html, TemplateResult } from "lit"; import { hasAction } from "custom-card-helpers"; import { actionHandler } from "../action-handler-directive"; @@ -7,9 +7,10 @@ import { IconActionConfig, } from "../types/types"; import { customElement, property } from "lit/decorators"; +import { RootlessLitElement } from "./rootless-lit-element"; @customElement("xvmc-single-icon") -export class SingleIcon extends LitElement { +export class SingleIcon extends RootlessLitElement { @property({attribute: false}) private config!: IconActionConfig; @@ -32,10 +33,6 @@ export class SingleIcon extends LitElement { `; } - protected createRenderRoot(): Element | ShadowRoot { - return this; - } - public static get styles(): CSSResultGroup { return css` .single-icon { diff --git a/src/components/tile.ts b/src/components/tile.ts index 53f7c7fb..0dbee0c2 100644 --- a/src/components/tile.ts +++ b/src/components/tile.ts @@ -1,4 +1,4 @@ -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { css, CSSResultGroup, html, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import { computeStateDomain, hasAction } from "custom-card-helpers"; @@ -17,10 +17,11 @@ import { HomeAssistantFixed } from "../types/fixes"; import { localizeEntity } from "../localize/localize"; import { computeAttributeNameDisplay } from "../localize/hass/compute_attribute_display"; import { blankBeforePercent } from "../localize/hass/blank_before_percent"; +import { RootlessLitElement } from "./rootless-lit-element"; @customElement("xvmc-tile") -export class Tile extends LitElement { +export class Tile extends RootlessLitElement { @property({attribute: false}) private config!: TileConfig; @@ -143,10 +144,6 @@ export class Tile extends LitElement { : ` ${this.config.unit}`; } - protected createRenderRoot(): Element | ShadowRoot { - return this; - } - public static get styles(): CSSResultGroup { return css` .tile-wrapper { diff --git a/src/components/tiles-wrapper.ts b/src/components/tiles-wrapper.ts new file mode 100644 index 00000000..38a21d6b --- /dev/null +++ b/src/components/tiles-wrapper.ts @@ -0,0 +1,61 @@ +import { css, CSSResultGroup, html, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import { + ActionHandlerFunctionCreator, + TileConfig, + VariablesStorage, +} from "../types/types"; +import { HomeAssistantFixed } from "../types/fixes"; +import { RootlessLitElement } from "./rootless-lit-element"; +import { Tile } from "./tile"; + +@customElement("xvmc-tiles-wrapper") +export class TilesWrapper extends RootlessLitElement { + + @property({attribute: false}) + private tiles!: TileConfig[] | undefined; + + @property({attribute: false}) + private hass!: HomeAssistantFixed; + + @property({attribute: false}) + private isInEditor!: boolean; + + @property({attribute: false}) + private onAction!: ActionHandlerFunctionCreator; + + @property({attribute: false}) + private internalVariables!: VariablesStorage; + + protected render(): TemplateResult | void { + if ((this.tiles?.length ?? 0) === 0){ + return; + } + return html` +
    + ${this.tiles?.map(tile => html` + + `)} + `; + } + + public static get styles(): CSSResultGroup { + return css` + .tiles-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + align-items: stretch; + gap: 5px; + } + + ${Tile.styles} + `; + } +} diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index 0d062272..abb53cbc 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -78,9 +78,10 @@ import { HomeAssistantFixed } from "./types/fixes"; import "./polyfills/objectEntries"; import "./polyfills/objectFromEntries"; -import { Tile } from "./components/tile"; import { DropdownMenu } from "./components/dropdown-menu"; -import { Icon } from "./components/icon"; +import { TilesWrapper } from "./components/tiles-wrapper"; +import { IconsWrapper } from "./components/icons-wrapper"; +import { PresetSelector } from "./components/preset-selector"; const line1 = " XIAOMI-VACUUM-MAP-CARD"; const line2 = ` ${localize("common.version")} ${CARD_VERSION}`; @@ -255,8 +256,6 @@ export class XiaomiVacuumMapCard extends LitElement { return this._showInvalidEntities(invalidEntities); } - const rtl = getComputedStyle(this)?.getPropertyValue("direction") === "rtl"; - let preset = this._getCurrentPreset(); const allPresets = this._getAllPresets(); let availablePresets = this._getAllAvailablePresets(); @@ -270,9 +269,7 @@ export class XiaomiVacuumMapCard extends LitElement { this._updateCalibration(preset); const tiles = preset.tiles?.filter(tile => areConditionsMet(tile, this.internalVariables, this.hass)); - // const icons = preset.icons?.filter(icon => areConditionsMet(icon, this.internalVariables, this.hass)); - - const icons = Icon.preprocessIcons(preset.icons, this.internalVariables, this.hass); + const icons = IconsWrapper.preprocessIcons(preset.icons, this.internalVariables, this.hass); const modes = this.modes; const mapSrc = this._getMapSrc(preset); @@ -314,39 +311,17 @@ export class XiaomiVacuumMapCard extends LitElement { (this.config.title ?? "").length > 0, () => html`

    ${this.config.title}

    `, )} - ${conditional( - availablePresets.length > 1, - () => html` -
    -
    - - -
    -
    -
    ${preset.preset_name}
    -
    - ${new Array(availablePresets.length) - .fill(0) - .map((_, i) => (i === availablePresetIndex ? "●" : "○"))} -
    -
    -
    - - -
    -
    - `, - )} + this._openPreviousPreset()} + .previousPresetIndex=${this._getPreviousPresetIndex()} + .presetActivable=${preset.activate !== undefined} + .presetName=${preset.preset_name} + .executePresetsActivation=${(): void => this._executePresetsActivation()} + .openNextPreset=${(): void => this._openNextPreset()} + .nextPresetIndex=${this._getNextPresetIndex()} + >
    `, )} - ${conditional( - (icons?.length ?? 0) !== 0, - () => html` -
    -
    - ${icons?.map((icon ) => html` - createActionWithConfigHandler(this, c, action)} - > - `)} -
    -
    - `, - )} - ${conditional( - (tiles?.length ?? 0) !== 0, - () => html` -
    - ${tiles?.map(tile => html` - createActionWithConfigHandler(this, c, action)} - .internalVariables=${this.internalVariables} - > - `)} -
    - `, - )} + createActionWithConfigHandler(this, c, action)} + > + createActionWithConfigHandler(this, c, action)} + .internalVariables=${this.internalVariables} + >
    ` )} ${ToastRenderer.render("map-card")} @@ -495,11 +450,11 @@ export class XiaomiVacuumMapCard extends LitElement { } } - private _getAllPresets(): Array { + private _getAllPresets(): CardPresetConfig[] { return [this.config, ...(this.config.additional_presets ?? [])]; } - private _getAllAvailablePresets(): Array { + private _getAllAvailablePresets(): CardPresetConfig[] { const allPresets = this._getAllPresets(); const available = allPresets.filter( p => (p.conditions?.length ?? 0) === 0 || areConditionsMet(p, this.internalVariables, this.hass), @@ -1614,40 +1569,6 @@ export class XiaomiVacuumMapCard extends LitElement { cursor: pointer; } - .preset-selector-wrapper { - width: 100%; - display: inline-flex; - align-content: center; - justify-content: space-between; - align-items: center; - } - - .preset-selector-icon-wrapper { - height: 44px; - width: 44px; - display: grid; - place-items: center; - } - - .preset-selector-icon { - cursor: pointer; - } - - .preset-selector-icon.disabled { - color: var(--map-card-internal-disabled-text-color); - cursor: default; - } - - .preset-label-wrapper { - display: flex; - flex-direction: column; - align-items: center; - } - - .preset-indicator { - line-height: 50%; - } - .map-wrapper { position: relative; height: max-content; @@ -1728,11 +1649,9 @@ export class XiaomiVacuumMapCard extends LitElement { .controls-wrapper { margin: 15px; - } - - .controls-wrapper > * { - margin-top: 10px; - margin-bottom: 10px; + display: flex; + flex-direction: column; + gap: 10px; } .map-controls { @@ -1768,31 +1687,6 @@ export class XiaomiVacuumMapCard extends LitElement { background-color: transparent; } - .vacuum-controls { - display: flex; - justify-content: center; - align-items: center; - } - - .vacuum-actions-list { - float: right; - border-radius: var(--map-card-internal-big-radius); - overflow: hidden; - background-color: var(--map-card-internal-secondary-color); - color: var(--map-card-internal-secondary-text-color); - display: flex; - flex-wrap: wrap; - justify-content: center; - } - - .tiles-wrapper { - display: flex; - flex-wrap: wrap; - justify-content: space-evenly; - align-items: stretch; - gap: 5px; - } - .ripple { position: relative; overflow: hidden; @@ -1823,6 +1717,7 @@ export class XiaomiVacuumMapCard extends LitElement { transition: 0s; } + ${PresetSelector.styles} ${MapObject.styles} ${ManualRectangle.styles} ${PredefinedMultiRectangle.styles} @@ -1830,8 +1725,8 @@ export class XiaomiVacuumMapCard extends LitElement { ${ManualPoint.styles} ${PredefinedPoint.styles} ${Room.styles} - ${Icon.styles} - ${Tile.styles} + ${IconsWrapper.styles} + ${TilesWrapper.styles} ${DropdownMenu.styles} ${ToastRenderer.styles} `; From 0c16d54c13efdfd074d3adb7e24cf672b0ef9a44 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Sun, 28 May 2023 03:46:31 +0200 Subject: [PATCH 060/100] Code formatting --- src/xiaomi-vacuum-map-card.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index abb53cbc..1717ae92 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -320,8 +320,8 @@ export class XiaomiVacuumMapCard extends LitElement { .presetName=${preset.preset_name} .executePresetsActivation=${(): void => this._executePresetsActivation()} .openNextPreset=${(): void => this._openNextPreset()} - .nextPresetIndex=${this._getNextPresetIndex()} - > + .nextPresetIndex=${this._getNextPresetIndex()}> +
    createActionWithConfigHandler(this, c, action)} - > + .onAction=${(c: ActionableObjectConfig, action?: string) => createActionWithConfigHandler(this, c, action)}> + createActionWithConfigHandler(this, c, action)} - .internalVariables=${this.internalVariables} - > + .internalVariables=${this.internalVariables}> +
    ` )} ${ToastRenderer.render("map-card")} From 78c52a8613949f186074f9180ab0a35653f4435c Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Sun, 28 May 2023 03:55:46 +0200 Subject: [PATCH 061/100] Adjust imports --- src/components/dropdown-menu.ts | 1 - src/components/icon.ts | 3 --- src/components/icons-wrapper.ts | 6 ++---- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/components/dropdown-menu.ts b/src/components/dropdown-menu.ts index 74d5b58a..ed6b4a5d 100644 --- a/src/components/dropdown-menu.ts +++ b/src/components/dropdown-menu.ts @@ -3,7 +3,6 @@ import { conditional } from "../utils"; import { customElement, property, query } from "lit/decorators"; import { RootlessLitElement } from "./rootless-lit-element"; - interface DropdownEntry { icon: string; name: string; diff --git a/src/components/icon.ts b/src/components/icon.ts index 2633bf13..b2a63cea 100644 --- a/src/components/icon.ts +++ b/src/components/icon.ts @@ -4,10 +4,7 @@ import { ActionHandlerFunctionCreator, DropdownIconActionConfig, IconActionConfig, - VariablesStorage, } from "../types/types"; -import { HomeAssistantFixed } from "../types/fixes"; -import "./single-icon"; import { DropdownIcon } from "./dropdown-icon"; import { customElement, property } from "lit/decorators"; import { SingleIcon } from "./single-icon"; diff --git a/src/components/icons-wrapper.ts b/src/components/icons-wrapper.ts index 6a623799..a6664e12 100644 --- a/src/components/icons-wrapper.ts +++ b/src/components/icons-wrapper.ts @@ -6,11 +6,10 @@ import { DropdownIconActionConfig, IconActionConfig, VariablesStorage, } from "../types/types"; -import { DropdownIcon } from "./dropdown-icon"; -import { SingleIcon } from "./single-icon"; import { RootlessLitElement } from "./rootless-lit-element"; import { HomeAssistantFixed } from "../types/fixes"; import { areConditionsMet } from "../utils"; +import { Icon } from "./icon"; @customElement("xvmc-icons-wrapper") export class IconsWrapper extends RootlessLitElement { @@ -58,8 +57,7 @@ export class IconsWrapper extends RootlessLitElement { justify-content: center; } - ${SingleIcon.styles} - ${DropdownIcon.styles} + ${Icon.styles} `; } From d56f6b6d55b86a23648e748a58c7ab28dd255d90 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Sun, 28 May 2023 04:18:28 +0200 Subject: [PATCH 062/100] Make coordinates divider configurable --- .../DeebotUniverse_Deebot-4-Home-Assistant.json | 2 ++ .../platform_templates/KrzysztofHajdamowicz_miio2.json | 5 +++++ .../platform_templates/Tasshack_dreame-vacuum.json | 4 ++++ .../platform_templates/al-one_hass-xiaomi-miot.json | 2 ++ .../humbertogontijo_homeassistant-roborock.json | 5 +++++ src/model/generators/platform_templates/hypfer_valetudo.json | 2 ++ .../generators/platform_templates/marotoweb_viomise.json | 4 ++++ .../generators/platform_templates/rand256_valetudo_re.json | 2 ++ src/model/generators/platform_templates/send-command.json | 5 +++++ src/model/generators/platform_templates/setup_decimal.json | 3 +++ src/model/generators/platform_templates/setup_integer.json | 3 +++ .../platform_templates/tykarol_viomi_vacuum_v8.json | 4 ++++ src/model/generators/platform_templates/xiaomiMiio.json | 5 +++++ src/model/map_mode/map-mode.ts | 5 +++++ src/model/map_objects/context.ts | 1 + src/model/map_objects/manual-rectangle.ts | 2 +- src/types/types.ts | 1 + src/xiaomi-vacuum-map-card.ts | 1 + 18 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json index 804099c1..4a1ea782 100644 --- a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json +++ b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json @@ -25,6 +25,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "max_selections": 1, "service_call_schema": { @@ -44,6 +45,7 @@ "selection_type": "PREDEFINED_RECTANGLE", "max_selections": 1, "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "service_call_schema": { "service": "xiaomi_miio.vacuum_clean_zone", diff --git a/src/model/generators/platform_templates/KrzysztofHajdamowicz_miio2.json b/src/model/generators/platform_templates/KrzysztofHajdamowicz_miio2.json index 8ddee42a..7d01c305 100644 --- a/src/model/generators/platform_templates/KrzysztofHajdamowicz_miio2.json +++ b/src/model/generators/platform_templates/KrzysztofHajdamowicz_miio2.json @@ -21,6 +21,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "max_selections": 5, "repeats_type": "EXTERNAL", "max_repeats": 3, @@ -39,6 +40,7 @@ "selection_type": "PREDEFINED_RECTANGLE", "max_selections": 5, "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "repeats_type": "EXTERNAL", "max_repeats": 3, "service_call_schema": { @@ -55,6 +57,7 @@ "icon": "mdi:map-marker-plus", "selection_type": "MANUAL_POINT", "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "repeats_type": "NONE", "service_call_schema": { "service": "vacuum.vacuum_goto", @@ -70,6 +73,7 @@ "icon": "mdi:map-marker", "selection_type": "PREDEFINED_POINT", "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "repeats_type": "NONE", "service_call_schema": { "service": "vacuum.vacuum_goto", @@ -85,6 +89,7 @@ "icon": "mdi:map-marker-path", "selection_type": "MANUAL_PATH", "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "repeats_type": "NONE", "service_call_schema": { "service": "script.vacuum_follow_path", diff --git a/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json b/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json index b83c654e..2b1123f6 100644 --- a/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json +++ b/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json @@ -26,6 +26,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "max_selections": 20, "repeats_type": "EXTERNAL", "max_repeats": 3, @@ -44,6 +45,7 @@ "selection_type": "PREDEFINED_RECTANGLE", "max_selections": 20, "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "EXTERNAL", "max_repeats": 3, "service_call_schema": { @@ -60,6 +62,7 @@ "icon": "mdi:map-marker-plus", "selection_type": "MANUAL_POINT", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "EXTERNAL", "max_repeats": 3, "service_call_schema": { @@ -76,6 +79,7 @@ "icon": "mdi:map-marker", "selection_type": "PREDEFINED_POINT", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "EXTERNAL", "max_selections": 20, "max_repeats": 3, diff --git a/src/model/generators/platform_templates/al-one_hass-xiaomi-miot.json b/src/model/generators/platform_templates/al-one_hass-xiaomi-miot.json index e3ea6e04..4421a8f4 100644 --- a/src/model/generators/platform_templates/al-one_hass-xiaomi-miot.json +++ b/src/model/generators/platform_templates/al-one_hass-xiaomi-miot.json @@ -18,6 +18,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "max_selections": 1, "repeats_type": "EXTERNAL", "max_repeats": 3, @@ -31,6 +32,7 @@ "selection_type": "PREDEFINED_RECTANGLE", "max_selections": 1, "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "EXTERNAL", "max_repeats": 3, "service_call_schema": { diff --git a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json index 480087ed..9f191e13 100644 --- a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json +++ b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json @@ -21,6 +21,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "max_selections": 5, "repeats_type": "EXTERNAL", "max_repeats": 3, @@ -39,6 +40,7 @@ "selection_type": "PREDEFINED_RECTANGLE", "max_selections": 5, "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "EXTERNAL", "max_repeats": 3, "service_call_schema": { @@ -55,6 +57,7 @@ "icon": "mdi:map-marker-plus", "selection_type": "MANUAL_POINT", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "service_call_schema": { "service": "roborock.vacuum_goto", @@ -70,6 +73,7 @@ "icon": "mdi:map-marker", "selection_type": "PREDEFINED_POINT", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "service_call_schema": { "service": "roborock.vacuum_goto", @@ -85,6 +89,7 @@ "icon": "mdi:map-marker-path", "selection_type": "MANUAL_PATH", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "service_call_schema": { "service": "script.vacuum_follow_path", diff --git a/src/model/generators/platform_templates/hypfer_valetudo.json b/src/model/generators/platform_templates/hypfer_valetudo.json index 6a8ca8bf..dbe6234e 100644 --- a/src/model/generators/platform_templates/hypfer_valetudo.json +++ b/src/model/generators/platform_templates/hypfer_valetudo.json @@ -21,6 +21,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": true, + "coordinates_to_meters_divider": 100, "repeats_type": "EXTERNAL", "max_selections": 5, "max_repeats": 3, @@ -53,6 +54,7 @@ "icon": "mdi:map-marker-plus", "selection_type": "MANUAL_POINT", "coordinates_rounding": true, + "coordinates_to_meters_divider": 100, "repeats_type": "NONE", "service_call_schema": { "service": "mqtt.publish", diff --git a/src/model/generators/platform_templates/marotoweb_viomise.json b/src/model/generators/platform_templates/marotoweb_viomise.json index d68f193b..e5082036 100644 --- a/src/model/generators/platform_templates/marotoweb_viomise.json +++ b/src/model/generators/platform_templates/marotoweb_viomise.json @@ -7,6 +7,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "max_selections": 5, "repeats_type": "EXTERNAL", "max_repeats": 3, @@ -25,6 +26,7 @@ "selection_type": "PREDEFINED_RECTANGLE", "max_selections": 5, "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "repeats_type": "EXTERNAL", "max_repeats": 3, "service_call_schema": { @@ -41,6 +43,7 @@ "icon": "mdi:map-marker-plus", "selection_type": "MANUAL_POINT", "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "repeats_type": "NONE", "service_call_schema": { "service": "vacuum.xiaomi_clean_point", @@ -55,6 +58,7 @@ "icon": "mdi:map-marker", "selection_type": "PREDEFINED_POINT", "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "repeats_type": "NONE", "service_call_schema": { "service": "vacuum.xiaomi_clean_point", diff --git a/src/model/generators/platform_templates/rand256_valetudo_re.json b/src/model/generators/platform_templates/rand256_valetudo_re.json index 66dd2fc5..a908223e 100644 --- a/src/model/generators/platform_templates/rand256_valetudo_re.json +++ b/src/model/generators/platform_templates/rand256_valetudo_re.json @@ -22,6 +22,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "EXTERNAL", "max_selections": 5, "max_repeats": 3, @@ -54,6 +55,7 @@ "icon": "mdi:map-marker-plus", "selection_type": "MANUAL_POINT", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "service_call_schema": { "service": "mqtt.publish", diff --git a/src/model/generators/platform_templates/send-command.json b/src/model/generators/platform_templates/send-command.json index 54c7fb7d..fbc7cfb9 100644 --- a/src/model/generators/platform_templates/send-command.json +++ b/src/model/generators/platform_templates/send-command.json @@ -22,6 +22,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "max_selections": 5, "repeats_type": "INTERNAL", "max_repeats": 3, @@ -40,6 +41,7 @@ "selection_type": "PREDEFINED_RECTANGLE", "max_selections": 5, "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "INTERNAL", "max_repeats": 3, "service_call_schema": { @@ -56,6 +58,7 @@ "icon": "mdi:map-marker-plus", "selection_type": "MANUAL_POINT", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "service_call_schema": { "service": "vacuum.send_command", @@ -71,6 +74,7 @@ "icon": "mdi:map-marker", "selection_type": "PREDEFINED_POINT", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "service_call_schema": { "service": "vacuum.send_command", @@ -86,6 +90,7 @@ "icon": "mdi:map-marker-path", "selection_type": "MANUAL_PATH", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "service_call_schema": { "service": "script.vacuum_follow_path", diff --git a/src/model/generators/platform_templates/setup_decimal.json b/src/model/generators/platform_templates/setup_decimal.json index 8ad791ff..c2a236e4 100644 --- a/src/model/generators/platform_templates/setup_decimal.json +++ b/src/model/generators/platform_templates/setup_decimal.json @@ -7,6 +7,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "max_selections": 1, "repeats_type": "NONE", "service_call_schema": { @@ -21,6 +22,7 @@ "icon": "mdi:map-marker-plus", "selection_type": "MANUAL_POINT", "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "repeats_type": "NONE", "service_call_schema": { "service": "persistent_notification.create", @@ -36,6 +38,7 @@ "icon": "mdi:map-marker-path", "selection_type": "MANUAL_PATH", "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "repeats_type": "NONE", "service_call_schema": { "service": "persistent_notification.create", diff --git a/src/model/generators/platform_templates/setup_integer.json b/src/model/generators/platform_templates/setup_integer.json index 916709a0..a66fcbef 100644 --- a/src/model/generators/platform_templates/setup_integer.json +++ b/src/model/generators/platform_templates/setup_integer.json @@ -7,6 +7,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "max_selections": 1, "repeats_type": "NONE", "service_call_schema": { @@ -21,6 +22,7 @@ "icon": "mdi:map-marker-plus", "selection_type": "MANUAL_POINT", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "service_call_schema": { "service": "persistent_notification.create", @@ -36,6 +38,7 @@ "icon": "mdi:map-marker-path", "selection_type": "MANUAL_PATH", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "service_call_schema": { "service": "persistent_notification.create", diff --git a/src/model/generators/platform_templates/tykarol_viomi_vacuum_v8.json b/src/model/generators/platform_templates/tykarol_viomi_vacuum_v8.json index 017fa77c..97243054 100644 --- a/src/model/generators/platform_templates/tykarol_viomi_vacuum_v8.json +++ b/src/model/generators/platform_templates/tykarol_viomi_vacuum_v8.json @@ -21,6 +21,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "max_selections": 5, "repeats_type": "EXTERNAL", "max_repeats": 3, @@ -39,6 +40,7 @@ "selection_type": "PREDEFINED_RECTANGLE", "max_selections": 5, "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "repeats_type": "EXTERNAL", "max_repeats": 3, "service_call_schema": { @@ -55,6 +57,7 @@ "icon": "mdi:map-marker-plus", "selection_type": "MANUAL_POINT", "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "repeats_type": "NONE", "service_call_schema": { "service": "viomi_vacuum_v8.clean_point", @@ -69,6 +72,7 @@ "icon": "mdi:map-marker", "selection_type": "PREDEFINED_POINT", "coordinates_rounding": false, + "coordinates_to_meters_divider": 1, "repeats_type": "NONE", "service_call_schema": { "service": "viomi_vacuum_v8.clean_point", diff --git a/src/model/generators/platform_templates/xiaomiMiio.json b/src/model/generators/platform_templates/xiaomiMiio.json index cb4a6c03..f8e4f847 100644 --- a/src/model/generators/platform_templates/xiaomiMiio.json +++ b/src/model/generators/platform_templates/xiaomiMiio.json @@ -27,6 +27,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "max_selections": 5, "repeats_type": "EXTERNAL", "max_repeats": 3, @@ -45,6 +46,7 @@ "selection_type": "PREDEFINED_RECTANGLE", "max_selections": 5, "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "EXTERNAL", "max_repeats": 3, "service_call_schema": { @@ -61,6 +63,7 @@ "icon": "mdi:map-marker-plus", "selection_type": "MANUAL_POINT", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "service_call_schema": { "service": "xiaomi_miio.vacuum_goto", @@ -76,6 +79,7 @@ "icon": "mdi:map-marker", "selection_type": "PREDEFINED_POINT", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "service_call_schema": { "service": "xiaomi_miio.vacuum_goto", @@ -91,6 +95,7 @@ "icon": "mdi:map-marker-path", "selection_type": "MANUAL_PATH", "coordinates_rounding": true, + "coordinates_to_meters_divider": 1000, "repeats_type": "NONE", "service_call_schema": { "service": "script.vacuum_follow_path", diff --git a/src/model/map_mode/map-mode.ts b/src/model/map_mode/map-mode.ts index ade02840..7e27d6a2 100644 --- a/src/model/map_mode/map-mode.ts +++ b/src/model/map_mode/map-mode.ts @@ -27,6 +27,7 @@ export class MapMode { public selectionType: SelectionType; public maxSelections: number; public coordinatesRounding: boolean; + public coordinatesToMetersDivider: number; public runImmediately: boolean; public repeatsType: RepeatsType; public maxRepeats: number; @@ -42,6 +43,7 @@ export class MapMode { : SelectionType.PREDEFINED_POINT; this.maxSelections = config.max_selections ?? 999; this.coordinatesRounding = config.coordinates_rounding ?? true; + this.coordinatesToMetersDivider = config.coordinates_to_meters_divider ?? 1000; this.runImmediately = config.run_immediately ?? false; this.repeatsType = config.repeats_type ? RepeatsType[config.repeats_type] : RepeatsType.NONE; this.maxRepeats = config.max_repeats ?? 1; @@ -89,6 +91,7 @@ export class MapMode { icon: this.icon, run_immediately: this.runImmediately, coordinates_rounding: this.coordinatesRounding, + coordinates_to_meters_divider: this.coordinatesToMetersDivider, selection_type: SelectionType[this.selectionType], max_selections: this.maxSelections, repeats_type: RepeatsType[this.repeatsType], @@ -111,6 +114,8 @@ export class MapMode { if (!config.max_selections && templateValue.max_selections) this.maxSelections = templateValue.max_selections; if (config.coordinates_rounding === undefined && templateValue.coordinates_rounding !== undefined) this.coordinatesRounding = templateValue.coordinates_rounding; + if (config.coordinates_to_meters_divider === undefined && templateValue.coordinates_to_meters_divider !== undefined) + this.coordinatesToMetersDivider = templateValue.coordinates_to_meters_divider; if (config.run_immediately === undefined && templateValue.run_immediately !== undefined) this.runImmediately = templateValue.run_immediately; if (!config.repeats_type && templateValue.repeats_type) diff --git a/src/model/map_objects/context.ts b/src/model/map_objects/context.ts index bf97a9b7..c5f3d91f 100644 --- a/src/model/map_objects/context.ts +++ b/src/model/map_objects/context.ts @@ -19,6 +19,7 @@ export class Context { public readonly selectedRooms: () => Room[], public readonly selectedPredefinedPoint: () => PredefinedPoint[], public readonly roundingEnabled: () => boolean, + public readonly coordinatesToMetersDivider: () => number, public readonly maxSelections: () => number, public readonly cssEvaluator: (_: string) => string, public readonly runImmediately: () => Promise, diff --git a/src/model/map_objects/manual-rectangle.ts b/src/model/map_objects/manual-rectangle.ts index f91643de..99e1a2d9 100644 --- a/src/model/map_objects/manual-rectangle.ts +++ b/src/model/map_objects/manual-rectangle.ts @@ -119,7 +119,7 @@ export class ManualRectangle extends MapObject { const [x1, y1, x2, y2] = this.toVacuum(); const width = Math.abs(x2 - x1); const height = Math.abs(y2 - y1); - const divider = this._context.roundingEnabled() ? 1000 : 1; + const divider = this._context.coordinatesToMetersDivider(); const rounder = (v: number): string => (v / divider).toFixed(1); return `${rounder(width)}${this.localize("unit.meter_shortcut")} x ${rounder(height)}${this.localize( "unit.meter_shortcut", diff --git a/src/types/types.ts b/src/types/types.ts index eb572f4c..37e94242 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -92,6 +92,7 @@ export interface MapModeConfig { readonly icon?: string; readonly run_immediately?: boolean; readonly coordinates_rounding?: boolean; + readonly coordinates_to_meters_divider?: number; readonly selection_type?: string; readonly max_selections?: number; readonly repeats_type?: string; diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index 1717ae92..726ee11a 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -696,6 +696,7 @@ export class XiaomiVacuumMapCard extends LitElement { () => this.selectedRooms, () => this.selectedPredefinedPoint, () => this._getCurrentMode().coordinatesRounding, + () => this._getCurrentMode().coordinatesToMetersDivider, () => this._getCurrentMode().maxSelections, property => this._getCssProperty(property), () => this._runImmediately(), From 5388d6e82cce31b48f279541c2c8a213759bfffa Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Sun, 28 May 2023 05:15:02 +0200 Subject: [PATCH 063/100] Refactor --- src/model/generators/platform-generator.ts | 4 ++-- .../DeebotUniverse_Deebot-4-Home-Assistant.json | 2 +- .../platform_templates/KrzysztofHajdamowicz_miio2.json | 2 +- .../generators/platform_templates/Tasshack_dreame-vacuum.json | 2 +- .../platform_templates/al-one_hass-xiaomi-miot.json | 2 +- .../humbertogontijo_homeassistant-roborock.json | 2 +- src/model/generators/platform_templates/hypfer_valetudo.json | 2 +- .../generators/platform_templates/marotoweb_viomise.json | 2 +- src/model/generators/platform_templates/neato.json | 2 +- src/model/generators/platform_templates/new.json | 2 +- .../generators/platform_templates/rand256_valetudo_re.json | 2 +- .../platform_templates/romedtino_simple-wyze-vac.json | 2 +- src/model/generators/platform_templates/roomba.json | 2 +- src/model/generators/platform_templates/send-command.json | 2 +- src/model/generators/platform_templates/setup_decimal.json | 2 +- src/model/generators/platform_templates/setup_integer.json | 2 +- .../platform_templates/tykarol_viomi_vacuum_v8.json | 2 +- src/model/generators/platform_templates/xiaomiMiio.json | 2 +- src/types/types.ts | 2 +- 19 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/model/generators/platform-generator.ts b/src/model/generators/platform-generator.ts index a4ed5c28..67d6bda6 100644 --- a/src/model/generators/platform-generator.ts +++ b/src/model/generators/platform-generator.ts @@ -100,7 +100,7 @@ export class PlatformGenerator { } public static generateDefaultModes(platform: string): MapModeConfig[] { - return this.getPlatformTemplate(platform).map_modes.defaultTemplates.map(dt => ({ template: dt })); + return this.getPlatformTemplate(platform).map_modes.default_templates.map(dt => ({ template: dt })); } public static getTilesFromAttributesTemplates(platform: string): TileFromAttributeTemplate[] { @@ -128,7 +128,7 @@ export class PlatformGenerator { this.TEMPLATES.get(this.XIAOMI_MIIO_PLATFORM) ?? ({ templates: [], - defaultTemplates: {}, + default_templates: {}, } as unknown as PlatformTemplate) ); } diff --git a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json index 4a1ea782..d561e218 100644 --- a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json +++ b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": ["vacuum_clean_zone"], + "default_templates": ["vacuum_clean_zone"], "templates": { "vacuum_clean_segment": { "name": "map_mode.vacuum_clean_segment", diff --git a/src/model/generators/platform_templates/KrzysztofHajdamowicz_miio2.json b/src/model/generators/platform_templates/KrzysztofHajdamowicz_miio2.json index 7d01c305..6a0ac828 100644 --- a/src/model/generators/platform_templates/KrzysztofHajdamowicz_miio2.json +++ b/src/model/generators/platform_templates/KrzysztofHajdamowicz_miio2.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": ["vacuum_clean_zone", "vacuum_goto"], + "default_templates": ["vacuum_clean_zone", "vacuum_goto"], "templates": { "vacuum_clean_segment": { "name": "map_mode.vacuum_clean_segment", diff --git a/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json b/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json index 2b1123f6..2d57a01f 100644 --- a/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json +++ b/src/model/generators/platform_templates/Tasshack_dreame-vacuum.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": [ + "default_templates": [ "vacuum_clean_zone", "vacuum_clean_point" ], diff --git a/src/model/generators/platform_templates/al-one_hass-xiaomi-miot.json b/src/model/generators/platform_templates/al-one_hass-xiaomi-miot.json index 4421a8f4..e4d949f7 100644 --- a/src/model/generators/platform_templates/al-one_hass-xiaomi-miot.json +++ b/src/model/generators/platform_templates/al-one_hass-xiaomi-miot.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": [], + "default_templates": [], "templates": { "vacuum_clean_segment": { "name": "map_mode.vacuum_clean_segment", diff --git a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json index 9f191e13..5da7c388 100644 --- a/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json +++ b/src/model/generators/platform_templates/humbertogontijo_homeassistant-roborock.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": ["vacuum_clean_zone", "vacuum_goto"], + "default_templates": ["vacuum_clean_zone", "vacuum_goto"], "templates": { "vacuum_clean_segment": { "name": "map_mode.vacuum_clean_segment", diff --git a/src/model/generators/platform_templates/hypfer_valetudo.json b/src/model/generators/platform_templates/hypfer_valetudo.json index dbe6234e..6d8d4336 100644 --- a/src/model/generators/platform_templates/hypfer_valetudo.json +++ b/src/model/generators/platform_templates/hypfer_valetudo.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": ["vacuum_clean_zone", "vacuum_goto"], + "default_templates": ["vacuum_clean_zone", "vacuum_goto"], "templates": { "vacuum_clean_segment": { "name": "map_mode.vacuum_clean_segment", diff --git a/src/model/generators/platform_templates/marotoweb_viomise.json b/src/model/generators/platform_templates/marotoweb_viomise.json index e5082036..b6a7c204 100644 --- a/src/model/generators/platform_templates/marotoweb_viomise.json +++ b/src/model/generators/platform_templates/marotoweb_viomise.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": ["vacuum_clean_zone", "vacuum_clean_point"], + "default_templates": ["vacuum_clean_zone", "vacuum_clean_point"], "templates": { "vacuum_clean_zone": { "name": "map_mode.vacuum_clean_zone", diff --git a/src/model/generators/platform_templates/neato.json b/src/model/generators/platform_templates/neato.json index 9e7716e0..ea0d538e 100644 --- a/src/model/generators/platform_templates/neato.json +++ b/src/model/generators/platform_templates/neato.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": [], + "default_templates": [], "templates": { "vacuum_clean_segment": { "name": "map_mode.vacuum_clean_segment", diff --git a/src/model/generators/platform_templates/new.json b/src/model/generators/platform_templates/new.json index 19d3c049..b8209802 100644 --- a/src/model/generators/platform_templates/new.json +++ b/src/model/generators/platform_templates/new.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": ["template_1", "template_2"], + "default_templates": ["template_1", "template_2"], "templates": { "template_1": {}, "template_2": {}, diff --git a/src/model/generators/platform_templates/rand256_valetudo_re.json b/src/model/generators/platform_templates/rand256_valetudo_re.json index a908223e..550630df 100644 --- a/src/model/generators/platform_templates/rand256_valetudo_re.json +++ b/src/model/generators/platform_templates/rand256_valetudo_re.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": ["vacuum_clean_zone", "vacuum_goto"], + "default_templates": ["vacuum_clean_zone", "vacuum_goto"], "templates": { "vacuum_clean_segment": { "name": "map_mode.vacuum_clean_segment", diff --git a/src/model/generators/platform_templates/romedtino_simple-wyze-vac.json b/src/model/generators/platform_templates/romedtino_simple-wyze-vac.json index a4c31622..69e785c9 100644 --- a/src/model/generators/platform_templates/romedtino_simple-wyze-vac.json +++ b/src/model/generators/platform_templates/romedtino_simple-wyze-vac.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": [], + "default_templates": [], "templates": { "vacuum_clean_segment": { "name": "map_mode.vacuum_clean_segment", diff --git a/src/model/generators/platform_templates/roomba.json b/src/model/generators/platform_templates/roomba.json index e47aa18f..e6e7be5e 100644 --- a/src/model/generators/platform_templates/roomba.json +++ b/src/model/generators/platform_templates/roomba.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": [], + "default_templates": [], "templates": { "vacuum_clean_segment": { "name": "map_mode.vacuum_clean_segment", diff --git a/src/model/generators/platform_templates/send-command.json b/src/model/generators/platform_templates/send-command.json index fbc7cfb9..fe5bed1a 100644 --- a/src/model/generators/platform_templates/send-command.json +++ b/src/model/generators/platform_templates/send-command.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": ["vacuum_clean_zone", "vacuum_goto"], + "default_templates": ["vacuum_clean_zone", "vacuum_goto"], "templates": { "vacuum_clean_segment": { "name": "map_mode.vacuum_clean_segment", diff --git a/src/model/generators/platform_templates/setup_decimal.json b/src/model/generators/platform_templates/setup_decimal.json index c2a236e4..3c058074 100644 --- a/src/model/generators/platform_templates/setup_decimal.json +++ b/src/model/generators/platform_templates/setup_decimal.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": ["vacuum_clean_zone", "vacuum_goto", "vacuum_follow_path"], + "default_templates": ["vacuum_clean_zone", "vacuum_goto", "vacuum_follow_path"], "templates": { "vacuum_clean_zone": { "name": "map_mode.vacuum_clean_zone", diff --git a/src/model/generators/platform_templates/setup_integer.json b/src/model/generators/platform_templates/setup_integer.json index a66fcbef..77d0a3d7 100644 --- a/src/model/generators/platform_templates/setup_integer.json +++ b/src/model/generators/platform_templates/setup_integer.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": ["vacuum_clean_zone", "vacuum_goto", "vacuum_follow_path"], + "default_templates": ["vacuum_clean_zone", "vacuum_goto", "vacuum_follow_path"], "templates": { "vacuum_clean_zone": { "name": "map_mode.vacuum_clean_zone", diff --git a/src/model/generators/platform_templates/tykarol_viomi_vacuum_v8.json b/src/model/generators/platform_templates/tykarol_viomi_vacuum_v8.json index 97243054..c0302fda 100644 --- a/src/model/generators/platform_templates/tykarol_viomi_vacuum_v8.json +++ b/src/model/generators/platform_templates/tykarol_viomi_vacuum_v8.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": ["vacuum_clean_zone", "vacuum_clean_point"], + "default_templates": ["vacuum_clean_zone", "vacuum_clean_point"], "templates": { "vacuum_clean_segment": { "name": "map_mode.vacuum_clean_segment", diff --git a/src/model/generators/platform_templates/xiaomiMiio.json b/src/model/generators/platform_templates/xiaomiMiio.json index f8e4f847..c256c23d 100644 --- a/src/model/generators/platform_templates/xiaomiMiio.json +++ b/src/model/generators/platform_templates/xiaomiMiio.json @@ -1,6 +1,6 @@ { "map_modes": { - "defaultTemplates": ["vacuum_clean_zone", "vacuum_goto"], + "default_templates": ["vacuum_clean_zone", "vacuum_goto"], "templates": { "vacuum_clean_segment": { "name": "map_mode.vacuum_clean_segment", diff --git a/src/types/types.ts b/src/types/types.ts index 37e94242..a73216a2 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -104,7 +104,7 @@ export interface MapModeConfig { export interface PlatformTemplate { readonly map_modes: { - readonly defaultTemplates: string[]; + readonly default_templates: string[]; readonly templates: { [templateName: string]: MapModeConfig }; }; readonly tiles: { From 6c7bc16002cb6392b3a38120d7869403e3ae6842 Mon Sep 17 00:00:00 2001 From: Piotr Machowski Date: Sun, 28 May 2023 05:27:15 +0200 Subject: [PATCH 064/100] Refactor setup platforms --- src/components/dropdown-menu.ts | 2 +- src/model/generators/platform-generator.ts | 4 ++-- src/model/generators/platform_templates/setup_decimal.json | 3 --- src/model/generators/platform_templates/setup_integer.json | 3 --- src/types/types.ts | 2 +- src/xiaomi-vacuum-map-card.ts | 2 +- 6 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/components/dropdown-menu.ts b/src/components/dropdown-menu.ts index ed6b4a5d..5b085a22 100644 --- a/src/components/dropdown-menu.ts +++ b/src/components/dropdown-menu.ts @@ -35,7 +35,7 @@ export class DropdownMenu extends RootlessLitElement { -