From daa36788e09b0eb38506456bb1c90d7aa7e9623b Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 10 Jul 2024 12:39:50 +0200 Subject: [PATCH 01/97] Reload the card when changing the configuration in editor (#21351) --- src/panels/lovelace/cards/hui-card.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/panels/lovelace/cards/hui-card.ts b/src/panels/lovelace/cards/hui-card.ts index 34c47a691e..5e2d832a4a 100644 --- a/src/panels/lovelace/cards/hui-card.ts +++ b/src/panels/lovelace/cards/hui-card.ts @@ -150,8 +150,10 @@ export class HuiCard extends ReactiveElement { if (changedProps.has("config")) { const elementConfig = this._elementConfig; if (this.config !== elementConfig && this.config) { - const typeChanged = this.config?.type !== elementConfig?.type; - if (typeChanged) { + const typeChanged = + this.config?.type !== elementConfig?.type || this.preview; + // Rebuild the card if the type of the card has changed or if we are in preview mode + if (typeChanged || this.preview) { this._loadElement(this.config); } else { this._updateElement(this.config); From cd4937b53984f9215766b4cc0cb3f8a08c10903c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 10 Jul 2024 14:58:26 +0200 Subject: [PATCH 02/97] Fix unhiding default hidden columns (#21354) * Fix unhiding default hidden columns * Update dialog-data-table-settings.ts --- src/components/data-table/dialog-data-table-settings.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/data-table/dialog-data-table-settings.ts b/src/components/data-table/dialog-data-table-settings.ts index dd8a6f2888..29c99762ee 100644 --- a/src/components/data-table/dialog-data-table-settings.ts +++ b/src/components/data-table/dialog-data-table-settings.ts @@ -193,6 +193,7 @@ export class DialogDataTableSettings extends LitElement { .filter(([_key, col]) => col.defaultHidden) .map(([key]) => key)), ]; + if (wasHidden && hidden.includes(column)) { hidden.splice(hidden.indexOf(column), 1); } else if (!wasHidden) { @@ -235,14 +236,14 @@ export class DialogDataTableSettings extends LitElement { } columns.forEach((col) => { - if (!newOrder.includes(col.key)) { + if (col.key !== column && !newOrder.includes(col.key)) { if (col.moveable === false) { newOrder.unshift(col.key); } else { newOrder.splice(lastMoveable + 1, 0, col.key); } - if (col.defaultHidden) { + if (col.defaultHidden && !hidden.includes(col.key)) { hidden.push(col.key); } } From 11b490d145a7dae5e386705f3033039a235cb048 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:01:56 +0200 Subject: [PATCH 03/97] Dev tools events: Add a clear events button (#21353) Clear events --- .../event/developer-tools-event.ts | 101 ++++++++++-------- .../event/event-subscribe-card.ts | 79 +++++++++----- src/translations/en.json | 1 + 3 files changed, 108 insertions(+), 73 deletions(-) diff --git a/src/panels/developer-tools/event/developer-tools-event.ts b/src/panels/developer-tools/event/developer-tools-event.ts index cd29d3176d..a72b570e3a 100644 --- a/src/panels/developer-tools/event/developer-tools-event.ts +++ b/src/panels/developer-tools/event/developer-tools-event.ts @@ -1,8 +1,9 @@ import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; import { customElement, property, state } from "lit/decorators"; -import "@material/mwc-button"; import "../../../components/ha-yaml-editor"; import "../../../components/ha-textfield"; +import "../../../components/ha-button"; +import "../../../components/ha-card"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { documentationUrl } from "../../../util/documentation-url"; import "./event-subscribe-card"; @@ -31,49 +32,61 @@ class HaPanelDevEvent extends LitElement { : "content layout horizontal"} >
-

- ${this.hass.localize( - "ui.panel.developer-tools.tabs.events.description" - )} - - ${this.hass.localize( - "ui.panel.developer-tools.tabs.events.documentation" - )} - -

-
- -

- ${this.hass.localize("ui.panel.developer-tools.tabs.events.data")} -

-
-
- -
- ${this.hass.localize( - "ui.panel.developer-tools.tabs.events.fire_event" - )} + +
+

+ ${this.hass.localize( + "ui.panel.developer-tools.tabs.events.description" + )} + + ${this.hass.localize( + "ui.panel.developer-tools.tabs.events.documentation" + )} + +

+
+ +

+ ${this.hass.localize( + "ui.panel.developer-tools.tabs.events.data" + )} +

+
+
+ +
+
+
+ ${this.hass.localize( + "ui.panel.developer-tools.tabs.events.fire_event" + )} +
+
+
diff --git a/src/panels/developer-tools/event/event-subscribe-card.ts b/src/panels/developer-tools/event/event-subscribe-card.ts index ada37484ce..564af64c4b 100644 --- a/src/panels/developer-tools/event/event-subscribe-card.ts +++ b/src/panels/developer-tools/event/event-subscribe-card.ts @@ -1,4 +1,3 @@ -import "@material/mwc-button"; import { HassEvent } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -7,6 +6,7 @@ import { formatTime } from "../../../common/datetime/format_time"; import "../../../components/ha-card"; import "../../../components/ha-textfield"; import "../../../components/ha-yaml-editor"; +import "../../../components/ha-button"; import { HomeAssistant } from "../../../types"; @customElement("event-subscribe-card") @@ -40,33 +40,46 @@ class EventSubscribeCard extends LitElement { )} >
-
- - - ${this._subscribed - ? this.hass!.localize( - "ui.panel.developer-tools.tabs.events.stop_listening" - ) - : this.hass!.localize( - "ui.panel.developer-tools.tabs.events.start_listening" - )} - -
+ +
+
+ + ${this._subscribed + ? this.hass!.localize( + "ui.panel.developer-tools.tabs.events.stop_listening" + ) + : this.hass!.localize( + "ui.panel.developer-tools.tabs.events.start_listening" + )} + + + ${this.hass!.localize( + "ui.panel.developer-tools.tabs.events.clear_events" + )} + +
+ + +
${repeat( this._events, @@ -99,7 +112,7 @@ class EventSubscribeCard extends LitElement { this._eventType = ev.target.value; } - private async _handleSubmit(): Promise { + private async _startOrStopListening(): Promise { if (this._subscribed) { this._subscribed(); this._subscribed = undefined; @@ -121,6 +134,11 @@ class EventSubscribeCard extends LitElement { } } + private _clearEvents(): void { + this._events = []; + this._eventCount = 0; + } + static get styles(): CSSResultGroup { return css` ha-textfield { @@ -140,6 +158,9 @@ class EventSubscribeCard extends LitElement { pre { font-family: var(--code-font-family, monospace); } + ha-card { + margin-bottom: 5px; + } `; } } diff --git a/src/translations/en.json b/src/translations/en.json index eb7e855219..865a4f9e75 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6686,6 +6686,7 @@ "subscribe_to": "Event to subscribe to", "start_listening": "Start listening", "stop_listening": "Stop listening", + "clear_events": "Clear events", "alert_event_type": "Event type is a mandatory field", "notification_event_fired": "Event {type} successfully fired!" }, From f2993602f991a44d32a74fea141730b2789abca7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 17:09:06 +0200 Subject: [PATCH 04/97] Update dependency superstruct to v2 (#21324) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 747e697699..25156691a3 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ "rrule": "2.8.1", "sortablejs": "1.15.2", "stacktrace-js": "2.0.2", - "superstruct": "1.0.4", + "superstruct": "2.0.2", "tinykeys": "2.1.0", "tsparticles-engine": "2.12.0", "tsparticles-preset-links": "2.12.0", diff --git a/yarn.lock b/yarn.lock index 8b5ee67ad1..1d34ef6c5c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9074,7 +9074,7 @@ __metadata: sortablejs: "npm:1.15.2" source-map-url: "npm:0.4.1" stacktrace-js: "npm:2.0.2" - superstruct: "npm:1.0.4" + superstruct: "npm:2.0.2" systemjs: "npm:6.15.1" tar: "npm:7.4.0" terser-webpack-plugin: "npm:5.3.10" @@ -13601,10 +13601,10 @@ __metadata: languageName: node linkType: hard -"superstruct@npm:1.0.4": - version: 1.0.4 - resolution: "superstruct@npm:1.0.4" - checksum: 10/9b3fd70a08c5ad3ea78b5c6b7ab90d31dde71af10448208d296c3d29ba2e55dfd817dfef75957163ee032163d04c4b2e0cb2fddff30313516aa60f748c1a48da +"superstruct@npm:2.0.2": + version: 2.0.2 + resolution: "superstruct@npm:2.0.2" + checksum: 10/10e1944a9da4baee187fbaa6c5d97d7af266b55786dfe50bce67f0f1e7d93f1a5a42dd51e245a2e16404f8336d07c21c67f1c1fbc4ad0a252d3d2601d6c926da languageName: node linkType: hard From 5ead5ed058150336987f7a2ca300b4ecb40b1e3f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 10 Jul 2024 18:25:57 +0200 Subject: [PATCH 05/97] Fix unhiding default hidden column (#21358) the first commit is the right commit... --- src/components/data-table/dialog-data-table-settings.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/data-table/dialog-data-table-settings.ts b/src/components/data-table/dialog-data-table-settings.ts index 29c99762ee..b3f8c51087 100644 --- a/src/components/data-table/dialog-data-table-settings.ts +++ b/src/components/data-table/dialog-data-table-settings.ts @@ -236,14 +236,18 @@ export class DialogDataTableSettings extends LitElement { } columns.forEach((col) => { - if (col.key !== column && !newOrder.includes(col.key)) { + if (!newOrder.includes(col.key)) { if (col.moveable === false) { newOrder.unshift(col.key); } else { newOrder.splice(lastMoveable + 1, 0, col.key); } - if (col.defaultHidden && !hidden.includes(col.key)) { + if ( + col.key !== column && + col.defaultHidden && + !hidden.includes(col.key) + ) { hidden.push(col.key); } } From d9583582e6406f11b755dceb46cb36d3c4d19779 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 11 Jul 2024 09:00:15 +0200 Subject: [PATCH 06/97] Rename service call to action (#21362) --- src/components/ha-service-control.ts | 6 +- src/components/ha-service-picker.ts | 2 +- .../config/cloud/account/cloud-tts-pref.ts | 9 +- .../entity-registry-settings-editor.ts | 2 +- .../config/entities/ha-config-entities.ts | 2 +- .../developer-tools-action.ts} | 47 +++++----- .../developer-tools/developer-tools-router.ts | 7 +- .../ha-panel-developer-tools.ts | 6 +- src/panels/lovelace/common/compute-tooltip.ts | 2 +- src/panels/lovelace/common/handle-action.ts | 2 +- .../config-elements/hui-tile-card-editor.ts | 1 - src/state/connection-mixin.ts | 2 +- src/translations/en.json | 93 ++++++++++--------- 13 files changed, 92 insertions(+), 89 deletions(-) rename src/panels/developer-tools/{service/developer-tools-service.ts => action/developer-tools-action.ts} (91%) diff --git a/src/components/ha-service-control.ts b/src/components/ha-service-control.ts index 47ee865393..cbf71987e0 100644 --- a/src/components/ha-service-control.ts +++ b/src/components/ha-service-control.ts @@ -451,7 +451,7 @@ export class HaServiceControl extends LitElement { > ${this.hass.localize( - "ui.components.service-control.target_description" + "ui.components.service-control.target_secondary" )}
- ${this.hass.localize("ui.panel.config.cloud.account.tts.info", { - service: '"tts.cloud_say"', - })} + ${this.hass.localize( + "ui.panel.config.cloud.account.tts.description", + { + service: '"tts.cloud_say"', + } + )}

${this.hass.localize( - "ui.dialogs.entity_registry.editor.hidden_description" + "ui.dialogs.entity_registry.editor.hidden_explanation" )}

${this.hass.localize( - "ui.panel.developer-tools.tabs.services.description" + "ui.panel.developer-tools.tabs.actions.description" )}

@@ -154,23 +155,23 @@ class HaPanelDevService extends LitElement { > ${this._yamlMode ? this.hass.localize( - "ui.panel.developer-tools.tabs.services.ui_mode" + "ui.panel.developer-tools.tabs.actions.ui_mode" ) : this.hass.localize( - "ui.panel.developer-tools.tabs.services.yaml_mode" + "ui.panel.developer-tools.tabs.actions.yaml_mode" )} ${!this._uiAvailable ? html`${this.hass.localize( - "ui.panel.developer-tools.tabs.services.no_template_ui_support" + "ui.panel.developer-tools.tabs.actions.no_template_ui_support" )}` : ""}
${this.hass.localize( - "ui.panel.developer-tools.tabs.services.call_service" + "ui.panel.developer-tools.tabs.actions.call_service" )}
@@ -179,7 +180,7 @@ class HaPanelDevService extends LitElement { ? html`
@@ -199,10 +200,10 @@ class HaPanelDevService extends LitElement { ${this.hass.localize( - "ui.panel.developer-tools.tabs.services.column_parameter" + "ui.panel.developer-tools.tabs.actions.column_parameter" )} ${this.hass.localize( - "ui.panel.developer-tools.tabs.services.column_description" + "ui.panel.developer-tools.tabs.actions.column_description" )} ${this.hass.localize( - "ui.panel.developer-tools.tabs.services.column_example" + "ui.panel.developer-tools.tabs.actions.column_example" )} @@ -281,7 +282,7 @@ class HaPanelDevService extends LitElement { ${this._yamlMode ? html`${this.hass.localize( - "ui.panel.developer-tools.tabs.services.fill_example_data" + "ui.panel.developer-tools.tabs.actions.fill_example_data" )}` : ""} @@ -305,14 +306,14 @@ class HaPanelDevService extends LitElement { const errorCategory = yamlMode ? "yaml" : "ui"; if (!serviceData?.service) { return localize( - `ui.panel.developer-tools.tabs.services.errors.${errorCategory}.no_service` + `ui.panel.developer-tools.tabs.actions.errors.${errorCategory}.no_service` ); } const domain = computeDomain(serviceData.service); const service = computeObjectId(serviceData.service); if (!domain || !service) { return localize( - `ui.panel.developer-tools.tabs.services.errors.${errorCategory}.invalid_service` + `ui.panel.developer-tools.tabs.actions.errors.${errorCategory}.invalid_service` ); } if ( @@ -323,7 +324,7 @@ class HaPanelDevService extends LitElement { !serviceData.data?.area_id ) { return localize( - `ui.panel.developer-tools.tabs.services.errors.${errorCategory}.no_target` + `ui.panel.developer-tools.tabs.actions.errors.${errorCategory}.no_target` ); } for (const field of fields) { @@ -332,7 +333,7 @@ class HaPanelDevService extends LitElement { (!serviceData.data || serviceData.data[field.key] === undefined) ) { return localize( - `ui.panel.developer-tools.tabs.services.errors.${errorCategory}.missing_required_field`, + `ui.panel.developer-tools.tabs.actions.errors.${errorCategory}.missing_required_field`, { key: field.key } ); } @@ -377,7 +378,7 @@ class HaPanelDevService extends LitElement { forwardHaptic("failure"); button.actionError(); this._error = this.hass.localize( - "ui.panel.developer-tools.tabs.services.errors.yaml.invalid_yaml" + "ui.panel.developer-tools.tabs.actions.errors.yaml.invalid_yaml" ); return; } @@ -439,7 +440,7 @@ class HaPanelDevService extends LitElement { } this._error = localizedErrorMessage || - this.hass.localize("ui.notification_toast.service_call_failed", { + this.hass.localize("ui.notification_toast.action_failed", { service: this._serviceData!.service!, }) + ` ${err.message}`; return; @@ -630,10 +631,8 @@ class HaPanelDevService extends LitElement { } } -customElements.define("developer-tools-service", HaPanelDevService); - declare global { interface HTMLElementTagNameMap { - "developer-tools-service": HaPanelDevService; + "developer-tools-action": HaPanelDevAction; } } diff --git a/src/panels/developer-tools/developer-tools-router.ts b/src/panels/developer-tools/developer-tools-router.ts index 6fdf6aa010..6018861ecb 100644 --- a/src/panels/developer-tools/developer-tools-router.ts +++ b/src/panels/developer-tools/developer-tools-router.ts @@ -24,9 +24,10 @@ class DeveloperToolsRouter extends HassRouterPage { tag: "developer-tools-event", load: () => import("./event/developer-tools-event"), }, - service: { - tag: "developer-tools-service", - load: () => import("./service/developer-tools-service"), + service: "action", + action: { + tag: "developer-tools-action", + load: () => import("./action/developer-tools-action"), }, state: { tag: "developer-tools-state", diff --git a/src/panels/developer-tools/ha-panel-developer-tools.ts b/src/panels/developer-tools/ha-panel-developer-tools.ts index 667f016ea5..237e082724 100644 --- a/src/panels/developer-tools/ha-panel-developer-tools.ts +++ b/src/panels/developer-tools/ha-panel-developer-tools.ts @@ -62,10 +62,8 @@ class PanelDeveloperTools extends LitElement { ${this.hass.localize("ui.panel.developer-tools.tabs.states.title")} - - ${this.hass.localize( - "ui.panel.developer-tools.tabs.services.title" - )} + + ${this.hass.localize("ui.panel.developer-tools.tabs.actions.title")} ${this.hass.localize( diff --git a/src/panels/lovelace/common/compute-tooltip.ts b/src/panels/lovelace/common/compute-tooltip.ts index b371984b44..b002cd2caf 100644 --- a/src/panels/lovelace/common/compute-tooltip.ts +++ b/src/panels/lovelace/common/compute-tooltip.ts @@ -45,7 +45,7 @@ function computeActionTooltip( break; case "call-service": tooltip += `${hass.localize( - "ui.panel.lovelace.cards.picture-elements.call_service", + "ui.panel.lovelace.cards.picture-elements.perform_action", { name: config.service } )}`; break; diff --git a/src/panels/lovelace/common/handle-action.ts b/src/panels/lovelace/common/handle-action.ts index b9b4041d5f..f4fdd90653 100644 --- a/src/panels/lovelace/common/handle-action.ts +++ b/src/panels/lovelace/common/handle-action.ts @@ -148,7 +148,7 @@ export const handleAction = async ( case "call-service": { if (!actionConfig.service) { showToast(node, { - message: hass.localize("ui.panel.lovelace.cards.actions.no_service"), + message: hass.localize("ui.panel.lovelace.cards.actions.no_action"), }); forwardHaptic("failure"); return; diff --git a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts index 491bec6e31..ca705fafb7 100644 --- a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts @@ -407,7 +407,6 @@ export class HuiTileCardEditor return this.hass!.localize( `ui.panel.lovelace.editor.card.tile.${schema.name}` ); - default: return this.hass!.localize( `ui.panel.lovelace.editor.card.generic.${schema.name}` diff --git a/src/state/connection-mixin.ts b/src/state/connection-mixin.ts index e376529501..d4ce2419ed 100644 --- a/src/state/connection-mixin.ts +++ b/src/state/connection-mixin.ts @@ -135,7 +135,7 @@ export const connectionMixin = >( const message = localizedErrorMessage || (this as any).hass.localize( - "ui.notification_toast.service_call_failed", + "ui.notification_toast.action_failed", "service", `${domain}/${service}` ) + diff --git a/src/translations/en.json b/src/translations/en.json index 865a4f9e75..424901461d 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -429,7 +429,7 @@ "triggered_by": "triggered by", "triggered_by_automation": "triggered by automation", "triggered_by_script": "triggered by script", - "triggered_by_service": "triggered by service", + "triggered_by_action": "triggered by action", "triggered_by_numeric_state_of": "triggered by numeric state of", "triggered_by_state_of": "triggered by state of", "triggered_by_event": "triggered by event", @@ -773,13 +773,13 @@ } }, "service-picker": { - "service": "Service" + "action": "Action" }, "service-control": { "required": "This field is required", "target": "Targets", - "target_description": "What should this service use as targeted areas, devices or entities.", - "data": "Service data", + "target_secondary": "What should this action use as targeted areas, devices or entities.", + "action_data": "Action data", "integration_doc": "Integration documentation" }, "related-items": { @@ -1352,7 +1352,7 @@ "enabled_description": "Disabled entities will not be added to Home Assistant.", "enabled_delay_confirm": "The enabled entities will be added to Home Assistant in {delay} seconds", "enabled_restart_confirm": "Restart Home Assistant to finish enabling the entities", - "hidden_description": "Hidden entities will not be shown on your dashboard or included when indirectly referenced (ie via an area or device). Their history is still tracked and you can still interact with them with services.", + "hidden_explanation": "Hidden entities will not be shown on your dashboard or included when indirectly referenced (ie via an area or device). Their history is still tracked and you can still interact with them with actions.", "delete": "Delete", "confirm_delete": "Are you sure you want to delete this entity?", "update": "Update", @@ -1603,7 +1603,7 @@ "view_network": "View network" }, "services": { - "reconfigure": "Reconfigure ZHA device (heal device). Use this if you are having issues with the device. If the device in question is a battery powered device please ensure it is awake and accepting commands when you use this service.", + "reconfigure": "Reconfigure ZHA device (heal device). Use this if you are having issues with the device. If the device in question is a battery powered device please ensure it is awake and accepting commands when you use this action.", "updateDeviceName": "Set a custom name for this device in the device registry.", "remove": "Remove a device from the Zigbee network.", "zigbee_information": "View the Zigbee information for the device." @@ -1732,7 +1732,7 @@ "step_3": "Tap {link_apps_services} and choose {home_assistant} from the list.", "linked_matter_apps_services": "Linked Matter apps and services", "link_apps_services": "Link apps & services", - "no_home_assistant": "I can’t find Home Assistant on the list", + "no_home_assistant": "I can''t find Home Assistant on the list", "redirect": "You are redirected to the Home Assistant app. Please follow the instructions." }, "google_home_fallback": { @@ -1807,7 +1807,7 @@ "dismiss_all": "Dismiss all" }, "notification_toast": { - "service_call_failed": "Failed to call service {service}.", + "action_failed": "Failed to perform the action {service}.", "connection_lost": "Connection lost. Reconnecting…", "started": "Home Assistant has started!", "starting": "Home Assistant is starting, not everything will be available until it is finished.", @@ -2660,7 +2660,7 @@ "run_text_pipeline": "Run text pipeline", "run_audio_pipeline": "Run audio pipeline", "run_audio_with_wake": "Run audio pipeline with wake word detection", - "response": "[%key:ui::panel::developer-tools::tabs::services::response%]", + "response": "[%key:ui::panel::developer-tools::tabs::actions::response%]", "send": "Send", "continue_listening": "Continue listening for wake word", "continue_talking": "Continue talking", @@ -3267,18 +3267,19 @@ }, "type": { "service": { - "label": "Call service", + "label": "Perform action", "response_variable": "Response variable", - "has_optional_response": "This service can return a response, if you want to use the response, enter the name of a variable the response will be saved in", - "has_response": "This service returns a response, enter the name of a variable the response will be saved in", + "has_optional_response": "This action can return a response, if you want to use the response, enter the name of a variable the response will be saved in", + "has_response": "This action returns a response, enter the name of a variable the response will be saved in", "description": { - "service_based_on_template": "Call a service based on a template on {targets}", - "service_based_on_name": "Call a service ''{name}'' on {targets}", + "picker": "Previously known as call service.", + "service_based_on_template": "Perform an action based on a template on {targets}", + "service_based_on_name": "Perform action ''{name}'' on {targets}", "service_name": "{domain} ''{name}'' on {targets}", - "service_based_on_template_no_targets": "Call a service based on a template", - "service_based_on_name_no_targets": "Call a service ''{name}''", + "service_based_on_template_no_targets": "Perform an action based on a template", + "service_based_on_name_no_targets": "Perform action ''{name}''", "service_name_no_targets": "{domain} ''{name}''", - "service": "Call a service", + "service": "Perform an action", "target_template": "templated {name}", "target_unknown_entity": "unknown entity", "target_unknown_device": "unknown device", @@ -3837,7 +3838,7 @@ "fetching_subscription": "Fetching subscription…", "tts": { "title": "Text-to-speech", - "info": "Bring personality to your home by having it speak to you by using our text-to-speech services. You can use this in automations and scripts by using the {service} service.", + "description": "Bring personality to your home by having it speak to you by using our text-to-speech services. You can use this in automations and scripts by using the {service} action.", "default_language": "Default language to use", "default_voice": "Default voice to use", "try": "Try", @@ -4122,7 +4123,7 @@ "hide_selected": { "button": "Hide selected", "confirm_title": "Do you want to hide {number} {number, plural,\n one {entity}\n other {entities}\n}?", - "confirm_text": "Hidden entities will not be shown on your dashboard. Their history is still tracked and you can still interact with them with services." + "confirm": "Hidden entities will not be shown on your dashboard. Their history is still tracked and you can still interact with them with actions." }, "unhide_selected": { "button": "Unhide selected" @@ -5280,7 +5281,7 @@ "no_entity_toggle": "No entity provided to toggle", "no_navigation_path": "No navigation path specified", "no_url": "No URL to open specified", - "no_service": "No service to run specified" + "no_action": "No action to run specified" }, "empty_state": { "title": "Welcome Home", @@ -5310,7 +5311,7 @@ "navigate_to": "Navigate to {location}", "url": "Open window to {url_path}", "toggle": "Toggle {name}", - "call_service": "Call service {name}", + "perform_action": "Perform action {name}", "more_info": "Show more info: {name}" }, "iframe": { @@ -5625,14 +5626,14 @@ "start_listening": "Start listening", "pipeline_id": "Assistant", "actions": { - "default_action": "Default action", - "call-service": "Call service", + "default_action": "Default", + "call-service": "Perform action", "more-info": "More info", "toggle": "Toggle", "navigate": "Navigate", "assist": "Assist", "url": "URL", - "none": "No action" + "none": "Nothing" } }, "condition-editor": { @@ -5740,7 +5741,7 @@ }, "entity_row": { "divider": "Divider", - "call-service": "Call service", + "call-service": "Perform action", "section": "Section", "weblink": "Web link", "attribute": "Attribute", @@ -5757,7 +5758,7 @@ "button": { "name": "Button", "description": "The Button card allows you to add buttons to perform tasks.", - "default_action_help": "The default action depends on the entity's capabilities, it will either be toggled or the more info dialog will be shown." + "default_action_help": "The default depends on the entity's capabilities, it will either be toggled or the more info dialog will be shown." }, "entity-filter": { "name": "Entity filter", @@ -5875,10 +5876,10 @@ "camera_image": "Camera entity", "image_entity": "Image entity", "camera_view": "Camera view", - "double_tap_action": "Double tap action", + "double_tap_action": "Double tap behavior", "entities": "Entities", "entity": "Entity", - "hold_action": "Hold action", + "hold_action": "Hold behavior", "hours_to_show": "Hours to show", "days_to_show": "Days to show", "icon": "Icon", @@ -5895,7 +5896,7 @@ "show_icon": "Show icon?", "show_name": "Show name?", "show_state": "Show state?", - "tap_action": "Tap action", + "tap_action": "Tap behavior", "title": "Title", "theme": "Theme", "unit": "Unit", @@ -5934,7 +5935,7 @@ }, "picture": { "name": "Picture", - "description": "The Picture card allows you to set an image to use for navigation to various paths in your interface or to call a service." + "description": "The Picture card allows you to set an image to use for navigation to various paths in your interface or to perform an action." }, "picture-elements": { "name": "Picture elements", @@ -5946,7 +5947,7 @@ }, "picture-glance": { "name": "Picture glance", - "description": "The Picture card allows you to set an image to use for navigation to various paths in your interface or to call a service.", + "description": "The Picture card allows you to set an image to use for navigation to various paths in your interface or to perform an action.", "state_entity": "State entity" }, "plant-status": { @@ -5973,7 +5974,7 @@ "name": "Tile", "description": "The tile card gives you a quick overview of your entity. The card allow you to toggle the entity, show the more info dialog or custom actions.", "color": "Color", - "icon_tap_action": "Icon tap action", + "icon_tap_action": "Icon tap behavior", "actions": "Actions", "appearance": "Appearance", "default_color": "Default color (state)", @@ -6690,10 +6691,10 @@ "alert_event_type": "Event type is a mandatory field", "notification_event_fired": "Event {type} successfully fired!" }, - "services": { - "title": "Services", - "description": "The service dev tool allows you to call any available service in Home Assistant.", - "call_service": "Call service", + "actions": { + "title": "Actions", + "description": "The actions dev tool allows you to perform any action available in Home Assistant.", + "call_service": "Perform action", "response": "Response", "column_parameter": "Parameter", "column_description": "Description", @@ -6703,21 +6704,21 @@ "ui_mode": "Go to UI mode", "yaml_parameters": "Parameters only available in YAML mode", "all_parameters": "All available parameters", - "accepts_target": "This service accepts a target, for example: `entity_id: light.bed_light`", + "accepts_target": "This action accepts a target, for example: `entity_id: light.bed_light`", "no_template_ui_support": "The UI does not support templates, you can still use the YAML editor.", "errors": { "ui": { - "no_service": "No service selected, please select a service", - "invalid_service": "Selected service is invalid, please select a valid service", - "no_target": "This service requires a target, please select a target from the picker", - "missing_required_field": "This service requires field {key}, please enter a valid value for {key}" + "no_service": "No action selected, please select an action", + "invalid_service": "Selected action is invalid, please select a valid action", + "no_target": "This action requires a target, please select a target from the picker", + "missing_required_field": "This action requires field {key}, please enter a valid value for {key}" }, "yaml": { - "invalid_yaml": "Service YAML contains syntax errors, please fix the syntax", - "no_service": "No service defined, please define a service: key", - "invalid_service": "Defined service is invalid, please provide a service in the format domain.service", - "no_target": "This service requires a target, please define a target entity_id, device_id, or area_id under target: or data:", - "missing_required_field": "This service requires field {key}, which must be provided under data:" + "invalid_yaml": "Action YAML contains syntax errors, please fix the syntax", + "no_service": "No action defined, please define an action: key", + "invalid_service": "Defined action is invalid, please provide an action in the format domain.action", + "no_target": "This action requires a target, please define a target entity_id, device_id, or area_id under target: or data:", + "missing_required_field": "This action requires field {key}, which must be provided under data:" } } }, From e59c04c68546396f95f71610983912b62cf98bab Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 11 Jul 2024 11:09:14 +0200 Subject: [PATCH 07/97] Display live remaining time for timer on tile card (#21290) Display timer by default on tile card --- src/data/timer.ts | 2 +- src/panels/lovelace/cards/hui-tile-card.ts | 17 +++- .../entity-rows/hui-timer-entity-row.ts | 87 ++----------------- src/state-display/state-display-timer.ts | 74 ++++++++++++++++ src/state-summary/state-card-timer.ts | 67 ++------------ 5 files changed, 101 insertions(+), 146 deletions(-) create mode 100644 src/state-display/state-display-timer.ts diff --git a/src/data/timer.ts b/src/data/timer.ts index 7dc4f8e41d..4b0ab1ce27 100644 --- a/src/data/timer.ts +++ b/src/data/timer.ts @@ -92,7 +92,7 @@ export const computeDisplayTimer = ( return hass.formatEntityState(stateObj); } - let display = secondsToDuration(timeRemaining || 0); + let display = secondsToDuration(timeRemaining || 0) || "0"; if (stateObj.state === "paused") { display = `${display} (${hass.formatEntityState(stateObj)})`; diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index ae8c8a99c0..3208928d02 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -311,10 +311,19 @@ export class HuiTileCard extends LitElement implements LovelaceCard { } if (domain === "update") { - return html`${computeUpdateStateDisplay( - stateObj as UpdateEntity, - this.hass! - )}`; + return html` + ${computeUpdateStateDisplay(stateObj as UpdateEntity, this.hass!)} + `; + } + + if (domain === "timer") { + import("../../../state-display/state-display-timer"); + return html` + + `; } return this._renderStateContent(stateObj, "state"); diff --git a/src/panels/lovelace/entity-rows/hui-timer-entity-row.ts b/src/panels/lovelace/entity-rows/hui-timer-entity-row.ts index bd58e41534..8cb3cac9a1 100644 --- a/src/panels/lovelace/entity-rows/hui-timer-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-timer-entity-row.ts @@ -1,7 +1,6 @@ -import { HassEntity } from "home-assistant-js-websocket"; -import { html, LitElement, PropertyValues, nothing } from "lit"; +import { LitElement, PropertyValues, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { computeDisplayTimer, timerTimeRemaining } from "../../../data/timer"; +import "../../../state-display/state-display-timer"; import { HomeAssistant } from "../../../types"; import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-generic-entity-row"; @@ -14,42 +13,11 @@ class HuiTimerEntityRow extends LitElement { @state() private _config?: EntityConfig; - @state() private _timeRemaining?: number; - - private _interval?: number; - public setConfig(config: EntityConfig): void { if (!config) { throw new Error("Invalid configuration"); } this._config = config; - - if (!this.hass) { - return; - } - - const stateObj = this.hass!.states[this._config.entity]; - - if (stateObj) { - this._startInterval(stateObj); - } else { - this._clearInterval(); - } - } - - public disconnectedCallback(): void { - super.disconnectedCallback(); - this._clearInterval(); - } - - public connectedCallback(): void { - super.connectedCallback(); - if (this._config && this._config.entity) { - const stateObj = this.hass?.states[this._config!.entity]; - if (stateObj) { - this._startInterval(stateObj); - } - } } protected render() { @@ -70,61 +38,18 @@ class HuiTimerEntityRow extends LitElement { return html`
- ${computeDisplayTimer(this.hass, stateObj, this._timeRemaining)} +
`; } protected shouldUpdate(changedProps: PropertyValues): boolean { - if (changedProps.has("_timeRemaining")) { - return true; - } - return hasConfigOrEntityChanged(this, changedProps); } - - protected updated(changedProps: PropertyValues) { - super.updated(changedProps); - - if (!this._config || !changedProps.has("hass")) { - return; - } - const stateObj = this.hass!.states[this._config!.entity]; - const oldHass = changedProps.get("hass") as this["hass"]; - const oldStateObj = oldHass - ? oldHass.states[this._config!.entity] - : undefined; - - if (oldStateObj !== stateObj) { - this._startInterval(stateObj); - } else if (!stateObj) { - this._clearInterval(); - } - } - - private _clearInterval(): void { - if (this._interval) { - window.clearInterval(this._interval); - this._interval = undefined; - } - } - - private _startInterval(stateObj: HassEntity): void { - this._clearInterval(); - this._calculateRemaining(stateObj); - - if (stateObj.state === "active") { - this._interval = window.setInterval( - () => this._calculateRemaining(stateObj), - 1000 - ); - } - } - - private _calculateRemaining(stateObj: HassEntity): void { - this._timeRemaining = timerTimeRemaining(stateObj); - } } declare global { diff --git a/src/state-display/state-display-timer.ts b/src/state-display/state-display-timer.ts new file mode 100644 index 0000000000..13960c088c --- /dev/null +++ b/src/state-display/state-display-timer.ts @@ -0,0 +1,74 @@ +import type { HassEntity } from "home-assistant-js-websocket"; +import { PropertyValues, ReactiveElement } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { computeDisplayTimer, timerTimeRemaining } from "../data/timer"; +import type { HomeAssistant } from "../types"; + +@customElement("state-display-timer") +class StateDisplayTimer extends ReactiveElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public stateObj!: HassEntity; + + @state() private timeRemaining?: number; + + private _updateRemaining: any; + + protected createRenderRoot() { + return this; + } + + protected update(changedProps: PropertyValues) { + super.update(changedProps); + this.innerHTML = + computeDisplayTimer(this.hass, this.stateObj, this.timeRemaining) ?? "-"; + } + + connectedCallback() { + super.connectedCallback(); + if (this.stateObj) { + this._startInterval(this.stateObj); + } + } + + disconnectedCallback() { + super.disconnectedCallback(); + this._clearInterval(); + } + + protected willUpdate(changedProp: PropertyValues): void { + super.willUpdate(changedProp); + if (changedProp.has("stateObj")) { + this._startInterval(this.stateObj); + } + } + + private _clearInterval() { + if (this._updateRemaining) { + clearInterval(this._updateRemaining); + this._updateRemaining = null; + } + } + + private _startInterval(stateObj: HassEntity) { + this._clearInterval(); + this._calculateRemaining(stateObj); + + if (stateObj.state === "active") { + this._updateRemaining = setInterval( + () => this._calculateRemaining(this.stateObj), + 1000 + ); + } + } + + private _calculateRemaining(stateObj: HassEntity) { + this.timeRemaining = timerTimeRemaining(stateObj); + } +} + +declare global { + interface HTMLElementTagNameMap { + "state-display-timer": StateDisplayTimer; + } +} diff --git a/src/state-summary/state-card-timer.ts b/src/state-summary/state-card-timer.ts index 57bb83e8e7..134ebdb89c 100644 --- a/src/state-summary/state-card-timer.ts +++ b/src/state-summary/state-card-timer.ts @@ -1,17 +1,10 @@ import type { HassEntity } from "home-assistant-js-websocket"; -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import "../components/entity/state-info"; -import { computeDisplayTimer, timerTimeRemaining } from "../data/timer"; -import { HomeAssistant } from "../types"; import { haStyle } from "../resources/styles"; +import "../state-display/state-display-timer"; +import { HomeAssistant } from "../types"; @customElement("state-card-timer") class StateCardTimer extends LitElement { @@ -21,10 +14,6 @@ class StateCardTimer extends LitElement { @property({ type: Boolean }) public inDialog = false; - @property({ type: Number }) public timeRemaining?: number; - - private _updateRemaining: any; - protected render(): TemplateResult { return html`
@@ -34,63 +23,21 @@ class StateCardTimer extends LitElement { .inDialog=${this.inDialog} >
- ${this._displayState(this.timeRemaining, this.stateObj)} +
`; } - connectedCallback() { - super.connectedCallback(); - this._startInterval(this.stateObj); - } - - disconnectedCallback() { - super.disconnectedCallback(); - this._clearInterval(); - } - - protected willUpdate(changedProp: PropertyValues): void { - super.willUpdate(changedProp); - if (changedProp.has("stateObj")) { - this._startInterval(this.stateObj); - } - } - - private _clearInterval() { - if (this._updateRemaining) { - clearInterval(this._updateRemaining); - this._updateRemaining = null; - } - } - - private _startInterval(stateObj) { - this._clearInterval(); - this._calculateRemaining(stateObj); - - if (stateObj.state === "active") { - this._updateRemaining = setInterval( - () => this._calculateRemaining(this.stateObj), - 1000 - ); - } - } - - private _calculateRemaining(stateObj) { - this.timeRemaining = timerTimeRemaining(stateObj); - } - - private _displayState(timeRemaining, stateObj) { - return computeDisplayTimer(this.hass, stateObj, timeRemaining); - } - static get styles(): CSSResultGroup { return [ haStyle, css` .state { color: var(--primary-text-color); - margin-left: 16px; margin-inline-start: 16px; margin-inline-end: initial; From 277650e1c15790aa2944dc2278725e8a3538e849 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 11 Jul 2024 12:26:23 +0200 Subject: [PATCH 08/97] Update en.json --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index 424901461d..5b9c976290 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1732,7 +1732,7 @@ "step_3": "Tap {link_apps_services} and choose {home_assistant} from the list.", "linked_matter_apps_services": "Linked Matter apps and services", "link_apps_services": "Link apps & services", - "no_home_assistant": "I can''t find Home Assistant on the list", + "no_home_assistant": "I can't find Home Assistant on the list", "redirect": "You are redirected to the Home Assistant app. Please follow the instructions." }, "google_home_fallback": { From 3f34dacec9185c5d41f68938bd2abda8e07b8ffa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 21:16:54 +0200 Subject: [PATCH 09/97] Update typescript-eslint monorepo to v7.16.0 (#21372) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 +- yarn.lock | 104 +++++++++++++++++++++++++-------------------------- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 25156691a3..c6e8147bb3 100644 --- a/package.json +++ b/package.json @@ -185,8 +185,8 @@ "@types/tar": "6.1.13", "@types/ua-parser-js": "0.7.39", "@types/webspeechapi": "0.0.29", - "@typescript-eslint/eslint-plugin": "7.15.0", - "@typescript-eslint/parser": "7.15.0", + "@typescript-eslint/eslint-plugin": "7.16.0", + "@typescript-eslint/parser": "7.16.0", "@web/dev-server": "0.1.38", "@web/dev-server-rollup": "0.4.1", "babel-loader": "9.1.3", diff --git a/yarn.lock b/yarn.lock index 1d34ef6c5c..15d45cdf84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4584,15 +4584,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:7.15.0": - version: 7.15.0 - resolution: "@typescript-eslint/eslint-plugin@npm:7.15.0" +"@typescript-eslint/eslint-plugin@npm:7.16.0": + version: 7.16.0 + resolution: "@typescript-eslint/eslint-plugin@npm:7.16.0" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:7.15.0" - "@typescript-eslint/type-utils": "npm:7.15.0" - "@typescript-eslint/utils": "npm:7.15.0" - "@typescript-eslint/visitor-keys": "npm:7.15.0" + "@typescript-eslint/scope-manager": "npm:7.16.0" + "@typescript-eslint/type-utils": "npm:7.16.0" + "@typescript-eslint/utils": "npm:7.16.0" + "@typescript-eslint/visitor-keys": "npm:7.16.0" graphemer: "npm:^1.4.0" ignore: "npm:^5.3.1" natural-compare: "npm:^1.4.0" @@ -4603,44 +4603,44 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/e6b21687ab9e9dc38eb1b1d90a3ac483f3f5e5e9c49aa8a434a24de016822d65c82b926cda2ae79bac2225bd9495fb04f7aa6afcaad2b09f6129fd8014fbcedd + checksum: 10/beda6b586bfc953843877395b09acc0525d727dcb77e6ded5fbc645a9008b7e60360ddbaf6a9b7deaf23cd42c206412b7150d8df27f1fe2da3dc24dfab1c8d71 languageName: node linkType: hard -"@typescript-eslint/parser@npm:7.15.0": - version: 7.15.0 - resolution: "@typescript-eslint/parser@npm:7.15.0" +"@typescript-eslint/parser@npm:7.16.0": + version: 7.16.0 + resolution: "@typescript-eslint/parser@npm:7.16.0" dependencies: - "@typescript-eslint/scope-manager": "npm:7.15.0" - "@typescript-eslint/types": "npm:7.15.0" - "@typescript-eslint/typescript-estree": "npm:7.15.0" - "@typescript-eslint/visitor-keys": "npm:7.15.0" + "@typescript-eslint/scope-manager": "npm:7.16.0" + "@typescript-eslint/types": "npm:7.16.0" + "@typescript-eslint/typescript-estree": "npm:7.16.0" + "@typescript-eslint/visitor-keys": "npm:7.16.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.56.0 peerDependenciesMeta: typescript: optional: true - checksum: 10/0b5e7a14fa5d0680efb17e750a095729a7fb7c785d7a0fea2f9e6cbfef9e65caab2b751654b348b9ab813d222c1c3f8189ebf48561b81224d1821cee5c99d658 + checksum: 10/dc374e6c9e7dfcdd968828bb32ef59d3ebabd0a18671dee22d14dda2c713dade6eb493fd11b127df17035c7451898b42f4a88102da9a4bf3ca6a3baed8c20309 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:7.15.0": - version: 7.15.0 - resolution: "@typescript-eslint/scope-manager@npm:7.15.0" +"@typescript-eslint/scope-manager@npm:7.16.0": + version: 7.16.0 + resolution: "@typescript-eslint/scope-manager@npm:7.16.0" dependencies: - "@typescript-eslint/types": "npm:7.15.0" - "@typescript-eslint/visitor-keys": "npm:7.15.0" - checksum: 10/45bfdbae2d080691a34f5b37679b4a4067981baa3b82922268abdd21f6917a8dd1c4ccb12133f6c9cce81cfd640040913b223e8125235b92f42fdb57db358a3e + "@typescript-eslint/types": "npm:7.16.0" + "@typescript-eslint/visitor-keys": "npm:7.16.0" + checksum: 10/bf39a3ab803503c33e6c33568e7b93793d53d18100cb2f2ec1a540121aeba74d291d19c9ad3933198ff15e53a46d2f92db0c54309259dc99c1e3e297becd5677 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:7.15.0": - version: 7.15.0 - resolution: "@typescript-eslint/type-utils@npm:7.15.0" +"@typescript-eslint/type-utils@npm:7.16.0": + version: 7.16.0 + resolution: "@typescript-eslint/type-utils@npm:7.16.0" dependencies: - "@typescript-eslint/typescript-estree": "npm:7.15.0" - "@typescript-eslint/utils": "npm:7.15.0" + "@typescript-eslint/typescript-estree": "npm:7.16.0" + "@typescript-eslint/utils": "npm:7.16.0" debug: "npm:^4.3.4" ts-api-utils: "npm:^1.3.0" peerDependencies: @@ -4648,23 +4648,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/64fa589b413567df3689a19ef88f3dbaed66d965e39cc548a58626eb5bd8fc4e2338496eb632f3472de9ae9800cb14d0e48ef3508efe80bdb91af8f3f1e56ad7 + checksum: 10/84925c851a515768317573984dc855ac93bf787ebaa6382379dea6b356adb936ebd38bf7ab2f95124c68de7ab1fd5c849fe6717929343a80b839757fb5bf3af0 languageName: node linkType: hard -"@typescript-eslint/types@npm:7.15.0": - version: 7.15.0 - resolution: "@typescript-eslint/types@npm:7.15.0" - checksum: 10/b36c98344469f4bc54a5199733ea4f6d4d0f2da1070605e60d4031e2da2946b84b91a90108516c8e6e83a21030ba4e935053a0906041c920156de40683297d0b +"@typescript-eslint/types@npm:7.16.0": + version: 7.16.0 + resolution: "@typescript-eslint/types@npm:7.16.0" + checksum: 10/0813d9eb158f984b9d7e9e83961533ddc1e8c8815ca9059dab820df276b1e537b183f4c83cc4fe79ab3865cde1a64f2ec3f7fffe7209872d7d404636299f630b languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:7.15.0": - version: 7.15.0 - resolution: "@typescript-eslint/typescript-estree@npm:7.15.0" +"@typescript-eslint/typescript-estree@npm:7.16.0": + version: 7.16.0 + resolution: "@typescript-eslint/typescript-estree@npm:7.16.0" dependencies: - "@typescript-eslint/types": "npm:7.15.0" - "@typescript-eslint/visitor-keys": "npm:7.15.0" + "@typescript-eslint/types": "npm:7.16.0" + "@typescript-eslint/visitor-keys": "npm:7.16.0" debug: "npm:^4.3.4" globby: "npm:^11.1.0" is-glob: "npm:^4.0.3" @@ -4674,31 +4674,31 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/c5fb15108fbbc1bc976e827218ff7bfbc78930c5906292325ee42ba03514623e7b861497b3e3087f71ede9a757b16441286b4d234450450b0dd70ff753782736 + checksum: 10/5719c0cb649d627a073f1c8994a6073acc211ecfce0daef61d2de4315e42a23cf79e4dacb3b3596c4792eab062fdd22080c62345e2a58d38e7268eb6103a46d4 languageName: node linkType: hard -"@typescript-eslint/utils@npm:7.15.0": - version: 7.15.0 - resolution: "@typescript-eslint/utils@npm:7.15.0" +"@typescript-eslint/utils@npm:7.16.0": + version: 7.16.0 + resolution: "@typescript-eslint/utils@npm:7.16.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:7.15.0" - "@typescript-eslint/types": "npm:7.15.0" - "@typescript-eslint/typescript-estree": "npm:7.15.0" + "@typescript-eslint/scope-manager": "npm:7.16.0" + "@typescript-eslint/types": "npm:7.16.0" + "@typescript-eslint/typescript-estree": "npm:7.16.0" peerDependencies: eslint: ^8.56.0 - checksum: 10/f6de1849dee610a8110638be98ab2ec09e7cdf2f756b538b0544df2dfad86a8e66d5326a765302fe31553e8d9d3170938c0d5d38bd9c7d36e3ee0beb1bdc8172 + checksum: 10/325eab6705e70322d8df613cba4b018abc5d8ef857eb6c86f7a8376334eac789e6a585d30c041045c7eeede18083744faae66f48033e7811b2a23ebe8f6d3407 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:7.15.0": - version: 7.15.0 - resolution: "@typescript-eslint/visitor-keys@npm:7.15.0" +"@typescript-eslint/visitor-keys@npm:7.16.0": + version: 7.16.0 + resolution: "@typescript-eslint/visitor-keys@npm:7.16.0" dependencies: - "@typescript-eslint/types": "npm:7.15.0" + "@typescript-eslint/types": "npm:7.16.0" eslint-visitor-keys: "npm:^3.4.3" - checksum: 10/0e17d7f5de767da7f98170c2efc905cdb0ceeaf04a667e12ca1a92eae64479a07f4f8e2a9b5023b055b01250916c3bcac86908cd06552610baff734fafae4464 + checksum: 10/aae065bdd6d5681d40df51af24933fc86c15f355f9d8f85c39a506f352ddc2a76fc72d4f8cf823ebb7550c84d543605a2fdd7d06979a0967cd48c1f542436714 languageName: node linkType: hard @@ -8986,8 +8986,8 @@ __metadata: "@types/tar": "npm:6.1.13" "@types/ua-parser-js": "npm:0.7.39" "@types/webspeechapi": "npm:0.0.29" - "@typescript-eslint/eslint-plugin": "npm:7.15.0" - "@typescript-eslint/parser": "npm:7.15.0" + "@typescript-eslint/eslint-plugin": "npm:7.16.0" + "@typescript-eslint/parser": "npm:7.16.0" "@vaadin/combo-box": "npm:24.4.1" "@vaadin/vaadin-themable-mixin": "npm:24.4.1" "@vibrant/color": "npm:3.2.1-alpha.1" From 7b66ee06eb7a2a90419b2fe197ba628dec1f2ec0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 13 Jul 2024 01:15:19 +0200 Subject: [PATCH 10/97] Update vaadinWebComponents monorepo to v24.4.2 (#21376) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 +- yarn.lock | 170 +++++++++++++++++++++++++-------------------------- 2 files changed, 87 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index c6e8147bb3..ea7561e20a 100644 --- a/package.json +++ b/package.json @@ -88,8 +88,8 @@ "@polymer/paper-tabs": "3.1.0", "@polymer/polymer": "3.5.1", "@thomasloven/round-slider": "0.6.0", - "@vaadin/combo-box": "24.4.1", - "@vaadin/vaadin-themable-mixin": "24.4.1", + "@vaadin/combo-box": "24.4.2", + "@vaadin/vaadin-themable-mixin": "24.4.2", "@vibrant/color": "3.2.1-alpha.1", "@vibrant/core": "3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "3.2.1-alpha.1", diff --git a/yarn.lock b/yarn.lock index 15d45cdf84..a1d302b789 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4709,129 +4709,129 @@ __metadata: languageName: node linkType: hard -"@vaadin/a11y-base@npm:~24.4.1": - version: 24.4.1 - resolution: "@vaadin/a11y-base@npm:24.4.1" +"@vaadin/a11y-base@npm:~24.4.2": + version: 24.4.2 + resolution: "@vaadin/a11y-base@npm:24.4.2" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.4.1" + "@vaadin/component-base": "npm:~24.4.2" lit: "npm:^3.0.0" - checksum: 10/b59f6b8ecab3b359ce3f4e0362674b45800bb6a61343895cecab7956eb64de4ed94f7ed34e943cb27865bc5fcdf980d9012533a1cbd11ed5f10ed14f6282cf9e + checksum: 10/6b3bd35507168e1524dd26e5b18c5e1f4e6d8db2ec576fdeb9928282257d47c5b6984395286786b11aeb6aeacdade42a495cb75517cb4fa2cb450cad88d9916a languageName: node linkType: hard -"@vaadin/combo-box@npm:24.4.1": - version: 24.4.1 - resolution: "@vaadin/combo-box@npm:24.4.1" +"@vaadin/combo-box@npm:24.4.2": + version: 24.4.2 + resolution: "@vaadin/combo-box@npm:24.4.2" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.4.1" - "@vaadin/component-base": "npm:~24.4.1" - "@vaadin/field-base": "npm:~24.4.1" - "@vaadin/input-container": "npm:~24.4.1" - "@vaadin/item": "npm:~24.4.1" - "@vaadin/lit-renderer": "npm:~24.4.1" - "@vaadin/overlay": "npm:~24.4.1" - "@vaadin/vaadin-lumo-styles": "npm:~24.4.1" - "@vaadin/vaadin-material-styles": "npm:~24.4.1" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.1" - checksum: 10/5caca3660fa91f8ae913a2159e7533aa545e21593574a80ca317a13cc2a5ddf8754beb852d6d3ff4b31e8171bf581d784de8925af17d57096a2f5842a4c699d3 + "@vaadin/a11y-base": "npm:~24.4.2" + "@vaadin/component-base": "npm:~24.4.2" + "@vaadin/field-base": "npm:~24.4.2" + "@vaadin/input-container": "npm:~24.4.2" + "@vaadin/item": "npm:~24.4.2" + "@vaadin/lit-renderer": "npm:~24.4.2" + "@vaadin/overlay": "npm:~24.4.2" + "@vaadin/vaadin-lumo-styles": "npm:~24.4.2" + "@vaadin/vaadin-material-styles": "npm:~24.4.2" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" + checksum: 10/6d75c4ce8eeffbb4015436115afec8f6d429c2840dc300eaa6c737ae262b41fe3bb3548c5bc948c55b41a30a66b7df47eabdb526c3805bd38dae99ee3bc63d0e languageName: node linkType: hard -"@vaadin/component-base@npm:~24.4.1": - version: 24.4.1 - resolution: "@vaadin/component-base@npm:24.4.1" +"@vaadin/component-base@npm:~24.4.2": + version: 24.4.2 + resolution: "@vaadin/component-base@npm:24.4.2" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" "@vaadin/vaadin-development-mode-detector": "npm:^2.0.0" "@vaadin/vaadin-usage-statistics": "npm:^2.1.0" lit: "npm:^3.0.0" - checksum: 10/5717aaa2b418930ceb9a77b8641e5e103ae2ac2b47dcb4dd16cf245edc51c4c79d9573338520d4c412d4dd39daa132606d5f3474bf217390e866b60a6291e7dd + checksum: 10/7e233c68da0ea673c6bcacc13488059a063d69734b222be663fb85542932cbbc4031e2855fbebc8e8aeec42d9ce1c05713890a8be3a642050921fb0b743e2eb7 languageName: node linkType: hard -"@vaadin/field-base@npm:~24.4.1": - version: 24.4.1 - resolution: "@vaadin/field-base@npm:24.4.1" +"@vaadin/field-base@npm:~24.4.2": + version: 24.4.2 + resolution: "@vaadin/field-base@npm:24.4.2" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.4.1" - "@vaadin/component-base": "npm:~24.4.1" + "@vaadin/a11y-base": "npm:~24.4.2" + "@vaadin/component-base": "npm:~24.4.2" lit: "npm:^3.0.0" - checksum: 10/8feed52803b9078b62fd0124a90f70cdb69d1620e507b605ec57a5a4523af0cf087d0b5864bb4fdd4676b9b96a5182b0e69b53dc36d4f791fcfcd4a372869883 + checksum: 10/98e680fa02b1dbd636df3244fb2b08add4a214613034de2b3bdfd1e66bad407788c3f192ded98d348d3ab2f3ac7992d7819413d46958c371f8ff9b08f66d80f1 languageName: node linkType: hard -"@vaadin/icon@npm:~24.4.1": - version: 24.4.1 - resolution: "@vaadin/icon@npm:24.4.1" +"@vaadin/icon@npm:~24.4.2": + version: 24.4.2 + resolution: "@vaadin/icon@npm:24.4.2" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.4.1" - "@vaadin/vaadin-lumo-styles": "npm:~24.4.1" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.1" + "@vaadin/component-base": "npm:~24.4.2" + "@vaadin/vaadin-lumo-styles": "npm:~24.4.2" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" lit: "npm:^3.0.0" - checksum: 10/0097ddeff4e705bae69dbd6e6fb6b2c55a969ef2b3dfefd794e6e7b9e70f987d31e4952d84cbf582d8f76b93f204fe92760eb68ef12d037c9d54f77bad7ace02 + checksum: 10/e8168ed771681688dc3fab96296cb6bf0568eab2788ecdb08b4b4f744873fb99a0b3c849283460356b2f05355d0ea03c801b4cf42b881e020b060bf4c0525b49 languageName: node linkType: hard -"@vaadin/input-container@npm:~24.4.1": - version: 24.4.1 - resolution: "@vaadin/input-container@npm:24.4.1" +"@vaadin/input-container@npm:~24.4.2": + version: 24.4.2 + resolution: "@vaadin/input-container@npm:24.4.2" dependencies: "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.4.1" - "@vaadin/vaadin-lumo-styles": "npm:~24.4.1" - "@vaadin/vaadin-material-styles": "npm:~24.4.1" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.1" + "@vaadin/component-base": "npm:~24.4.2" + "@vaadin/vaadin-lumo-styles": "npm:~24.4.2" + "@vaadin/vaadin-material-styles": "npm:~24.4.2" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" lit: "npm:^3.0.0" - checksum: 10/b6c4d448182cd7f55e671ed4ed06e8076c320ff9659a2e19e7e80181b2a338cd4e2a2de328ff7839c965a81e32c2609f825c9df2da55d99af081951e568c9457 + checksum: 10/1519819566d5afef998d155be96a6acf2ffceef9980bf34b60ee1d0c1048a2e5282fd7658d043f130114f08cac5662c8bc3d4337c8aa7cd0ba1b6048cbb62f46 languageName: node linkType: hard -"@vaadin/item@npm:~24.4.1": - version: 24.4.1 - resolution: "@vaadin/item@npm:24.4.1" +"@vaadin/item@npm:~24.4.2": + version: 24.4.2 + resolution: "@vaadin/item@npm:24.4.2" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.4.1" - "@vaadin/component-base": "npm:~24.4.1" - "@vaadin/vaadin-lumo-styles": "npm:~24.4.1" - "@vaadin/vaadin-material-styles": "npm:~24.4.1" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.1" - checksum: 10/81d4560461ea13818a3c5604cf3c4e3acd6de9e8cac51e1ab41d7b2d0d14d09519790c8cfbab5b391f88461cde662915e4093e95491dc83942c460d11bb698e4 + "@vaadin/a11y-base": "npm:~24.4.2" + "@vaadin/component-base": "npm:~24.4.2" + "@vaadin/vaadin-lumo-styles": "npm:~24.4.2" + "@vaadin/vaadin-material-styles": "npm:~24.4.2" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" + checksum: 10/871711a437429a32583c73ee55d9c0734bd6c5a82a04b39be89082190aef91fd1690784902e0d7951c5c03c701af2ba9a390e7eef13800363302df41896586ca languageName: node linkType: hard -"@vaadin/lit-renderer@npm:~24.4.1": - version: 24.4.1 - resolution: "@vaadin/lit-renderer@npm:24.4.1" +"@vaadin/lit-renderer@npm:~24.4.2": + version: 24.4.2 + resolution: "@vaadin/lit-renderer@npm:24.4.2" dependencies: lit: "npm:^3.0.0" - checksum: 10/9a2ebf90ca4f3a08152058a5e431a4019bac78fb94a38b382550e85ca2993b35bb09a94a47e573529cd94a1b99bcbcdd89d71e1ec9c12a3a796e2e41cc79221f + checksum: 10/668fac55b69999e682c23f24ce3fdbf8ee13aa926b1b5f6ae7fb11111244bf50fbb46c3d673c2d650c8f55202ec529bfe0fb940cf6117ed12b8067d591bbbdc3 languageName: node linkType: hard -"@vaadin/overlay@npm:~24.4.1": - version: 24.4.1 - resolution: "@vaadin/overlay@npm:24.4.1" +"@vaadin/overlay@npm:~24.4.2": + version: 24.4.2 + resolution: "@vaadin/overlay@npm:24.4.2" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.4.1" - "@vaadin/component-base": "npm:~24.4.1" - "@vaadin/vaadin-lumo-styles": "npm:~24.4.1" - "@vaadin/vaadin-material-styles": "npm:~24.4.1" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.1" + "@vaadin/a11y-base": "npm:~24.4.2" + "@vaadin/component-base": "npm:~24.4.2" + "@vaadin/vaadin-lumo-styles": "npm:~24.4.2" + "@vaadin/vaadin-material-styles": "npm:~24.4.2" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" lit: "npm:^3.0.0" - checksum: 10/4b899ffe4bd22104e7c038f9669d18d3ba482875cbe7425ac9e98da6fec397c4419580e2f0f6dcd1411acd42cd4cc6dc5267393e7deedd1701195ddcd99e9d2d + checksum: 10/7a0b1a32d61a5a17961e57c95a62cb7a666d2a78c67f115d405011a5271f78239eb8e776db80c27428f97626dd1ec1a2371eeb0991d17581fc672524572c4247 languageName: node linkType: hard @@ -4842,36 +4842,36 @@ __metadata: languageName: node linkType: hard -"@vaadin/vaadin-lumo-styles@npm:~24.4.1": - version: 24.4.1 - resolution: "@vaadin/vaadin-lumo-styles@npm:24.4.1" +"@vaadin/vaadin-lumo-styles@npm:~24.4.2": + version: 24.4.2 + resolution: "@vaadin/vaadin-lumo-styles@npm:24.4.2" dependencies: "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.4.1" - "@vaadin/icon": "npm:~24.4.1" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.1" - checksum: 10/cec42991f1bdab3a1da179f77593735dbc6b6ec677195cd1558eeb79fc1c5707badad3818103f57cf0a68d141e7cc2860ab634e2b99b00d71e289c6dd884b375 + "@vaadin/component-base": "npm:~24.4.2" + "@vaadin/icon": "npm:~24.4.2" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" + checksum: 10/dfb95f3c4ab998022c51f816cfb9a39c2ddec8fb340e5c99c3617cfefc8d4915bd7d06be9a878930594afa70c5ed1f6dab9daa02515033d26fdae3570274ae04 languageName: node linkType: hard -"@vaadin/vaadin-material-styles@npm:~24.4.1": - version: 24.4.1 - resolution: "@vaadin/vaadin-material-styles@npm:24.4.1" +"@vaadin/vaadin-material-styles@npm:~24.4.2": + version: 24.4.2 + resolution: "@vaadin/vaadin-material-styles@npm:24.4.2" dependencies: "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.4.1" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.1" - checksum: 10/d128217dbd0333ecf6c935dfe09a60bf9e67ad1c65430d4a052a9f5a3691677854f60c45faa40b33889380feac0f29b2a6376accb928dd6c3114cd167cbbcc9f + "@vaadin/component-base": "npm:~24.4.2" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" + checksum: 10/f94c531be9da3cd9a70caffd2925f348e0a06043ae512209ca0926599bff77ba0b70643fc375fb71b9debc0c0d8204618ccf5e986642bbb3335f733650f2b58b languageName: node linkType: hard -"@vaadin/vaadin-themable-mixin@npm:24.4.1, @vaadin/vaadin-themable-mixin@npm:~24.4.1": - version: 24.4.1 - resolution: "@vaadin/vaadin-themable-mixin@npm:24.4.1" +"@vaadin/vaadin-themable-mixin@npm:24.4.2, @vaadin/vaadin-themable-mixin@npm:~24.4.2": + version: 24.4.2 + resolution: "@vaadin/vaadin-themable-mixin@npm:24.4.2" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" lit: "npm:^3.0.0" - checksum: 10/576f39795fa7112f1f872449d97690814dc0dd982125a8ceeee7a8e7d3aa66b18ea6288f38a95b40a7d9e3008fd5cd9bbb73af9a7cb16cae84f75206f76327b4 + checksum: 10/d86979fb9eb6e214685a01c9d596f905eac5444af7bfadf3422c0bdbf80ea0f7cd8dae8b286cd95c422560426c3321c62d69b58c7f72473240c7a824df61644c languageName: node linkType: hard @@ -8988,8 +8988,8 @@ __metadata: "@types/webspeechapi": "npm:0.0.29" "@typescript-eslint/eslint-plugin": "npm:7.16.0" "@typescript-eslint/parser": "npm:7.16.0" - "@vaadin/combo-box": "npm:24.4.1" - "@vaadin/vaadin-themable-mixin": "npm:24.4.1" + "@vaadin/combo-box": "npm:24.4.2" + "@vaadin/vaadin-themable-mixin": "npm:24.4.2" "@vibrant/color": "npm:3.2.1-alpha.1" "@vibrant/core": "npm:3.2.1-alpha.1" "@vibrant/quantizer-mmcq": "npm:3.2.1-alpha.1" From db314522d78d3eb2277a0e22ad7abc7e816b02e8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 13 Jul 2024 01:18:34 +0200 Subject: [PATCH 11/97] Update dependency glob to v10.4.5 (#21374) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index ea7561e20a..e6f719e007 100644 --- a/package.json +++ b/package.json @@ -205,7 +205,7 @@ "eslint-plugin-wc": "2.1.0", "fancy-log": "2.0.0", "fs-extra": "11.2.0", - "glob": "10.4.3", + "glob": "10.4.5", "gulp": "5.0.0", "gulp-json-transform": "0.5.0", "gulp-rename": "2.0.0", diff --git a/yarn.lock b/yarn.lock index a1d302b789..dd0e51c705 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8542,9 +8542,9 @@ __metadata: languageName: node linkType: hard -"glob@npm:10.4.3, glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.1": - version: 10.4.3 - resolution: "glob@npm:10.4.3" +"glob@npm:10.4.5, glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.1": + version: 10.4.5 + resolution: "glob@npm:10.4.5" dependencies: foreground-child: "npm:^3.1.0" jackspeak: "npm:^3.1.2" @@ -8554,7 +8554,7 @@ __metadata: path-scurry: "npm:^1.11.1" bin: glob: dist/esm/bin.mjs - checksum: 10/7670e257bc7cf62a5649e79a71fc3b74806516eabfbfef0a949e11c5530c215d0f6d75c8c0c35266ff44ef6cb29b6c0e59be63906909be946d4c65df5d336be8 + checksum: 10/698dfe11828b7efd0514cd11e573eaed26b2dff611f0400907281ce3eab0c1e56143ef9b35adc7c77ecc71fba74717b510c7c223d34ca8a98ec81777b293d4ac languageName: node linkType: hard @@ -9026,7 +9026,7 @@ __metadata: fancy-log: "npm:2.0.0" fs-extra: "npm:11.2.0" fuse.js: "npm:7.0.0" - glob: "npm:10.4.3" + glob: "npm:10.4.5" google-timezones-json: "npm:1.2.0" gulp: "npm:5.0.0" gulp-json-transform: "npm:0.5.0" From a60242f0424a4bedf3d0e2456fad46eb520546a2 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Sat, 13 Jul 2024 11:35:03 +0200 Subject: [PATCH 12/97] Fix state color for locking and unlocking state (#21369) --- src/resources/ha-style.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/resources/ha-style.ts b/src/resources/ha-style.ts index 1fa724ac0a..2fbabcfbba 100644 --- a/src/resources/ha-style.ts +++ b/src/resources/ha-style.ts @@ -163,10 +163,11 @@ const mainStyles = css` --state-light-active-color: var(--amber-color); --state-lock-jammed-color: var(--red-color); --state-lock-locked-color: var(--green-color); - --state-lock-pending-color: var(--orange-color); + --state-lock-locking-color: var(--orange-color); --state-lock-unlocked-color: var(--red-color); - --state-lock-opening-color: var(--orange-color); + --state-lock-unlocking-color: var(--orange-color); --state-lock-open-color: var(--red-color); + --state-lock-opening-color: var(--orange-color); --state-media_player-active-color: var(--light-blue-color); --state-person-active-color: var(--blue-color); --state-person-home-color: var(--green-color); From 8d74174be197c0f5757da581278965c37b2cf510 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 13 Jul 2024 11:39:17 +0200 Subject: [PATCH 13/97] Update dependency glob to v11 (#21375) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 71 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index e6f719e007..3f53739f5a 100644 --- a/package.json +++ b/package.json @@ -205,7 +205,7 @@ "eslint-plugin-wc": "2.1.0", "fancy-log": "2.0.0", "fs-extra": "11.2.0", - "glob": "10.4.5", + "glob": "11.0.0", "gulp": "5.0.0", "gulp-json-transform": "0.5.0", "gulp-rename": "2.0.0", diff --git a/yarn.lock b/yarn.lock index dd0e51c705..4db921d9d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8542,19 +8542,19 @@ __metadata: languageName: node linkType: hard -"glob@npm:10.4.5, glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.1": - version: 10.4.5 - resolution: "glob@npm:10.4.5" +"glob@npm:11.0.0": + version: 11.0.0 + resolution: "glob@npm:11.0.0" dependencies: foreground-child: "npm:^3.1.0" - jackspeak: "npm:^3.1.2" - minimatch: "npm:^9.0.4" + jackspeak: "npm:^4.0.1" + minimatch: "npm:^10.0.0" minipass: "npm:^7.1.2" package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^1.11.1" + path-scurry: "npm:^2.0.0" bin: glob: dist/esm/bin.mjs - checksum: 10/698dfe11828b7efd0514cd11e573eaed26b2dff611f0400907281ce3eab0c1e56143ef9b35adc7c77ecc71fba74717b510c7c223d34ca8a98ec81777b293d4ac + checksum: 10/e66939201d11ae30fe97e3364ac2be5c59d6c9bfce18ac633edfad473eb6b46a7553f6f73658f67caaf6cccc1df1ae336298a45e9021fa5695fd78754cc1603e languageName: node linkType: hard @@ -8571,6 +8571,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.1": + version: 10.4.5 + resolution: "glob@npm:10.4.5" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10/698dfe11828b7efd0514cd11e573eaed26b2dff611f0400907281ce3eab0c1e56143ef9b35adc7c77ecc71fba74717b510c7c223d34ca8a98ec81777b293d4ac + languageName: node + linkType: hard + "glob@npm:^7.1.3, glob@npm:^7.1.6": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -9026,7 +9042,7 @@ __metadata: fancy-log: "npm:2.0.0" fs-extra: "npm:11.2.0" fuse.js: "npm:7.0.0" - glob: "npm:10.4.5" + glob: "npm:11.0.0" google-timezones-json: "npm:1.2.0" gulp: "npm:5.0.0" gulp-json-transform: "npm:0.5.0" @@ -10052,6 +10068,19 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^4.0.1": + version: 4.0.1 + resolution: "jackspeak@npm:4.0.1" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10/b20dc0df0dbb2903e4d540ae68308ec7d1dd60944b130e867e218c98b5d77481d65ea734b6c81c812d481500076e8b3fdfccfb38fc81cb1acf165e853da3e26c + languageName: node + linkType: hard + "jake@npm:^10.8.5": version: 10.9.1 resolution: "jake@npm:10.9.1" @@ -10733,6 +10762,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^11.0.0": + version: 11.0.0 + resolution: "lru-cache@npm:11.0.0" + checksum: 10/41f36fbff8b6f199cce3e9cb2b625714f97a535dfd7f16d0988c2627f9ed4c38b6dc8f9ea7fdba19262a7c917ba41c89cad15ca3e3791fc9a2068af472b5bc8d + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -10997,6 +11033,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.0.0": + version: 10.0.1 + resolution: "minimatch@npm:10.0.1" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10/082e7ccbc090d5f8c4e4e029255d5a1d1e3af37bda837da2b8b0085b1503a1210c91ac90d9ebfe741d8a5f286ece820a1abb4f61dc1f82ce602a055d461d93f3 + languageName: node + linkType: hard + "minimatch@npm:^5.0.1": version: 5.1.6 resolution: "minimatch@npm:5.1.6" @@ -11910,6 +11955,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^2.0.0": + version: 2.0.0 + resolution: "path-scurry@npm:2.0.0" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10/285ae0c2d6c34ae91dc1d5378ede21981c9a2f6de1ea9ca5a88b5a270ce9763b83dbadc7a324d512211d8d36b0c540427d3d0817030849d97a60fa840a2c59ec + languageName: node + linkType: hard + "path-to-regexp@npm:0.1.7": version: 0.1.7 resolution: "path-to-regexp@npm:0.1.7" From 29aa57229c0fc8a5377917484ae5b9bfb6a7d7d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 13 Jul 2024 20:40:32 -0400 Subject: [PATCH 14/97] Update dependency gulp-zopfli-green to v6.0.2 (#21385) --- package.json | 2 +- yarn.lock | 25 +++++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 3f53739f5a..21079b9797 100644 --- a/package.json +++ b/package.json @@ -209,7 +209,7 @@ "gulp": "5.0.0", "gulp-json-transform": "0.5.0", "gulp-rename": "2.0.0", - "gulp-zopfli-green": "6.0.1", + "gulp-zopfli-green": "6.0.2", "html-minifier-terser": "7.2.0", "husky": "9.0.11", "instant-mocha": "1.5.2", diff --git a/yarn.lock b/yarn.lock index 4db921d9d7..f669ebb3a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8769,18 +8769,18 @@ __metadata: languageName: node linkType: hard -"gulp-zopfli-green@npm:6.0.1": - version: 6.0.1 - resolution: "gulp-zopfli-green@npm:6.0.1" +"gulp-zopfli-green@npm:6.0.2": + version: 6.0.2 + resolution: "gulp-zopfli-green@npm:6.0.2" dependencies: "@gfx/zopfli": "npm:^1.0.15" bytes: "npm:^3.1.2" defaults: "npm:^1.0.4" fancy-log: "npm:^2.0.0" - plugin-error: "npm:^1.0.1" - stream-to-array: "npm:^2.0.2" + plugin-error: "npm:^2.0.1" + stream-to-array: "npm:^2.3.0" through2: "npm:^4.0.2" - checksum: 10/89591fbdd919d4a343ec2a721df913f2daf6330c8699ea8cb491a91324b8e93fbedb2c7b68e2dfeb2126f412410042dc03f3bc29747a5415e5e1adbb6b9cc10c + checksum: 10/52e899dfb86777ff8f97a23af99c59e203ea485fbf04d0a8f4f1cfbd4d4c496808a3593ae8dac16584fc4b4d81cf127b2eda5355a61bcc213875c95cc86d41da languageName: node linkType: hard @@ -9047,7 +9047,7 @@ __metadata: gulp: "npm:5.0.0" gulp-json-transform: "npm:0.5.0" gulp-rename: "npm:2.0.0" - gulp-zopfli-green: "npm:6.0.1" + gulp-zopfli-green: "npm:6.0.2" hls.js: "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch" home-assistant-js-websocket: "npm:9.4.0" html-minifier-terser: "npm:7.2.0" @@ -12087,6 +12087,15 @@ __metadata: languageName: node linkType: hard +"plugin-error@npm:^2.0.1": + version: 2.0.1 + resolution: "plugin-error@npm:2.0.1" + dependencies: + ansi-colors: "npm:^1.0.1" + checksum: 10/9a4f91461cd24cce401112098969991d7aa6b4c94f78e0381234280c07da779570a8b21ab143292b534ec0117c09705a67e5d756c1c303d4706fdd7f861bf5bc + languageName: node + linkType: hard + "pngjs@npm:^3.0.0, pngjs@npm:^3.3.3": version: 3.4.0 resolution: "pngjs@npm:3.4.0" @@ -13439,7 +13448,7 @@ __metadata: languageName: node linkType: hard -"stream-to-array@npm:^2.0.2": +"stream-to-array@npm:^2.3.0": version: 2.3.0 resolution: "stream-to-array@npm:2.3.0" dependencies: From dbc2db25913cebaa15f1ba919de78e97e0672dcf Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Sun, 14 Jul 2024 08:01:21 -0700 Subject: [PATCH 15/97] Make config flow previews more generic (#21382) * Make config flow previews more generic --- src/data/group.ts | 21 ----- src/data/{threshold.ts => preview.ts} | 14 ++- src/data/time_date.ts | 21 ----- ...w-threshold.ts => flow-preview-generic.ts} | 19 ++-- .../previews/flow-preview-group.ts | 90 ------------------ .../previews/flow-preview-time_date.ts | 94 ------------------- src/dialogs/config-flow/step-flow-form.ts | 6 +- 7 files changed, 23 insertions(+), 242 deletions(-) rename src/data/{threshold.ts => preview.ts} (55%) delete mode 100644 src/data/time_date.ts rename src/dialogs/config-flow/previews/{flow-preview-threshold.ts => flow-preview-generic.ts} (86%) delete mode 100644 src/dialogs/config-flow/previews/flow-preview-group.ts delete mode 100644 src/dialogs/config-flow/previews/flow-preview-time_date.ts diff --git a/src/data/group.ts b/src/data/group.ts index fbaaab4faf..cfae3ed858 100644 --- a/src/data/group.ts +++ b/src/data/group.ts @@ -1,10 +1,8 @@ import { HassEntityAttributeBase, HassEntityBase, - UnsubscribeFunc, } from "home-assistant-js-websocket"; import { computeDomain } from "../common/entity/compute_domain"; -import { HomeAssistant } from "../types"; interface GroupEntityAttributes extends HassEntityAttributeBase { entity_id: string[]; @@ -17,11 +15,6 @@ export interface GroupEntity extends HassEntityBase { attributes: GroupEntityAttributes; } -export interface GroupPreview { - state: string; - attributes: Record; -} - export const computeGroupDomain = ( stateObj: GroupEntity ): string | undefined => { @@ -31,17 +24,3 @@ export const computeGroupDomain = ( ]; return uniqueDomains.length === 1 ? uniqueDomains[0] : undefined; }; - -export const subscribePreviewGroup = ( - hass: HomeAssistant, - flow_id: string, - flow_type: "config_flow" | "options_flow", - user_input: Record, - callback: (preview: GroupPreview) => void -): Promise => - hass.connection.subscribeMessage(callback, { - type: "group/start_preview", - flow_id, - flow_type, - user_input, - }); diff --git a/src/data/threshold.ts b/src/data/preview.ts similarity index 55% rename from src/data/threshold.ts rename to src/data/preview.ts index 886ebcf0d9..1c3a24c777 100644 --- a/src/data/threshold.ts +++ b/src/data/preview.ts @@ -1,21 +1,27 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { HomeAssistant } from "../types"; -export interface ThresholdPreview { +const HAS_CUSTOM_PREVIEW = ["template"]; + +export interface GenericPreview { state: string; attributes: Record; } -export const subscribePreviewThreshold = ( +export const subscribePreviewGeneric = ( hass: HomeAssistant, + domain: string, flow_id: string, flow_type: "config_flow" | "options_flow", user_input: Record, - callback: (preview: ThresholdPreview) => void + callback: (preview: GenericPreview) => void ): Promise => hass.connection.subscribeMessage(callback, { - type: "threshold/start_preview", + type: `${domain}/start_preview`, flow_id, flow_type, user_input, }); + +export const previewModule = (domain: string): string => + HAS_CUSTOM_PREVIEW.includes(domain) ? domain : "generic"; diff --git a/src/data/time_date.ts b/src/data/time_date.ts deleted file mode 100644 index 5f572cb658..0000000000 --- a/src/data/time_date.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { UnsubscribeFunc } from "home-assistant-js-websocket"; -import { HomeAssistant } from "../types"; - -export interface TimeDatePreview { - state: string; - attributes: Record; -} - -export const subscribePreviewTimeDate = ( - hass: HomeAssistant, - flow_id: string, - flow_type: "config_flow" | "options_flow", - user_input: Record, - callback: (preview: TimeDatePreview) => void -): Promise => - hass.connection.subscribeMessage(callback, { - type: "time_date/start_preview", - flow_id, - flow_type, - user_input, - }); diff --git a/src/dialogs/config-flow/previews/flow-preview-threshold.ts b/src/dialogs/config-flow/previews/flow-preview-generic.ts similarity index 86% rename from src/dialogs/config-flow/previews/flow-preview-threshold.ts rename to src/dialogs/config-flow/previews/flow-preview-generic.ts index 74f2daa8e9..78209ce310 100644 --- a/src/dialogs/config-flow/previews/flow-preview-threshold.ts +++ b/src/dialogs/config-flow/previews/flow-preview-generic.ts @@ -2,23 +2,22 @@ import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import { LitElement, html } from "lit"; import { customElement, property, state } from "lit/decorators"; import { FlowType } from "../../../data/data_entry_flow"; -import { - ThresholdPreview, - subscribePreviewThreshold, -} from "../../../data/threshold"; +import { GenericPreview, subscribePreviewGeneric } from "../../../data/preview"; import { HomeAssistant } from "../../../types"; import "./entity-preview-row"; import { debounce } from "../../../common/util/debounce"; import { fireEvent } from "../../../common/dom/fire_event"; -@customElement("flow-preview-threshold") -class FlowPreviewThreshold extends LitElement { +@customElement("flow-preview-generic") +class FlowPreviewGeneric extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property() public flowType!: FlowType; public handler!: string; + @property() public domain!: string; + @property() public stepId!: string; @property() public flowId!: string; @@ -55,7 +54,7 @@ class FlowPreviewThreshold extends LitElement { >`; } - private _setPreview = (preview: ThresholdPreview) => { + private _setPreview = (preview: GenericPreview) => { const now = new Date().toISOString(); this._preview = { entity_id: `${this.stepId}.___flow_preview___`, @@ -79,14 +78,14 @@ class FlowPreviewThreshold extends LitElement { return; } try { - this._unsub = subscribePreviewThreshold( + this._unsub = subscribePreviewGeneric( this.hass, + this.domain, this.flowId, this.flowType, this.stepData, this._setPreview ); - await this._unsub; fireEvent(this, "set-flow-errors", { errors: {} }); } catch (err: any) { if (typeof err.message === "string") { @@ -103,6 +102,6 @@ class FlowPreviewThreshold extends LitElement { declare global { interface HTMLElementTagNameMap { - "flow-preview-threshold": FlowPreviewThreshold; + "flow-preview-generic": FlowPreviewGeneric; } } diff --git a/src/dialogs/config-flow/previews/flow-preview-group.ts b/src/dialogs/config-flow/previews/flow-preview-group.ts deleted file mode 100644 index 5afc93a038..0000000000 --- a/src/dialogs/config-flow/previews/flow-preview-group.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; -import { LitElement, html } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { FlowType } from "../../../data/data_entry_flow"; -import { GroupPreview, subscribePreviewGroup } from "../../../data/group"; -import { HomeAssistant } from "../../../types"; -import "./entity-preview-row"; -import { debounce } from "../../../common/util/debounce"; - -@customElement("flow-preview-group") -class FlowPreviewGroup extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property() public flowType!: FlowType; - - public handler!: string; - - @property() public stepId!: string; - - @property() public flowId!: string; - - @property({ attribute: false }) public stepData!: Record; - - @state() private _preview?: HassEntity; - - private _unsub?: Promise; - - disconnectedCallback(): void { - super.disconnectedCallback(); - if (this._unsub) { - this._unsub.then((unsub) => unsub()); - this._unsub = undefined; - } - } - - willUpdate(changedProps) { - if (changedProps.has("stepData")) { - this._debouncedSubscribePreview(); - } - } - - protected render() { - return html``; - } - - private _setPreview = (preview: GroupPreview) => { - const now = new Date().toISOString(); - this._preview = { - entity_id: `${this.stepId}.___flow_preview___`, - last_changed: now, - last_updated: now, - context: { id: "", parent_id: null, user_id: null }, - ...preview, - }; - }; - - private _debouncedSubscribePreview = debounce(() => { - this._subscribePreview(); - }, 250); - - private async _subscribePreview() { - if (this._unsub) { - (await this._unsub)(); - this._unsub = undefined; - } - if (this.flowType === "repair_flow") { - return; - } - try { - this._unsub = subscribePreviewGroup( - this.hass, - this.flowId, - this.flowType, - this.stepData, - this._setPreview - ); - } catch (err) { - this._preview = undefined; - } - } -} - -declare global { - interface HTMLElementTagNameMap { - "flow-preview-group": FlowPreviewGroup; - } -} diff --git a/src/dialogs/config-flow/previews/flow-preview-time_date.ts b/src/dialogs/config-flow/previews/flow-preview-time_date.ts deleted file mode 100644 index fd4676ec43..0000000000 --- a/src/dialogs/config-flow/previews/flow-preview-time_date.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; -import { LitElement, html } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { FlowType } from "../../../data/data_entry_flow"; -import { - TimeDatePreview, - subscribePreviewTimeDate, -} from "../../../data/time_date"; -import { HomeAssistant } from "../../../types"; -import "./entity-preview-row"; -import { debounce } from "../../../common/util/debounce"; - -@customElement("flow-preview-time_date") -class FlowPreviewTimeDate extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property() public flowType!: FlowType; - - public handler!: string; - - @property() public stepId!: string; - - @property() public flowId!: string; - - @property() public stepData!: Record; - - @state() private _preview?: HassEntity; - - private _unsub?: Promise; - - disconnectedCallback(): void { - super.disconnectedCallback(); - if (this._unsub) { - this._unsub.then((unsub) => unsub()); - this._unsub = undefined; - } - } - - willUpdate(changedProps) { - if (changedProps.has("stepData")) { - this._debouncedSubscribePreview(); - } - } - - protected render() { - return html``; - } - - private _setPreview = (preview: TimeDatePreview) => { - const now = new Date().toISOString(); - this._preview = { - entity_id: `${this.stepId}.___flow_preview___`, - last_changed: now, - last_updated: now, - context: { id: "", parent_id: null, user_id: null }, - ...preview, - }; - }; - - private _debouncedSubscribePreview = debounce(() => { - this._subscribePreview(); - }, 250); - - private async _subscribePreview() { - if (this._unsub) { - (await this._unsub)(); - this._unsub = undefined; - } - if (this.flowType === "repair_flow") { - return; - } - try { - this._unsub = subscribePreviewTimeDate( - this.hass, - this.flowId, - this.flowType, - this.stepData, - this._setPreview - ); - await this._unsub; - } catch (err) { - this._preview = undefined; - } - } -} - -declare global { - interface HTMLElementTagNameMap { - "flow-preview-time_date": FlowPreviewTimeDate; - } -} diff --git a/src/dialogs/config-flow/step-flow-form.ts b/src/dialogs/config-flow/step-flow-form.ts index 1390c3f02e..20c665df90 100644 --- a/src/dialogs/config-flow/step-flow-form.ts +++ b/src/dialogs/config-flow/step-flow-form.ts @@ -25,6 +25,7 @@ import type { HomeAssistant } from "../../types"; import type { FlowConfig } from "./show-dialog-data-entry-flow"; import { configFlowContentStyles } from "./styles"; import { haStyle } from "../../resources/styles"; +import { previewModule } from "../../data/preview"; @customElement("step-flow-form") class StepFlowForm extends LitElement { @@ -76,8 +77,9 @@ class StepFlowForm extends LitElement { "ui.panel.config.integrations.config_flow.preview" )}: - ${dynamicElement(`flow-preview-${this.step.preview}`, { + ${dynamicElement(`flow-preview-${previewModule(step.preview)}`, { hass: this.hass, + domain: step.preview, flowType: this.flowConfig.flowType, handler: step.handler, stepId: step.step_id, @@ -120,7 +122,7 @@ class StepFlowForm extends LitElement { protected willUpdate(changedProps: PropertyValues): void { super.willUpdate(changedProps); if (changedProps.has("step") && this.step?.preview) { - import(`./previews/flow-preview-${this.step.preview}`); + import(`./previews/flow-preview-${previewModule(this.step.preview)}`); } } From 7dd860c5394d4107a51381deb8600924fcbd8c1d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Jul 2024 17:20:19 +0200 Subject: [PATCH 16/97] Update babel monorepo to v7.24.8 (#21391) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 6 +- yarn.lock | 264 +++++++++++++++++++++++++-------------------------- 2 files changed, 135 insertions(+), 135 deletions(-) diff --git a/package.json b/package.json index 21079b9797..775477fbb9 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "license": "Apache-2.0", "type": "module", "dependencies": { - "@babel/runtime": "7.24.7", + "@babel/runtime": "7.24.8", "@braintree/sanitize-url": "7.0.4", "@codemirror/autocomplete": "6.17.0", "@codemirror/commands": "6.6.0", @@ -149,11 +149,11 @@ "xss": "1.0.15" }, "devDependencies": { - "@babel/core": "7.24.7", + "@babel/core": "7.24.8", "@babel/helper-define-polyfill-provider": "0.6.2", "@babel/plugin-proposal-decorators": "7.24.7", "@babel/plugin-transform-runtime": "7.24.7", - "@babel/preset-env": "7.24.7", + "@babel/preset-env": "7.24.8", "@babel/preset-typescript": "7.24.7", "@bundle-stats/plugin-webpack-filter": "4.13.3", "@koa/cors": "5.0.0", diff --git a/yarn.lock b/yarn.lock index f669ebb3a6..af31c676a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -48,45 +48,45 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/compat-data@npm:7.24.7" - checksum: 10/6edc09152ca51a22c33741c441f33f9475598fa59edc53369edb74b49f4ea4bef1281f5b0ed2b9b67fb66faef2da2069e21c4eef83405d8326e524b301f4e7e2 +"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/compat-data@npm:7.24.8" + checksum: 10/6989b8a61782d9c6c7a1fc58b4efd4fb68e5f5a5b6be3463a3de3752f39a30d21438b8b4485c18cb6b8d7f29e07f79d79639caa08737fae57838e81d7da055c0 languageName: node linkType: hard -"@babel/core@npm:7.24.7, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.0, @babel/core@npm:^7.24.4": - version: 7.24.7 - resolution: "@babel/core@npm:7.24.7" +"@babel/core@npm:7.24.8, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.0, @babel/core@npm:^7.24.4": + version: 7.24.8 + resolution: "@babel/core@npm:7.24.8" dependencies: "@ampproject/remapping": "npm:^2.2.0" "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.24.7" - "@babel/helper-compilation-targets": "npm:^7.24.7" - "@babel/helper-module-transforms": "npm:^7.24.7" - "@babel/helpers": "npm:^7.24.7" - "@babel/parser": "npm:^7.24.7" + "@babel/generator": "npm:^7.24.8" + "@babel/helper-compilation-targets": "npm:^7.24.8" + "@babel/helper-module-transforms": "npm:^7.24.8" + "@babel/helpers": "npm:^7.24.8" + "@babel/parser": "npm:^7.24.8" "@babel/template": "npm:^7.24.7" - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" + "@babel/traverse": "npm:^7.24.8" + "@babel/types": "npm:^7.24.8" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10/ef8cc1afa3ccecee6d1f5660c487ccc2a3f25106830ea9040e80ef4b2092e053607ee4ddd03493e4f7ef2f9967a956ca53b830d54c5bee738eeb58cce679dd4a + checksum: 10/79818e6e8ecd5f50ffbfb8dfb1748928e6e17b198bd8da0d6f725bf67aece5141020cd3aba56dc425a33e118c0e80e5569ad6fa615897e49726087dd875279d7 languageName: node linkType: hard -"@babel/generator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/generator@npm:7.24.7" +"@babel/generator@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/generator@npm:7.24.8" dependencies: - "@babel/types": "npm:^7.24.7" + "@babel/types": "npm:^7.24.8" "@jridgewell/gen-mapping": "npm:^0.3.5" "@jridgewell/trace-mapping": "npm:^0.3.25" jsesc: "npm:^2.5.1" - checksum: 10/c71d24a4b41b19c10d2f2eb819f27d4cf94220e2322f7c8fed8bfbbb115b2bebbdd6dc1f27dac78a175e90604def58d763af87e0fa81ce4ab1582858162cf768 + checksum: 10/dc1bd931120f93e7a5b35fdf66c13ca56b966b07ee9ba124f7e24b1905cbcf7d7891cc7c281961876eff9fcff67c46652cce89847665e263bc04d283d4343164 languageName: node linkType: hard @@ -109,16 +109,16 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-compilation-targets@npm:7.24.7" +"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.24.7, @babel/helper-compilation-targets@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-compilation-targets@npm:7.24.8" dependencies: - "@babel/compat-data": "npm:^7.24.7" - "@babel/helper-validator-option": "npm:^7.24.7" - browserslist: "npm:^4.22.2" + "@babel/compat-data": "npm:^7.24.8" + "@babel/helper-validator-option": "npm:^7.24.8" + browserslist: "npm:^4.23.1" lru-cache: "npm:^5.1.1" semver: "npm:^6.3.1" - checksum: 10/8f8bc89af70a606ccb208513aa25d83e19b88f91b64a33174f7701a9479e67ddbb0a9c89033265070375cd24e690b93380b3a3ea11e4b3a711d742f0f4699ee7 + checksum: 10/3489280d07b871af565b32f9b11946ff9a999fac0db9bec5df960760f6836c7a4b52fccb9d64229ccce835d37a43afb85659beb439ecedde04dcea7eb062a143 languageName: node linkType: hard @@ -217,9 +217,9 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-module-transforms@npm:7.24.7" +"@babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-module-transforms@npm:7.24.8" dependencies: "@babel/helper-environment-visitor": "npm:^7.24.7" "@babel/helper-module-imports": "npm:^7.24.7" @@ -228,7 +228,7 @@ __metadata: "@babel/helper-validator-identifier": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/4f2b232bf6d1be8d3a72b084a2a7ac1b0b93ea85717411a11ae1fb6375d4392019e781d8cc155789e649a2caa7eec378dd1404210603d6d4230f042c5feacffb + checksum: 10/912ad994da126c3150d8f8702030380849608094a7a352523ffa8e697080da9358d63af2582d38902c929839f394bbc6f1ae4921ba132ba3f65f27f0696aa2c7 languageName: node linkType: hard @@ -241,10 +241,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": - version: 7.24.7 - resolution: "@babel/helper-plugin-utils@npm:7.24.7" - checksum: 10/dad51622f0123fdba4e2d40a81a6b7d6ef4b1491b2f92fd9749447a36bde809106cf117358705057a2adc8fd73d5dc090222e0561b1213dae8601c8367f5aac8 +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.24.8, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": + version: 7.24.8 + resolution: "@babel/helper-plugin-utils@npm:7.24.8" + checksum: 10/adbc9fc1142800a35a5eb0793296924ee8057fe35c61657774208670468a9fbfbb216f2d0bc46c680c5fefa785e5ff917cc1674b10bd75cdf9a6aa3444780630 languageName: node linkType: hard @@ -303,10 +303,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-string-parser@npm:7.24.7" - checksum: 10/603d8d962bbe89907aa99a8f19a006759ab7b2464615f20a6a22e3e2e8375af37ddd0e5175c9e622e1c4b2d83607ffb41055a59d0ce34404502af30fde573a5c +"@babel/helper-string-parser@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-string-parser@npm:7.24.8" + checksum: 10/6d1bf8f27dd725ce02bdc6dffca3c95fb9ab8a06adc2edbd9c1c9d68500274230d1a609025833ed81981eff560045b6b38f7b4c6fb1ab19fc90e5004e3932535 languageName: node linkType: hard @@ -317,10 +317,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-validator-option@npm:7.24.7" - checksum: 10/9689166bf3f777dd424c026841c8cd651e41b21242dbfd4569a53086179a3e744c8eddd56e9d10b54142270141c91581b53af0d7c00c82d552d2540e2a919f7e +"@babel/helper-validator-option@npm:^7.24.7, @babel/helper-validator-option@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-validator-option@npm:7.24.8" + checksum: 10/a52442dfa74be6719c0608fee3225bd0493c4057459f3014681ea1a4643cd38b68ff477fe867c4b356da7330d085f247f0724d300582fa4ab9a02efaf34d107c languageName: node linkType: hard @@ -336,13 +336,13 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helpers@npm:7.24.7" +"@babel/helpers@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helpers@npm:7.24.8" dependencies: "@babel/template": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10/f7496f0d7a0b13ea86136ac2053371027125734170328215f8a90eac96fafaaae4e5398c0729bdadf23261c00582a31e14bc70113427653b718220641a917f9d + "@babel/types": "npm:^7.24.8" + checksum: 10/61c08a2baa87382a87c7110e9b5574c782603e247b7e6267769ee0e8b7b54b70ff05f16466f05bb318622b7ac28e79b449edff565abf5adcb1adb1b0f42fee9c languageName: node linkType: hard @@ -358,12 +358,12 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.23.5, @babel/parser@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/parser@npm:7.24.7" +"@babel/parser@npm:^7.23.5, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/parser@npm:7.24.8" bin: parser: ./bin/babel-parser.js - checksum: 10/ef9ebce60e13db560ccc7af9235d460f6726bb7e23ae2d675098c1fc43d5249067be60d4118889dad33b1d4f85162cf66baf554719e1669f29bb20e71322568e + checksum: 10/e44b8327da46e8659bc9fb77f66e2dc4364dd66495fb17d046b96a77bf604f0446f1e9a89cf2f011d78fc3f5cdfbae2e9e0714708e1c985988335683b2e781ef languageName: node linkType: hard @@ -754,21 +754,21 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-classes@npm:7.24.7" +"@babel/plugin-transform-classes@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/plugin-transform-classes@npm:7.24.8" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-compilation-targets": "npm:^7.24.7" + "@babel/helper-compilation-targets": "npm:^7.24.8" "@babel/helper-environment-visitor": "npm:^7.24.7" "@babel/helper-function-name": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.8" "@babel/helper-replace-supers": "npm:^7.24.7" "@babel/helper-split-export-declaration": "npm:^7.24.7" globals: "npm:^11.1.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/5d5577fcb0ec9ef33d889358c54720abe462325bed5483d71f9aa0a704f491520777be5411d6fd8a08a8ebe352e2445d46d1e6577a5a2c9333bc37b9ff8b9a74 + checksum: 10/3d586018691423ed1fbcb4589cc29001226c96e5e060932bf99379568c684a4a230cca7871e7c825335336ef0326066ba6e3bf5e6d0209425b0f5ceeda3eaed2 languageName: node linkType: hard @@ -784,14 +784,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-destructuring@npm:7.24.7" +"@babel/plugin-transform-destructuring@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/plugin-transform-destructuring@npm:7.24.8" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.8" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/eec43df24a07b3c61f335883e50c6642762fdd3cc5c5f95532cebeb51ea9bf77ca9a38011b678d91549dd75e29e1c58bd6e0ebc34bb763c300bc2cc65801e663 + checksum: 10/e3bba0bb050592615fbf062ea07ae94f99e9cf22add006eaa66ed672d67ff7051b578a5ea68a7d79f9184fb3c27c65333d86b0b8ea04f9810bcccbeea2ffbe76 languageName: node linkType: hard @@ -937,16 +937,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.7" +"@babel/plugin-transform-modules-commonjs@npm:^7.24.7, @babel/plugin-transform-modules-commonjs@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.8" dependencies: - "@babel/helper-module-transforms": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-module-transforms": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.24.8" "@babel/helper-simple-access": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/9bd10cd03cce138a644f4e671025058348d8ff364253122bed60f9a2a32759445b93e8a6501773491cb19906602b18fd26255df0caac425343a1584599b36b24 + checksum: 10/18e5d229767c7b5b6ff0cbf1a8d2d555965b90201839d0ac2dc043b56857624ea344e59f733f028142a8c1d54923b82e2a0185694ef36f988d797bfbaf59819c languageName: node linkType: hard @@ -1061,16 +1061,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.24.7" +"@babel/plugin-transform-optional-chaining@npm:^7.24.7, @babel/plugin-transform-optional-chaining@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.24.8" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.8" "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/0835caa8fa8561ba5da8edb82aee93aef8e5145eae33e5400569bb4fae879c596cd35d3bfe7519b222261fc370b1291c499870ca6ad9903e1a71cfaaa27a5454 + checksum: 10/1f873fb9d86c280b64dfe5ebc59244b459b717ed72a7682da2386db3d9e11fc9d831cfc2e11d37262b4325a7a0e3ccbccfb8cd0b944caf199d3c9e03fff7b0af languageName: node linkType: hard @@ -1206,14 +1206,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-typeof-symbol@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-typeof-symbol@npm:7.24.7" +"@babel/plugin-transform-typeof-symbol@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/plugin-transform-typeof-symbol@npm:7.24.8" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.8" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/c07847a3bcb27509d392de7a59b9836669b90ca508d4b63b36bb73b63413bc0b2571a64410b65999a73abeac99957b31053225877dcbfaf4eb21d8cc0ae4002f + checksum: 10/5f113fed94b694ec4a40a27b8628ce736cfa172b69fcffa2833c9a41895032127f3daeea552e94fdb4a3ce4e8cd51de67a670ab87a1f447a0cf55c9cb2d7ed11 languageName: node linkType: hard @@ -1278,14 +1278,14 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:7.24.7, @babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.13.0": - version: 7.24.7 - resolution: "@babel/preset-env@npm:7.24.7" +"@babel/preset-env@npm:7.24.8, @babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.13.0": + version: 7.24.8 + resolution: "@babel/preset-env@npm:7.24.8" dependencies: - "@babel/compat-data": "npm:^7.24.7" - "@babel/helper-compilation-targets": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-validator-option": "npm:^7.24.7" + "@babel/compat-data": "npm:^7.24.8" + "@babel/helper-compilation-targets": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-validator-option": "npm:^7.24.8" "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.24.7" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.24.7" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.24.7" @@ -1316,9 +1316,9 @@ __metadata: "@babel/plugin-transform-block-scoping": "npm:^7.24.7" "@babel/plugin-transform-class-properties": "npm:^7.24.7" "@babel/plugin-transform-class-static-block": "npm:^7.24.7" - "@babel/plugin-transform-classes": "npm:^7.24.7" + "@babel/plugin-transform-classes": "npm:^7.24.8" "@babel/plugin-transform-computed-properties": "npm:^7.24.7" - "@babel/plugin-transform-destructuring": "npm:^7.24.7" + "@babel/plugin-transform-destructuring": "npm:^7.24.8" "@babel/plugin-transform-dotall-regex": "npm:^7.24.7" "@babel/plugin-transform-duplicate-keys": "npm:^7.24.7" "@babel/plugin-transform-dynamic-import": "npm:^7.24.7" @@ -1331,7 +1331,7 @@ __metadata: "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" "@babel/plugin-transform-member-expression-literals": "npm:^7.24.7" "@babel/plugin-transform-modules-amd": "npm:^7.24.7" - "@babel/plugin-transform-modules-commonjs": "npm:^7.24.7" + "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" "@babel/plugin-transform-modules-systemjs": "npm:^7.24.7" "@babel/plugin-transform-modules-umd": "npm:^7.24.7" "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" @@ -1341,7 +1341,7 @@ __metadata: "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" "@babel/plugin-transform-object-super": "npm:^7.24.7" "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" - "@babel/plugin-transform-optional-chaining": "npm:^7.24.7" + "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" "@babel/plugin-transform-parameters": "npm:^7.24.7" "@babel/plugin-transform-private-methods": "npm:^7.24.7" "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" @@ -1352,7 +1352,7 @@ __metadata: "@babel/plugin-transform-spread": "npm:^7.24.7" "@babel/plugin-transform-sticky-regex": "npm:^7.24.7" "@babel/plugin-transform-template-literals": "npm:^7.24.7" - "@babel/plugin-transform-typeof-symbol": "npm:^7.24.7" + "@babel/plugin-transform-typeof-symbol": "npm:^7.24.8" "@babel/plugin-transform-unicode-escapes": "npm:^7.24.7" "@babel/plugin-transform-unicode-property-regex": "npm:^7.24.7" "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" @@ -1361,11 +1361,11 @@ __metadata: babel-plugin-polyfill-corejs2: "npm:^0.4.10" babel-plugin-polyfill-corejs3: "npm:^0.10.4" babel-plugin-polyfill-regenerator: "npm:^0.6.1" - core-js-compat: "npm:^3.31.0" + core-js-compat: "npm:^3.37.1" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/2fd90c46efefadb48dae6d13de190ac48753af187ee394924cf532c79870ebb87658bd31f06649630827a478b17a4adc41717cc6d4c460ff2ed9fafa51e5b515 + checksum: 10/6d32d4554b34230031c0fb0c0e636e7e78e2219a26d5145209d9417cabcd2bd09637b1470187d2613a0b0d2128ed4a6e27a40ea268e44a62fc13b5d242e2cf82 languageName: node linkType: hard @@ -1404,12 +1404,12 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.24.7, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.4": - version: 7.24.7 - resolution: "@babel/runtime@npm:7.24.7" +"@babel/runtime@npm:7.24.8, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.4": + version: 7.24.8 + resolution: "@babel/runtime@npm:7.24.8" dependencies: regenerator-runtime: "npm:^0.14.0" - checksum: 10/7b77f566165dee62db3db0296e71d08cafda3f34e1b0dcefcd68427272e17c1704f4e4369bff76651b07b6e49d3ea5a0ce344818af9116e9292e4381e0918c76 + checksum: 10/e6f335e472a8a337379effc15815dd0eddf6a7d0c00b50deb4f9e9585819b45431d0ff3c2d3d0fa58c227a9b04dcc4a85e7245fb57493adb2863b5208c769cbd languageName: node linkType: hard @@ -1424,32 +1424,32 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/traverse@npm:7.24.7" +"@babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/traverse@npm:7.24.8" dependencies: "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.24.7" + "@babel/generator": "npm:^7.24.8" "@babel/helper-environment-visitor": "npm:^7.24.7" "@babel/helper-function-name": "npm:^7.24.7" "@babel/helper-hoist-variables": "npm:^7.24.7" "@babel/helper-split-export-declaration": "npm:^7.24.7" - "@babel/parser": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" + "@babel/parser": "npm:^7.24.8" + "@babel/types": "npm:^7.24.8" debug: "npm:^4.3.1" globals: "npm:^11.1.0" - checksum: 10/785cf26383a992740e492efba7016de964cd06c05c9d7146fa1b5ead409e054c444f50b36dc37856884a56e32cf9d3105ddf1543486b6df68300bffb117a245a + checksum: 10/47d8ecf8cfff58fe621fc4d8454b82c97c407816d8f9c435caa0c849ea7c357b91119a06f3c69f21a0228b5d06ac0b44f49d1f78cff032d6266317707f1fe615 languageName: node linkType: hard -"@babel/types@npm:^7.24.7, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": - version: 7.24.7 - resolution: "@babel/types@npm:7.24.7" +"@babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": + version: 7.24.8 + resolution: "@babel/types@npm:7.24.8" dependencies: - "@babel/helper-string-parser": "npm:^7.24.7" + "@babel/helper-string-parser": "npm:^7.24.8" "@babel/helper-validator-identifier": "npm:^7.24.7" to-fast-properties: "npm:^2.0.0" - checksum: 10/ad3c8c0d6fb4acb0bb74bb5b4bb849b181bf6185677ef9c59c18856c81e43628d0858253cf232f0eca806f02e08eff85a1d3e636a3e94daea737597796b0b430 + checksum: 10/29b080b2753c22ee5e2455ff767a971443245d945dea4d1b3130e036dcdf0949a89539a581753c68d03d2f2f2325244ee0f91fb83dabee1cbac5db5246838137 languageName: node linkType: hard @@ -6051,17 +6051,17 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.21.10, browserslist@npm:^4.22.2, browserslist@npm:^4.23.0": - version: 4.23.1 - resolution: "browserslist@npm:4.23.1" +"browserslist@npm:^4.21.10, browserslist@npm:^4.23.0, browserslist@npm:^4.23.1": + version: 4.23.2 + resolution: "browserslist@npm:4.23.2" dependencies: - caniuse-lite: "npm:^1.0.30001629" - electron-to-chromium: "npm:^1.4.796" + caniuse-lite: "npm:^1.0.30001640" + electron-to-chromium: "npm:^1.4.820" node-releases: "npm:^2.0.14" - update-browserslist-db: "npm:^1.0.16" + update-browserslist-db: "npm:^1.1.0" bin: browserslist: cli.js - checksum: 10/91da59f70a8e01ece97133670f9857d6d7e96be78e1b7ffa54b869f97d01d01c237612471b595cee41c1ab212e26e536ce0b6716ad1d6c4368a40c222698cac1 + checksum: 10/326a98b1c39bcc9a99b197f15790dc28e122b1aead3257c837421899377ac96239123f26868698085b3d9be916d72540602738e1f857e86a387e810af3fda6e5 languageName: node linkType: hard @@ -6203,10 +6203,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001629": - version: 1.0.30001636 - resolution: "caniuse-lite@npm:1.0.30001636" - checksum: 10/9e6c5ab4c20df31df36720dda77cf6a781549ac2ad844bc0a416b327a793da21486358a1f85fdd6c39e22d336f70aac3b0e232f5f228cdff0ceb6e3e1c5e98fd +"caniuse-lite@npm:^1.0.30001640": + version: 1.0.30001642 + resolution: "caniuse-lite@npm:1.0.30001642" + checksum: 10/8d80ea82be453ae0fdfea8766d82740a4945c1b99189650f29bfc458d4e235d7e99027a8f8bc5a4228d8c4457ba896315284b0703f300353ad5f09d8e693de10 languageName: node linkType: hard @@ -6701,7 +6701,7 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.31.0, core-js-compat@npm:^3.36.1": +"core-js-compat@npm:^3.36.1, core-js-compat@npm:^3.37.1": version: 3.37.1 resolution: "core-js-compat@npm:3.37.1" dependencies: @@ -7191,10 +7191,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.796": - version: 1.4.805 - resolution: "electron-to-chromium@npm:1.4.805" - checksum: 10/69ff2b7098ec80666c7cfed3ae045894b1bf8ae72a77233c37b4496698a1c20a24e9b97822df7fe5dad226d32ca52defa10dee4b36438da35af6d51810d652d3 +"electron-to-chromium@npm:^1.4.820": + version: 1.4.827 + resolution: "electron-to-chromium@npm:1.4.827" + checksum: 10/7fa44aeebc5548874d33e417579d998d8e9a3d7b07fae22429ee7de5866c73b3158d56969146df3dcf44a222dcd91972ee786d0427f461e0c98bff79e408e782 languageName: node linkType: hard @@ -8903,13 +8903,13 @@ __metadata: version: 0.0.0-use.local resolution: "home-assistant-frontend@workspace:." dependencies: - "@babel/core": "npm:7.24.7" + "@babel/core": "npm:7.24.8" "@babel/helper-define-polyfill-provider": "npm:0.6.2" "@babel/plugin-proposal-decorators": "npm:7.24.7" "@babel/plugin-transform-runtime": "npm:7.24.7" - "@babel/preset-env": "npm:7.24.7" + "@babel/preset-env": "npm:7.24.8" "@babel/preset-typescript": "npm:7.24.7" - "@babel/runtime": "npm:7.24.7" + "@babel/runtime": "npm:7.24.8" "@braintree/sanitize-url": "npm:7.0.4" "@bundle-stats/plugin-webpack-filter": "npm:4.13.3" "@codemirror/autocomplete": "npm:6.17.0" @@ -14472,9 +14472,9 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.0.16": - version: 1.0.16 - resolution: "update-browserslist-db@npm:1.0.16" +"update-browserslist-db@npm:^1.1.0": + version: 1.1.0 + resolution: "update-browserslist-db@npm:1.1.0" dependencies: escalade: "npm:^3.1.2" picocolors: "npm:^1.0.1" @@ -14482,7 +14482,7 @@ __metadata: browserslist: ">= 4.21.0" bin: update-browserslist-db: cli.js - checksum: 10/071bf0b2fb8568db6cd42ee2598ac9b87c794a7229fcbf1b035ae7f883e770c07143f16a5371525d5bcb94b99f9a1b279036142b0195ffd4cf5a0008fc4a500e + checksum: 10/d70b9efeaf4601aadb1a4f6456a7a5d9118e0063d995866b8e0c5e0cf559482671dab6ce7b079f9536b06758a344fbd83f974b965211e1c6e8d1958540b0c24c languageName: node linkType: hard From 9b01c0b2f01cb2b527b73f14489c1c8842c10b31 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Jul 2024 17:21:05 +0200 Subject: [PATCH 17/97] Update dependency eslint-plugin-lit-a11y to v4.1.4 (#21392) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 775477fbb9..ed09139dab 100644 --- a/package.json +++ b/package.json @@ -200,7 +200,7 @@ "eslint-import-resolver-webpack": "0.13.8", "eslint-plugin-import": "2.29.1", "eslint-plugin-lit": "1.14.0", - "eslint-plugin-lit-a11y": "4.1.3", + "eslint-plugin-lit-a11y": "4.1.4", "eslint-plugin-unused-imports": "4.0.0", "eslint-plugin-wc": "2.1.0", "fancy-log": "2.0.0", diff --git a/yarn.lock b/yarn.lock index af31c676a3..3562b5c470 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7581,9 +7581,9 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-lit-a11y@npm:4.1.3": - version: 4.1.3 - resolution: "eslint-plugin-lit-a11y@npm:4.1.3" +"eslint-plugin-lit-a11y@npm:4.1.4": + version: 4.1.4 + resolution: "eslint-plugin-lit-a11y@npm:4.1.4" dependencies: "@thepassle/axobject-query": "npm:^4.0.0" aria-query: "npm:^5.1.3" @@ -7598,7 +7598,7 @@ __metadata: requireindex: "npm:~1.2.0" peerDependencies: eslint: ">= 5" - checksum: 10/730a82cfefbeba87e604172db8c29fc18d3361b5c913531c05e83af26edbe612df955d5f124daf6066c3703b0e25f2352a8ea9cae8b2f8a3e6121937c297d3a9 + checksum: 10/9e3ff1aa6575030275f9cc19b33c2bbcdedea1110c4491fcd972d953f61528a248c0597e79b80a1f6cfe96023b1179bfb861217d864f43e24c7ab12563f41d3a languageName: node linkType: hard @@ -9036,7 +9036,7 @@ __metadata: eslint-import-resolver-webpack: "npm:0.13.8" eslint-plugin-import: "npm:2.29.1" eslint-plugin-lit: "npm:1.14.0" - eslint-plugin-lit-a11y: "npm:4.1.3" + eslint-plugin-lit-a11y: "npm:4.1.4" eslint-plugin-unused-imports: "npm:4.0.0" eslint-plugin-wc: "npm:2.1.0" fancy-log: "npm:2.0.0" From 5ec257fa182aa9e1211635452ed02430bf525957 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Jul 2024 22:30:44 +0200 Subject: [PATCH 18/97] Update dependency webpack to v5.93.0 (#21394) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index ed09139dab..e8fe5f8632 100644 --- a/package.json +++ b/package.json @@ -238,7 +238,7 @@ "transform-async-modules-webpack-plugin": "1.1.1", "ts-lit-plugin": "2.0.2", "typescript": "5.5.3", - "webpack": "5.92.1", + "webpack": "5.93.0", "webpack-cli": "5.1.4", "webpack-dev-server": "5.0.4", "webpack-manifest-plugin": "5.0.0", diff --git a/yarn.lock b/yarn.lock index 3562b5c470..9a4cf77451 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9106,7 +9106,7 @@ __metadata: vis-network: "npm:9.1.9" vue: "npm:2.7.16" vue2-daterange-picker: "npm:0.6.8" - webpack: "npm:5.92.1" + webpack: "npm:5.93.0" webpack-cli: "npm:5.1.4" webpack-dev-server: "npm:5.0.4" webpack-manifest-plugin: "npm:5.0.0" @@ -14926,9 +14926,9 @@ __metadata: languageName: node linkType: hard -"webpack@npm:5.92.1": - version: 5.92.1 - resolution: "webpack@npm:5.92.1" +"webpack@npm:5.93.0": + version: 5.93.0 + resolution: "webpack@npm:5.93.0" dependencies: "@types/eslint-scope": "npm:^3.7.3" "@types/estree": "npm:^1.0.5" @@ -14959,7 +14959,7 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: 10/76fcfbebcc0719c4734c65a01dcef7a0f18f3f2647484e8a7e8606adbd128ac42756bb3a8b7e2d486fe97f6286ebdc7b937ccdf3cf1d21b4684134bb89bbed89 + checksum: 10/a48bef7a511d826db7f9ebee2c84317214923ac40cb2aabe6a649546c54a76a55fc3b91ff03c05fed22a13a176891c47bbff7fcc644c53bcbe5091555863641b languageName: node linkType: hard From a08bbcd1b41349be5ec72b1f00a08eabd81f81ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 06:59:05 +0000 Subject: [PATCH 19/97] Bump actions/setup-node from 4.0.2 to 4.0.3 (#21397) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.0.2 to 4.0.3. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v4.0.2...v4.0.3) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cast_deployment.yaml | 4 ++-- .github/workflows/ci.yaml | 8 ++++---- .github/workflows/demo_deployment.yaml | 4 ++-- .github/workflows/design_deployment.yaml | 2 +- .github/workflows/design_preview.yaml | 2 +- .github/workflows/nightly.yaml | 2 +- .github/workflows/release.yaml | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/cast_deployment.yaml b/.github/workflows/cast_deployment.yaml index e665b15060..b0c6fbad01 100644 --- a/.github/workflows/cast_deployment.yaml +++ b/.github/workflows/cast_deployment.yaml @@ -26,7 +26,7 @@ jobs: ref: dev - name: Setup Node - uses: actions/setup-node@v4.0.2 + uses: actions/setup-node@v4.0.3 with: node-version-file: ".nvmrc" cache: yarn @@ -62,7 +62,7 @@ jobs: ref: master - name: Setup Node - uses: actions/setup-node@v4.0.2 + uses: actions/setup-node@v4.0.3 with: node-version-file: ".nvmrc" cache: yarn diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 42d7d54e65..661824f1d0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,7 +26,7 @@ jobs: - name: Check out files from GitHub uses: actions/checkout@v4.1.7 - name: Setup Node - uses: actions/setup-node@v4.0.2 + uses: actions/setup-node@v4.0.3 with: node-version-file: ".nvmrc" cache: yarn @@ -60,7 +60,7 @@ jobs: - name: Check out files from GitHub uses: actions/checkout@v4.1.7 - name: Setup Node - uses: actions/setup-node@v4.0.2 + uses: actions/setup-node@v4.0.3 with: node-version-file: ".nvmrc" cache: yarn @@ -78,7 +78,7 @@ jobs: - name: Check out files from GitHub uses: actions/checkout@v4.1.7 - name: Setup Node - uses: actions/setup-node@v4.0.2 + uses: actions/setup-node@v4.0.3 with: node-version-file: ".nvmrc" cache: yarn @@ -102,7 +102,7 @@ jobs: - name: Check out files from GitHub uses: actions/checkout@v4.1.7 - name: Setup Node - uses: actions/setup-node@v4.0.2 + uses: actions/setup-node@v4.0.3 with: node-version-file: ".nvmrc" cache: yarn diff --git a/.github/workflows/demo_deployment.yaml b/.github/workflows/demo_deployment.yaml index 0e1bc73716..89ca805c29 100644 --- a/.github/workflows/demo_deployment.yaml +++ b/.github/workflows/demo_deployment.yaml @@ -27,7 +27,7 @@ jobs: ref: dev - name: Setup Node - uses: actions/setup-node@v4.0.2 + uses: actions/setup-node@v4.0.3 with: node-version-file: ".nvmrc" cache: yarn @@ -63,7 +63,7 @@ jobs: ref: master - name: Setup Node - uses: actions/setup-node@v4.0.2 + uses: actions/setup-node@v4.0.3 with: node-version-file: ".nvmrc" cache: yarn diff --git a/.github/workflows/design_deployment.yaml b/.github/workflows/design_deployment.yaml index ac6297eb1f..c8728b6479 100644 --- a/.github/workflows/design_deployment.yaml +++ b/.github/workflows/design_deployment.yaml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v4.1.7 - name: Setup Node - uses: actions/setup-node@v4.0.2 + uses: actions/setup-node@v4.0.3 with: node-version-file: ".nvmrc" cache: yarn diff --git a/.github/workflows/design_preview.yaml b/.github/workflows/design_preview.yaml index 607be985a1..2ed60915fa 100644 --- a/.github/workflows/design_preview.yaml +++ b/.github/workflows/design_preview.yaml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v4.1.7 - name: Setup Node - uses: actions/setup-node@v4.0.2 + uses: actions/setup-node@v4.0.3 with: node-version-file: ".nvmrc" cache: yarn diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index c095b89c24..a4a2a17ec9 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -28,7 +28,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Setup Node - uses: actions/setup-node@v4.0.2 + uses: actions/setup-node@v4.0.3 with: node-version-file: ".nvmrc" cache: yarn diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3ba8422fff..f7bf8706e2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -34,7 +34,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Setup Node - uses: actions/setup-node@v4.0.2 + uses: actions/setup-node@v4.0.3 with: node-version-file: ".nvmrc" cache: yarn From 71a2c40dd7ce1f2288176973fc71e432bef3c9b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 09:00:16 +0200 Subject: [PATCH 20/97] Bump home-assistant/wheels from 2024.01.0 to 2024.07.1 (#21398) Bumps [home-assistant/wheels](https://github.com/home-assistant/wheels) from 2024.01.0 to 2024.07.1. - [Release notes](https://github.com/home-assistant/wheels/releases) - [Commits](https://github.com/home-assistant/wheels/compare/2024.01.0...2024.07.1) --- updated-dependencies: - dependency-name: home-assistant/wheels dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f7bf8706e2..17278c6123 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -74,7 +74,7 @@ jobs: echo "home-assistant-frontend==$version" > ./requirements.txt - name: Build wheels - uses: home-assistant/wheels@2024.01.0 + uses: home-assistant/wheels@2024.07.1 with: abi: cp311 tag: musllinux_1_2 From 15a7ace27841c6e4dd9657c9cc45e5952e0d6e26 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:38:48 +0200 Subject: [PATCH 21/97] Add on primary color to ha-slider (#21389) add on primary --- src/components/ha-slider.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ha-slider.ts b/src/components/ha-slider.ts index 8bf5a098e5..33a1fbb16d 100644 --- a/src/components/ha-slider.ts +++ b/src/components/ha-slider.ts @@ -15,6 +15,7 @@ export class HaSlider extends MdSlider { css` :host { --md-sys-color-primary: var(--primary-color); + --md-sys-color-on-primary: var(--text-primary-color); --md-sys-color-outline: var(--outline-color); --md-sys-color-on-surface: var(--primary-text-color); --md-slider-handle-width: 14px; From 2890d5c8cf712648023a4e5ff383c573f79908bf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 09:45:05 +0000 Subject: [PATCH 22/97] Update vaadinWebComponents monorepo to v24.4.3 (#21399) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 +- yarn.lock | 170 +++++++++++++++++++++++++-------------------------- 2 files changed, 87 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index e8fe5f8632..8a7457e9f1 100644 --- a/package.json +++ b/package.json @@ -88,8 +88,8 @@ "@polymer/paper-tabs": "3.1.0", "@polymer/polymer": "3.5.1", "@thomasloven/round-slider": "0.6.0", - "@vaadin/combo-box": "24.4.2", - "@vaadin/vaadin-themable-mixin": "24.4.2", + "@vaadin/combo-box": "24.4.3", + "@vaadin/vaadin-themable-mixin": "24.4.3", "@vibrant/color": "3.2.1-alpha.1", "@vibrant/core": "3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "3.2.1-alpha.1", diff --git a/yarn.lock b/yarn.lock index 9a4cf77451..136ad75283 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4709,129 +4709,129 @@ __metadata: languageName: node linkType: hard -"@vaadin/a11y-base@npm:~24.4.2": - version: 24.4.2 - resolution: "@vaadin/a11y-base@npm:24.4.2" +"@vaadin/a11y-base@npm:~24.4.3": + version: 24.4.3 + resolution: "@vaadin/a11y-base@npm:24.4.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.4.2" + "@vaadin/component-base": "npm:~24.4.3" lit: "npm:^3.0.0" - checksum: 10/6b3bd35507168e1524dd26e5b18c5e1f4e6d8db2ec576fdeb9928282257d47c5b6984395286786b11aeb6aeacdade42a495cb75517cb4fa2cb450cad88d9916a + checksum: 10/a837941c1789f25b41fe79e66b139663f4d29415eacc275fe06dcbb631f5631fcec0cce765274a68f9f52daa06211503db4e2bf5a4d343af49580be0f0642b7c languageName: node linkType: hard -"@vaadin/combo-box@npm:24.4.2": - version: 24.4.2 - resolution: "@vaadin/combo-box@npm:24.4.2" +"@vaadin/combo-box@npm:24.4.3": + version: 24.4.3 + resolution: "@vaadin/combo-box@npm:24.4.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.4.2" - "@vaadin/component-base": "npm:~24.4.2" - "@vaadin/field-base": "npm:~24.4.2" - "@vaadin/input-container": "npm:~24.4.2" - "@vaadin/item": "npm:~24.4.2" - "@vaadin/lit-renderer": "npm:~24.4.2" - "@vaadin/overlay": "npm:~24.4.2" - "@vaadin/vaadin-lumo-styles": "npm:~24.4.2" - "@vaadin/vaadin-material-styles": "npm:~24.4.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" - checksum: 10/6d75c4ce8eeffbb4015436115afec8f6d429c2840dc300eaa6c737ae262b41fe3bb3548c5bc948c55b41a30a66b7df47eabdb526c3805bd38dae99ee3bc63d0e + "@vaadin/a11y-base": "npm:~24.4.3" + "@vaadin/component-base": "npm:~24.4.3" + "@vaadin/field-base": "npm:~24.4.3" + "@vaadin/input-container": "npm:~24.4.3" + "@vaadin/item": "npm:~24.4.3" + "@vaadin/lit-renderer": "npm:~24.4.3" + "@vaadin/overlay": "npm:~24.4.3" + "@vaadin/vaadin-lumo-styles": "npm:~24.4.3" + "@vaadin/vaadin-material-styles": "npm:~24.4.3" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.3" + checksum: 10/4442f99488a97558fc130a11dece34b4acdef115e7cd846a71bbba000b846310579180a035a9b8d0594bf1aa9dd6a6481874822f39d47f9a0a8f2368e3c5db1b languageName: node linkType: hard -"@vaadin/component-base@npm:~24.4.2": - version: 24.4.2 - resolution: "@vaadin/component-base@npm:24.4.2" +"@vaadin/component-base@npm:~24.4.3": + version: 24.4.3 + resolution: "@vaadin/component-base@npm:24.4.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" "@vaadin/vaadin-development-mode-detector": "npm:^2.0.0" "@vaadin/vaadin-usage-statistics": "npm:^2.1.0" lit: "npm:^3.0.0" - checksum: 10/7e233c68da0ea673c6bcacc13488059a063d69734b222be663fb85542932cbbc4031e2855fbebc8e8aeec42d9ce1c05713890a8be3a642050921fb0b743e2eb7 + checksum: 10/dace8fce311746a122453bb59b0f052f80cb79eb9343d5a22358287e4f3bb17b6d2bbe4ca951e0882262df28a30e944123bd288b6d9e5baedb405a3eaac42d08 languageName: node linkType: hard -"@vaadin/field-base@npm:~24.4.2": - version: 24.4.2 - resolution: "@vaadin/field-base@npm:24.4.2" +"@vaadin/field-base@npm:~24.4.3": + version: 24.4.3 + resolution: "@vaadin/field-base@npm:24.4.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.4.2" - "@vaadin/component-base": "npm:~24.4.2" + "@vaadin/a11y-base": "npm:~24.4.3" + "@vaadin/component-base": "npm:~24.4.3" lit: "npm:^3.0.0" - checksum: 10/98e680fa02b1dbd636df3244fb2b08add4a214613034de2b3bdfd1e66bad407788c3f192ded98d348d3ab2f3ac7992d7819413d46958c371f8ff9b08f66d80f1 + checksum: 10/1038466c2df718b28ad7ee19fbd42264a6004dc435deede00894d908773dfd0cd4040c707d5c5d7155e712f25fe0df4483fa544c85160715794dacbe1f23681c languageName: node linkType: hard -"@vaadin/icon@npm:~24.4.2": - version: 24.4.2 - resolution: "@vaadin/icon@npm:24.4.2" +"@vaadin/icon@npm:~24.4.3": + version: 24.4.3 + resolution: "@vaadin/icon@npm:24.4.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.4.2" - "@vaadin/vaadin-lumo-styles": "npm:~24.4.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" + "@vaadin/component-base": "npm:~24.4.3" + "@vaadin/vaadin-lumo-styles": "npm:~24.4.3" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.3" lit: "npm:^3.0.0" - checksum: 10/e8168ed771681688dc3fab96296cb6bf0568eab2788ecdb08b4b4f744873fb99a0b3c849283460356b2f05355d0ea03c801b4cf42b881e020b060bf4c0525b49 + checksum: 10/93ad68a83e7470481cc84e9726ad067e6066307b0bb812e97e41d87f17f8c59dd8e686010c677195920cb282c430f5405bdf23f6b7a5a54b8379f5b2987ea6a0 languageName: node linkType: hard -"@vaadin/input-container@npm:~24.4.2": - version: 24.4.2 - resolution: "@vaadin/input-container@npm:24.4.2" +"@vaadin/input-container@npm:~24.4.3": + version: 24.4.3 + resolution: "@vaadin/input-container@npm:24.4.3" dependencies: "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.4.2" - "@vaadin/vaadin-lumo-styles": "npm:~24.4.2" - "@vaadin/vaadin-material-styles": "npm:~24.4.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" + "@vaadin/component-base": "npm:~24.4.3" + "@vaadin/vaadin-lumo-styles": "npm:~24.4.3" + "@vaadin/vaadin-material-styles": "npm:~24.4.3" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.3" lit: "npm:^3.0.0" - checksum: 10/1519819566d5afef998d155be96a6acf2ffceef9980bf34b60ee1d0c1048a2e5282fd7658d043f130114f08cac5662c8bc3d4337c8aa7cd0ba1b6048cbb62f46 + checksum: 10/8f0aa31065cd956be76ae50600caf5305267d508cef836f98ebec85fda7b125592190ef0c28af89c5b0c17a762500659b95d932d3c998059c67045ffb72f3869 languageName: node linkType: hard -"@vaadin/item@npm:~24.4.2": - version: 24.4.2 - resolution: "@vaadin/item@npm:24.4.2" +"@vaadin/item@npm:~24.4.3": + version: 24.4.3 + resolution: "@vaadin/item@npm:24.4.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.4.2" - "@vaadin/component-base": "npm:~24.4.2" - "@vaadin/vaadin-lumo-styles": "npm:~24.4.2" - "@vaadin/vaadin-material-styles": "npm:~24.4.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" - checksum: 10/871711a437429a32583c73ee55d9c0734bd6c5a82a04b39be89082190aef91fd1690784902e0d7951c5c03c701af2ba9a390e7eef13800363302df41896586ca + "@vaadin/a11y-base": "npm:~24.4.3" + "@vaadin/component-base": "npm:~24.4.3" + "@vaadin/vaadin-lumo-styles": "npm:~24.4.3" + "@vaadin/vaadin-material-styles": "npm:~24.4.3" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.3" + checksum: 10/6ad0b3c2c9359475f24ded6ad4b280cd4691cfd5d1783c9519335f779990a92ab36bc2345905dcc222f9ee2cbe79dc46d5c577a45bcdfcf656668fb4a1297134 languageName: node linkType: hard -"@vaadin/lit-renderer@npm:~24.4.2": - version: 24.4.2 - resolution: "@vaadin/lit-renderer@npm:24.4.2" +"@vaadin/lit-renderer@npm:~24.4.3": + version: 24.4.3 + resolution: "@vaadin/lit-renderer@npm:24.4.3" dependencies: lit: "npm:^3.0.0" - checksum: 10/668fac55b69999e682c23f24ce3fdbf8ee13aa926b1b5f6ae7fb11111244bf50fbb46c3d673c2d650c8f55202ec529bfe0fb940cf6117ed12b8067d591bbbdc3 + checksum: 10/a6f672e469a13cd1fa0cada01c2a6033e2d90e7ee933f4d8deb15ab5eed2e9fd00f64306455cd9b924f3c74797fb1d4fdf34d48191b17241ec38a0448307bb85 languageName: node linkType: hard -"@vaadin/overlay@npm:~24.4.2": - version: 24.4.2 - resolution: "@vaadin/overlay@npm:24.4.2" +"@vaadin/overlay@npm:~24.4.3": + version: 24.4.3 + resolution: "@vaadin/overlay@npm:24.4.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.4.2" - "@vaadin/component-base": "npm:~24.4.2" - "@vaadin/vaadin-lumo-styles": "npm:~24.4.2" - "@vaadin/vaadin-material-styles": "npm:~24.4.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" + "@vaadin/a11y-base": "npm:~24.4.3" + "@vaadin/component-base": "npm:~24.4.3" + "@vaadin/vaadin-lumo-styles": "npm:~24.4.3" + "@vaadin/vaadin-material-styles": "npm:~24.4.3" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.3" lit: "npm:^3.0.0" - checksum: 10/7a0b1a32d61a5a17961e57c95a62cb7a666d2a78c67f115d405011a5271f78239eb8e776db80c27428f97626dd1ec1a2371eeb0991d17581fc672524572c4247 + checksum: 10/04fde1971c01df40a05f28177ab0b45a9ed293721413a4cf0d3b833f208a76477d2b8f6b8fc79f562ed92490faee5cae99e301f80f791504c2ceaa28f25f1dbc languageName: node linkType: hard @@ -4842,36 +4842,36 @@ __metadata: languageName: node linkType: hard -"@vaadin/vaadin-lumo-styles@npm:~24.4.2": - version: 24.4.2 - resolution: "@vaadin/vaadin-lumo-styles@npm:24.4.2" +"@vaadin/vaadin-lumo-styles@npm:~24.4.3": + version: 24.4.3 + resolution: "@vaadin/vaadin-lumo-styles@npm:24.4.3" dependencies: "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.4.2" - "@vaadin/icon": "npm:~24.4.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" - checksum: 10/dfb95f3c4ab998022c51f816cfb9a39c2ddec8fb340e5c99c3617cfefc8d4915bd7d06be9a878930594afa70c5ed1f6dab9daa02515033d26fdae3570274ae04 + "@vaadin/component-base": "npm:~24.4.3" + "@vaadin/icon": "npm:~24.4.3" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.3" + checksum: 10/76a1120dce651a3fd3fec4dd98c1a09ae54da4c7f3e16637951c8924b73d02c8d479df68a0b28e14a01c6f87033c376d4cf827f1ddc435e547136dafd4289587 languageName: node linkType: hard -"@vaadin/vaadin-material-styles@npm:~24.4.2": - version: 24.4.2 - resolution: "@vaadin/vaadin-material-styles@npm:24.4.2" +"@vaadin/vaadin-material-styles@npm:~24.4.3": + version: 24.4.3 + resolution: "@vaadin/vaadin-material-styles@npm:24.4.3" dependencies: "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.4.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.4.2" - checksum: 10/f94c531be9da3cd9a70caffd2925f348e0a06043ae512209ca0926599bff77ba0b70643fc375fb71b9debc0c0d8204618ccf5e986642bbb3335f733650f2b58b + "@vaadin/component-base": "npm:~24.4.3" + "@vaadin/vaadin-themable-mixin": "npm:~24.4.3" + checksum: 10/8e95384d9ed0e4ad1993e91c1b93a64f1296d117a68e344b1ba51f245a94b3f8e28ff392c5682bd445ab79a00d1d1cab79b77e6187f5a115183d81d8d461a327 languageName: node linkType: hard -"@vaadin/vaadin-themable-mixin@npm:24.4.2, @vaadin/vaadin-themable-mixin@npm:~24.4.2": - version: 24.4.2 - resolution: "@vaadin/vaadin-themable-mixin@npm:24.4.2" +"@vaadin/vaadin-themable-mixin@npm:24.4.3, @vaadin/vaadin-themable-mixin@npm:~24.4.3": + version: 24.4.3 + resolution: "@vaadin/vaadin-themable-mixin@npm:24.4.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" lit: "npm:^3.0.0" - checksum: 10/d86979fb9eb6e214685a01c9d596f905eac5444af7bfadf3422c0bdbf80ea0f7cd8dae8b286cd95c422560426c3321c62d69b58c7f72473240c7a824df61644c + checksum: 10/2e967b8d7b271b3c6f9814e5fce0b8e4088e4647449142b13d0875b1a865830da30874bed209c8a5714538ef0fea0f8dd175ec8a6ca3318959a959ad70d35d1e languageName: node linkType: hard @@ -9004,8 +9004,8 @@ __metadata: "@types/webspeechapi": "npm:0.0.29" "@typescript-eslint/eslint-plugin": "npm:7.16.0" "@typescript-eslint/parser": "npm:7.16.0" - "@vaadin/combo-box": "npm:24.4.2" - "@vaadin/vaadin-themable-mixin": "npm:24.4.2" + "@vaadin/combo-box": "npm:24.4.3" + "@vaadin/vaadin-themable-mixin": "npm:24.4.3" "@vibrant/color": "npm:3.2.1-alpha.1" "@vibrant/core": "npm:3.2.1-alpha.1" "@vibrant/quantizer-mmcq": "npm:3.2.1-alpha.1" From f87296d978aa08e97882e05381fc81b54a482dfd Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 15 Jul 2024 14:08:00 +0200 Subject: [PATCH 23/97] Add state content component (#21370) * Move state content into its own component for reusability * Add entity state content selector * Use live timer * Rename live timer to remaining time and remove remaining attribute from state content list * Move default in state content component * Fix picker --- .../entity/ha-entity-state-content-picker.ts | 314 ++++++++++++++++++ .../ha-selector-ui-state-content.ts | 48 +++ src/components/ha-selector/ha-selector.ts | 1 + src/data/selector.ts | 14 +- src/panels/lovelace/cards/hui-tile-card.ts | 156 +-------- .../config-elements/hui-tile-card-editor.ts | 112 +------ .../entity-rows/hui-timer-entity-row.ts | 6 +- ...ay-timer.ts => ha-timer-remaining-time.ts} | 6 +- src/state-display/state-display.ts | 174 ++++++++++ src/state-summary/state-card-timer.ts | 6 +- src/translations/en.json | 14 +- 11 files changed, 585 insertions(+), 266 deletions(-) create mode 100644 src/components/entity/ha-entity-state-content-picker.ts create mode 100644 src/components/ha-selector/ha-selector-ui-state-content.ts rename src/state-display/{state-display-timer.ts => ha-timer-remaining-time.ts} (92%) create mode 100644 src/state-display/state-display.ts diff --git a/src/components/entity/ha-entity-state-content-picker.ts b/src/components/entity/ha-entity-state-content-picker.ts new file mode 100644 index 0000000000..70272ad361 --- /dev/null +++ b/src/components/entity/ha-entity-state-content-picker.ts @@ -0,0 +1,314 @@ +import { mdiDrag } from "@mdi/js"; +import { HassEntity } from "home-assistant-js-websocket"; +import { LitElement, PropertyValues, css, html, nothing } from "lit"; +import { customElement, property, query, state } from "lit/decorators"; +import { repeat } from "lit/directives/repeat"; +import memoizeOne from "memoize-one"; +import { ensureArray } from "../../common/array/ensure-array"; +import { fireEvent } from "../../common/dom/fire_event"; +import { computeDomain } from "../../common/entity/compute_domain"; +import { + STATE_DISPLAY_SPECIAL_CONTENT, + STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS, +} from "../../state-display/state-display"; +import { HomeAssistant, ValueChangedEvent } from "../../types"; +import "../ha-combo-box"; +import type { HaComboBox } from "../ha-combo-box"; + +const HIDDEN_ATTRIBUTES = [ + "access_token", + "available_modes", + "battery_icon", + "battery_level", + "code_arm_required", + "code_format", + "color_modes", + "device_class", + "editable", + "effect_list", + "entity_id", + "entity_picture", + "event_types", + "fan_modes", + "fan_speed_list", + "friendly_name", + "frontend_stream_type", + "has_date", + "has_time", + "hvac_modes", + "icon", + "id", + "max_color_temp_kelvin", + "max_mireds", + "max_temp", + "max", + "min_color_temp_kelvin", + "min_mireds", + "min_temp", + "min", + "mode", + "operation_list", + "options", + "percentage_step", + "precipitation_unit", + "preset_modes", + "pressure_unit", + "remaining", + "sound_mode_list", + "source_list", + "state_class", + "step", + "supported_color_modes", + "supported_features", + "swing_modes", + "target_temp_step", + "temperature_unit", + "token", + "unit_of_measurement", + "visibility_unit", + "wind_speed_unit", +]; + +@customElement("ha-entity-state-content-picker") +class HaEntityStatePicker extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public entityId?: string; + + @property({ type: Boolean }) public autofocus = false; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean }) public required = false; + + @property() public label?: string; + + @property() public value?: string[] | string; + + @property() public helper?: string; + + @state() private _opened = false; + + @query("ha-combo-box", true) private _comboBox!: HaComboBox; + + protected shouldUpdate(changedProps: PropertyValues) { + return !(!changedProps.has("_opened") && this._opened); + } + + private options = memoizeOne((entityId?: string, stateObj?: HassEntity) => { + const domain = entityId ? computeDomain(entityId) : undefined; + return [ + { + label: this.hass.localize("ui.components.state-content-picker.state"), + value: "state", + }, + { + label: this.hass.localize( + "ui.components.state-content-picker.last_changed" + ), + value: "last_changed", + }, + { + label: this.hass.localize( + "ui.components.state-content-picker.last_updated" + ), + value: "last_updated", + }, + ...(domain + ? STATE_DISPLAY_SPECIAL_CONTENT.filter((content) => + STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain]?.includes(content) + ).map((content) => ({ + label: this.hass.localize( + `ui.components.state-content-picker.${content}` + ), + value: content, + })) + : []), + ...Object.keys(stateObj?.attributes ?? {}) + .filter((a) => !HIDDEN_ATTRIBUTES.includes(a)) + .map((attribute) => ({ + value: attribute, + label: this.hass.formatEntityAttributeName(stateObj!, attribute), + })), + ]; + }); + + private _filter = ""; + + protected render() { + if (!this.hass) { + return nothing; + } + + const value = this._value; + + const stateObj = this.entityId + ? this.hass.states[this.entityId] + : undefined; + + const options = this.options(this.entityId, stateObj); + const optionItems = options.filter( + (option) => !this._value.includes(option.value) + ); + + return html` + ${value?.length + ? html` + + + ${repeat( + this._value, + (item) => item, + (item, idx) => { + const label = + options.find((option) => option.value === item)?.label || + item; + return html` + + + + ${label} + + `; + } + )} + + + ` + : nothing} + + + `; + } + + private get _value() { + return !this.value ? [] : ensureArray(this.value); + } + + private _openedChanged(ev: ValueChangedEvent) { + this._opened = ev.detail.value; + } + + private _filterChanged(ev?: CustomEvent): void { + this._filter = ev?.detail.value || ""; + + const filteredItems = this._comboBox.items?.filter((item) => { + const label = item.label || item.value; + return label.toLowerCase().includes(this._filter?.toLowerCase()); + }); + + if (this._filter) { + filteredItems?.unshift({ label: this._filter, value: this._filter }); + } + + this._comboBox.filteredItems = filteredItems; + } + + private async _moveItem(ev: CustomEvent) { + ev.stopPropagation(); + const { oldIndex, newIndex } = ev.detail; + const value = this._value; + const newValue = value.concat(); + const element = newValue.splice(oldIndex, 1)[0]; + newValue.splice(newIndex, 0, element); + this._setValue(newValue); + await this.updateComplete; + this._filterChanged(); + } + + private async _removeItem(ev) { + ev.stopPropagation(); + const value: string[] = [...this._value]; + value.splice(ev.target.idx, 1); + this._setValue(value); + await this.updateComplete; + this._filterChanged(); + } + + private _comboBoxValueChanged(ev: CustomEvent): void { + ev.stopPropagation(); + const newValue = ev.detail.value; + + if (this.disabled || newValue === "") { + return; + } + + const currentValue = this._value; + + if (currentValue.includes(newValue)) { + return; + } + + setTimeout(() => { + this._filterChanged(); + this._comboBox.setInputValue(""); + }, 0); + + this._setValue([...currentValue, newValue]); + } + + private _setValue(value: string[]) { + const newValue = + value.length === 0 ? undefined : value.length === 1 ? value[0] : value; + this.value = newValue; + fireEvent(this, "value-changed", { + value: newValue, + }); + } + + static styles = css` + :host { + position: relative; + } + + ha-chip-set { + padding: 8px 0; + } + + .sortable-fallback { + display: none; + opacity: 0; + } + + .sortable-ghost { + opacity: 0.4; + } + + .sortable-drag { + cursor: grabbing; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-entity-state-content-picker": HaEntityStatePicker; + } +} diff --git a/src/components/ha-selector/ha-selector-ui-state-content.ts b/src/components/ha-selector/ha-selector-ui-state-content.ts new file mode 100644 index 0000000000..c0d521b939 --- /dev/null +++ b/src/components/ha-selector/ha-selector-ui-state-content.ts @@ -0,0 +1,48 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { UiStateContentSelector } from "../../data/selector"; +import { SubscribeMixin } from "../../mixins/subscribe-mixin"; +import { HomeAssistant } from "../../types"; +import "../entity/ha-entity-state-content-picker"; + +@customElement("ha-selector-ui_state_content") +export class HaSelectorUiStateContent extends SubscribeMixin(LitElement) { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public selector!: UiStateContentSelector; + + @property() public value?: string | string[]; + + @property() public label?: string; + + @property() public helper?: string; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean }) public required = true; + + @property({ attribute: false }) public context?: { + filter_entity?: string; + }; + + protected render() { + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-ui_state_content": HaSelectorUiStateContent; + } +} diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index 8cab4393b8..11a9136abc 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -57,6 +57,7 @@ const LOAD_ELEMENTS = { color_temp: () => import("./ha-selector-color-temp"), ui_action: () => import("./ha-selector-ui-action"), ui_color: () => import("./ha-selector-ui-color"), + ui_state_content: () => import("./ha-selector-ui-state-content"), }; const LEGACY_UI_SELECTORS = new Set(["ui-action", "ui-color"]); diff --git a/src/data/selector.ts b/src/data/selector.ts index 60b9e4973b..7e4ccc8f38 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -2,6 +2,8 @@ import type { HassEntity } from "home-assistant-js-websocket"; import { ensureArray } from "../common/array/ensure-array"; import { computeStateDomain } from "../common/entity/compute_state_domain"; import { supportsFeature } from "../common/entity/supports-feature"; +import type { CropOptions } from "../dialogs/image-cropper-dialog/show-image-cropper-dialog"; +import { isHelperDomain } from "../panels/config/helpers/const"; import { UiAction } from "../panels/lovelace/components/hui-action-editor"; import { HomeAssistant, ItemPath } from "../types"; import { @@ -13,8 +15,6 @@ import { EntityRegistryEntry, } from "./entity_registry"; import { EntitySources } from "./entity_sources"; -import { isHelperDomain } from "../panels/config/helpers/const"; -import type { CropOptions } from "../dialogs/image-cropper-dialog/show-image-cropper-dialog"; export type Selector = | ActionSelector @@ -64,7 +64,8 @@ export type Selector = | TTSSelector | TTSVoiceSelector | UiActionSelector - | UiColorSelector; + | UiColorSelector + | UiStateContentSelector; export interface ActionSelector { action: { @@ -455,6 +456,13 @@ export interface UiColorSelector { ui_color: { default_color?: boolean } | null; } +export interface UiStateContentSelector { + // eslint-disable-next-line @typescript-eslint/ban-types + ui_state_content: { + entity_id?: string; + } | null; +} + export const expandLabelTarget = ( hass: HomeAssistant, labelId: string, diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 3208928d02..419b5bdfc6 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -1,19 +1,11 @@ import { mdiExclamationThick, mdiHelp } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { - CSSResultGroup, - LitElement, - TemplateResult, - css, - html, - nothing, -} from "lit"; +import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { ifDefined } from "lit/directives/if-defined"; import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; -import { ensureArray } from "../../../common/array/ensure-array"; import { computeCssColor } from "../../../common/color/compute-color"; import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color"; import { DOMAINS_TOGGLE } from "../../../common/const"; @@ -30,17 +22,14 @@ import "../../../components/tile/ha-tile-image"; import type { TileImageStyle } from "../../../components/tile/ha-tile-image"; import "../../../components/tile/ha-tile-info"; import { cameraUrlWithWidthHeight } from "../../../data/camera"; -import { isUnavailableState } from "../../../data/entity"; import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; -import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor"; -import { UpdateEntity, computeUpdateStateDisplay } from "../../../data/update"; +import "../../../state-display/state-display"; import { HomeAssistant } from "../../../types"; import "../card-features/hui-card-features"; import { actionHandler } from "../common/directives/action-handler-directive"; import { findEntities } from "../common/find-entities"; import { handleAction } from "../common/handle-action"; import { hasAction } from "../common/has-action"; -import "../components/hui-timestamp-display"; import type { LovelaceCard, LovelaceCardEditor, @@ -49,8 +38,6 @@ import type { import { renderTileBadge } from "./tile/badges/tile-badge"; import type { ThermostatCardConfig, TileCardConfig } from "./types"; -const TIMESTAMP_STATE_DOMAINS = ["button", "input_button", "scene"]; - export const getEntityDefaultTileIconAction = (entityId: string) => { const domain = computeDomain(entityId); const supportsIconAction = @@ -208,127 +195,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard { } ); - private _renderStateContent( - stateObj: HassEntity, - stateContent: string | string[] - ) { - const contents = ensureArray(stateContent); - - const values = contents - .map((content) => { - if (content === "state") { - const domain = computeDomain(stateObj.entity_id); - if ( - (stateObj.attributes.device_class === - SENSOR_DEVICE_CLASS_TIMESTAMP || - TIMESTAMP_STATE_DOMAINS.includes(domain)) && - !isUnavailableState(stateObj.state) - ) { - return html` - - `; - } - - return this.hass!.formatEntityState(stateObj); - } - if (content === "last-changed") { - return html` - - `; - } - if (content === "last-updated") { - return html` - - `; - } - if (content === "last_triggered") { - return html` - - `; - } - if (stateObj.attributes[content] == null) { - return undefined; - } - return this.hass!.formatEntityAttributeValue(stateObj, content); - }) - .filter(Boolean); - - if (!values.length) { - return html`${this.hass!.formatEntityState(stateObj)}`; - } - - return html` - ${values.map( - (value, index, array) => - html`${value}${index < array.length - 1 ? " ⸱ " : nothing}` - )} - `; - } - - private _renderState(stateObj: HassEntity): TemplateResult | typeof nothing { - const domain = computeDomain(stateObj.entity_id); - const active = stateActive(stateObj); - - if (domain === "light" && active) { - return this._renderStateContent(stateObj, ["brightness"]); - } - - if (domain === "fan" && active) { - return this._renderStateContent(stateObj, ["percentage"]); - } - - if (domain === "cover" && active) { - return this._renderStateContent(stateObj, ["state", "current_position"]); - } - - if (domain === "valve" && active) { - return this._renderStateContent(stateObj, ["state", "current_position"]); - } - - if (domain === "humidifier") { - return this._renderStateContent(stateObj, ["state", "current_humidity"]); - } - - if (domain === "climate") { - return this._renderStateContent(stateObj, [ - "state", - "current_temperature", - ]); - } - - if (domain === "update") { - return html` - ${computeUpdateStateDisplay(stateObj as UpdateEntity, this.hass!)} - `; - } - - if (domain === "timer") { - import("../../../state-display/state-display-timer"); - return html` - - `; - } - - return this._renderStateContent(stateObj, "state"); - } - get hasCardAction() { return ( !this._config?.tap_action || @@ -375,17 +241,21 @@ export class HuiTileCard extends LitElement implements LovelaceCard { } const name = this._config.name || stateObj.attributes.friendly_name; - - const localizedState = this._config.hide_state - ? nothing - : this._config.state_content - ? this._renderStateContent(stateObj, this._config.state_content) - : this._renderState(stateObj); - const active = stateActive(stateObj); const color = this._computeStateColor(stateObj, this._config.color); const domain = computeDomain(stateObj.entity_id); + const localizedState = this._config.hide_state + ? nothing + : html` + + + `; + const style = { "--tile-color": color, }; diff --git a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts index ca705fafb7..172d2c95ed 100644 --- a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts @@ -1,5 +1,4 @@ import { mdiGestureTap, mdiPalette } from "@mdi/js"; -import { HassEntity } from "home-assistant-js-websocket"; import { LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -14,9 +13,7 @@ import { string, union, } from "superstruct"; -import { ensureArray } from "../../../../common/array/ensure-array"; import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event"; -import { formatEntityAttributeNameFunc } from "../../../../common/translations/entity-state"; import { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/ha-form/ha-form"; import type { @@ -38,59 +35,6 @@ import { EditSubElementEvent, SubElementEditorConfig } from "../types"; import { configElementStyle } from "./config-elements-style"; import "./hui-card-features-editor"; -const HIDDEN_ATTRIBUTES = [ - "access_token", - "available_modes", - "code_arm_required", - "code_format", - "color_modes", - "device_class", - "editable", - "effect_list", - "entity_id", - "entity_picture", - "event_types", - "fan_modes", - "fan_speed_list", - "friendly_name", - "frontend_stream_type", - "has_date", - "has_time", - "hvac_modes", - "icon", - "id", - "max_color_temp_kelvin", - "max_mireds", - "max_temp", - "max", - "min_color_temp_kelvin", - "min_mireds", - "min_temp", - "min", - "mode", - "operation_list", - "options", - "percentage_step", - "precipitation_unit", - "preset_modes", - "pressure_unit", - "sound_mode_list", - "source_list", - "state_class", - "step", - "supported_color_modes", - "supported_features", - "swing_modes", - "target_temp_step", - "temperature_unit", - "token", - "unit_of_measurement", - "visibility_unit", - "wind_speed_unit", - "battery_icon", - "battery_level", -]; - const cardConfigStruct = assign( baseLovelaceCardConfig, object({ @@ -127,9 +71,7 @@ export class HuiTileCardEditor private _schema = memoizeOne( ( localize: LocalizeFunc, - formatEntityAttributeName: formatEntityAttributeNameFunc, entityId: string | undefined, - stateObj: HassEntity | undefined, hideState: boolean ) => [ @@ -183,41 +125,10 @@ export class HuiTileCardEditor { name: "state_content", selector: { - select: { - mode: "dropdown", - reorder: true, - custom_value: true, - multiple: true, - options: [ - { - label: localize( - `ui.panel.lovelace.editor.card.tile.state_content_options.state` - ), - value: "state", - }, - { - label: localize( - `ui.panel.lovelace.editor.card.tile.state_content_options.last-changed` - ), - value: "last-changed", - }, - { - label: localize( - `ui.panel.lovelace.editor.card.tile.state_content_options.last-updated` - ), - value: "last-updated", - }, - ...Object.keys(stateObj?.attributes ?? {}) - .filter((a) => !HIDDEN_ATTRIBUTES.includes(a)) - .map((attribute) => ({ - value: attribute, - label: formatEntityAttributeName( - stateObj!, - attribute - ), - })), - ], - }, + ui_state_content: {}, + }, + context: { + filter_entity: "entity", }, }, ] as const satisfies readonly HaFormSchema[]) @@ -268,9 +179,7 @@ export class HuiTileCardEditor const schema = this._schema( this.hass!.localize, - this.hass.formatEntityAttributeName, this._config.entity, - stateObj, this._config.hide_state ?? false ); @@ -287,10 +196,7 @@ export class HuiTileCardEditor `; } - const data = { - ...this._config, - state_content: ensureArray(this._config.state_content), - }; + const data = this._config; return html`
- + >
`; diff --git a/src/state-display/state-display-timer.ts b/src/state-display/ha-timer-remaining-time.ts similarity index 92% rename from src/state-display/state-display-timer.ts rename to src/state-display/ha-timer-remaining-time.ts index 13960c088c..36d242f92a 100644 --- a/src/state-display/state-display-timer.ts +++ b/src/state-display/ha-timer-remaining-time.ts @@ -4,8 +4,8 @@ import { customElement, property, state } from "lit/decorators"; import { computeDisplayTimer, timerTimeRemaining } from "../data/timer"; import type { HomeAssistant } from "../types"; -@customElement("state-display-timer") -class StateDisplayTimer extends ReactiveElement { +@customElement("ha-timer-remaining-time") +class HaTimerRemainingTime extends ReactiveElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public stateObj!: HassEntity; @@ -69,6 +69,6 @@ class StateDisplayTimer extends ReactiveElement { declare global { interface HTMLElementTagNameMap { - "state-display-timer": StateDisplayTimer; + "ha-timer-remaining-time": HaTimerRemainingTime; } } diff --git a/src/state-display/state-display.ts b/src/state-display/state-display.ts new file mode 100644 index 0000000000..a1042681a8 --- /dev/null +++ b/src/state-display/state-display.ts @@ -0,0 +1,174 @@ +import type { HassEntity } from "home-assistant-js-websocket"; +import { html, LitElement, nothing, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import { ensureArray } from "../common/array/ensure-array"; +import { computeStateDomain } from "../common/entity/compute_state_domain"; +import "../components/ha-relative-time"; +import { isUnavailableState } from "../data/entity"; +import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../data/sensor"; +import { computeUpdateStateDisplay, UpdateEntity } from "../data/update"; +import "../panels/lovelace/components/hui-timestamp-display"; +import type { HomeAssistant } from "../types"; + +const TIMESTAMP_STATE_DOMAINS = ["button", "input_button", "scene"]; + +export const STATE_DISPLAY_SPECIAL_CONTENT = [ + "remaining_time", + "install_status", +] as const; + +// Special handling of state attributes per domain +export const STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS: Record< + string, + (typeof STATE_DISPLAY_SPECIAL_CONTENT)[number][] +> = { + timer: ["remaining_time"], + update: ["install_status"], +}; + +// Attributes that should not be shown if their value is 0 */ +export const HIDDEN_ZERO_ATTRIBUTES_DOMAINS: Record = { + valve: ["current_position"], + cover: ["current_position"], + fan: ["percentage"], + light: ["brightness"], +}; + +type StateContent = string | string[]; + +export const DEFAULT_STATE_CONTENT_DOMAINS: Record = { + climate: ["state", "current_temperature"], + cover: ["state", "current_position"], + fan: "percentage", + humidifier: ["state", "current_humidity"], + light: "brightness", + timer: "remaining_time", + update: "install_status", + valve: ["state", "current_position"], +}; + +@customElement("state-display") +class StateDisplay extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public stateObj!: HassEntity; + + @property({ attribute: false }) public content?: StateContent; + + protected createRenderRoot() { + return this; + } + + private get _content(): StateContent { + const domain = computeStateDomain(this.stateObj); + return this.content ?? DEFAULT_STATE_CONTENT_DOMAINS[domain] ?? "state"; + } + + private _computeContent( + content: string + ): TemplateResult<1> | string | undefined { + const stateObj = this.stateObj; + const domain = computeStateDomain(stateObj); + + if (content === "state") { + if ( + (stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP || + TIMESTAMP_STATE_DOMAINS.includes(domain)) && + !isUnavailableState(stateObj.state) + ) { + return html` + + `; + } + + return this.hass!.formatEntityState(stateObj); + } + // Check last-changed for backwards compatibility + if (content === "last_changed" || content === "last-changed") { + return html` + + `; + } + // Check last_updated for backwards compatibility + if (content === "last_updated" || content === "last-updated") { + return html` + + `; + } + if (content === "last_triggered") { + return html` + + `; + } + + const specialContent = (STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain] ?? + []) as string[]; + + if (specialContent.includes(content)) { + if (content === "install_status") { + return html` + ${computeUpdateStateDisplay(stateObj as UpdateEntity, this.hass!)} + `; + } + if (content === "remaining_time") { + import("./ha-timer-remaining-time"); + return html` + + `; + } + } + + const attribute = stateObj.attributes[content]; + + if ( + attribute == null || + (HIDDEN_ZERO_ATTRIBUTES_DOMAINS[domain]?.includes(content) && !attribute) + ) { + return undefined; + } + return this.hass!.formatEntityAttributeValue(stateObj, content); + } + + protected render() { + const stateObj = this.stateObj; + const contents = ensureArray(this._content); + + const values = contents + .map((content) => this._computeContent(content)) + .filter(Boolean); + + if (!values.length) { + return html`${this.hass!.formatEntityState(stateObj)}`; + } + + return html` + ${values.map( + (value, index, array) => + html`${value}${index < array.length - 1 ? " ⸱ " : nothing}` + )} + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "state-display": StateDisplay; + } +} diff --git a/src/state-summary/state-card-timer.ts b/src/state-summary/state-card-timer.ts index 134ebdb89c..661ce17bdc 100644 --- a/src/state-summary/state-card-timer.ts +++ b/src/state-summary/state-card-timer.ts @@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import "../components/entity/state-info"; import { haStyle } from "../resources/styles"; -import "../state-display/state-display-timer"; +import "../state-display/ha-timer-remaining-time"; import { HomeAssistant } from "../types"; @customElement("state-card-timer") @@ -23,10 +23,10 @@ class StateCardTimer extends LitElement { .inDialog=${this.inDialog} >
- + >
`; diff --git a/src/translations/en.json b/src/translations/en.json index 5b9c976290..1a2f6d871f 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1018,6 +1018,13 @@ }, "yaml-editor": { "copy_to_clipboard": "[%key:ui::panel::config::automation::editor::copy_to_clipboard%]" + }, + "state-content-picker": { + "state": "State", + "last_changed": "Last changed", + "last_updated": "Last updated", + "remaining_time": "Remaining time", + "install_status": "Install status" } }, "dialogs": { @@ -5981,12 +5988,7 @@ "show_entity_picture": "Show entity picture", "vertical": "Vertical", "hide_state": "Hide state", - "state_content": "State content", - "state_content_options": { - "state": "State", - "last-changed": "Last changed", - "last-updated": "Last updated" - } + "state_content": "State content" }, "vertical-stack": { "name": "Vertical stack", From f70126eb62acecda5c89081459e3681f7550d2c6 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 15 Jul 2024 14:11:03 +0200 Subject: [PATCH 24/97] Adjust row height in grid (#21311) * Set row height to 56px * Adjust padding and sizes * Adjust margin * Fix pointer-events * Fix image size * Clean code --- src/components/ha-grid-size-picker.ts | 4 +- src/components/tile/ha-tile-icon.ts | 8 +- src/components/tile/ha-tile-image.ts | 6 +- src/components/tile/ha-tile-info.ts | 2 +- .../common/card-feature-styles.ts | 30 +++++ .../hui-alarm-modes-card-feature.ts | 53 +++----- .../card-features/hui-card-feature.ts | 51 ++++++++ .../card-features/hui-card-features.ts | 74 +++++------ .../hui-climate-fan-modes-card-feature.ts | 111 +++++++--------- .../hui-climate-hvac-modes-card-feature.ts | 108 +++++++--------- .../hui-climate-preset-modes-card-feature.ts | 118 +++++++---------- .../hui-climate-swing-modes-card-feature.ts | 117 +++++++---------- .../hui-cover-open-close-card-feature.ts | 10 +- .../hui-cover-position-card-feature.ts | 60 ++++----- .../hui-cover-tilt-card-feature.ts | 12 +- .../hui-cover-tilt-position-card-feature.ts | 71 +++++------ .../hui-fan-preset-modes-card-feature.ts | 120 +++++++----------- .../hui-fan-speed-card-feature.ts | 95 ++++++-------- .../hui-humidifier-modes-card-feature.ts | 111 +++++++--------- .../hui-humidifier-toggle-card-feature.ts | 43 +++---- .../hui-lawn-mower-commands-card-feature.ts | 10 +- .../hui-light-brightness-card-feature.ts | 41 ++---- .../hui-light-color-temp-card-feature.ts | 61 ++++----- .../hui-lock-commands-card-feature.ts | 10 +- .../hui-lock-open-door-card-feature.ts | 58 ++++----- .../hui-numeric-input-card-feature.ts | 75 +++++------ .../hui-select-options-card-feature.ts | 60 ++++----- .../hui-target-humidity-card-feature.ts | 44 ++----- .../hui-target-temperature-card-feature.ts | 10 +- .../hui-update-actions-card-feature.ts | 10 +- .../hui-vacuum-commands-card-feature.ts | 10 +- ...ter-heater-operation-modes-card-feature.ts | 49 +++---- src/panels/lovelace/cards/hui-tile-card.ts | 117 +++++++++-------- .../lovelace/sections/hui-grid-section.ts | 2 +- 34 files changed, 762 insertions(+), 999 deletions(-) create mode 100644 src/panels/lovelace/card-features/common/card-feature-styles.ts create mode 100644 src/panels/lovelace/card-features/hui-card-feature.ts diff --git a/src/components/ha-grid-size-picker.ts b/src/components/ha-grid-size-picker.ts index 6ae6b7b37d..f62fc5c01d 100644 --- a/src/components/ha-grid-size-picker.ts +++ b/src/components/ha-grid-size-picker.ts @@ -20,7 +20,7 @@ export class HaGridSizeEditor extends LitElement { @property({ attribute: false }) public value?: GridSizeValue; - @property({ attribute: false }) public rows = 6; + @property({ attribute: false }) public rows = 8; @property({ attribute: false }) public columns = 4; @@ -205,7 +205,7 @@ export class HaGridSizeEditor extends LitElement { .preview { position: relative; grid-area: preview; - aspect-ratio: 1 / 1; + aspect-ratio: 1 / 1.2; } .preview > div { position: absolute; diff --git a/src/components/tile/ha-tile-icon.ts b/src/components/tile/ha-tile-icon.ts index f5ebe8863c..f1cd7ec633 100644 --- a/src/components/tile/ha-tile-icon.ts +++ b/src/components/tile/ha-tile-icon.ts @@ -17,7 +17,7 @@ export class HaTileIcon extends LitElement { return css` :host { --tile-icon-color: var(--disabled-color); - --mdc-icon-size: 24px; + --mdc-icon-size: 22px; } .shape::before { content: ""; @@ -32,9 +32,9 @@ export class HaTileIcon extends LitElement { } .shape { position: relative; - width: 40px; - height: 40px; - border-radius: 20px; + width: 36px; + height: 36px; + border-radius: 18px; display: flex; align-items: center; justify-content: center; diff --git a/src/components/tile/ha-tile-image.ts b/src/components/tile/ha-tile-image.ts index fc29eb57d0..143f4c176b 100644 --- a/src/components/tile/ha-tile-image.ts +++ b/src/components/tile/ha-tile-image.ts @@ -25,9 +25,9 @@ export class HaTileImage extends LitElement { return css` .image { position: relative; - width: 40px; - height: 40px; - border-radius: 20px; + width: 36px; + height: 36px; + border-radius: 18px; display: flex; flex: none; align-items: center; diff --git a/src/components/tile/ha-tile-info.ts b/src/components/tile/ha-tile-info.ts index b28e373077..68123eee6f 100644 --- a/src/components/tile/ha-tile-info.ts +++ b/src/components/tile/ha-tile-info.ts @@ -33,7 +33,7 @@ export class HaTileInfo extends LitElement { flex-direction: column; align-items: flex-start; justify-content: center; - min-height: 40px; + height: 36px; } span { text-overflow: ellipsis; diff --git a/src/panels/lovelace/card-features/common/card-feature-styles.ts b/src/panels/lovelace/card-features/common/card-feature-styles.ts new file mode 100644 index 0000000000..6a0fdcb1c0 --- /dev/null +++ b/src/panels/lovelace/card-features/common/card-feature-styles.ts @@ -0,0 +1,30 @@ +import { css } from "lit"; + +export const cardFeatureStyles = css` + ha-control-select-menu { + box-sizing: border-box; + --control-select-menu-height: var(--feature-height); + --control-select-menu-border-radius: var(--feature-border-radius); + line-height: 1.2; + display: block; + width: 100%; + } + ha-control-select { + --control-select-color: var(--feature-color); + --control-select-padding: 0; + --control-select-thickness: var(--feature-height); + --control-select-border-radius: var(--feature-border-radius); + --control-select-button-border-radius: var(--feature-border-radius); + } + ha-control-button-group { + --control-button-group-spacing: var(--feature-button-spacing); + --control-button-group-thickness: var(--feature-height); + } + ha-control-slider { + --control-slider-color: var(--feature-color); + --control-slider-background: var(--feature-color); + --control-slider-background-opacity: 0.2; + --control-slider-thickness: var(--feature-height); + --control-slider-border-radius: var(--feature-border-radius); + } +`; diff --git a/src/panels/lovelace/card-features/hui-alarm-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-alarm-modes-card-feature.ts index 2572f237c4..425f6b90e1 100644 --- a/src/panels/lovelace/card-features/hui-alarm-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-alarm-modes-card-feature.ts @@ -1,6 +1,6 @@ import { mdiShieldOff } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; @@ -21,6 +21,7 @@ import { import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { filterModes } from "./common/filter-modes"; import { AlarmModesCardFeatureConfig } from "./types"; @@ -135,44 +136,26 @@ class HuiAlarmModeCardFeature } return html` -
- - -
+ + `; } static get styles() { - return css` - ha-control-select { - --control-select-color: var(--feature-color); - --control-select-padding: 0; - --control-select-thickness: 40px; - --control-select-border-radius: 10px; - --control-select-button-border-radius: 10px; - } - ha-control-button-group { - margin: 0 12px 12px 12px; - --control-button-group-spacing: 12px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-card-feature.ts b/src/panels/lovelace/card-features/hui-card-feature.ts new file mode 100644 index 0000000000..a65fbd5ad5 --- /dev/null +++ b/src/panels/lovelace/card-features/hui-card-feature.ts @@ -0,0 +1,51 @@ +import type { HassEntity } from "home-assistant-js-websocket"; +import { LitElement, html, nothing } from "lit"; +import { customElement, property } from "lit/decorators"; +import { HomeAssistant } from "../../../types"; +import type { HuiErrorCard } from "../cards/hui-error-card"; +import { createCardFeatureElement } from "../create-element/create-card-feature-element"; +import type { LovelaceCardFeature } from "../types"; +import type { LovelaceCardFeatureConfig } from "./types"; + +@customElement("hui-card-feature") +export class HuiCardFeature extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public stateObj!: HassEntity; + + @property({ attribute: false }) public feature?: LovelaceCardFeatureConfig; + + @property({ attribute: false }) public color?: string; + + private _element?: LovelaceCardFeature | HuiErrorCard; + + private _getFeatureElement(feature: LovelaceCardFeatureConfig) { + if (!this._element) { + this._element = createCardFeatureElement(feature); + return this._element; + } + + return this._element; + } + + protected render() { + if (!this.feature) { + return nothing; + } + + const element = this._getFeatureElement(this.feature); + + if (this.hass) { + element.hass = this.hass; + (element as LovelaceCardFeature).stateObj = this.stateObj; + (element as LovelaceCardFeature).color = this.color; + } + return html`${element}`; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-card-feature": HuiCardFeature; + } +} diff --git a/src/panels/lovelace/card-features/hui-card-features.ts b/src/panels/lovelace/card-features/hui-card-features.ts index 101561c78d..c0c8bb9429 100644 --- a/src/panels/lovelace/card-features/hui-card-features.ts +++ b/src/panels/lovelace/card-features/hui-card-features.ts @@ -1,17 +1,8 @@ import type { HassEntity } from "home-assistant-js-websocket"; -import { - CSSResultGroup, - LitElement, - TemplateResult, - css, - html, - nothing, -} from "lit"; +import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { HomeAssistant } from "../../../types"; -import type { HuiErrorCard } from "../cards/hui-error-card"; -import { createCardFeatureElement } from "../create-element/create-card-feature-element"; -import type { LovelaceCardFeature } from "../types"; +import "./hui-card-feature"; import type { LovelaceCardFeatureConfig } from "./types"; @customElement("hui-card-features") @@ -24,44 +15,23 @@ export class HuiCardFeatures extends LitElement { @property({ attribute: false }) public color?: string; - private _featuresElements = new WeakMap< - LovelaceCardFeatureConfig, - LovelaceCardFeature | HuiErrorCard - >(); - - private _getFeatureElement(feature: LovelaceCardFeatureConfig) { - if (!this._featuresElements.has(feature)) { - const element = createCardFeatureElement(feature); - this._featuresElements.set(feature, element); - return element; - } - - return this._featuresElements.get(feature)!; - } - - private renderFeature( - featureConf: LovelaceCardFeatureConfig, - stateObj: HassEntity - ): TemplateResult { - const element = this._getFeatureElement(featureConf); - - if (this.hass) { - element.hass = this.hass; - (element as LovelaceCardFeature).stateObj = stateObj; - (element as LovelaceCardFeature).color = this.color; - } - - return html`${element}`; - } - protected render() { if (!this.features) { return nothing; } return html` - ${this.features.map((featureConf) => - this.renderFeature(featureConf, this.stateObj) - )} +
+ ${this.features.map( + (feature) => html` + + ` + )} +
`; } @@ -69,8 +39,24 @@ export class HuiCardFeatures extends LitElement { return css` :host { --feature-color: var(--state-icon-color); + --feature-padding: 12px; + --feature-height: 42px; + --feature-border-radius: 12px; + --feature-button-spacing: 12px; + position: relative; + width: 100%; + } + .container { + position: relative; display: flex; flex-direction: column; + padding: var(--feature-padding); + padding-top: 0px; + gap: var(--feature-padding); + width: 100%; + height: 100%; + box-sizing: border-box; + justify-content: space-evenly; } `; } diff --git a/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts index 3cfa5624b5..fb07ce89e5 100644 --- a/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts @@ -1,6 +1,6 @@ import { mdiFan } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeDomain } from "../../../common/entity/compute_domain"; @@ -14,8 +14,9 @@ import { ClimateEntity, ClimateEntityFeature } from "../../../data/climate"; import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; -import { ClimateFanModesCardFeatureConfig } from "./types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { filterModes } from "./common/filter-modes"; +import { ClimateFanModesCardFeatureConfig } from "./types"; export const supportsClimateFanModesCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -140,79 +141,55 @@ class HuiClimateFanModesCardFeature if (this._config.style === "icons") { return html` -
- - -
+ + `; } return html` -
- - ${this._currentFanMode - ? html`` - : html` `} - ${options.map( - (option) => html` - - ${option.icon}${option.label} - - ` - )} - -
+ + ${this._currentFanMode + ? html`` + : html` `} + ${options.map( + (option) => html` + + ${option.icon}${option.label} + + ` + )} + `; } static get styles() { - return css` - ha-control-select-menu { - box-sizing: border-box; - --control-select-menu-height: 40px; - --control-select-menu-border-radius: 10px; - line-height: 1.2; - display: block; - width: 100%; - } - ha-control-select { - --control-select-color: var(--feature-color); - --control-select-padding: 0; - --control-select-thickness: 40px; - --control-select-border-radius: 10px; - --control-select-button-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts index 3eab79b85e..65cf59959f 100644 --- a/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts @@ -1,6 +1,6 @@ import { mdiThermostat } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import { stopPropagation } from "../../../common/dom/stop_propagation"; @@ -19,6 +19,7 @@ import { import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { filterModes } from "./common/filter-modes"; import { ClimateHvacModesCardFeatureConfig } from "./types"; @@ -139,79 +140,56 @@ class HuiClimateHvacModesCardFeature if (this._config.style === "dropdown") { return html` -
- - ${this._currentHvacMode - ? html` - - ` - : html` - - `} - ${options.map( - (option) => html` - - ${option.icon}${option.label} - + + ${this._currentHvacMode + ? html` + ` - )} - -
+ : html` + + `} + ${options.map( + (option) => html` + + ${option.icon}${option.label} + + ` + )} + `; } return html` -
- - -
+ + `; } static get styles() { - return css` - ha-control-select-menu { - box-sizing: border-box; - --control-select-menu-height: 40px; - --control-select-menu-border-radius: 10px; - line-height: 1.2; - display: block; - width: 100%; - } - ha-control-select { - --control-select-padding: 0; - --control-select-thickness: 40px; - --control-select-border-radius: 10px; - --control-select-button-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts index 5cd1e233e5..90178bcb1d 100644 --- a/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts @@ -1,6 +1,6 @@ import { mdiTuneVariant } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeDomain } from "../../../common/entity/compute_domain"; @@ -14,8 +14,9 @@ import { ClimateEntity, ClimateEntityFeature } from "../../../data/climate"; import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; -import { ClimatePresetModesCardFeatureConfig } from "./types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { filterModes } from "./common/filter-modes"; +import { ClimatePresetModesCardFeatureConfig } from "./types"; export const supportsClimatePresetModesCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -142,84 +143,57 @@ class HuiClimatePresetModesCardFeature if (this._config.style === "icons") { return html` -
- - -
+ + `; } return html` -
- - ${this._currentPresetMode - ? html`` - : html` - - `} - ${options.map( - (option) => html` - - ${option.icon}${option.label} - - ` - )} - -
+ + ${this._currentPresetMode + ? html`` + : html` + + `} + ${options.map( + (option) => html` + + ${option.icon}${option.label} + + ` + )} + `; } static get styles() { - return css` - ha-control-select-menu { - box-sizing: border-box; - --control-select-menu-height: 40px; - --control-select-menu-border-radius: 10px; - line-height: 1.2; - display: block; - width: 100%; - } - ha-control-select { - --control-select-color: var(--feature-color); - --control-select-padding: 0; - --control-select-thickness: 40px; - --control-select-border-radius: 10px; - --control-select-button-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-climate-swing-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-swing-modes-card-feature.ts index adc88c1948..b61f096600 100644 --- a/src/panels/lovelace/card-features/hui-climate-swing-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-swing-modes-card-feature.ts @@ -1,6 +1,6 @@ import { mdiArrowOscillating } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeDomain } from "../../../common/entity/compute_domain"; @@ -14,8 +14,9 @@ import { ClimateEntity, ClimateEntityFeature } from "../../../data/climate"; import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; -import { ClimateSwingModesCardFeatureConfig } from "./types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { filterModes } from "./common/filter-modes"; +import { ClimateSwingModesCardFeatureConfig } from "./types"; export const supportsClimateSwingModesCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -142,82 +143,58 @@ class HuiClimateSwingModesCardFeature if (this._config.style === "icons") { return html` -
- - -
+ + `; } return html` -
- - ${this._currentSwingMode - ? html`` - : html` `} - ${options.map( - (option) => html` - - ${option.icon}${option.label} - - ` - )} - -
+ + ${this._currentSwingMode + ? html`` + : html` `} + ${options.map( + (option) => html` + + ${option.icon}${option.label} + + ` + )} + `; } static get styles() { - return css` - ha-control-select-menu { - box-sizing: border-box; - --control-select-menu-height: 40px; - --control-select-menu-border-radius: 10px; - line-height: 1.2; - display: block; - width: 100%; - } - ha-control-select { - --control-select-color: var(--feature-color); - --control-select-padding: 0; - --control-select-thickness: 40px; - --control-select-border-radius: 10px; - --control-select-button-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-cover-open-close-card-feature.ts b/src/panels/lovelace/card-features/hui-cover-open-close-card-feature.ts index 1858112361..06a7eca48e 100644 --- a/src/panels/lovelace/card-features/hui-cover-open-close-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-cover-open-close-card-feature.ts @@ -1,6 +1,6 @@ import { mdiStop } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, nothing } from "lit"; +import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { computeDomain } from "../../../common/entity/compute_domain"; import { @@ -18,6 +18,7 @@ import { } from "../../../data/cover"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { CoverOpenCloseCardFeatureConfig } from "./types"; export const supportsCoverOpenCloseCardFeature = (stateObj: HassEntity) => { @@ -128,12 +129,7 @@ class HuiCoverOpenCloseCardFeature } static get styles() { - return css` - ha-control-button-group { - margin: 0 12px 12px 12px; - --control-button-group-spacing: 12px; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-cover-position-card-feature.ts b/src/panels/lovelace/card-features/hui-cover-position-card-feature.ts index af7e8a5f23..43fadb8bda 100644 --- a/src/panels/lovelace/card-features/hui-cover-position-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-cover-position-card-feature.ts @@ -1,5 +1,5 @@ import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, nothing } from "lit"; +import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import { computeCssColor } from "../../../common/color/compute-color"; @@ -10,10 +10,11 @@ import { stateColorCss } from "../../../common/entity/state_color"; import { supportsFeature } from "../../../common/entity/supports-feature"; import { CoverEntityFeature } from "../../../data/cover"; import { UNAVAILABLE } from "../../../data/entity"; +import { DOMAIN_ATTRIBUTES_UNITS } from "../../../data/entity_attributes"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { CoverPositionCardFeatureConfig } from "./types"; -import { DOMAIN_ATTRIBUTES_UNITS } from "../../../data/entity_attributes"; export const supportsCoverPositionCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -72,32 +73,31 @@ class HuiCoverPositionCardFeature : stateColorCss(this.stateObj); const style = { - "--color": color, + "--feature-color": color, // Use open color for inactive state to avoid grey slider that looks disabled "--state-cover-inactive-color": openColor, }; return html` -
- -
+ `; } @@ -112,19 +112,7 @@ class HuiCoverPositionCardFeature } static get styles() { - return css` - ha-control-slider { - --control-slider-color: var(--color); - --control-slider-background: var(--color); - --control-slider-background-opacity: 0.2; - --control-slider-thickness: 40px; - --control-slider-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-cover-tilt-card-feature.ts b/src/panels/lovelace/card-features/hui-cover-tilt-card-feature.ts index d09c71aa62..9fbee23208 100644 --- a/src/panels/lovelace/card-features/hui-cover-tilt-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-cover-tilt-card-feature.ts @@ -1,19 +1,20 @@ import { mdiArrowBottomLeft, mdiArrowTopRight, mdiStop } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, nothing } from "lit"; +import { LitElement, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { computeDomain } from "../../../common/entity/compute_domain"; import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-control-button"; import "../../../components/ha-control-button-group"; import { + CoverEntityFeature, canCloseTilt, canOpenTilt, canStopTilt, - CoverEntityFeature, } from "../../../data/cover"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { CoverTiltCardFeatureConfig } from "./types"; export const supportsCoverTiltCardFeature = (stateObj: HassEntity) => { @@ -120,12 +121,7 @@ class HuiCoverTiltCardFeature } static get styles() { - return css` - ha-control-button-group { - margin: 0 12px 12px 12px; - --control-button-group-spacing: 12px; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-cover-tilt-position-card-feature.ts b/src/panels/lovelace/card-features/hui-cover-tilt-position-card-feature.ts index 48e1a809c5..5942c70045 100644 --- a/src/panels/lovelace/card-features/hui-cover-tilt-position-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-cover-tilt-position-card-feature.ts @@ -13,6 +13,7 @@ import { DOMAIN_ATTRIBUTES_UNITS } from "../../../data/entity_attributes"; import { generateTiltSliderTrackBackgroundGradient } from "../../../state-control/cover/ha-state-control-cover-tilt-position"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { CoverTiltPositionCardFeatureConfig } from "./types"; const GRADIENT = generateTiltSliderTrackBackgroundGradient(); @@ -72,33 +73,32 @@ class HuiCoverTiltPositionCardFeature : stateColorCss(this.stateObj); const style = { - "--color": color, + "--feature-color": color, // Use open color for inactive state to avoid grey slider that looks disabled "--state-cover-inactive-color": openColor, }; return html` -
- -
-
+ +
`; } @@ -113,24 +113,15 @@ class HuiCoverTiltPositionCardFeature } static get styles() { - return css` - ha-control-slider { - /* Force inactive state to be colored for the slider */ - --control-slider-color: var(--color); - --control-slider-background: var(--color); - --control-slider-background-opacity: 0.2; - --control-slider-thickness: 40px; - --control-slider-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - .gradient { - background: -webkit-linear-gradient(left, ${GRADIENT}); - opacity: 0.6; - } - `; + return [ + cardFeatureStyles, + css` + .gradient { + background: -webkit-linear-gradient(left, ${GRADIENT}); + opacity: 0.6; + } + `, + ]; } } diff --git a/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts index d1cfb0fcc2..51c054e26f 100644 --- a/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts @@ -1,6 +1,6 @@ import { mdiTuneVariant } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeDomain } from "../../../common/entity/compute_domain"; @@ -10,12 +10,13 @@ import "../../../components/ha-control-select"; import type { ControlSelectOption } from "../../../components/ha-control-select"; import "../../../components/ha-control-select-menu"; import type { HaControlSelectMenu } from "../../../components/ha-control-select-menu"; -import { FanEntity, FanEntityFeature } from "../../../data/fan"; import { UNAVAILABLE } from "../../../data/entity"; +import { FanEntity, FanEntityFeature } from "../../../data/fan"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; -import { FanPresetModesCardFeatureConfig } from "./types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { filterModes } from "./common/filter-modes"; +import { FanPresetModesCardFeatureConfig } from "./types"; export const supportsFanPresetModesCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -139,84 +140,57 @@ class HuiFanPresetModesCardFeature if (this._config.style === "icons") { return html` -
- - -
+ + `; } return html` -
- - ${this._currentPresetMode - ? html`` - : html` - - `} - ${options.map( - (option) => html` - - ${option.icon}${option.label} - - ` - )} - -
+ + ${this._currentPresetMode + ? html`` + : html` + + `} + ${options.map( + (option) => html` + + ${option.icon}${option.label} + + ` + )} + `; } static get styles() { - return css` - ha-control-select-menu { - box-sizing: border-box; - --control-select-menu-height: 40px; - --control-select-menu-border-radius: 10px; - line-height: 1.2; - display: block; - width: 100%; - } - ha-control-select { - --control-select-color: var(--feature-color); - --control-select-padding: 0; - --control-select-thickness: 40px; - --control-select-border-radius: 10px; - --control-select-button-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-fan-speed-card-feature.ts b/src/panels/lovelace/card-features/hui-fan-speed-card-feature.ts index 932c3a93d2..9ebc5b3ebb 100644 --- a/src/panels/lovelace/card-features/hui-fan-speed-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-fan-speed-card-feature.ts @@ -24,6 +24,7 @@ import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature } from "../types"; import { FanSpeedCardFeatureConfig } from "./types"; import { DOMAIN_ATTRIBUTES_UNITS } from "../../../data/entity_attributes"; +import { cardFeatureStyles } from "./common/card-feature-styles"; export const supportsFanSpeedCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -88,35 +89,11 @@ class HuiFanSpeedCardFeature extends LitElement implements LovelaceCardFeature { const speed = fanPercentageToSpeed(this.stateObj, percentage); return html` -
- - -
- `; - } - - const value = Math.max(Math.round(percentage), 0); - - return html` -
- -
+ > + + `; + } + + const value = Math.max(Math.round(percentage), 0); + + return html` + `; } @@ -153,28 +150,16 @@ class HuiFanSpeedCardFeature extends LitElement implements LovelaceCardFeature { } static get styles() { - return css` - ha-control-slider { - --control-slider-color: var(--feature-color); - --control-slider-background: var(--feature-color); - --control-slider-background-opacity: 0.2; - --control-slider-thickness: 40px; - --control-slider-border-radius: 10px; - } - ha-control-select { - --control-select-color: var(--feature-color); - --control-select-background: var(--feature-color); - --control-select-background-opacity: 0.2; - --control-select-padding: 0; - --control-select-thickness: 40px; - --control-select-border-radius: 10px; - --control-select-button-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return [ + cardFeatureStyles, + css` + ha-control-select { + /* Color the background to match the slider style */ + --control-select-background: var(--feature-color); + --control-select-background-opacity: 0.2; + } + `, + ]; } } diff --git a/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts index ee5176f9ad..510d946857 100644 --- a/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts @@ -1,6 +1,6 @@ import { mdiTuneVariant } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeDomain } from "../../../common/entity/compute_domain"; @@ -17,8 +17,9 @@ import { } from "../../../data/humidifier"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; -import { HumidifierModesCardFeatureConfig } from "./types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { filterModes } from "./common/filter-modes"; +import { HumidifierModesCardFeatureConfig } from "./types"; export const supportsHumidifierModesCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -143,79 +144,55 @@ class HuiHumidifierModesCardFeature if (this._config.style === "icons") { return html` -
- - -
+ + `; } return html` -
- - ${this._currentMode - ? html`` - : html``} - ${options.map( - (option) => html` - - ${option.icon}${option.label} - - ` - )} - -
+ + ${this._currentMode + ? html`` + : html``} + ${options.map( + (option) => html` + + ${option.icon}${option.label} + + ` + )} + `; } static get styles() { - return css` - ha-control-select-menu { - box-sizing: border-box; - --control-select-menu-height: 40px; - --control-select-menu-border-radius: 10px; - line-height: 1.2; - display: block; - width: 100%; - } - ha-control-select { - --control-select-color: var(--feature-color); - --control-select-padding: 0; - --control-select-thickness: 40px; - --control-select-border-radius: 10px; - --control-select-button-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-humidifier-toggle-card-feature.ts b/src/panels/lovelace/card-features/hui-humidifier-toggle-card-feature.ts index 647da49f1a..a1c2abba64 100644 --- a/src/panels/lovelace/card-features/hui-humidifier-toggle-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-humidifier-toggle-card-feature.ts @@ -1,6 +1,6 @@ import { mdiPower, mdiWaterPercent } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { LitElement, PropertyValues, TemplateResult, css, html } from "lit"; +import { LitElement, PropertyValues, TemplateResult, html } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import { computeDomain } from "../../../common/entity/compute_domain"; @@ -11,6 +11,7 @@ import { UNAVAILABLE } from "../../../data/entity"; import { HumidifierEntity, HumidifierState } from "../../../data/humidifier"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { HumidifierToggleCardFeatureConfig } from "./types"; export const supportsHumidifierToggleCardFeature = (stateObj: HassEntity) => { @@ -95,37 +96,23 @@ class HuiHumidifierToggleCardFeature })); return html` -
- - -
+ + `; } static get styles() { - return css` - ha-control-select { - --control-select-color: var(--feature-color); - --control-select-padding: 0; - --control-select-thickness: 40px; - --control-select-border-radius: 10px; - --control-select-button-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-lawn-mower-commands-card-feature.ts b/src/panels/lovelace/card-features/hui-lawn-mower-commands-card-feature.ts index 52ec9c59d2..de74459b01 100644 --- a/src/panels/lovelace/card-features/hui-lawn-mower-commands-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-lawn-mower-commands-card-feature.ts @@ -1,6 +1,6 @@ import { mdiHomeImportOutline, mdiPause, mdiPlay } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { LitElement, css, html, nothing } from "lit"; +import { LitElement, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { computeDomain } from "../../../common/entity/compute_domain"; import { supportsFeature } from "../../../common/entity/supports-feature"; @@ -14,6 +14,7 @@ import { } from "../../../data/lawn_mower"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { LAWN_MOWER_COMMANDS, LawnMowerCommand, @@ -171,12 +172,7 @@ class HuiLawnMowerCommandCardFeature } static get styles() { - return css` - ha-control-button-group { - margin: 0 12px 12px 12px; - --control-button-group-spacing: 12px; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-light-brightness-card-feature.ts b/src/panels/lovelace/card-features/hui-light-brightness-card-feature.ts index 7eeeb3d3bd..3390f01e65 100644 --- a/src/panels/lovelace/card-features/hui-light-brightness-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-light-brightness-card-feature.ts @@ -1,5 +1,5 @@ import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, nothing } from "lit"; +import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { computeDomain } from "../../../common/entity/compute_domain"; import { stateActive } from "../../../common/entity/state_active"; @@ -8,6 +8,7 @@ import { UNAVAILABLE } from "../../../data/entity"; import { lightSupportsBrightness } from "../../../data/light"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { LightBrightnessCardFeatureConfig } from "./types"; export const supportsLightBrightnessCardFeature = (stateObj: HassEntity) => { @@ -58,19 +59,17 @@ class HuiLightBrightnessCardFeature : undefined; return html` -
- -
+ `; } @@ -85,19 +84,7 @@ class HuiLightBrightnessCardFeature } static get styles() { - return css` - ha-control-slider { - --control-slider-color: var(--feature-color); - --control-slider-background: var(--feature-color); - --control-slider-background-opacity: 0.2; - --control-slider-thickness: 40px; - --control-slider-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-light-color-temp-card-feature.ts b/src/panels/lovelace/card-features/hui-light-color-temp-card-feature.ts index fa7ed6b9d9..f7a71b4ece 100644 --- a/src/panels/lovelace/card-features/hui-light-color-temp-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-light-color-temp-card-feature.ts @@ -16,6 +16,7 @@ import { LightColorMode, lightSupportsColorMode } from "../../../data/light"; import { generateColorTemperatureGradient } from "../../../dialogs/more-info/components/lights/light-color-temp-picker"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { LightColorTempCardFeatureConfig } from "./types"; export const supportsLightColorTempCardFeature = (stateObj: HassEntity) => { @@ -73,23 +74,21 @@ class HuiLightColorTempCardFeature const gradient = this._generateTemperatureGradient(minKelvin!, maxKelvin); return html` -
- -
+ `; } @@ -108,22 +107,18 @@ class HuiLightColorTempCardFeature } static get styles() { - return css` - ha-control-slider { - --control-slider-color: var(--feature-color); - --control-slider-background: -webkit-linear-gradient( - left, - var(--gradient) - ); - --control-slider-background-opacity: 1; - --control-slider-thickness: 40px; - --control-slider-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return [ + cardFeatureStyles, + css` + ha-control-slider { + --control-slider-background: -webkit-linear-gradient( + left, + var(--gradient) + ); + --control-slider-background-opacity: 1; + } + `, + ]; } } diff --git a/src/panels/lovelace/card-features/hui-lock-commands-card-feature.ts b/src/panels/lovelace/card-features/hui-lock-commands-card-feature.ts index 19d737bb21..1c321134fd 100644 --- a/src/panels/lovelace/card-features/hui-lock-commands-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-lock-commands-card-feature.ts @@ -1,6 +1,6 @@ import { mdiLock, mdiLockOpenVariant } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; +import { CSSResultGroup, LitElement, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { computeDomain } from "../../../common/entity/compute_domain"; @@ -14,6 +14,7 @@ import { } from "../../../data/lock"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { LockCommandsCardFeatureConfig } from "./types"; export const supportsLockCommandsCardFeature = (stateObj: HassEntity) => { @@ -88,12 +89,7 @@ class HuiLockCommandsCardFeature } static get styles(): CSSResultGroup { - return css` - ha-control-button-group { - margin: 0 12px 12px 12px; - --control-button-group-spacing: 12px; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-lock-open-door-card-feature.ts b/src/panels/lovelace/card-features/hui-lock-open-door-card-feature.ts index 298daabb55..a5a0462289 100644 --- a/src/panels/lovelace/card-features/hui-lock-open-door-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-lock-open-door-card-feature.ts @@ -15,6 +15,7 @@ import { import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature } from "../types"; import { LockOpenDoorCardFeatureConfig } from "./types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; export const supportsLockOpenDoorCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -112,35 +113,34 @@ class HuiLockOpenDoorCardFeature } static get styles(): CSSResultGroup { - return css` - ha-control-button { - font-size: 14px; - } - ha-control-button-group { - margin: 0 12px 12px 12px; - --control-button-group-spacing: 12px; - } - .open-button { - width: 130px; - } - .open-button.confirm { - --control-button-background-color: var(--warning-color); - } - .open-done { - font-size: 14px; - line-height: 14px; - display: flex; - align-items: center; - justify-content: center; - flex-direction: row; - gap: 8px; - font-weight: 500; - color: var(--success-color); - margin: 0 12px 12px 12px; - height: 40px; - text-align: center; - } - `; + return [ + cardFeatureStyles, + css` + ha-control-button { + font-size: 14px; + } + .open-button { + width: 130px; + } + .open-button.confirm { + --control-button-background-color: var(--warning-color); + } + .open-done { + font-size: 14px; + line-height: 14px; + display: flex; + align-items: center; + justify-content: center; + flex-direction: row; + gap: 8px; + font-weight: 500; + color: var(--success-color); + margin: 0 12px 12px 12px; + height: 40px; + text-align: center; + } + `, + ]; } } diff --git a/src/panels/lovelace/card-features/hui-numeric-input-card-feature.ts b/src/panels/lovelace/card-features/hui-numeric-input-card-feature.ts index e725a446ab..50ab4a5d6f 100644 --- a/src/panels/lovelace/card-features/hui-numeric-input-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-numeric-input-card-feature.ts @@ -1,16 +1,17 @@ import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, nothing, PropertyValues } from "lit"; +import { html, LitElement, nothing, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { computeDomain } from "../../../common/entity/compute_domain"; -import { isUnavailableState } from "../../../data/entity"; -import { HomeAssistant } from "../../../types"; -import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; -import { NumericInputCardFeatureConfig } from "./types"; import "../../../components/ha-control-button"; import "../../../components/ha-control-button-group"; import "../../../components/ha-control-number-buttons"; import "../../../components/ha-control-slider"; import "../../../components/ha-icon"; +import { isUnavailableState } from "../../../data/entity"; +import { HomeAssistant } from "../../../types"; +import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; +import { NumericInputCardFeatureConfig } from "./types"; export const supportsNumericInputCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -81,50 +82,36 @@ class HuiNumericInputCardFeature const stateObj = this.stateObj; + if (this._config.style === "buttons") { + return html` + + `; + } return html` -
- ${this._config.style === "buttons" - ? html`` - : html``} -
+ `; } static get styles() { - return css` - ha-control-number-buttons { - width: auto; - } - ha-control-slider { - --control-slider-color: var(--feature-color); - --control-slider-background: var(--feature-color); - --control-slider-background-opacity: 0.2; - --control-slider-thickness: 40px; - --control-slider-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-select-options-card-feature.ts b/src/panels/lovelace/card-features/hui-select-options-card-feature.ts index 60b99116e4..8f63910ca4 100644 --- a/src/panels/lovelace/card-features/hui-select-options-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-select-options-card-feature.ts @@ -1,5 +1,5 @@ import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, nothing, PropertyValues } from "lit"; +import { html, LitElement, nothing, PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeDomain } from "../../../common/entity/compute_domain"; @@ -10,8 +10,9 @@ import { InputSelectEntity } from "../../../data/input_select"; import { SelectEntity } from "../../../data/select"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; -import { SelectOptionsCardFeatureConfig } from "./types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { filterModes } from "./common/filter-modes"; +import { SelectOptionsCardFeatureConfig } from "./types"; export const supportsSelectOptionsCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -119,45 +120,30 @@ class HuiSelectOptionsCardFeature ); return html` -
- - ${options.map( - (option) => html` - - ${this.hass!.formatEntityState(stateObj, option)} - - ` - )} - -
+ + ${options.map( + (option) => html` + + ${this.hass!.formatEntityState(stateObj, option)} + + ` + )} + `; } static get styles() { - return css` - ha-control-select-menu { - box-sizing: border-box; - --control-select-menu-height: 40px; - --control-select-menu-border-radius: 10px; - line-height: 1.2; - display: block; - width: 100%; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-target-humidity-card-feature.ts b/src/panels/lovelace/card-features/hui-target-humidity-card-feature.ts index 2f637d050b..cb10385bc6 100644 --- a/src/panels/lovelace/card-features/hui-target-humidity-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-target-humidity-card-feature.ts @@ -1,5 +1,5 @@ import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, nothing, PropertyValues } from "lit"; +import { html, LitElement, nothing, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { computeDomain } from "../../../common/entity/compute_domain"; import "../../../components/ha-control-slider"; @@ -7,6 +7,7 @@ import { UNAVAILABLE } from "../../../data/entity"; import { HumidifierEntity } from "../../../data/humidifier"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { TargetHumidityCardFeatureConfig } from "./types"; export const supportsTargetHumidityCardFeature = (stateObj: HassEntity) => { @@ -84,39 +85,22 @@ class HuiTargetHumidityCardFeature } return html` -
- -
+ `; } static get styles() { - return css` - ha-control-slider { - --control-slider-color: var(--feature-color); - --control-slider-background: var(--feature-color); - --control-slider-background-opacity: 0.2; - --control-slider-thickness: 40px; - --control-slider-border-radius: 10px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-target-temperature-card-feature.ts b/src/panels/lovelace/card-features/hui-target-temperature-card-feature.ts index 638eeab585..7ed7bdb3f2 100644 --- a/src/panels/lovelace/card-features/hui-target-temperature-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-target-temperature-card-feature.ts @@ -1,5 +1,5 @@ import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, nothing, PropertyValues } from "lit"; +import { html, LitElement, nothing, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import { UNIT_F } from "../../../common/const"; @@ -18,6 +18,7 @@ import { } from "../../../data/water_heater"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { TargetTemperatureCardFeatureConfig } from "./types"; type Target = "value" | "low" | "high"; @@ -283,12 +284,7 @@ class HuiTargetTemperatureCardFeature } static get styles() { - return css` - ha-control-button-group { - margin: 0 12px 12px 12px; - --control-button-group-spacing: 12px; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-update-actions-card-feature.ts b/src/panels/lovelace/card-features/hui-update-actions-card-feature.ts index a2f9ddb8ad..173a32883d 100644 --- a/src/panels/lovelace/card-features/hui-update-actions-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-update-actions-card-feature.ts @@ -1,6 +1,6 @@ import { mdiCancel, mdiCellphoneArrowDown } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { LitElement, css, html, nothing } from "lit"; +import { LitElement, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { computeDomain } from "../../../common/entity/compute_domain"; import { stateActive } from "../../../common/entity/state_active"; @@ -16,6 +16,7 @@ import { import { showUpdateBackupDialogParams } from "../../../dialogs/update_backup/show-update-backup-dialog"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { UpdateActionsCardFeatureConfig } from "./types"; export const DEFAULT_UPDATE_BACKUP_OPTION = "ask"; @@ -149,12 +150,7 @@ class HuiUpdateActionsCardFeature } static get styles() { - return css` - ha-control-button-group { - margin: 0 12px 12px 12px; - --control-button-group-spacing: 12px; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-vacuum-commands-card-feature.ts b/src/panels/lovelace/card-features/hui-vacuum-commands-card-feature.ts index 02ceff183c..228d3032d7 100644 --- a/src/panels/lovelace/card-features/hui-vacuum-commands-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-vacuum-commands-card-feature.ts @@ -8,7 +8,7 @@ import { mdiTargetVariant, } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { LitElement, css, html, nothing } from "lit"; +import { LitElement, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { computeDomain } from "../../../common/entity/compute_domain"; import { supportsFeature } from "../../../common/entity/supports-feature"; @@ -25,6 +25,7 @@ import { } from "../../../data/vacuum"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { VACUUM_COMMANDS, VacuumCommand, @@ -210,12 +211,7 @@ class HuiVacuumCommandCardFeature } static get styles() { - return css` - ha-control-button-group { - margin: 0 12px 12px 12px; - --control-button-group-spacing: 12px; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/card-features/hui-water-heater-operation-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-water-heater-operation-modes-card-feature.ts index f676b16ca6..a1c7c6690b 100644 --- a/src/panels/lovelace/card-features/hui-water-heater-operation-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-water-heater-operation-modes-card-feature.ts @@ -1,5 +1,5 @@ import { HassEntity } from "home-assistant-js-websocket"; -import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import { computeDomain } from "../../../common/entity/compute_domain"; @@ -18,8 +18,9 @@ import { } from "../../../data/water_heater"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; -import { WaterHeaterOperationModesCardFeatureConfig } from "./types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; import { filterModes } from "./common/filter-modes"; +import { WaterHeaterOperationModesCardFeatureConfig } from "./types"; export const supportsWaterHeaterOperationModesCardFeature = ( stateObj: HassEntity @@ -118,41 +119,23 @@ class HuiWaterHeaterOperationModeCardFeature })); return html` -
- - -
+ + `; } static get styles() { - return css` - ha-control-select { - --control-select-color: var(--feature-color); - --control-select-padding: 0; - --control-select-thickness: 40px; - --control-select-border-radius: 10px; - --control-select-button-border-radius: 10px; - } - ha-control-button-group { - margin: 0 12px 12px 12px; - --control-button-group-spacing: 12px; - } - .container { - padding: 0 12px 12px 12px; - width: auto; - } - `; + return cardFeatureStyles; } } diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 419b5bdfc6..50d2838143 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -113,8 +113,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard { let grid_min_columns = 2; let grid_rows = 1; if (this._config?.features?.length) { - const featureHeight = Math.ceil((this._config.features.length * 2) / 3); - grid_rows += featureHeight; + grid_rows += this._config.features.length; } if (this._config?.vertical) { grid_rows++; @@ -279,51 +278,53 @@ export class HuiTileCard extends LitElement implements LovelaceCard { >
-
-
- ${imageUrl - ? html` - - ` - : html` - - - - `} - ${renderTileBadge(stateObj, this.hass)} +
+
+
+ ${imageUrl + ? html` + + ` + : html` + + + + `} + ${renderTileBadge(stateObj, this.hass)} +
+
- + ${this._config.features + ? html` + + ` + : nothing}
- ${this._config.features - ? html` - - ` - : nothing} `; } @@ -371,31 +372,43 @@ export class HuiTileCard extends LitElement implements LovelaceCard { margin: calc(-1 * var(--ha-card-border-width, 1px)); overflow: hidden; } + .container { + margin: calc(-1 * var(--ha-card-border-width, 1px)); + display: flex; + flex-direction: column; + flex: 1; + } .content { + position: relative; display: flex; flex-direction: row; align-items: center; - padding: 12px; + padding: 0 10px; + min-height: var(--row-height, 56px); + flex: 1; + pointer-events: none; } .vertical { flex-direction: column; text-align: center; + justify-content: center; } .vertical .icon-container { - margin-bottom: 12px; + margin-bottom: 10px; margin-right: 0; margin-inline-start: initial; margin-inline-end: initial; } .vertical ha-tile-info { width: 100%; + flex: none; } .icon-container { position: relative; flex: none; - margin-right: 12px; + margin-right: 10px; margin-inline-start: initial; - margin-inline-end: 12px; + margin-inline-end: 10px; direction: var(--direction); transition: transform 180ms ease-in-out; } @@ -414,8 +427,8 @@ export class HuiTileCard extends LitElement implements LovelaceCard { inset-inline-end: -3px; inset-inline-start: initial; } - .icon-container:not([role="button"]) { - pointer-events: none; + .icon-container[role="button"] { + pointer-events: auto; } .icon-container[role="button"]:focus-visible, .icon-container[role="button"]:active { @@ -423,11 +436,9 @@ export class HuiTileCard extends LitElement implements LovelaceCard { } ha-tile-info { position: relative; - flex: 1; min-width: 0; transition: background-color 180ms ease-in-out; box-sizing: border-box; - pointer-events: none; } hui-card-features { --feature-color: var(--tile-color); diff --git a/src/panels/lovelace/sections/hui-grid-section.ts b/src/panels/lovelace/sections/hui-grid-section.ts index 47e33c7102..c29334cd51 100644 --- a/src/panels/lovelace/sections/hui-grid-section.ts +++ b/src/panels/lovelace/sections/hui-grid-section.ts @@ -214,7 +214,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement { --column-count: 4; --row-gap: var(--ha-section-grid-row-gap, 8px); --column-gap: var(--ha-section-grid-column-gap, 8px); - --row-height: 66px; + --row-height: var(--ha-section-grid-row-height, 56px); display: flex; flex-direction: column; gap: var(--row-gap); From 2a970b8416e07c6cb3a4fec4119544b78ca77ff8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:54:29 +0200 Subject: [PATCH 25/97] Update fullcalendar monorepo to v6.1.15 (#21404) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 14 +++++----- yarn.lock | 72 ++++++++++++++++++++++++++-------------------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index 8a7457e9f1..6bbc959c6b 100644 --- a/package.json +++ b/package.json @@ -43,12 +43,12 @@ "@formatjs/intl-numberformat": "8.10.3", "@formatjs/intl-pluralrules": "5.2.14", "@formatjs/intl-relativetimeformat": "11.2.14", - "@fullcalendar/core": "6.1.11", - "@fullcalendar/daygrid": "6.1.11", - "@fullcalendar/interaction": "6.1.11", - "@fullcalendar/list": "6.1.11", - "@fullcalendar/luxon3": "6.1.11", - "@fullcalendar/timegrid": "6.1.11", + "@fullcalendar/core": "6.1.15", + "@fullcalendar/daygrid": "6.1.15", + "@fullcalendar/interaction": "6.1.15", + "@fullcalendar/list": "6.1.15", + "@fullcalendar/luxon3": "6.1.15", + "@fullcalendar/timegrid": "6.1.15", "@lezer/highlight": "1.2.0", "@lit-labs/context": "0.4.1", "@lit-labs/motion": "1.0.7", @@ -253,7 +253,7 @@ "lit": "2.8.0", "clean-css": "5.3.3", "@lit/reactive-element": "1.6.3", - "@fullcalendar/daygrid": "6.1.11", + "@fullcalendar/daygrid": "6.1.15", "sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch", "leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch" }, diff --git a/yarn.lock b/yarn.lock index 136ad75283..18ffe57d7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1762,60 +1762,60 @@ __metadata: languageName: node linkType: hard -"@fullcalendar/core@npm:6.1.11": - version: 6.1.11 - resolution: "@fullcalendar/core@npm:6.1.11" +"@fullcalendar/core@npm:6.1.15": + version: 6.1.15 + resolution: "@fullcalendar/core@npm:6.1.15" dependencies: preact: "npm:~10.12.1" - checksum: 10/7624841879d34fdf769c7610e95cef820ea52eb45c74d51be426bb2acc05cb57806785fc1b1a1282b156470c1f04c976314327fbff63e1b115b7108649aeb1c8 + checksum: 10/abb08942b5b281a7b7e4a0c6924cc3e7d0470fde3fc15b74fea2e420880aa062bf1ddd64ba55e10c93a2f576815facaaaa81d968f997045a3cc7fd7116f092a0 languageName: node linkType: hard -"@fullcalendar/daygrid@npm:6.1.11": - version: 6.1.11 - resolution: "@fullcalendar/daygrid@npm:6.1.11" +"@fullcalendar/daygrid@npm:6.1.15": + version: 6.1.15 + resolution: "@fullcalendar/daygrid@npm:6.1.15" peerDependencies: - "@fullcalendar/core": ~6.1.11 - checksum: 10/c6165c22405d0fee19b2e4ac54b35115c8bc7e176e3ef60f06b1d7af93d4da1fa571acbd169faa3ea4743c55f6374c6e7f7cc774026a1ae0b83d80db33c4b391 + "@fullcalendar/core": ~6.1.15 + checksum: 10/70ec662470bda7a3d1e08060676f07d4ede18ca761992b39d1200925cf1feca5429e2fdab17234d100a204e254e7eb132d45d8eb0f89c775a65fa3e3d453d719 languageName: node linkType: hard -"@fullcalendar/interaction@npm:6.1.11": - version: 6.1.11 - resolution: "@fullcalendar/interaction@npm:6.1.11" +"@fullcalendar/interaction@npm:6.1.15": + version: 6.1.15 + resolution: "@fullcalendar/interaction@npm:6.1.15" peerDependencies: - "@fullcalendar/core": ~6.1.11 - checksum: 10/6d669d59f20615b71d42d0d053af39d512e824381d575554e016c9221f3f98e17b7770341711322f8455a9c4729450479e2cf4bf026ff123540bf671c6f59f33 + "@fullcalendar/core": ~6.1.15 + checksum: 10/43db4a1346abceca6229ce6185a19625ec574815f68d8b85da6e0a5cfed372a2a620aab9cf23ceed09255974fea9661e2659578a0f1da18402c5ea1ac50c592a languageName: node linkType: hard -"@fullcalendar/list@npm:6.1.11": - version: 6.1.11 - resolution: "@fullcalendar/list@npm:6.1.11" +"@fullcalendar/list@npm:6.1.15": + version: 6.1.15 + resolution: "@fullcalendar/list@npm:6.1.15" peerDependencies: - "@fullcalendar/core": ~6.1.11 - checksum: 10/13bb92c7ca4c7808ec57c1ada55a724ff7e8928bb118e1d9bbaf20fab8c6fb2486101e37b9422dc88ef50342354d35c436bbfa161b5ff09379a11c317f26c725 + "@fullcalendar/core": ~6.1.15 + checksum: 10/acc65a35c0737f09edfae58e4503ebec9812e238dd1dfeb9cc4fee5c4c976e247464904622ae6dd049c06ee46ca0ee75f06e5903a8375a6495a9d832d25b566c languageName: node linkType: hard -"@fullcalendar/luxon3@npm:6.1.11": - version: 6.1.11 - resolution: "@fullcalendar/luxon3@npm:6.1.11" +"@fullcalendar/luxon3@npm:6.1.15": + version: 6.1.15 + resolution: "@fullcalendar/luxon3@npm:6.1.15" peerDependencies: - "@fullcalendar/core": ~6.1.11 + "@fullcalendar/core": ~6.1.15 luxon: ^3.0.0 - checksum: 10/fc302aad0d1b080aac800956921358f10682d8b62d49ff78db85fbc6ea15c8799729679d164d70ac71cee77e0744da55cdfc9b295dcb35e0d6bf673649fd805d + checksum: 10/c3174389f94f013425ee466e540b300888fa4db8d5d4978534e92503dd6aad11a005ece709cc37fae699aef42974f2c149aa3283d816c57fb25f1c1684950bc5 languageName: node linkType: hard -"@fullcalendar/timegrid@npm:6.1.11": - version: 6.1.11 - resolution: "@fullcalendar/timegrid@npm:6.1.11" +"@fullcalendar/timegrid@npm:6.1.15": + version: 6.1.15 + resolution: "@fullcalendar/timegrid@npm:6.1.15" dependencies: - "@fullcalendar/daygrid": "npm:~6.1.11" + "@fullcalendar/daygrid": "npm:~6.1.15" peerDependencies: - "@fullcalendar/core": ~6.1.11 - checksum: 10/b2794502a0aa35c33405e35b05d367abab983d15af568a285123a673414bf545c298f7227b661290f99a2ef37e90013513cab3e9d4d0c15840212cd3b429c99d + "@fullcalendar/core": ~6.1.15 + checksum: 10/28d7b829f31b9593933b4688878b1ac745406bab3c5970ad3bf0800b2689cff9296f27e1ae6a1c5729da336b30d2a47345186d9482aeb967e8157708d2609004 languageName: node linkType: hard @@ -8928,12 +8928,12 @@ __metadata: "@formatjs/intl-numberformat": "npm:8.10.3" "@formatjs/intl-pluralrules": "npm:5.2.14" "@formatjs/intl-relativetimeformat": "npm:11.2.14" - "@fullcalendar/core": "npm:6.1.11" - "@fullcalendar/daygrid": "npm:6.1.11" - "@fullcalendar/interaction": "npm:6.1.11" - "@fullcalendar/list": "npm:6.1.11" - "@fullcalendar/luxon3": "npm:6.1.11" - "@fullcalendar/timegrid": "npm:6.1.11" + "@fullcalendar/core": "npm:6.1.15" + "@fullcalendar/daygrid": "npm:6.1.15" + "@fullcalendar/interaction": "npm:6.1.15" + "@fullcalendar/list": "npm:6.1.15" + "@fullcalendar/luxon3": "npm:6.1.15" + "@fullcalendar/timegrid": "npm:6.1.15" "@koa/cors": "npm:5.0.0" "@lezer/highlight": "npm:1.2.0" "@lit-labs/context": "npm:0.4.1" From ef3758da5545fec9a776d3cea6ee4dd265a373ac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 18:32:13 +0200 Subject: [PATCH 26/97] Update dependency prettier to v3.3.3 (#21408) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com> --- package.json | 2 +- src/common/navigate.ts | 6 ++++-- src/components/chart/state-history-chart-timeline.ts | 4 ++-- .../data-table/dialog-data-table-settings.ts | 3 ++- src/components/data-table/ha-data-table.ts | 9 +++++---- src/components/entity/ha-state-label-badge.ts | 2 +- src/components/map/ha-map.ts | 4 ++-- src/data/cover.ts | 4 ++-- src/fake_data/provide_hass.ts | 2 +- .../card-features/hui-cover-position-card-feature.ts | 2 +- .../card-features/hui-fan-speed-card-feature.ts | 2 +- .../lovelace/components/hui-generic-entity-row.ts | 2 +- .../editor/conditions/types/ha-card-condition-state.ts | 4 ++-- src/state-control/fan/ha-state-control-fan-speed.ts | 2 +- src/state/connection-mixin.ts | 2 +- yarn.lock | 10 +++++----- 16 files changed, 32 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 6bbc959c6b..f22757c1d0 100644 --- a/package.json +++ b/package.json @@ -224,7 +224,7 @@ "object-hash": "3.0.0", "open": "10.1.0", "pinst": "3.0.0", - "prettier": "3.3.2", + "prettier": "3.3.3", "rollup": "2.79.1", "rollup-plugin-string": "3.0.0", "rollup-plugin-terser": "7.0.2", diff --git a/src/common/navigate.ts b/src/common/navigate.ts index 86c61173fb..53a9497bd6 100644 --- a/src/common/navigate.ts +++ b/src/common/navigate.ts @@ -25,7 +25,9 @@ export const navigate = (path: string, options?: NavigateOptions) => { if (__DEMO__) { if (replace) { mainWindow.history.replaceState( - mainWindow.history.state?.root ? { root: true } : options?.data ?? null, + mainWindow.history.state?.root + ? { root: true } + : (options?.data ?? null), "", `${mainWindow.location.pathname}#${path}` ); @@ -34,7 +36,7 @@ export const navigate = (path: string, options?: NavigateOptions) => { } } else if (replace) { mainWindow.history.replaceState( - mainWindow.history.state?.root ? { root: true } : options?.data ?? null, + mainWindow.history.state?.root ? { root: true } : (options?.data ?? null), "", path ); diff --git a/src/components/chart/state-history-chart-timeline.ts b/src/components/chart/state-history-chart-timeline.ts index c70711ae64..0129ef5ca8 100644 --- a/src/components/chart/state-history-chart-timeline.ts +++ b/src/components/chart/state-history-chart-timeline.ts @@ -159,10 +159,10 @@ export class StateHistoryChartTimeline extends LitElement { }, afterUpdate: (y) => { const yWidth = this.showNames - ? y.width ?? 0 + ? (y.width ?? 0) : computeRTL(this.hass) ? 0 - : y.left ?? 0; + : (y.left ?? 0); if ( this._yWidth !== Math.floor(yWidth) && y.ticks.length === this.data.length diff --git a/src/components/data-table/dialog-data-table-settings.ts b/src/components/data-table/dialog-data-table-settings.ts index b3f8c51087..ef751acc4a 100644 --- a/src/components/data-table/dialog-data-table-settings.ts +++ b/src/components/data-table/dialog-data-table-settings.ts @@ -109,7 +109,8 @@ export class DialogDataTableSettings extends LitElement { const canHide = !col.main && col.hideable !== false; const isVisible = !(this._columnOrder && this._columnOrder.includes(col.key) - ? this._hiddenColumns?.includes(col.key) ?? col.defaultHidden + ? (this._hiddenColumns?.includes(col.key) ?? + col.defaultHidden) : col.defaultHidden); return html` ${!image && showIcon diff --git a/src/components/map/ha-map.ts b/src/components/map/ha-map.ts index d9592abfd4..929114dee9 100644 --- a/src/components/map/ha-map.ts +++ b/src/components/map/ha-map.ts @@ -483,12 +483,12 @@ export class HaMap extends ReactiveElement { const entityName = typeof entity !== "string" && entity.label_mode === "state" ? this.hass.formatEntityState(stateObj) - : customTitle ?? + : (customTitle ?? title .split(" ") .map((part) => part[0]) .join("") - .substr(0, 3); + .substr(0, 3)); // create marker with the icon const marker = Leaflet.marker([latitude, longitude], { diff --git a/src/data/cover.ts b/src/data/cover.ts index 046da65aa3..4916792fee 100644 --- a/src/data/cover.ts +++ b/src/data/cover.ts @@ -115,8 +115,8 @@ export function computeCoverPositionStateDisplay( position?: number ) { const statePosition = stateActive(stateObj) - ? stateObj.attributes.current_position ?? - stateObj.attributes.current_tilt_position + ? (stateObj.attributes.current_position ?? + stateObj.attributes.current_tilt_position) : undefined; const currentPosition = position ?? statePosition; diff --git a/src/fake_data/provide_hass.ts b/src/fake_data/provide_hass.ts index 62787c4fef..692d975cd3 100644 --- a/src/fake_data/provide_hass.ts +++ b/src/fake_data/provide_hass.ts @@ -354,7 +354,7 @@ export const provideHass = ( (state !== null ? state : stateObj.state) ?? "", formatEntityAttributeName: (_stateObj, attribute) => attribute, formatEntityAttributeValue: (stateObj, attribute, value) => - value !== null ? value : stateObj.attributes[attribute] ?? "", + value !== null ? value : (stateObj.attributes[attribute] ?? ""), ...overrideData, }; diff --git a/src/panels/lovelace/card-features/hui-cover-position-card-feature.ts b/src/panels/lovelace/card-features/hui-cover-position-card-feature.ts index 43fadb8bda..cb3a5cf85b 100644 --- a/src/panels/lovelace/card-features/hui-cover-position-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-cover-position-card-feature.ts @@ -61,7 +61,7 @@ class HuiCoverPositionCardFeature } const percentage = stateActive(this.stateObj) - ? this.stateObj.attributes.current_position ?? 0 + ? (this.stateObj.attributes.current_position ?? 0) : 0; const value = Math.max(Math.round(percentage), 0); diff --git a/src/panels/lovelace/card-features/hui-fan-speed-card-feature.ts b/src/panels/lovelace/card-features/hui-fan-speed-card-feature.ts index 9ebc5b3ebb..93314761d2 100644 --- a/src/panels/lovelace/card-features/hui-fan-speed-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-fan-speed-card-feature.ts @@ -74,7 +74,7 @@ class HuiFanSpeedCardFeature extends LitElement implements LovelaceCardFeature { const speedCount = computeFanSpeedCount(this.stateObj); const percentage = stateActive(this.stateObj) - ? this.stateObj.attributes.percentage ?? 0 + ? (this.stateObj.attributes.percentage ?? 0) : 0; if (speedCount <= FAN_SPEED_COUNT_MAX_FOR_BUTTONS) { diff --git a/src/panels/lovelace/components/hui-generic-entity-row.ts b/src/panels/lovelace/components/hui-generic-entity-row.ts index 262459b2be..ce683a6a68 100644 --- a/src/panels/lovelace/components/hui-generic-entity-row.ts +++ b/src/panels/lovelace/components/hui-generic-entity-row.ts @@ -161,7 +161,7 @@ export class HuiGenericEntityRow extends LitElement { : ""}
` : nothing} - ${this.catchInteraction ?? !DOMAINS_INPUT_ROW.includes(domain) + ${(this.catchInteraction ?? !DOMAINS_INPUT_ROW.includes(domain)) ? html`
): void { if (changedProp.has("stateObj")) { const percentage = stateActive(this.stateObj) - ? this.stateObj.attributes.percentage ?? 0 + ? (this.stateObj.attributes.percentage ?? 0) : 0; this.sliderValue = Math.max(Math.round(percentage), 0); this.speedValue = fanPercentageToSpeed(this.stateObj, percentage); diff --git a/src/state/connection-mixin.ts b/src/state/connection-mixin.ts index d4ce2419ed..ff74eed37a 100644 --- a/src/state/connection-mixin.ts +++ b/src/state/connection-mixin.ts @@ -201,7 +201,7 @@ export const connectionMixin = >( (state != null ? state : stateObj.state) ?? "", formatEntityAttributeName: (_stateObj, attribute) => attribute, formatEntityAttributeValue: (stateObj, attribute, value) => - value != null ? value : stateObj.attributes[attribute] ?? "", + value != null ? value : (stateObj.attributes[attribute] ?? ""), ...getState(), ...this._pendingHass, }; diff --git a/yarn.lock b/yarn.lock index 18ffe57d7c..1f137c10d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9074,7 +9074,7 @@ __metadata: object-hash: "npm:3.0.0" open: "npm:10.1.0" pinst: "npm:3.0.0" - prettier: "npm:3.3.2" + prettier: "npm:3.3.3" proxy-polyfill: "npm:0.3.2" punycode: "npm:2.3.1" qr-scanner: "npm:1.4.2" @@ -12153,12 +12153,12 @@ __metadata: languageName: node linkType: hard -"prettier@npm:3.3.2": - version: 3.3.2 - resolution: "prettier@npm:3.3.2" +"prettier@npm:3.3.3": + version: 3.3.3 + resolution: "prettier@npm:3.3.3" bin: prettier: bin/prettier.cjs - checksum: 10/83214e154afa5aa9b664c2506640212323eb1376b13379b2413dc351b7de0687629dca3f00ff2ec895ebd7e3a2adb7d7e231b6c77606e2358137f2150807405b + checksum: 10/5beac1f30b5b40162532b8e2f7c3a4eb650910a2695e9c8512a62ffdc09dae93190c29db9107fa7f26d1b6c71aad3628ecb9b5de1ecb0911191099be109434d7 languageName: node linkType: hard From 7468ab985ad1d80e1f48230e9e73dab8cebcbbf2 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 17 Jul 2024 08:06:09 +0100 Subject: [PATCH 27/97] Use Thread BR extended address in WS API calls (#21172) * Use extended address in WS API calls This follows the HA Core change and passes the extended address to the OTBR WS API calls where necessary. It also follows the new OTBR info format which potentially includes multiple OTBRs. This allows to support multiple OTBR managed by a single system. Note: There is one corner case when none of the OTBR is found via discovery. In this case we offer to reset the OTBR. Currently we simply offer this for the primary or first one found. * Fix lint error * Update src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts Co-authored-by: Bram Kragten * Update src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts Co-authored-by: Bram Kragten * Use Record type to map OBTR data * Add labels to Thread network operation icons * Apply suggestions from code review Co-authored-by: Bram Kragten --------- Co-authored-by: Bram Kragten --- src/data/otbr.ts | 15 +++- .../thread/thread-config-panel.ts | 88 ++++++++++++------- src/translations/en.json | 4 +- 3 files changed, 72 insertions(+), 35 deletions(-) diff --git a/src/data/otbr.ts b/src/data/otbr.ts index 8af577efd3..ec831bf671 100644 --- a/src/data/otbr.ts +++ b/src/data/otbr.ts @@ -5,33 +5,44 @@ export interface OTBRInfo { border_agent_id: string; channel: number; extended_address: string; + extended_pan_id: string; url: string; } -export const getOTBRInfo = (hass: HomeAssistant): Promise => +export type OTBRInfoDict = Record; + +export const getOTBRInfo = (hass: HomeAssistant): Promise => hass.callWS({ type: "otbr/info", }); -export const OTBRCreateNetwork = (hass: HomeAssistant): Promise => +export const OTBRCreateNetwork = ( + hass: HomeAssistant, + extended_address: string +): Promise => hass.callWS({ type: "otbr/create_network", + extended_address, }); export const OTBRSetNetwork = ( hass: HomeAssistant, + extended_address: string, dataset_id: string ): Promise => hass.callWS({ type: "otbr/set_network", + extended_address, dataset_id, }); export const OTBRSetChannel = ( hass: HomeAssistant, + extended_address: string, channel: number ): Promise<{ delay: number }> => hass.callWS({ type: "otbr/set_channel", + extended_address, channel, }); diff --git a/src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts b/src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts index 3d99a4f9e3..aa4a98a867 100644 --- a/src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts +++ b/src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts @@ -28,6 +28,7 @@ import { getConfigEntryDiagnosticsDownloadUrl } from "../../../../../data/diagno import { OTBRCreateNetwork, OTBRInfo, + OTBRInfoDict, OTBRSetChannel, OTBRSetNetwork, getOTBRInfo, @@ -75,7 +76,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { @state() private _datasets: ThreadDataSet[] = []; - @state() private _otbrInfo?: OTBRInfo; + @state() private _otbrInfo?: OTBRInfoDict; protected render(): TemplateResult { const networks = this._groupRoutersByNetwork(this._routers, this._datasets); @@ -160,25 +161,36 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { } private _renderNetwork(network: ThreadNetwork) { + const otbrForNetwork = + this._otbrInfo && + network.dataset && + ((network.dataset.preferred_extended_address && + this._otbrInfo[network.dataset.preferred_extended_address]) || + Object.values(this._otbrInfo).find( + (otbr) => otbr.extended_pan_id === network.dataset!.extended_pan_id + )); const canImportKeychain = this.hass.auth.external?.config.canTransferThreadCredentialsToKeychain && - network.dataset?.extended_pan_id && - this._otbrInfo && - this._otbrInfo?.active_dataset_tlvs?.includes( - network.dataset.extended_pan_id - ); + otbrForNetwork; return html`
${network.name}${network.dataset ? html`
${!network.dataset.preferred && !network.routers?.length ? html`
${network.routers.map((router) => { + const otbr = + this._otbrInfo && this._otbrInfo[router.extended_address]; const showOverflow = - ("dataset" in network && router.border_agent_id) || - router.extended_address === this._otbrInfo?.extended_address; + ("dataset" in network && router.border_agent_id) || otbr; return html` ` : ""} - ${router.extended_address === - this._otbrInfo?.extended_address + ${otbr ? html` ${this.hass.localize( "ui.panel.config.thread.reset_border_router" @@ -288,14 +301,13 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { })}` : html`
- ${network.dataset?.extended_pan_id && - this._otbrInfo?.active_dataset_tlvs?.includes( - network.dataset.extended_pan_id - ) + ${otbrForNetwork ? html`${this.hass.localize( "ui.panel.config.thread.no_routers_otbr_network" )} - ${this.hass.localize( "ui.panel.config.thread.reset_border_router" )} - Send credentials to phone
` @@ -321,23 +333,25 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { `; } - private _sendCredentials() { - if (!this._otbrInfo) { + private _sendCredentials(ev) { + const otbr = (ev.currentTarget as any).otbr as OTBRInfo; + if (!otbr) { return; } this.hass.auth.external!.fireMessage({ type: "thread/store_in_platform_keychain", payload: { - mac_extended_address: this._otbrInfo.extended_address, - border_agent_id: this._otbrInfo.border_agent_id ?? "", - active_operational_dataset: this._otbrInfo.active_dataset_tlvs ?? "", + mac_extended_address: otbr.extended_address, + border_agent_id: otbr.border_agent_id ?? "", + active_operational_dataset: otbr.active_dataset_tlvs ?? "", }, }); } private async _showDatasetInfo(ev: Event) { const network = (ev.currentTarget as any).network as ThreadNetwork; - showThreadDatasetDialog(this, { network, otbrInfo: this._otbrInfo }); + const otbr = (ev.currentTarget as any).otbr as OTBRInfo; + showThreadDatasetDialog(this, { network, otbrInfo: otbr }); } private _importExternalThreadCredentials() { @@ -454,6 +468,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { private _handleRouterAction(ev: CustomEvent) { const network = (ev.currentTarget as any).network as ThreadNetwork; const router = (ev.currentTarget as any).router as ThreadRouter; + const otbr = (ev.currentTarget as any).otbr as OTBRInfo; const index = network.dataset && router.border_agent_id ? Number(ev.detail.index) @@ -463,18 +478,23 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { this._setPreferredBorderAgent(network.dataset!, router); break; case 1: - this._resetBorderRouter(); + this._resetBorderRouter(otbr); break; case 2: - this._changeChannel(); + this._changeChannel(otbr); break; case 3: - this._setDataset(); + this._setDataset(otbr); break; } } - private async _resetBorderRouter() { + private _resetBorderRouterEvent(ev) { + const otbr = (ev.currentTarget as any).otbr as OTBRInfo; + this._resetBorderRouter(otbr); + } + + private async _resetBorderRouter(otbr: OTBRInfo) { const confirm = await showConfirmationDialog(this, { title: this.hass.localize( "ui.panel.config.thread.confirm_reset_border_router" @@ -487,7 +507,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { return; } try { - await OTBRCreateNetwork(this.hass); + await OTBRCreateNetwork(this.hass, otbr.extended_address); } catch (err: any) { showAlertDialog(this, { title: this.hass.localize("ui.panel.config.thread.otbr_config_failed"), @@ -497,7 +517,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { this._refresh(); } - private async _setDataset() { + private async _setDataset(otbr: OTBRInfo) { const networks = this._groupRoutersByNetwork(this._routers, this._datasets); const preferedDatasetId = networks.preferred?.dataset?.dataset_id; if (!preferedDatasetId) { @@ -515,7 +535,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { return; } try { - await OTBRSetNetwork(this.hass, preferedDatasetId); + await OTBRSetNetwork(this.hass, otbr.extended_address, preferedDatasetId); } catch (err: any) { showAlertDialog(this, { title: this.hass.localize("ui.panel.config.thread.otbr_config_failed"), @@ -595,8 +615,8 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { this._refresh(); } - private async _changeChannel() { - const currentChannel = this._otbrInfo?.channel; + private async _changeChannel(otbr: OTBRInfo) { + const currentChannel = otbr.channel; const channelStr = await showPromptDialog(this, { title: this.hass.localize("ui.panel.config.thread.change_channel"), text: this.hass.localize("ui.panel.config.thread.change_channel_text"), @@ -623,7 +643,11 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { return; } try { - const result = await OTBRSetChannel(this.hass, channel); + const result = await OTBRSetChannel( + this.hass, + otbr.extended_address, + channel + ); showAlertDialog(this, { title: this.hass.localize( "ui.panel.config.thread.change_channel_initiated_title" diff --git a/src/translations/en.json b/src/translations/en.json index 1a2f6d871f..4302e553af 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4553,7 +4553,9 @@ "change_channel_multiprotocol_enabled_title": "The Thread radio has multiprotocol enabled", "change_channel_multiprotocol_enabled_text": "To change channel when the Thread radio has multiprotocol enabled, please use the hardware settings menu.", "change_channel_range": "Channel must be in the range 11 to 26", - "change_channel_text": "Initiating a channel change for your Home Assistant Thread network should be performed with caution. Some Thread devices may not migrate to the new channel automatically and, if the new channel is congested, your Thread devices may become intermittently unavailable. Some devices may need to be manually re-joined to your Thread network before they show in Home Assistant again. This action cannot be reversed (without performing another channel change)." + "change_channel_text": "Initiating a channel change for your Home Assistant Thread network should be performed with caution. Some Thread devices may not migrate to the new channel automatically and, if the new channel is congested, your Thread devices may become intermittently unavailable. Some devices may need to be manually re-joined to your Thread network before they show in Home Assistant again. This action cannot be reversed (without performing another channel change).", + "thread_network_info": "Thread network information", + "thread_network_delete_credentials": "Delete Thread network credentials" }, "zha": { "common": { From 30d0293a4b8f19efe428ca264cbe6fbbdcef12f4 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 17 Jul 2024 18:09:25 +0200 Subject: [PATCH 28/97] Add model_id to device info card (#21417) * Add model_id to device info card * Update src/panels/config/devices/device-detail/ha-device-info-card.ts Co-authored-by: Paul Bottein * Add model_id to device info card --------- Co-authored-by: Paul Bottein --- gallery/src/pages/components/ha-form.ts | 3 +++ gallery/src/pages/components/ha-selector.ts | 3 +++ gallery/src/pages/misc/integration-card.ts | 1 + src/data/device_registry.ts | 1 + .../config/devices/device-detail/ha-device-info-card.ts | 9 +++++++-- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts index 0e4c9019f6..722bfefe8e 100644 --- a/gallery/src/pages/components/ha-form.ts +++ b/gallery/src/pages/components/ha-form.ts @@ -53,6 +53,7 @@ const DEVICES = [ identifiers: [["demo", "volume1"] as [string, string]], manufacturer: null, model: null, + model_id: null, name_by_user: null, name: "Dishwasher", sw_version: null, @@ -72,6 +73,7 @@ const DEVICES = [ identifiers: [["demo", "pwm1"] as [string, string]], manufacturer: null, model: null, + model_id: null, name_by_user: null, name: "Lamp", sw_version: null, @@ -91,6 +93,7 @@ const DEVICES = [ identifiers: [["demo", "pwm1"] as [string, string]], manufacturer: null, model: null, + model_id: null, name_by_user: "User name", name: "Technical name", sw_version: null, diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index 0fb6d83263..8657bc4e7b 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -53,6 +53,7 @@ const DEVICES = [ identifiers: [["demo", "volume1"] as [string, string]], manufacturer: null, model: null, + model_id: null, name_by_user: null, name: "Dishwasher", sw_version: null, @@ -72,6 +73,7 @@ const DEVICES = [ identifiers: [["demo", "pwm1"] as [string, string]], manufacturer: null, model: null, + model_id: null, name_by_user: null, name: "Lamp", sw_version: null, @@ -91,6 +93,7 @@ const DEVICES = [ identifiers: [["demo", "pwm1"] as [string, string]], manufacturer: null, model: null, + model_id: null, name_by_user: "User name", name: "Technical name", sw_version: null, diff --git a/gallery/src/pages/misc/integration-card.ts b/gallery/src/pages/misc/integration-card.ts index 72a99c7c28..3144d99aa6 100644 --- a/gallery/src/pages/misc/integration-card.ts +++ b/gallery/src/pages/misc/integration-card.ts @@ -215,6 +215,7 @@ const createDeviceRegistryEntries = ( connections: [], manufacturer: "ESPHome", model: "Mock Device", + model_id: "ABC-001", name: "Tag Reader", sw_version: null, hw_version: "1.0.0", diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index 90bd894e8a..7772febaf2 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -20,6 +20,7 @@ export interface DeviceRegistryEntry { identifiers: Array<[string, string]>; manufacturer: string | null; model: string | null; + model_id: string | null; name: string | null; labels: string[]; sw_version: string | null; diff --git a/src/panels/config/devices/device-detail/ha-device-info-card.ts b/src/panels/config/devices/device-detail/ha-device-info-card.ts index 0af9a8bdc6..bf69819f87 100644 --- a/src/panels/config/devices/device-detail/ha-device-info-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-info-card.ts @@ -31,8 +31,13 @@ export class HaDeviceCard extends LitElement { >
${this.device.model - ? html`
${this.device.model}
` - : ""} + ? html`
+ ${this.device.model} + ${this.device.model_id ? html`(${this.device.model_id})` : ""} +
` + : this.device.model_id + ? html`
${this.device.model_id}
` + : ""} ${this.device.manufacturer ? html`
From e3b0630797a6a5f4b99ecb5c414b8cf952edfa14 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 20:15:53 +0200 Subject: [PATCH 29/97] Update dependency @babel/core to v7.24.9 (#21422) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 42 +++++++++++++++++++++--------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index f22757c1d0..035c160187 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "xss": "1.0.15" }, "devDependencies": { - "@babel/core": "7.24.8", + "@babel/core": "7.24.9", "@babel/helper-define-polyfill-provider": "0.6.2", "@babel/plugin-proposal-decorators": "7.24.7", "@babel/plugin-transform-runtime": "7.24.7", diff --git a/yarn.lock b/yarn.lock index 1f137c10d3..0076891a5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -55,38 +55,38 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:7.24.8, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.0, @babel/core@npm:^7.24.4": - version: 7.24.8 - resolution: "@babel/core@npm:7.24.8" +"@babel/core@npm:7.24.9, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.0, @babel/core@npm:^7.24.4": + version: 7.24.9 + resolution: "@babel/core@npm:7.24.9" dependencies: "@ampproject/remapping": "npm:^2.2.0" "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.24.8" + "@babel/generator": "npm:^7.24.9" "@babel/helper-compilation-targets": "npm:^7.24.8" - "@babel/helper-module-transforms": "npm:^7.24.8" + "@babel/helper-module-transforms": "npm:^7.24.9" "@babel/helpers": "npm:^7.24.8" "@babel/parser": "npm:^7.24.8" "@babel/template": "npm:^7.24.7" "@babel/traverse": "npm:^7.24.8" - "@babel/types": "npm:^7.24.8" + "@babel/types": "npm:^7.24.9" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10/79818e6e8ecd5f50ffbfb8dfb1748928e6e17b198bd8da0d6f725bf67aece5141020cd3aba56dc425a33e118c0e80e5569ad6fa615897e49726087dd875279d7 + checksum: 10/f00a372fa547f6e21f4db1b6e521e6eb01f77f5931726897aae6f4cf29a687f615b9b77147b539e851a68bf94e4850bcfba7eb11091dd8e2bc625f6d831ce257 languageName: node linkType: hard -"@babel/generator@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/generator@npm:7.24.8" +"@babel/generator@npm:^7.24.8, @babel/generator@npm:^7.24.9": + version: 7.24.10 + resolution: "@babel/generator@npm:7.24.10" dependencies: - "@babel/types": "npm:^7.24.8" + "@babel/types": "npm:^7.24.9" "@jridgewell/gen-mapping": "npm:^0.3.5" "@jridgewell/trace-mapping": "npm:^0.3.25" jsesc: "npm:^2.5.1" - checksum: 10/dc1bd931120f93e7a5b35fdf66c13ca56b966b07ee9ba124f7e24b1905cbcf7d7891cc7c281961876eff9fcff67c46652cce89847665e263bc04d283d4343164 + checksum: 10/c2491fb7d985527a165546cbcf9e5f6a2518f2a968c7564409c012acce1019056b21e67a152af89b3f4d4a295ca2e75a1a16858152f750efbc4b5087f0cb7253 languageName: node linkType: hard @@ -217,9 +217,9 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/helper-module-transforms@npm:7.24.8" +"@babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.24.9": + version: 7.24.9 + resolution: "@babel/helper-module-transforms@npm:7.24.9" dependencies: "@babel/helper-environment-visitor": "npm:^7.24.7" "@babel/helper-module-imports": "npm:^7.24.7" @@ -228,7 +228,7 @@ __metadata: "@babel/helper-validator-identifier": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/912ad994da126c3150d8f8702030380849608094a7a352523ffa8e697080da9358d63af2582d38902c929839f394bbc6f1ae4921ba132ba3f65f27f0696aa2c7 + checksum: 10/eaed9cb93edb11626758f76bfb482f9c3b6583f6756813c5ef849d6d52bbe7c2cb39f61646758e860732d14c2588b60eb4e2af78d7751450649a8d3d7ca41697 languageName: node linkType: hard @@ -1442,14 +1442,14 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": - version: 7.24.8 - resolution: "@babel/types@npm:7.24.8" +"@babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.24.9, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": + version: 7.24.9 + resolution: "@babel/types@npm:7.24.9" dependencies: "@babel/helper-string-parser": "npm:^7.24.8" "@babel/helper-validator-identifier": "npm:^7.24.7" to-fast-properties: "npm:^2.0.0" - checksum: 10/29b080b2753c22ee5e2455ff767a971443245d945dea4d1b3130e036dcdf0949a89539a581753c68d03d2f2f2325244ee0f91fb83dabee1cbac5db5246838137 + checksum: 10/21873a08a124646824aa230de06af52149ab88206dca59849dcb3003990a6306ec2cdaa4147ec1127c0cfc5f133853cfc18f80d7f6337b6662a3c378ed565f15 languageName: node linkType: hard @@ -8903,7 +8903,7 @@ __metadata: version: 0.0.0-use.local resolution: "home-assistant-frontend@workspace:." dependencies: - "@babel/core": "npm:7.24.8" + "@babel/core": "npm:7.24.9" "@babel/helper-define-polyfill-provider": "npm:0.6.2" "@babel/plugin-proposal-decorators": "npm:7.24.7" "@babel/plugin-transform-runtime": "npm:7.24.7" From ee2b10912c7d46991e3a5cc22b72abd04f1225a4 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Thu, 18 Jul 2024 11:34:13 -0700 Subject: [PATCH 30/97] Stop closed event propagating in automation editor elements (#21424) --- .../config/automation/action/ha-automation-action-row.ts | 2 ++ .../automation/action/types/ha-automation-action-choose.ts | 2 ++ .../config/automation/condition/ha-automation-condition-row.ts | 2 ++ .../config/automation/trigger/ha-automation-trigger-row.ts | 2 ++ .../automation/trigger/types/ha-automation-trigger-webhook.ts | 3 ++- 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index fc43b6a3ca..0b15bc8acf 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -30,6 +30,7 @@ import { classMap } from "lit/directives/class-map"; import { storage } from "../../../../common/decorators/storage"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; import { handleStructError } from "../../../../common/structs/handle-errors"; import "../../../../components/ha-alert"; @@ -253,6 +254,7 @@ export default class HaAutomationActionRow extends LitElement { slot="icons" @action=${this._handleAction} @click=${preventDefault} + @closed=${stopPropagation} fixed > - + Date: Thu, 18 Jul 2024 20:34:51 +0200 Subject: [PATCH 31/97] Update typescript-eslint monorepo to v7.16.1 (#21426) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 +- yarn.lock | 104 +++++++++++++++++++++++++-------------------------- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 035c160187..adf7e38ef9 100644 --- a/package.json +++ b/package.json @@ -185,8 +185,8 @@ "@types/tar": "6.1.13", "@types/ua-parser-js": "0.7.39", "@types/webspeechapi": "0.0.29", - "@typescript-eslint/eslint-plugin": "7.16.0", - "@typescript-eslint/parser": "7.16.0", + "@typescript-eslint/eslint-plugin": "7.16.1", + "@typescript-eslint/parser": "7.16.1", "@web/dev-server": "0.1.38", "@web/dev-server-rollup": "0.4.1", "babel-loader": "9.1.3", diff --git a/yarn.lock b/yarn.lock index 0076891a5f..f4f862143b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4584,15 +4584,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:7.16.0": - version: 7.16.0 - resolution: "@typescript-eslint/eslint-plugin@npm:7.16.0" +"@typescript-eslint/eslint-plugin@npm:7.16.1": + version: 7.16.1 + resolution: "@typescript-eslint/eslint-plugin@npm:7.16.1" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:7.16.0" - "@typescript-eslint/type-utils": "npm:7.16.0" - "@typescript-eslint/utils": "npm:7.16.0" - "@typescript-eslint/visitor-keys": "npm:7.16.0" + "@typescript-eslint/scope-manager": "npm:7.16.1" + "@typescript-eslint/type-utils": "npm:7.16.1" + "@typescript-eslint/utils": "npm:7.16.1" + "@typescript-eslint/visitor-keys": "npm:7.16.1" graphemer: "npm:^1.4.0" ignore: "npm:^5.3.1" natural-compare: "npm:^1.4.0" @@ -4603,44 +4603,44 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/beda6b586bfc953843877395b09acc0525d727dcb77e6ded5fbc645a9008b7e60360ddbaf6a9b7deaf23cd42c206412b7150d8df27f1fe2da3dc24dfab1c8d71 + checksum: 10/fddbfe461f85d10ee3967b89efa3c704806074af6806833f982915b21754567a98c5a486627174cc6b0ac4cb5f1282865d64ae251a5cbf6dbbbe191d0268520a languageName: node linkType: hard -"@typescript-eslint/parser@npm:7.16.0": - version: 7.16.0 - resolution: "@typescript-eslint/parser@npm:7.16.0" +"@typescript-eslint/parser@npm:7.16.1": + version: 7.16.1 + resolution: "@typescript-eslint/parser@npm:7.16.1" dependencies: - "@typescript-eslint/scope-manager": "npm:7.16.0" - "@typescript-eslint/types": "npm:7.16.0" - "@typescript-eslint/typescript-estree": "npm:7.16.0" - "@typescript-eslint/visitor-keys": "npm:7.16.0" + "@typescript-eslint/scope-manager": "npm:7.16.1" + "@typescript-eslint/types": "npm:7.16.1" + "@typescript-eslint/typescript-estree": "npm:7.16.1" + "@typescript-eslint/visitor-keys": "npm:7.16.1" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.56.0 peerDependenciesMeta: typescript: optional: true - checksum: 10/dc374e6c9e7dfcdd968828bb32ef59d3ebabd0a18671dee22d14dda2c713dade6eb493fd11b127df17035c7451898b42f4a88102da9a4bf3ca6a3baed8c20309 + checksum: 10/7af36bacc2c38e9fb367edf886a04fde292ff28b49adfc3f4fc0dd456364c5e18444346112ae52557f2f32fe2e5abd144b87b4db89b6960b4957d69a9d390f91 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:7.16.0": - version: 7.16.0 - resolution: "@typescript-eslint/scope-manager@npm:7.16.0" +"@typescript-eslint/scope-manager@npm:7.16.1": + version: 7.16.1 + resolution: "@typescript-eslint/scope-manager@npm:7.16.1" dependencies: - "@typescript-eslint/types": "npm:7.16.0" - "@typescript-eslint/visitor-keys": "npm:7.16.0" - checksum: 10/bf39a3ab803503c33e6c33568e7b93793d53d18100cb2f2ec1a540121aeba74d291d19c9ad3933198ff15e53a46d2f92db0c54309259dc99c1e3e297becd5677 + "@typescript-eslint/types": "npm:7.16.1" + "@typescript-eslint/visitor-keys": "npm:7.16.1" + checksum: 10/57ce02c2624e49988b01666b3e13d1adb44ab78f2dafc47a56800d57bff624779b348928a905393fa5f2cce94a5844173ab81f32b81f0bb2897f10bbaf9cab6a languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:7.16.0": - version: 7.16.0 - resolution: "@typescript-eslint/type-utils@npm:7.16.0" +"@typescript-eslint/type-utils@npm:7.16.1": + version: 7.16.1 + resolution: "@typescript-eslint/type-utils@npm:7.16.1" dependencies: - "@typescript-eslint/typescript-estree": "npm:7.16.0" - "@typescript-eslint/utils": "npm:7.16.0" + "@typescript-eslint/typescript-estree": "npm:7.16.1" + "@typescript-eslint/utils": "npm:7.16.1" debug: "npm:^4.3.4" ts-api-utils: "npm:^1.3.0" peerDependencies: @@ -4648,23 +4648,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/84925c851a515768317573984dc855ac93bf787ebaa6382379dea6b356adb936ebd38bf7ab2f95124c68de7ab1fd5c849fe6717929343a80b839757fb5bf3af0 + checksum: 10/38a72a3de8a2c3455d19e6d43e67ac6e1dc23e93b2d84571282b0323fadadcab33df1a89787c76fc99e45514e41a08bc9f5cb51287a7da48f56c64b512a3269b languageName: node linkType: hard -"@typescript-eslint/types@npm:7.16.0": - version: 7.16.0 - resolution: "@typescript-eslint/types@npm:7.16.0" - checksum: 10/0813d9eb158f984b9d7e9e83961533ddc1e8c8815ca9059dab820df276b1e537b183f4c83cc4fe79ab3865cde1a64f2ec3f7fffe7209872d7d404636299f630b +"@typescript-eslint/types@npm:7.16.1": + version: 7.16.1 + resolution: "@typescript-eslint/types@npm:7.16.1" + checksum: 10/cfb48821ffb5a5307e67ce05b9ec2f4775c560dc53011e313d4fa75d033e0130ce0d364ac92ad3634d325c16a889ddc3201e8a742217c73be8d34385da85620b languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:7.16.0": - version: 7.16.0 - resolution: "@typescript-eslint/typescript-estree@npm:7.16.0" +"@typescript-eslint/typescript-estree@npm:7.16.1": + version: 7.16.1 + resolution: "@typescript-eslint/typescript-estree@npm:7.16.1" dependencies: - "@typescript-eslint/types": "npm:7.16.0" - "@typescript-eslint/visitor-keys": "npm:7.16.0" + "@typescript-eslint/types": "npm:7.16.1" + "@typescript-eslint/visitor-keys": "npm:7.16.1" debug: "npm:^4.3.4" globby: "npm:^11.1.0" is-glob: "npm:^4.0.3" @@ -4674,31 +4674,31 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/5719c0cb649d627a073f1c8994a6073acc211ecfce0daef61d2de4315e42a23cf79e4dacb3b3596c4792eab062fdd22080c62345e2a58d38e7268eb6103a46d4 + checksum: 10/7f88176f2d25779ec2d40df4c6bd0a26aa41494ee0302d4895b4d0cb4e284385c1e218ac2ad67ed90b5e1bf82b78b8aa4b903b5906fbf7101b08c409ce778e9c languageName: node linkType: hard -"@typescript-eslint/utils@npm:7.16.0": - version: 7.16.0 - resolution: "@typescript-eslint/utils@npm:7.16.0" +"@typescript-eslint/utils@npm:7.16.1": + version: 7.16.1 + resolution: "@typescript-eslint/utils@npm:7.16.1" dependencies: "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:7.16.0" - "@typescript-eslint/types": "npm:7.16.0" - "@typescript-eslint/typescript-estree": "npm:7.16.0" + "@typescript-eslint/scope-manager": "npm:7.16.1" + "@typescript-eslint/types": "npm:7.16.1" + "@typescript-eslint/typescript-estree": "npm:7.16.1" peerDependencies: eslint: ^8.56.0 - checksum: 10/325eab6705e70322d8df613cba4b018abc5d8ef857eb6c86f7a8376334eac789e6a585d30c041045c7eeede18083744faae66f48033e7811b2a23ebe8f6d3407 + checksum: 10/b3c279d706ff1b3a0002c8e0f0fcf559b63f4296e218199a25863054bda5b28d5a7ab6ad4ad1d0b7fa2c6cd9f2d0dcd7f784c3f75026fae7b58846695481ec45 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:7.16.0": - version: 7.16.0 - resolution: "@typescript-eslint/visitor-keys@npm:7.16.0" +"@typescript-eslint/visitor-keys@npm:7.16.1": + version: 7.16.1 + resolution: "@typescript-eslint/visitor-keys@npm:7.16.1" dependencies: - "@typescript-eslint/types": "npm:7.16.0" + "@typescript-eslint/types": "npm:7.16.1" eslint-visitor-keys: "npm:^3.4.3" - checksum: 10/aae065bdd6d5681d40df51af24933fc86c15f355f9d8f85c39a506f352ddc2a76fc72d4f8cf823ebb7550c84d543605a2fdd7d06979a0967cd48c1f542436714 + checksum: 10/f5088d72b6ca48f4e525b7b5d6c6c9254d0d039d2959fd91200691218e8ac8f3e56287ec8bc411a79609e9d85ed5fc6c4f7d2edd80fadf734aeb6f6bfc833322 languageName: node linkType: hard @@ -9002,8 +9002,8 @@ __metadata: "@types/tar": "npm:6.1.13" "@types/ua-parser-js": "npm:0.7.39" "@types/webspeechapi": "npm:0.0.29" - "@typescript-eslint/eslint-plugin": "npm:7.16.0" - "@typescript-eslint/parser": "npm:7.16.0" + "@typescript-eslint/eslint-plugin": "npm:7.16.1" + "@typescript-eslint/parser": "npm:7.16.1" "@vaadin/combo-box": "npm:24.4.3" "@vaadin/vaadin-themable-mixin": "npm:24.4.3" "@vibrant/color": "npm:3.2.1-alpha.1" From d997cfcef0a2f7e42a8a2270300a15855bb80308 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 18 Jul 2024 19:50:46 +0100 Subject: [PATCH 32/97] Add error handling to device delete (#21403) Currently, if device delete fails, the frontend stays silent. The user might get hints in the Core logs, but nothing shown on the frontend. This adds error handling similar to other places. --- .../config/devices/ha-config-device-page.ts | 19 ++++++++++++++----- src/translations/en.json | 1 + 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 1a9809b9b6..fb691cc7e0 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -999,11 +999,20 @@ export class HaConfigDevicePage extends LitElement { return; } - await removeConfigEntryFromDevice( - this.hass!, - this.deviceId, - entry.entry_id - ); + try { + await removeConfigEntryFromDevice( + this.hass!, + this.deviceId, + entry.entry_id + ); + } catch (err: any) { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.devices.error_delete" + ), + text: err.message, + }); + } }, classes: "warning", icon: mdiDelete, diff --git a/src/translations/en.json b/src/translations/en.json index 4302e553af..afe034cf79 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4068,6 +4068,7 @@ "delete": "Delete", "confirm_delete": "Are you sure you want to delete this device?", "confirm_delete_integration": "Are you sure you want to remove this device from {integration}?", + "error_delete": "Error deleting device", "picker": { "search": "Search {number} devices", "state": "State", From e63d82d2913f19cbd0c68cc2628f244dcd19ec2a Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Thu, 18 Jul 2024 11:53:17 -0700 Subject: [PATCH 33/97] Fix persistent notification count on server restart (#21405) * Fix persistent notification count on server restart --- src/components/ha-sidebar.ts | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index da4b698616..bd705917aa 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -210,6 +210,8 @@ class HaSidebar extends SubscribeMixin(LitElement) { private _editStyleLoaded = false; + private _unsubPersistentNotifications: UnsubscribeFunc | undefined; + @storage({ key: "sidebarPanelOrder", state: true, @@ -283,15 +285,26 @@ class HaSidebar extends SubscribeMixin(LitElement) { hass.localize !== oldHass.localize || hass.locale !== oldHass.locale || hass.states !== oldHass.states || - hass.defaultPanel !== oldHass.defaultPanel + hass.defaultPanel !== oldHass.defaultPanel || + hass.connected !== oldHass.connected ); } protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); - subscribeNotifications(this.hass.connection, (notifications) => { - this._notifications = notifications; - }); + this.subscribePersistentNotifications(); + } + + private subscribePersistentNotifications(): void { + if (this._unsubPersistentNotifications) { + this._unsubPersistentNotifications(); + } + this._unsubPersistentNotifications = subscribeNotifications( + this.hass.connection, + (notifications) => { + this._notifications = notifications; + } + ); } protected updated(changedProps) { @@ -306,6 +319,14 @@ class HaSidebar extends SubscribeMixin(LitElement) { return; } + if ( + this.hass && + changedProps.get("hass")?.connected === false && + this.hass.connected === true + ) { + this.subscribePersistentNotifications(); + } + this._calculateCounts(); if (!SUPPORT_SCROLL_IF_NEEDED) { From ce43774b5f14cf4266547ba99dd1b3435757286b Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 18 Jul 2024 21:03:21 +0200 Subject: [PATCH 34/97] Fix tile card padding in vertical mode (#21409) --- src/panels/lovelace/cards/hui-tile-card.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 50d2838143..1f07d1f6fc 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -383,9 +383,9 @@ export class HuiTileCard extends LitElement implements LovelaceCard { display: flex; flex-direction: row; align-items: center; - padding: 0 10px; - min-height: var(--row-height, 56px); + padding: 10px; flex: 1; + box-sizing: border-box; pointer-events: none; } .vertical { From 729a12af0cb027b87ae6ce23397877d5781537b2 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 19 Jul 2024 10:52:22 +0200 Subject: [PATCH 35/97] Add new badges design with UI editor (#21401) * Add new entity badge * Improve badge render * Add edit mode * Add editor * Increase height * Use hui-badge * Add editor * Add drag and drop * Fix editor translations * Fix icon * Fix inactive color * Add state content * Add default config * Fix types * Add custom badge support to editor * Fix custom badges * Add new badges to masonry view * fix lint * Fix inactive color * Fix entity filter card * Add display type option * Add support for picture * Improve focus style * Add visibility editor * Fix visibility * Fix add/delete card inside section * Fix translations * Add error badge * Rename classes * Fix badge type * Remove badges from section type * Add missing types --- src/data/lovelace.ts | 5 +- src/data/lovelace/config/badge.ts | 8 + src/data/lovelace/config/view.ts | 2 +- src/data/lovelace_custom_cards.ts | 16 + src/panels/lovelace/badges/hui-badge.ts | 200 +++++++ .../lovelace/badges/hui-entity-badge.ts | 306 ++++++++++ .../badges/hui-entity-filter-badge.ts | 17 +- src/panels/lovelace/badges/hui-error-badge.ts | 62 ++- src/panels/lovelace/badges/hui-view-badges.ts | 177 ++++++ src/panels/lovelace/badges/types.ts | 15 + src/panels/lovelace/cards/hui-tile-card.ts | 4 +- .../components/hui-badge-edit-mode.ts | 275 +++++++++ .../lovelace/components/hui-card-edit-mode.ts | 4 +- .../lovelace/components/hui-card-options.ts | 4 +- .../create-element/create-badge-element.ts | 13 +- .../create-element/create-element-base.ts | 44 +- .../badge-editor/hui-badge-element-editor.ts | 109 ++++ .../hui-badge-visibility-editor.ts | 59 ++ .../badge-editor/hui-dialog-edit-badge.ts | 521 ++++++++++++++++++ .../badge-editor/show-edit-badge-dialog.ts | 30 + .../card-editor/hui-dialog-edit-card.ts | 4 +- .../hui-entity-badge-editor.ts | 236 ++++++++ src/panels/lovelace/editor/config-util.ts | 148 ++++- .../lovelace/editor/get-badge-stub-config.ts | 26 + ....ts => get-dashboard-documentation-url.ts} | 14 +- src/panels/lovelace/editor/lovelace-path.ts | 83 +-- .../editor/structs/base-badge-struct.ts | 6 + src/panels/lovelace/types.ts | 14 + src/panels/lovelace/views/hui-masonry-view.ts | 21 +- .../lovelace/views/hui-sections-view.ts | 48 +- src/panels/lovelace/views/hui-view.ts | 123 +++-- src/translations/en.json | 46 +- 32 files changed, 2460 insertions(+), 180 deletions(-) create mode 100644 src/panels/lovelace/badges/hui-badge.ts create mode 100644 src/panels/lovelace/badges/hui-entity-badge.ts create mode 100644 src/panels/lovelace/badges/hui-view-badges.ts create mode 100644 src/panels/lovelace/components/hui-badge-edit-mode.ts create mode 100644 src/panels/lovelace/editor/badge-editor/hui-badge-element-editor.ts create mode 100644 src/panels/lovelace/editor/badge-editor/hui-badge-visibility-editor.ts create mode 100644 src/panels/lovelace/editor/badge-editor/hui-dialog-edit-badge.ts create mode 100644 src/panels/lovelace/editor/badge-editor/show-edit-badge-dialog.ts create mode 100644 src/panels/lovelace/editor/config-elements/hui-entity-badge-editor.ts create mode 100644 src/panels/lovelace/editor/get-badge-stub-config.ts rename src/panels/lovelace/editor/{get-card-documentation-url.ts => get-dashboard-documentation-url.ts} (55%) create mode 100644 src/panels/lovelace/editor/structs/base-badge-struct.ts diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts index bfd794ef8d..1b4f47aa38 100644 --- a/src/data/lovelace.ts +++ b/src/data/lovelace.ts @@ -3,9 +3,10 @@ import { getCollection, HassEventBase, } from "home-assistant-js-websocket"; +import { HuiBadge } from "../panels/lovelace/badges/hui-badge"; import type { HuiCard } from "../panels/lovelace/cards/hui-card"; import type { HuiSection } from "../panels/lovelace/sections/hui-section"; -import { Lovelace, LovelaceBadge } from "../panels/lovelace/types"; +import { Lovelace } from "../panels/lovelace/types"; import { HomeAssistant } from "../types"; import { LovelaceSectionConfig } from "./lovelace/config/section"; import { fetchConfig, LegacyLovelaceConfig } from "./lovelace/config/types"; @@ -21,7 +22,7 @@ export interface LovelaceViewElement extends HTMLElement { narrow?: boolean; index?: number; cards?: HuiCard[]; - badges?: LovelaceBadge[]; + badges?: HuiBadge[]; sections?: HuiSection[]; isStrategy: boolean; setConfig(config: LovelaceViewConfig): void; diff --git a/src/data/lovelace/config/badge.ts b/src/data/lovelace/config/badge.ts index 661464a935..7226adc982 100644 --- a/src/data/lovelace/config/badge.ts +++ b/src/data/lovelace/config/badge.ts @@ -1,4 +1,12 @@ +import { Condition } from "../../../panels/lovelace/common/validate-condition"; + export interface LovelaceBadgeConfig { type?: string; [key: string]: any; + visibility?: Condition[]; } + +export const defaultBadgeConfig = (entity_id: string): LovelaceBadgeConfig => ({ + type: "entity", + entity: entity_id, +}); diff --git a/src/data/lovelace/config/view.ts b/src/data/lovelace/config/view.ts index db0385173d..708d307ab5 100644 --- a/src/data/lovelace/config/view.ts +++ b/src/data/lovelace/config/view.ts @@ -27,7 +27,7 @@ export interface LovelaceBaseViewConfig { export interface LovelaceViewConfig extends LovelaceBaseViewConfig { type?: string; - badges?: Array; + badges?: (string | LovelaceBadgeConfig)[]; // Badge can be just an entity_id cards?: LovelaceCardConfig[]; sections?: LovelaceSectionRawConfig[]; } diff --git a/src/data/lovelace_custom_cards.ts b/src/data/lovelace_custom_cards.ts index 0e4a7d79ae..c79cfb4584 100644 --- a/src/data/lovelace_custom_cards.ts +++ b/src/data/lovelace_custom_cards.ts @@ -8,6 +8,14 @@ export interface CustomCardEntry { documentationURL?: string; } +export interface CustomBadgeEntry { + type: string; + name?: string; + description?: string; + preview?: boolean; + documentationURL?: string; +} + export interface CustomCardFeatureEntry { type: string; name?: string; @@ -18,6 +26,7 @@ export interface CustomCardFeatureEntry { export interface CustomCardsWindow { customCards?: CustomCardEntry[]; customCardFeatures?: CustomCardFeatureEntry[]; + customBadges?: CustomBadgeEntry[]; /** * @deprecated Use customCardFeatures */ @@ -34,6 +43,9 @@ if (!("customCards" in customCardsWindow)) { if (!("customCardFeatures" in customCardsWindow)) { customCardsWindow.customCardFeatures = []; } +if (!("customBadges" in customCardsWindow)) { + customCardsWindow.customBadges = []; +} if (!("customTileFeatures" in customCardsWindow)) { customCardsWindow.customTileFeatures = []; } @@ -43,10 +55,14 @@ export const getCustomCardFeatures = () => [ ...customCardsWindow.customCardFeatures!, ...customCardsWindow.customTileFeatures!, ]; +export const customBadges = customCardsWindow.customBadges!; export const getCustomCardEntry = (type: string) => customCards.find((card) => card.type === type); +export const getCustomBadgeEntry = (type: string) => + customBadges.find((badge) => badge.type === type); + export const isCustomType = (type: string) => type.startsWith(CUSTOM_TYPE_PREFIX); diff --git a/src/panels/lovelace/badges/hui-badge.ts b/src/panels/lovelace/badges/hui-badge.ts new file mode 100644 index 0000000000..9b362fd173 --- /dev/null +++ b/src/panels/lovelace/badges/hui-badge.ts @@ -0,0 +1,200 @@ +import { PropertyValues, ReactiveElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { fireEvent } from "../../../common/dom/fire_event"; +import { MediaQueriesListener } from "../../../common/dom/media_query"; +import "../../../components/ha-svg-icon"; +import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; +import type { HomeAssistant } from "../../../types"; +import { + attachConditionMediaQueriesListeners, + checkConditionsMet, +} from "../common/validate-condition"; +import { createBadgeElement } from "../create-element/create-badge-element"; +import { createErrorBadgeConfig } from "../create-element/create-element-base"; +import type { LovelaceBadge } from "../types"; + +declare global { + interface HASSDomEvents { + "badge-updated": undefined; + } +} + +@customElement("hui-badge") +export class HuiBadge extends ReactiveElement { + @property({ type: Boolean }) public preview = false; + + @property({ attribute: false }) public config?: LovelaceBadgeConfig; + + @property({ attribute: false }) public hass?: HomeAssistant; + + private _elementConfig?: LovelaceBadgeConfig; + + public load() { + if (!this.config) { + throw new Error("Cannot build badge without config"); + } + this._loadElement(this.config); + } + + private _element?: LovelaceBadge; + + private _listeners: MediaQueriesListener[] = []; + + protected createRenderRoot() { + return this; + } + + public disconnectedCallback() { + super.disconnectedCallback(); + this._clearMediaQueries(); + } + + public connectedCallback() { + super.connectedCallback(); + this._listenMediaQueries(); + this._updateVisibility(); + } + + private _updateElement(config: LovelaceBadgeConfig) { + if (!this._element) { + return; + } + this._element.setConfig(config); + this._elementConfig = config; + fireEvent(this, "badge-updated"); + } + + private _loadElement(config: LovelaceBadgeConfig) { + this._element = createBadgeElement(config); + this._elementConfig = config; + if (this.hass) { + this._element.hass = this.hass; + } + this._element.addEventListener( + "ll-upgrade", + (ev: Event) => { + ev.stopPropagation(); + if (this.hass) { + this._element!.hass = this.hass; + } + fireEvent(this, "badge-updated"); + }, + { once: true } + ); + this._element.addEventListener( + "ll-rebuild", + (ev: Event) => { + ev.stopPropagation(); + this._loadElement(config); + fireEvent(this, "badge-updated"); + }, + { once: true } + ); + while (this.lastChild) { + this.removeChild(this.lastChild); + } + this._updateVisibility(); + } + + protected willUpdate(changedProps: PropertyValues): void { + super.willUpdate(changedProps); + + if (!this._element) { + this.load(); + } + } + + protected update(changedProps: PropertyValues) { + super.update(changedProps); + + if (this._element) { + if (changedProps.has("config")) { + const elementConfig = this._elementConfig; + if (this.config !== elementConfig && this.config) { + const typeChanged = this.config?.type !== elementConfig?.type; + if (typeChanged) { + this._loadElement(this.config); + } else { + this._updateElement(this.config); + } + } + } + if (changedProps.has("hass")) { + try { + if (this.hass) { + this._element.hass = this.hass; + } + } catch (e: any) { + this._loadElement(createErrorBadgeConfig(e.message, null)); + } + } + } + + if (changedProps.has("hass") || changedProps.has("preview")) { + this._updateVisibility(); + } + } + + private _clearMediaQueries() { + this._listeners.forEach((unsub) => unsub()); + this._listeners = []; + } + + private _listenMediaQueries() { + this._clearMediaQueries(); + if (!this.config?.visibility) { + return; + } + const conditions = this.config.visibility; + const hasOnlyMediaQuery = + conditions.length === 1 && + conditions[0].condition === "screen" && + !!conditions[0].media_query; + + this._listeners = attachConditionMediaQueriesListeners( + this.config.visibility, + (matches) => { + this._updateVisibility(hasOnlyMediaQuery && matches); + } + ); + } + + private _updateVisibility(forceVisible?: boolean) { + if (!this._element || !this.hass) { + return; + } + + if (this._element.hidden) { + this._setElementVisibility(false); + return; + } + + const visible = + forceVisible || + this.preview || + !this.config?.visibility || + checkConditionsMet(this.config.visibility, this.hass); + this._setElementVisibility(visible); + } + + private _setElementVisibility(visible: boolean) { + if (!this._element) return; + + if (this.hidden !== !visible) { + this.style.setProperty("display", visible ? "" : "none"); + this.toggleAttribute("hidden", !visible); + } + + if (!visible && this._element.parentElement) { + this.removeChild(this._element); + } else if (visible && !this._element.parentElement) { + this.appendChild(this._element); + } + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-badge": HuiBadge; + } +} diff --git a/src/panels/lovelace/badges/hui-entity-badge.ts b/src/panels/lovelace/badges/hui-entity-badge.ts new file mode 100644 index 0000000000..05a6839da0 --- /dev/null +++ b/src/panels/lovelace/badges/hui-entity-badge.ts @@ -0,0 +1,306 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { ifDefined } from "lit/directives/if-defined"; +import { styleMap } from "lit/directives/style-map"; +import memoizeOne from "memoize-one"; +import { computeCssColor } from "../../../common/color/compute-color"; +import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color"; +import { computeDomain } from "../../../common/entity/compute_domain"; +import { stateActive } from "../../../common/entity/state_active"; +import { stateColorCss } from "../../../common/entity/state_color"; +import "../../../components/ha-ripple"; +import "../../../components/ha-state-icon"; +import { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; +import { HomeAssistant } from "../../../types"; +import { actionHandler } from "../common/directives/action-handler-directive"; +import { findEntities } from "../common/find-entities"; +import { handleAction } from "../common/handle-action"; +import { hasAction } from "../common/has-action"; +import { LovelaceBadge, LovelaceBadgeEditor } from "../types"; +import { EntityBadgeConfig } from "./types"; +import { computeStateDomain } from "../../../common/entity/compute_state_domain"; +import { cameraUrlWithWidthHeight } from "../../../data/camera"; + +export const DISPLAY_TYPES = ["minimal", "standard", "complete"] as const; + +export type DisplayType = (typeof DISPLAY_TYPES)[number]; + +export const DEFAULT_DISPLAY_TYPE: DisplayType = "standard"; + +@customElement("hui-entity-badge") +export class HuiEntityBadge extends LitElement implements LovelaceBadge { + public static async getConfigElement(): Promise { + await import("../editor/config-elements/hui-entity-badge-editor"); + return document.createElement("hui-entity-badge-editor"); + } + + public static getStubConfig( + hass: HomeAssistant, + entities: string[], + entitiesFallback: string[] + ): EntityBadgeConfig { + const includeDomains = ["sensor", "light", "switch"]; + const maxEntities = 1; + const foundEntities = findEntities( + hass, + maxEntities, + entities, + entitiesFallback, + includeDomains + ); + + return { + type: "entity", + entity: foundEntities[0] || "", + }; + } + + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() protected _config?: EntityBadgeConfig; + + public setConfig(config: EntityBadgeConfig): void { + this._config = config; + } + + get hasAction() { + return ( + !this._config?.tap_action || + hasAction(this._config?.tap_action) || + hasAction(this._config?.hold_action) || + hasAction(this._config?.double_tap_action) + ); + } + + private _computeStateColor = memoizeOne( + (stateObj: HassEntity, color?: string) => { + // Use custom color if active + if (color) { + return stateActive(stateObj) ? computeCssColor(color) : undefined; + } + + // Use light color if the light support rgb + if ( + computeDomain(stateObj.entity_id) === "light" && + stateObj.attributes.rgb_color + ) { + const hsvColor = rgb2hsv(stateObj.attributes.rgb_color); + + // Modify the real rgb color for better contrast + if (hsvColor[1] < 0.4) { + // Special case for very light color (e.g: white) + if (hsvColor[1] < 0.1) { + hsvColor[2] = 225; + } else { + hsvColor[1] = 0.4; + } + } + return rgb2hex(hsv2rgb(hsvColor)); + } + + // Fallback to state color + return stateColorCss(stateObj); + } + ); + + private _getImageUrl(stateObj: HassEntity): string | undefined { + const entityPicture = + stateObj.attributes.entity_picture_local || + stateObj.attributes.entity_picture; + + if (!entityPicture) return undefined; + + let imageUrl = this.hass!.hassUrl(entityPicture); + if (computeStateDomain(stateObj) === "camera") { + imageUrl = cameraUrlWithWidthHeight(imageUrl, 32, 32); + } + + return imageUrl; + } + + protected render() { + if (!this._config || !this.hass) { + return nothing; + } + + const entityId = this._config.entity; + const stateObj = entityId ? this.hass.states[entityId] : undefined; + + if (!stateObj) { + return nothing; + } + + const active = stateActive(stateObj); + const color = this._computeStateColor(stateObj, this._config.color); + + const style = { + "--badge-color": color, + }; + + const stateDisplay = html` + + + `; + + const name = this._config.name || stateObj.attributes.friendly_name; + + const displayType = this._config.display_type || DEFAULT_DISPLAY_TYPE; + + const imageUrl = this._config.show_entity_picture + ? this._getImageUrl(stateObj) + : undefined; + + return html` +
+ + ${imageUrl + ? html`` + : html` + + `} + ${displayType !== "minimal" + ? html` + + ${displayType === "complete" + ? html`${name}` + : nothing} + ${stateDisplay} + + ` + : nothing} +
+ `; + } + + private _handleAction(ev: ActionHandlerEvent) { + handleAction(this, this.hass!, this._config!, ev.detail.action!); + } + + static get styles(): CSSResultGroup { + return css` + :host { + --badge-color: var(--state-inactive-color); + -webkit-tap-highlight-color: transparent; + } + .badge { + position: relative; + --ha-ripple-color: var(--badge-color); + --ha-ripple-hover-opacity: 0.04; + --ha-ripple-pressed-opacity: 0.12; + transition: + box-shadow 180ms ease-in-out, + border-color 180ms ease-in-out; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 8px; + height: 36px; + min-width: 36px; + padding: 0px 8px; + box-sizing: border-box; + width: auto; + border-radius: 18px; + background-color: var(--card-background-color, white); + border-width: var(--ha-card-border-width, 1px); + border-style: solid; + border-color: var( + --ha-card-border-color, + var(--divider-color, #e0e0e0) + ); + --mdc-icon-size: 18px; + text-align: center; + font-family: Roboto; + } + .badge:focus-visible { + --shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent); + --shadow-focus: 0 0 0 1px var(--badge-color); + border-color: var(--badge-color); + box-shadow: var(--shadow-default), var(--shadow-focus); + } + button, + [role="button"] { + cursor: pointer; + } + button:focus, + [role="button"]:focus { + outline: none; + } + .badge.active { + --badge-color: var(--primary-color); + } + .content { + display: flex; + flex-direction: column; + align-items: flex-start; + padding-right: 4px; + padding-inline-end: 4px; + padding-inline-start: initial; + } + .name { + font-size: 10px; + font-style: normal; + font-weight: 500; + line-height: 10px; + letter-spacing: 0.1px; + color: var(--secondary-text-color); + } + .state { + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 16px; + letter-spacing: 0.1px; + color: var(--primary-text-color); + } + ha-state-icon { + color: var(--badge-color); + line-height: 0; + } + img { + width: 30px; + height: 30px; + border-radius: 50%; + object-fit: cover; + overflow: hidden; + } + .badge.minimal { + padding: 0; + } + .badge:not(.minimal) img { + margin-left: -6px; + margin-inline-start: -6px; + margin-inline-end: initial; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-entity-badge": HuiEntityBadge; + } +} diff --git a/src/panels/lovelace/badges/hui-entity-filter-badge.ts b/src/panels/lovelace/badges/hui-entity-filter-badge.ts index e6a889febe..5cba77434c 100644 --- a/src/panels/lovelace/badges/hui-entity-filter-badge.ts +++ b/src/panels/lovelace/badges/hui-entity-filter-badge.ts @@ -8,9 +8,10 @@ import { checkConditionsMet, extractConditionEntityIds, } from "../common/validate-condition"; -import { createBadgeElement } from "../create-element/create-badge-element"; import { EntityFilterEntityConfig } from "../entity-rows/types"; import { LovelaceBadge } from "../types"; +import "./hui-badge"; +import type { HuiBadge } from "./hui-badge"; import { EntityFilterBadgeConfig } from "./types"; @customElement("hui-entity-filter-badge") @@ -18,11 +19,13 @@ export class HuiEntityFilterBadge extends ReactiveElement implements LovelaceBadge { + @property({ attribute: false }) public preview = false; + @property({ attribute: false }) public hass!: HomeAssistant; @state() private _config?: EntityFilterBadgeConfig; - private _elements?: LovelaceBadge[]; + private _elements?: HuiBadge[]; private _configEntities?: EntityFilterEntityConfig[]; @@ -121,8 +124,11 @@ export class HuiEntityFilterBadge if (!isSame) { this._elements = []; for (const badgeConfig of entitiesList) { - const element = createBadgeElement(badgeConfig); + const element = document.createElement("hui-badge"); element.hass = this.hass; + element.preview = this.preview; + element.config = badgeConfig; + element.load(); this._elements.push(element); } this._oldEntities = entitiesList; @@ -140,7 +146,10 @@ export class HuiEntityFilterBadge this.appendChild(element); } - this.style.display = "inline"; + this.style.display = "flex"; + this.style.flexWrap = "wrap"; + this.style.justifyContent = "center"; + this.style.gap = "8px"; } private haveEntitiesChanged(oldHass?: HomeAssistant): boolean { diff --git a/src/panels/lovelace/badges/hui-error-badge.ts b/src/panels/lovelace/badges/hui-error-badge.ts index aa02366f26..19c6e95826 100644 --- a/src/panels/lovelace/badges/hui-error-badge.ts +++ b/src/panels/lovelace/badges/hui-error-badge.ts @@ -1,10 +1,13 @@ -import { mdiAlert } from "@mdi/js"; +import { mdiAlertCircle } from "@mdi/js"; +import { dump } from "js-yaml"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, state } from "lit/decorators"; import "../../../components/ha-label-badge"; import "../../../components/ha-svg-icon"; import { HomeAssistant } from "../../../types"; +import { showAlertDialog } from "../custom-card-helpers"; import { LovelaceBadge } from "../types"; +import { HuiEntityBadge } from "./hui-entity-badge"; import { ErrorBadgeConfig } from "./types"; export const createErrorBadgeElement = (config) => { @@ -28,24 +31,65 @@ export class HuiErrorBadge extends LitElement implements LovelaceBadge { this._config = config; } + private _viewDetail() { + let dumped: string | undefined; + + if (this._config!.origConfig) { + try { + dumped = dump(this._config!.origConfig); + } catch (err: any) { + dumped = `[Error dumping ${this._config!.origConfig}]`; + } + } + + showAlertDialog(this, { + title: this._config?.error, + warning: true, + text: dumped ? html`
${dumped}
` : "", + }); + } + protected render() { if (!this._config) { return nothing; } return html` - - - + `; } static get styles(): CSSResultGroup { - return css` - :host { - --ha-label-badge-color: var(--label-badge-red, #fce588); - } - `; + return [ + HuiEntityBadge.styles, + css` + .badge.error { + --badge-color: var(--error-color); + border-color: var(--badge-color); + } + ha-svg-icon { + color: var(--badge-color); + } + .state { + max-width: 100px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + pre { + font-family: var(--code-font-family, monospace); + white-space: break-spaces; + user-select: text; + } + `, + ]; } } diff --git a/src/panels/lovelace/badges/hui-view-badges.ts b/src/panels/lovelace/badges/hui-view-badges.ts new file mode 100644 index 0000000000..64bfa106b8 --- /dev/null +++ b/src/panels/lovelace/badges/hui-view-badges.ts @@ -0,0 +1,177 @@ +import { mdiPlus } from "@mdi/js"; +import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { repeat } from "lit/directives/repeat"; +import { fireEvent } from "../../../common/dom/fire_event"; +import "../../../components/ha-sortable"; +import type { HaSortableOptions } from "../../../components/ha-sortable"; +import "../../../components/ha-svg-icon"; +import { HomeAssistant } from "../../../types"; +import "../components/hui-badge-edit-mode"; +import { moveBadge } from "../editor/config-util"; +import { Lovelace } from "../types"; +import { HuiBadge } from "./hui-badge"; + +const BADGE_SORTABLE_OPTIONS: HaSortableOptions = { + delay: 100, + delayOnTouchOnly: true, + direction: "horizontal", + invertedSwapThreshold: 0.7, +} as HaSortableOptions; + +@customElement("hui-view-badges") +export class HuiViewBadges extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public lovelace!: Lovelace; + + @property({ attribute: false }) public badges: HuiBadge[] = []; + + @property({ attribute: false }) public viewIndex!: number; + + @state() _dragging = false; + + private _badgeConfigKeys = new WeakMap(); + + private _getBadgeKey(badge: HuiBadge) { + if (!this._badgeConfigKeys.has(badge)) { + this._badgeConfigKeys.set(badge, Math.random().toString()); + } + return this._badgeConfigKeys.get(badge)!; + } + + private _badgeMoved(ev) { + ev.stopPropagation(); + const { oldIndex, newIndex, oldPath, newPath } = ev.detail; + const newConfig = moveBadge( + this.lovelace!.config, + [...oldPath, oldIndex] as [number, number, number], + [...newPath, newIndex] as [number, number, number] + ); + this.lovelace!.saveConfig(newConfig); + } + + private _dragStart() { + this._dragging = true; + } + + private _dragEnd() { + this._dragging = false; + } + + private _addBadge() { + fireEvent(this, "ll-create-badge"); + } + + render() { + if (!this.lovelace) return nothing; + + const editMode = this.lovelace.editMode; + + const badges = this.badges; + + return html` + ${badges?.length > 0 || editMode + ? html` + +
+ ${repeat( + badges, + (badge) => this._getBadgeKey(badge), + (badge, idx) => html` + ${editMode + ? html` + + ${badge} + + ` + : badge} + ` + )} + ${editMode + ? html` + + ` + : nothing} +
+
+ ` + : nothing} + `; + } + + static get styles(): CSSResultGroup { + return css` + .badges { + display: flex; + align-items: flex-start; + flex-wrap: wrap; + justify-content: center; + gap: 8px; + margin: 0; + } + + hui-badge-edit-mode { + display: block; + position: relative; + } + + .add { + position: relative; + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + height: 36px; + padding: 6px 20px 6px 20px; + box-sizing: border-box; + width: auto; + border-radius: 18px; + background-color: transparent; + border-width: 2px; + border-style: dashed; + border-color: var(--primary-color); + --mdc-icon-size: 18px; + cursor: pointer; + color: var(--primary-text-color); + } + .add:focus { + border-style: solid; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-view-badges": HuiViewBadges; + } +} diff --git a/src/panels/lovelace/badges/types.ts b/src/panels/lovelace/badges/types.ts index 345c1c3d8e..682308d39a 100644 --- a/src/panels/lovelace/badges/types.ts +++ b/src/panels/lovelace/badges/types.ts @@ -13,6 +13,7 @@ export interface EntityFilterBadgeConfig extends LovelaceBadgeConfig { export interface ErrorBadgeConfig extends LovelaceBadgeConfig { error: string; + origConfig: LovelaceBadgeConfig; } export interface StateLabelBadgeConfig extends LovelaceBadgeConfig { @@ -25,3 +26,17 @@ export interface StateLabelBadgeConfig extends LovelaceBadgeConfig { hold_action?: ActionConfig; double_tap_action?: ActionConfig; } + +export interface EntityBadgeConfig extends LovelaceBadgeConfig { + type: "entity"; + entity?: string; + name?: string; + icon?: string; + color?: string; + show_entity_picture?: boolean; + display_type?: "minimal" | "standard" | "complete"; + state_content?: string | string[]; + tap_action?: ActionConfig; + hold_action?: ActionConfig; + double_tap_action?: ActionConfig; +} diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 1f07d1f6fc..19ece5430b 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -244,7 +244,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard { const color = this._computeStateColor(stateObj, this._config.color); const domain = computeDomain(stateObj.entity_id); - const localizedState = this._config.hide_state + const stateDisplay = this._config.hide_state ? nothing : html`
${this._config.features diff --git a/src/panels/lovelace/components/hui-badge-edit-mode.ts b/src/panels/lovelace/components/hui-badge-edit-mode.ts new file mode 100644 index 0000000000..5ccee7a24a --- /dev/null +++ b/src/panels/lovelace/components/hui-badge-edit-mode.ts @@ -0,0 +1,275 @@ +import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; +import { + mdiContentDuplicate, + mdiDelete, + mdiDotsVertical, + mdiPencil, +} from "@mdi/js"; +import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { storage } from "../../../common/decorators/storage"; +import { fireEvent } from "../../../common/dom/fire_event"; +import "../../../components/ha-button-menu"; +import "../../../components/ha-icon-button"; +import "../../../components/ha-list-item"; +import "../../../components/ha-svg-icon"; +import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import { haStyle } from "../../../resources/styles"; +import { HomeAssistant } from "../../../types"; +import { showEditBadgeDialog } from "../editor/badge-editor/show-edit-badge-dialog"; +import { + LovelaceCardPath, + findLovelaceItems, + getLovelaceContainerPath, + parseLovelaceCardPath, +} from "../editor/lovelace-path"; +import { Lovelace } from "../types"; + +@customElement("hui-badge-edit-mode") +export class HuiBadgeEditMode extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public lovelace!: Lovelace; + + @property({ type: Array }) public path!: LovelaceCardPath; + + @property({ type: Boolean }) public hiddenOverlay = false; + + @state() + public _menuOpened: boolean = false; + + @state() + public _hover: boolean = false; + + @state() + public _focused: boolean = false; + + @storage({ + key: "lovelaceClipboard", + state: false, + subscribe: false, + storage: "sessionStorage", + }) + protected _clipboard?: LovelaceCardConfig; + + private get _badges() { + const containerPath = getLovelaceContainerPath(this.path!); + return findLovelaceItems("badges", this.lovelace!.config, containerPath)!; + } + + private _touchStarted = false; + + protected firstUpdated(): void { + this.addEventListener("focus", () => { + this._focused = true; + }); + this.addEventListener("blur", () => { + this._focused = false; + }); + this.addEventListener("touchstart", () => { + this._touchStarted = true; + }); + this.addEventListener("touchend", () => { + setTimeout(() => { + this._touchStarted = false; + }, 10); + }); + this.addEventListener("mouseenter", () => { + if (this._touchStarted) return; + this._hover = true; + }); + this.addEventListener("mouseout", () => { + this._hover = false; + }); + this.addEventListener("click", () => { + this._hover = true; + document.addEventListener("click", this._documentClicked); + }); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + document.removeEventListener("click", this._documentClicked); + } + + _documentClicked = (ev) => { + this._hover = ev.composedPath().includes(this); + document.removeEventListener("click", this._documentClicked); + }; + + protected render(): TemplateResult { + const showOverlay = + (this._hover || this._menuOpened || this._focused) && !this.hiddenOverlay; + + return html` +
+
+
+
+ +
+ + + + + + ${this.hass.localize( + "ui.panel.lovelace.editor.edit_card.duplicate" + )} + +
  • + + ${this.hass.localize("ui.panel.lovelace.editor.edit_card.delete")} + + +
    +
    + `; + } + + private _handleOpened() { + this._menuOpened = true; + } + + private _handleClosed() { + this._menuOpened = false; + } + + private _handleAction(ev: CustomEvent) { + switch (ev.detail.index) { + case 0: + this._duplicateCard(); + break; + case 1: + this._deleteCard(); + break; + } + } + + private _duplicateCard(): void { + const { cardIndex } = parseLovelaceCardPath(this.path!); + const containerPath = getLovelaceContainerPath(this.path!); + const badgeConfig = this._badges![cardIndex]; + showEditBadgeDialog(this, { + lovelaceConfig: this.lovelace!.config, + saveConfig: this.lovelace!.saveConfig, + path: containerPath, + badgeConfig, + }); + } + + private _editBadge(ev): void { + if (ev.defaultPrevented) { + return; + } + if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") { + return; + } + ev.preventDefault(); + ev.stopPropagation(); + fireEvent(this, "ll-edit-badge", { path: this.path! }); + } + + private _deleteCard(): void { + fireEvent(this, "ll-delete-badge", { path: this.path! }); + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + css` + .badge-overlay { + position: absolute; + opacity: 0; + pointer-events: none; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + transition: opacity 180ms ease-in-out; + } + + .badge-overlay.visible { + opacity: 1; + pointer-events: auto; + } + + .badge-wrapper { + position: relative; + height: 100%; + z-index: 0; + } + + .edit { + outline: none !important; + cursor: pointer; + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--ha-card-border-radius, 12px); + z-index: 0; + } + .edit-overlay { + position: absolute; + inset: 0; + opacity: 0.8; + background-color: var(--primary-background-color); + border-radius: var(--ha-card-border-radius, 12px); + z-index: 0; + } + .edit ha-svg-icon { + display: flex; + position: relative; + color: var(--primary-text-color); + border-radius: 50%; + padding: 4px; + background: var(--secondary-background-color); + --mdc-icon-size: 16px; + } + .more { + position: absolute; + right: -8px; + top: -8px; + inset-inline-end: -10px; + inset-inline-start: initial; + } + .more ha-icon-button { + cursor: pointer; + border-radius: 50%; + background: var(--secondary-background-color); + --mdc-icon-button-size: 24px; + --mdc-icon-size: 16px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-badge-edit-mode": HuiBadgeEditMode; + } +} diff --git a/src/panels/lovelace/components/hui-card-edit-mode.ts b/src/panels/lovelace/components/hui-card-edit-mode.ts index a3f611cefe..f66e6c5a5f 100644 --- a/src/panels/lovelace/components/hui-card-edit-mode.ts +++ b/src/panels/lovelace/components/hui-card-edit-mode.ts @@ -24,7 +24,7 @@ import { HomeAssistant } from "../../../types"; import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"; import { LovelaceCardPath, - findLovelaceCards, + findLovelaceItems, getLovelaceContainerPath, parseLovelaceCardPath, } from "../editor/lovelace-path"; @@ -59,7 +59,7 @@ export class HuiCardEditMode extends LitElement { private get _cards() { const containerPath = getLovelaceContainerPath(this.path!); - return findLovelaceCards(this.lovelace!.config, containerPath)!; + return findLovelaceItems("cards", this.lovelace!.config, containerPath)!; } private _touchStarted = false; diff --git a/src/panels/lovelace/components/hui-card-options.ts b/src/panels/lovelace/components/hui-card-options.ts index db87e8d291..163f325895 100644 --- a/src/panels/lovelace/components/hui-card-options.ts +++ b/src/panels/lovelace/components/hui-card-options.ts @@ -46,7 +46,7 @@ import { } from "../editor/config-util"; import { LovelaceCardPath, - findLovelaceCards, + findLovelaceItems, getLovelaceContainerPath, parseLovelaceCardPath, } from "../editor/lovelace-path"; @@ -91,7 +91,7 @@ export class HuiCardOptions extends LitElement { private get _cards() { const containerPath = getLovelaceContainerPath(this.path!); - return findLovelaceCards(this.lovelace!.config, containerPath)!; + return findLovelaceItems("cards", this.lovelace!.config, containerPath)!; } protected render(): TemplateResult { diff --git a/src/panels/lovelace/create-element/create-badge-element.ts b/src/panels/lovelace/create-element/create-badge-element.ts index 2335a48c99..a6f8efeb63 100644 --- a/src/panels/lovelace/create-element/create-badge-element.ts +++ b/src/panels/lovelace/create-element/create-badge-element.ts @@ -1,8 +1,12 @@ import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; +import "../badges/hui-entity-badge"; import "../badges/hui-state-label-badge"; -import { createLovelaceElement } from "./create-element-base"; +import { + createLovelaceElement, + getLovelaceElementClass, +} from "./create-element-base"; -const ALWAYS_LOADED_TYPES = new Set(["error", "state-label"]); +const ALWAYS_LOADED_TYPES = new Set(["error", "state-label", "entity"]); const LAZY_LOAD_TYPES = { "entity-filter": () => import("../badges/hui-entity-filter-badge"), }; @@ -14,5 +18,8 @@ export const createBadgeElement = (config: LovelaceBadgeConfig) => ALWAYS_LOADED_TYPES, LAZY_LOAD_TYPES, undefined, - "state-label" + "entity" ); + +export const getBadgeElementClass = (type: string) => + getLovelaceElementClass(type, "badge", ALWAYS_LOADED_TYPES, LAZY_LOAD_TYPES); diff --git a/src/panels/lovelace/create-element/create-element-base.ts b/src/panels/lovelace/create-element/create-element-base.ts index 5ba763733c..65f3ecbcc5 100644 --- a/src/panels/lovelace/create-element/create-element-base.ts +++ b/src/panels/lovelace/create-element/create-element-base.ts @@ -12,13 +12,13 @@ import { stripCustomPrefix, } from "../../../data/lovelace_custom_cards"; import { LovelaceCardFeatureConfig } from "../card-features/types"; -import type { HuiErrorCard } from "../cards/hui-error-card"; import type { ErrorCardConfig } from "../cards/types"; import { LovelaceElement, LovelaceElementConfig } from "../elements/types"; import { LovelaceRow, LovelaceRowConfig } from "../entity-rows/types"; import { LovelaceHeaderFooterConfig } from "../header-footer/types"; import { LovelaceBadge, + LovelaceBadgeConstructor, LovelaceCard, LovelaceCardConstructor, LovelaceCardFeature, @@ -39,7 +39,7 @@ interface CreateElementConfigTypes { badge: { config: LovelaceBadgeConfig; element: LovelaceBadge; - constructor: unknown; + constructor: LovelaceBadgeConstructor; }; element: { config: LovelaceElementConfig; @@ -87,16 +87,36 @@ export const createErrorCardElement = (config: ErrorCardConfig) => { return el; }; +export const createErrorBadgeElement = (config: ErrorCardConfig) => { + const el = document.createElement("hui-error-badge"); + if (customElements.get("hui-error-badge")) { + el.setConfig(config); + } else { + import("../badges/hui-error-badge"); + customElements.whenDefined("hui-error-badge").then(() => { + customElements.upgrade(el); + el.setConfig(config); + }); + } + return el; +}; + export const createErrorCardConfig = (error, origConfig) => ({ type: "error", error, origConfig, }); +export const createErrorBadgeConfig = (error, origConfig) => ({ + type: "error", + error, + origConfig, +}); + const _createElement = ( tag: string, config: CreateElementConfigTypes[T]["config"] -): CreateElementConfigTypes[T]["element"] | HuiErrorCard => { +): CreateElementConfigTypes[T]["element"] => { const element = document.createElement( tag ) as CreateElementConfigTypes[T]["element"]; @@ -106,11 +126,18 @@ const _createElement = ( }; const _createErrorElement = ( + tagSuffix: T, error: string, config: CreateElementConfigTypes[T]["config"] -): HuiErrorCard => createErrorCardElement(createErrorCardConfig(error, config)); +): CreateElementConfigTypes[T]["element"] => { + if (tagSuffix === "badge") { + return createErrorBadgeElement(createErrorBadgeConfig(error, config)); + } + return createErrorCardElement(createErrorCardConfig(error, config)); +}; const _customCreate = ( + tagSuffix: T, tag: string, config: CreateElementConfigTypes[T]["config"] ) => { @@ -119,6 +146,7 @@ const _customCreate = ( } const element = _createErrorElement( + tagSuffix, `Custom element doesn't exist: ${tag}.`, config ); @@ -175,7 +203,7 @@ export const createLovelaceElement = ( domainTypes?: { _domain_not_found: string; [domain: string]: string }, // Default type if no type given. If given, entity types will not work. defaultType?: string -): CreateElementConfigTypes[T]["element"] | HuiErrorCard => { +): CreateElementConfigTypes[T]["element"] => { try { return tryCreateLovelaceElement( tagSuffix, @@ -188,7 +216,7 @@ export const createLovelaceElement = ( } catch (err: any) { // eslint-disable-next-line console.error(tagSuffix, config.type, err); - return _createErrorElement(err.message, config); + return _createErrorElement(tagSuffix, err.message, config); } }; @@ -203,7 +231,7 @@ export const tryCreateLovelaceElement = < domainTypes?: { _domain_not_found: string; [domain: string]: string }, // Default type if no type given. If given, entity types will not work. defaultType?: string -): CreateElementConfigTypes[T]["element"] | HuiErrorCard => { +): CreateElementConfigTypes[T]["element"] => { if (!config || typeof config !== "object") { throw new Error("Config is not an object"); } @@ -220,7 +248,7 @@ export const tryCreateLovelaceElement = < const customTag = config.type ? _getCustomTag(config.type) : undefined; if (customTag) { - return _customCreate(customTag, config); + return _customCreate(tagSuffix, customTag, config); } let type: string | undefined; diff --git a/src/panels/lovelace/editor/badge-editor/hui-badge-element-editor.ts b/src/panels/lovelace/editor/badge-editor/hui-badge-element-editor.ts new file mode 100644 index 0000000000..dad6679e8c --- /dev/null +++ b/src/panels/lovelace/editor/badge-editor/hui-badge-element-editor.ts @@ -0,0 +1,109 @@ +import { css, CSSResultGroup, html, nothing, TemplateResult } from "lit"; +import { customElement, state } from "lit/decorators"; +import { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge"; +import { getBadgeElementClass } from "../../create-element/create-badge-element"; +import type { LovelaceCardEditor, LovelaceConfigForm } from "../../types"; +import { HuiElementEditor } from "../hui-element-editor"; +import "./hui-badge-visibility-editor"; + +type Tab = "config" | "visibility"; + +@customElement("hui-badge-element-editor") +export class HuiBadgeElementEditor extends HuiElementEditor { + @state() private _curTab: Tab = "config"; + + protected async getConfigElement(): Promise { + const elClass = await getBadgeElementClass(this.configElementType!); + + // Check if a GUI editor exists + if (elClass && elClass.getConfigElement) { + return elClass.getConfigElement(); + } + + return undefined; + } + + protected async getConfigForm(): Promise { + const elClass = await getBadgeElementClass(this.configElementType!); + + // Check if a schema exists + if (elClass && elClass.getConfigForm) { + return elClass.getConfigForm(); + } + + return undefined; + } + + private _handleTabSelected(ev: CustomEvent): void { + if (!ev.detail.value) { + return; + } + this._curTab = ev.detail.value.id; + } + + private _configChanged(ev: CustomEvent): void { + ev.stopPropagation(); + this.value = ev.detail.value; + } + + protected renderConfigElement(): TemplateResult { + const displayedTabs: Tab[] = ["config", "visibility"]; + + let content: TemplateResult<1> | typeof nothing = nothing; + + switch (this._curTab) { + case "config": + content = html`${super.renderConfigElement()}`; + break; + case "visibility": + content = html` + + `; + break; + } + return html` + + ${displayedTabs.map( + (tab, index) => html` + + ${this.hass.localize( + `ui.panel.lovelace.editor.edit_badge.tab_${tab}` + )} + + ` + )} + + ${content} + `; + } + + static get styles(): CSSResultGroup { + return [ + HuiElementEditor.styles, + css` + paper-tabs { + --paper-tabs-selection-bar-color: var(--primary-color); + color: var(--primary-text-color); + text-transform: uppercase; + margin-bottom: 16px; + border-bottom: 1px solid var(--divider-color); + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-badge-element-editor": HuiBadgeElementEditor; + } +} diff --git a/src/panels/lovelace/editor/badge-editor/hui-badge-visibility-editor.ts b/src/panels/lovelace/editor/badge-editor/hui-badge-visibility-editor.ts new file mode 100644 index 0000000000..f7f1612d9f --- /dev/null +++ b/src/panels/lovelace/editor/badge-editor/hui-badge-visibility-editor.ts @@ -0,0 +1,59 @@ +import { LitElement, html, css } from "lit"; +import { customElement, property } from "lit/decorators"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-alert"; +import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; +import { HomeAssistant } from "../../../../types"; +import { Condition } from "../../common/validate-condition"; +import "../conditions/ha-card-conditions-editor"; + +@customElement("hui-badge-visibility-editor") +export class HuiBadgeVisibilityEditor extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public config!: LovelaceCardConfig; + + render() { + const conditions = this.config.visibility ?? []; + return html` +

    + ${this.hass.localize( + `ui.panel.lovelace.editor.edit_badge.visibility.explanation` + )} +

    + + + `; + } + + private _valueChanged(ev: CustomEvent): void { + ev.stopPropagation(); + const conditions = ev.detail.value as Condition[]; + const newConfig: LovelaceCardConfig = { + ...this.config, + visibility: conditions, + }; + if (newConfig.visibility?.length === 0) { + delete newConfig.visibility; + } + fireEvent(this, "value-changed", { value: newConfig }); + } + + static styles = css` + .intro { + margin: 0; + color: var(--secondary-text-color); + margin-bottom: 8px; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-badge-visibility-editor": HuiBadgeVisibilityEditor; + } +} diff --git a/src/panels/lovelace/editor/badge-editor/hui-dialog-edit-badge.ts b/src/panels/lovelace/editor/badge-editor/hui-dialog-edit-badge.ts new file mode 100644 index 0000000000..19ae477d5e --- /dev/null +++ b/src/panels/lovelace/editor/badge-editor/hui-dialog-edit-badge.ts @@ -0,0 +1,521 @@ +import { mdiClose, mdiHelpCircle } from "@mdi/js"; +import deepFreeze from "deep-freeze"; +import { + css, + CSSResultGroup, + html, + LitElement, + nothing, + PropertyValues, +} from "lit"; +import { customElement, property, query, state } from "lit/decorators"; +import type { HASSDomEvent } from "../../../../common/dom/fire_event"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { computeRTLDirection } from "../../../../common/util/compute_rtl"; +import "../../../../components/ha-circular-progress"; +import "../../../../components/ha-dialog"; +import "../../../../components/ha-dialog-header"; +import "../../../../components/ha-icon-button"; +import { + defaultBadgeConfig, + LovelaceBadgeConfig, +} from "../../../../data/lovelace/config/badge"; +import { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; +import { + getCustomBadgeEntry, + isCustomType, + stripCustomPrefix, +} from "../../../../data/lovelace_custom_cards"; +import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; +import type { HassDialog } from "../../../../dialogs/make-dialog-manager"; +import { haStyleDialog } from "../../../../resources/styles"; +import type { HomeAssistant } from "../../../../types"; +import { showSaveSuccessToast } from "../../../../util/toast-saved-success"; +import "../../badges/hui-badge"; +import "../../sections/hui-section"; +import { addBadge, replaceBadge } from "../config-util"; +import { getBadgeDocumentationURL } from "../get-dashboard-documentation-url"; +import type { ConfigChangedEvent } from "../hui-element-editor"; +import { findLovelaceContainer } from "../lovelace-path"; +import type { GUIModeChangedEvent } from "../types"; +import "./hui-badge-element-editor"; +import type { HuiBadgeElementEditor } from "./hui-badge-element-editor"; +import type { EditBadgeDialogParams } from "./show-edit-badge-dialog"; + +declare global { + // for fire event + interface HASSDomEvents { + "reload-lovelace": undefined; + } + // for add event listener + interface HTMLElementEventMap { + "reload-lovelace": HASSDomEvent; + } +} + +@customElement("hui-dialog-edit-badge") +export class HuiDialogEditBadge + extends LitElement + implements HassDialog +{ + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean, reflect: true }) public large = false; + + @state() private _params?: EditBadgeDialogParams; + + @state() private _badgeConfig?: LovelaceBadgeConfig; + + @state() private _containerConfig!: LovelaceViewConfig; + + @state() private _saving = false; + + @state() private _error?: string; + + @state() private _guiModeAvailable? = true; + + @query("hui-badge-element-editor") + private _badgeEditorEl?: HuiBadgeElementEditor; + + @state() private _GUImode = true; + + @state() private _documentationURL?: string; + + @state() private _dirty = false; + + @state() private _isEscapeEnabled = true; + + public async showDialog(params: EditBadgeDialogParams): Promise { + this._params = params; + this._GUImode = true; + this._guiModeAvailable = true; + + const containerConfig = findLovelaceContainer( + params.lovelaceConfig, + params.path + ); + + if ("strategy" in containerConfig) { + throw new Error("Can't edit strategy"); + } + + this._containerConfig = containerConfig; + + if ("badgeConfig" in params) { + this._badgeConfig = params.badgeConfig; + this._dirty = true; + } else { + const badge = this._containerConfig.badges?.[params.badgeIndex]; + this._badgeConfig = + typeof badge === "string" ? defaultBadgeConfig(badge) : badge; + } + + this.large = false; + if (this._badgeConfig && !Object.isFrozen(this._badgeConfig)) { + this._badgeConfig = deepFreeze(this._badgeConfig); + } + } + + public closeDialog(): boolean { + this._isEscapeEnabled = true; + window.removeEventListener("dialog-closed", this._enableEscapeKeyClose); + window.removeEventListener("hass-more-info", this._disableEscapeKeyClose); + if (this._dirty) { + this._confirmCancel(); + return false; + } + this._params = undefined; + this._badgeConfig = undefined; + this._error = undefined; + this._documentationURL = undefined; + this._dirty = false; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + return true; + } + + protected updated(changedProps: PropertyValues): void { + if ( + !this._badgeConfig || + this._documentationURL !== undefined || + !changedProps.has("_badgeConfig") + ) { + return; + } + + const oldConfig = changedProps.get("_badgeConfig") as LovelaceBadgeConfig; + + if (oldConfig?.type !== this._badgeConfig!.type) { + this._documentationURL = this._badgeConfig!.type + ? getBadgeDocumentationURL(this.hass, this._badgeConfig!.type) + : undefined; + } + } + + private _enableEscapeKeyClose = (ev: any) => { + if (ev.detail.dialog === "ha-more-info-dialog") { + this._isEscapeEnabled = true; + } + }; + + private _disableEscapeKeyClose = () => { + this._isEscapeEnabled = false; + }; + + protected render() { + if (!this._params) { + return nothing; + } + + let heading: string; + if (this._badgeConfig && this._badgeConfig.type) { + let badgeName: string | undefined; + if (isCustomType(this._badgeConfig.type)) { + // prettier-ignore + badgeName = getCustomBadgeEntry( + stripCustomPrefix(this._badgeConfig.type) + )?.name; + // Trim names that end in " Card" so as not to redundantly duplicate it + if (badgeName?.toLowerCase().endsWith(" badge")) { + badgeName = badgeName.substring(0, badgeName.length - 6); + } + } else { + badgeName = this.hass!.localize( + `ui.panel.lovelace.editor.badge.${this._badgeConfig.type}.name` + ); + } + heading = this.hass!.localize( + "ui.panel.lovelace.editor.edit_badge.typed_header", + { type: badgeName } + ); + } else if (!this._badgeConfig) { + heading = this._containerConfig.title + ? this.hass!.localize( + "ui.panel.lovelace.editor.edit_badge.pick_badge_view_title", + { name: this._containerConfig.title } + ) + : this.hass!.localize("ui.panel.lovelace.editor.edit_badge.pick_badge"); + } else { + heading = this.hass!.localize( + "ui.panel.lovelace.editor.edit_badge.header" + ); + } + + return html` + + + + ${heading} + ${this._documentationURL !== undefined + ? html` + + + + ` + : nothing} + +
    +
    + +
    +
    + + ${this._error + ? html` + + ` + : ``} +
    +
    + ${this._badgeConfig !== undefined + ? html` + + ${this.hass!.localize( + !this._badgeEditorEl || this._GUImode + ? "ui.panel.lovelace.editor.edit_badge.show_code_editor" + : "ui.panel.lovelace.editor.edit_badge.show_visual_editor" + )} + + ` + : ""} +
    + + ${this.hass!.localize("ui.common.cancel")} + + ${this._badgeConfig !== undefined && this._dirty + ? html` + + ${this._saving + ? html` + + ` + : this.hass!.localize("ui.common.save")} + + ` + : ``} +
    +
    + `; + } + + private _enlarge() { + this.large = !this.large; + } + + private _ignoreKeydown(ev: KeyboardEvent) { + ev.stopPropagation(); + } + + private _handleConfigChanged(ev: HASSDomEvent) { + this._badgeConfig = deepFreeze(ev.detail.config); + this._error = ev.detail.error; + this._guiModeAvailable = ev.detail.guiModeAvailable; + this._dirty = true; + } + + private _handleGUIModeChanged(ev: HASSDomEvent): void { + ev.stopPropagation(); + this._GUImode = ev.detail.guiMode; + this._guiModeAvailable = ev.detail.guiModeAvailable; + } + + private _toggleMode(): void { + this._badgeEditorEl?.toggleMode(); + } + + private _opened() { + window.addEventListener("dialog-closed", this._enableEscapeKeyClose); + window.addEventListener("hass-more-info", this._disableEscapeKeyClose); + this._badgeEditorEl?.focusYamlEditor(); + } + + private get _canSave(): boolean { + if (this._saving) { + return false; + } + if (this._badgeConfig === undefined) { + return false; + } + if (this._badgeEditorEl && this._badgeEditorEl.hasError) { + return false; + } + return true; + } + + private async _confirmCancel() { + // Make sure the open state of this dialog is handled before the open state of confirm dialog + await new Promise((resolve) => { + setTimeout(resolve, 0); + }); + const confirm = await showConfirmationDialog(this, { + title: this.hass!.localize( + "ui.panel.lovelace.editor.edit_badge.unsaved_changes" + ), + text: this.hass!.localize( + "ui.panel.lovelace.editor.edit_badge.confirm_cancel" + ), + dismissText: this.hass!.localize("ui.common.stay"), + confirmText: this.hass!.localize("ui.common.leave"), + }); + if (confirm) { + this._cancel(); + } + } + + private _cancel(ev?: Event) { + if (ev) { + ev.stopPropagation(); + } + this._dirty = false; + this.closeDialog(); + } + + private async _save(): Promise { + if (!this._canSave) { + return; + } + if (!this._dirty) { + this.closeDialog(); + return; + } + this._saving = true; + const path = this._params!.path; + await this._params!.saveConfig( + "badgeConfig" in this._params! + ? addBadge(this._params!.lovelaceConfig, path, this._badgeConfig!) + : replaceBadge( + this._params!.lovelaceConfig, + [...path, this._params!.badgeIndex], + this._badgeConfig! + ) + ); + this._saving = false; + this._dirty = false; + showSaveSuccessToast(this, this.hass); + this.closeDialog(); + } + + static get styles(): CSSResultGroup { + return [ + haStyleDialog, + css` + :host { + --code-mirror-max-height: calc(100vh - 176px); + } + + ha-dialog { + --mdc-dialog-max-width: 100px; + --dialog-z-index: 6; + --dialog-surface-position: fixed; + --dialog-surface-top: 40px; + --mdc-dialog-max-width: 90vw; + --dialog-content-padding: 24px 12px; + } + + .content { + width: calc(90vw - 48px); + max-width: 1000px; + } + + @media all and (max-width: 450px), all and (max-height: 500px) { + /* overrule the ha-style-dialog max-height on small screens */ + ha-dialog { + height: 100%; + --mdc-dialog-max-height: 100%; + --dialog-surface-top: 0px; + --mdc-dialog-max-width: 100vw; + } + .content { + width: 100%; + max-width: 100%; + } + } + + @media all and (min-width: 451px) and (min-height: 501px) { + :host([large]) .content { + max-width: none; + } + } + + .center { + margin-left: auto; + margin-right: auto; + } + + .content { + display: flex; + flex-direction: column; + } + + .content .element-editor { + margin: 0 10px; + } + + @media (min-width: 1000px) { + .content { + flex-direction: row; + } + .content > * { + flex-basis: 0; + flex-grow: 1; + flex-shrink: 1; + min-width: 0; + } + } + .hidden { + display: none; + } + .element-editor { + margin-bottom: 8px; + } + .blur { + filter: blur(2px) grayscale(100%); + } + .element-preview { + position: relative; + height: max-content; + background: var(--primary-background-color); + padding: 10px; + border-radius: 4px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + .element-preview ha-circular-progress { + top: 50%; + left: 50%; + position: absolute; + z-index: 10; + } + .gui-mode-button { + margin-right: auto; + margin-inline-end: auto; + margin-inline-start: initial; + } + .header { + display: flex; + align-items: center; + justify-content: space-between; + } + ha-dialog-header a { + color: inherit; + text-decoration: none; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-dialog-edit-badge": HuiDialogEditBadge; + } +} diff --git a/src/panels/lovelace/editor/badge-editor/show-edit-badge-dialog.ts b/src/panels/lovelace/editor/badge-editor/show-edit-badge-dialog.ts new file mode 100644 index 0000000000..e7640e9887 --- /dev/null +++ b/src/panels/lovelace/editor/badge-editor/show-edit-badge-dialog.ts @@ -0,0 +1,30 @@ +import { fireEvent } from "../../../../common/dom/fire_event"; +import type { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge"; +import type { LovelaceConfig } from "../../../../data/lovelace/config/types"; +import { LovelaceContainerPath } from "../lovelace-path"; + +export type EditBadgeDialogParams = { + lovelaceConfig: LovelaceConfig; + saveConfig: (config: LovelaceConfig) => void; + path: LovelaceContainerPath; +} & ( + | { + badgeIndex: number; + } + | { + badgeConfig: LovelaceBadgeConfig; + } +); + +export const importEditBadgeDialog = () => import("./hui-dialog-edit-badge"); + +export const showEditBadgeDialog = ( + element: HTMLElement, + editBadgeDialogParams: EditBadgeDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "hui-dialog-edit-badge", + dialogImport: importEditBadgeDialog, + dialogParams: editBadgeDialogParams, + }); +}; diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts index eb9294f9e5..9b3c3aa9d1 100644 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts @@ -30,15 +30,15 @@ import type { HassDialog } from "../../../../dialogs/make-dialog-manager"; import { haStyleDialog } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; import { showSaveSuccessToast } from "../../../../util/toast-saved-success"; +import "../../cards/hui-card"; import "../../sections/hui-section"; import { addCard, replaceCard } from "../config-util"; -import { getCardDocumentationURL } from "../get-card-documentation-url"; +import { getCardDocumentationURL } from "../get-dashboard-documentation-url"; import type { ConfigChangedEvent } from "../hui-element-editor"; import { findLovelaceContainer } from "../lovelace-path"; import type { GUIModeChangedEvent } from "../types"; import "./hui-card-element-editor"; import type { HuiCardElementEditor } from "./hui-card-element-editor"; -import "../../cards/hui-card"; import type { EditCardDialogParams } from "./show-edit-card-dialog"; declare global { diff --git a/src/panels/lovelace/editor/config-elements/hui-entity-badge-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entity-badge-editor.ts new file mode 100644 index 0000000000..73f5b65c07 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-entity-badge-editor.ts @@ -0,0 +1,236 @@ +import { mdiGestureTap, mdiPalette } from "@mdi/js"; +import { LitElement, css, html, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { + array, + assert, + assign, + boolean, + enums, + object, + optional, + string, + union, +} from "superstruct"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { LocalizeFunc } from "../../../../common/translations/localize"; +import "../../../../components/ha-form/ha-form"; +import type { + HaFormSchema, + SchemaUnion, +} from "../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../types"; +import { + DEFAULT_DISPLAY_TYPE, + DISPLAY_TYPES, +} from "../../badges/hui-entity-badge"; +import { EntityBadgeConfig } from "../../badges/types"; +import type { LovelaceBadgeEditor } from "../../types"; +import "../hui-sub-element-editor"; +import { actionConfigStruct } from "../structs/action-struct"; +import { baseLovelaceBadgeConfig } from "../structs/base-badge-struct"; +import { configElementStyle } from "./config-elements-style"; +import "./hui-card-features-editor"; + +const badgeConfigStruct = assign( + baseLovelaceBadgeConfig, + object({ + entity: optional(string()), + display_type: optional(enums(DISPLAY_TYPES)), + name: optional(string()), + icon: optional(string()), + state_content: optional(union([string(), array(string())])), + color: optional(string()), + show_entity_picture: optional(boolean()), + tap_action: optional(actionConfigStruct), + }) +); + +@customElement("hui-entity-badge-editor") +export class HuiEntityBadgeEditor + extends LitElement + implements LovelaceBadgeEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: EntityBadgeConfig; + + public setConfig(config: EntityBadgeConfig): void { + assert(config, badgeConfigStruct); + this._config = config; + } + + private _schema = memoizeOne( + (localize: LocalizeFunc) => + [ + { name: "entity", selector: { entity: {} } }, + { + name: "", + type: "expandable", + iconPath: mdiPalette, + title: localize(`ui.panel.lovelace.editor.badge.entity.appearance`), + schema: [ + { + name: "display_type", + selector: { + select: { + mode: "dropdown", + options: DISPLAY_TYPES.map((type) => ({ + value: type, + label: localize( + `ui.panel.lovelace.editor.badge.entity.display_type_options.${type}` + ), + })), + }, + }, + }, + { + name: "", + type: "grid", + schema: [ + { + name: "name", + selector: { + text: {}, + }, + }, + { + name: "icon", + selector: { + icon: {}, + }, + context: { icon_entity: "entity" }, + }, + { + name: "color", + selector: { + ui_color: { default_color: true }, + }, + }, + { + name: "show_entity_picture", + selector: { + boolean: {}, + }, + }, + ], + }, + + { + name: "state_content", + selector: { + ui_state_content: {}, + }, + context: { + filter_entity: "entity", + }, + }, + ], + }, + { + name: "", + type: "expandable", + title: localize(`ui.panel.lovelace.editor.badge.entity.actions`), + iconPath: mdiGestureTap, + schema: [ + { + name: "tap_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, + ], + }, + ] as const satisfies readonly HaFormSchema[] + ); + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + const schema = this._schema(this.hass!.localize); + + const data = { ...this._config }; + + if (!data.display_type) { + data.display_type = DEFAULT_DISPLAY_TYPE; + } + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + ev.stopPropagation(); + if (!this._config || !this.hass) { + return; + } + + const newConfig = ev.detail.value as EntityBadgeConfig; + + const config: EntityBadgeConfig = { + ...newConfig, + }; + + if (!config.state_content) { + delete config.state_content; + } + + if (config.display_type === "standard") { + delete config.display_type; + } + + fireEvent(this, "config-changed", { config }); + } + + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "color": + case "state_content": + case "display_type": + case "show_entity_picture": + return this.hass!.localize( + `ui.panel.lovelace.editor.badge.entity.${schema.name}` + ); + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); + } + }; + + static get styles() { + return [ + configElementStyle, + css` + .container { + display: flex; + flex-direction: column; + } + ha-form { + display: block; + margin-bottom: 24px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-entity-badge-editor": HuiEntityBadgeEditor; + } +} diff --git a/src/panels/lovelace/editor/config-util.ts b/src/panels/lovelace/editor/config-util.ts index 0207a90e97..7eb5d5f105 100644 --- a/src/panels/lovelace/editor/config-util.ts +++ b/src/panels/lovelace/editor/config-util.ts @@ -1,3 +1,4 @@ +import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import { LovelaceSectionRawConfig } from "../../../data/lovelace/config/section"; import { LovelaceConfig } from "../../../data/lovelace/config/types"; @@ -9,13 +10,13 @@ import type { HomeAssistant } from "../../../types"; import { LovelaceCardPath, LovelaceContainerPath, - findLovelaceCards, findLovelaceContainer, + findLovelaceItems, getLovelaceContainerPath, parseLovelaceCardPath, parseLovelaceContainerPath, - updateLovelaceCards, updateLovelaceContainer, + updateLovelaceItems, } from "./lovelace-path"; export const addCard = ( @@ -23,9 +24,9 @@ export const addCard = ( path: LovelaceContainerPath, cardConfig: LovelaceCardConfig ): LovelaceConfig => { - const cards = findLovelaceCards(config, path); + const cards = findLovelaceItems("cards", config, path); const newCards = cards ? [...cards, cardConfig] : [cardConfig]; - const newConfig = updateLovelaceCards(config, path, newCards); + const newConfig = updateLovelaceItems("cards", config, path, newCards); return newConfig; }; @@ -34,9 +35,9 @@ export const addCards = ( path: LovelaceContainerPath, cardConfigs: LovelaceCardConfig[] ): LovelaceConfig => { - const cards = findLovelaceCards(config, path); + const cards = findLovelaceItems("cards", config, path); const newCards = cards ? [...cards, ...cardConfigs] : [...cardConfigs]; - const newConfig = updateLovelaceCards(config, path, newCards); + const newConfig = updateLovelaceItems("cards", config, path, newCards); return newConfig; }; @@ -48,13 +49,18 @@ export const replaceCard = ( const { cardIndex } = parseLovelaceCardPath(path); const containerPath = getLovelaceContainerPath(path); - const cards = findLovelaceCards(config, containerPath); + const cards = findLovelaceItems("cards", config, containerPath); const newCards = (cards ?? []).map((origConf, ind) => ind === cardIndex ? cardConfig : origConf ); - const newConfig = updateLovelaceCards(config, containerPath, newCards); + const newConfig = updateLovelaceItems( + "cards", + config, + containerPath, + newCards + ); return newConfig; }; @@ -65,11 +71,16 @@ export const deleteCard = ( const { cardIndex } = parseLovelaceCardPath(path); const containerPath = getLovelaceContainerPath(path); - const cards = findLovelaceCards(config, containerPath); + const cards = findLovelaceItems("cards", config, containerPath); const newCards = (cards ?? []).filter((_origConf, ind) => ind !== cardIndex); - const newConfig = updateLovelaceCards(config, containerPath, newCards); + const newConfig = updateLovelaceItems( + "cards", + config, + containerPath, + newCards + ); return newConfig; }; @@ -81,13 +92,18 @@ export const insertCard = ( const { cardIndex } = parseLovelaceCardPath(path); const containerPath = getLovelaceContainerPath(path); - const cards = findLovelaceCards(config, containerPath); + const cards = findLovelaceItems("cards", config, containerPath); const newCards = cards ? [...cards.slice(0, cardIndex), cardConfig, ...cards.slice(cardIndex)] : [cardConfig]; - const newConfig = updateLovelaceCards(config, containerPath, newCards); + const newConfig = updateLovelaceItems( + "cards", + config, + containerPath, + newCards + ); return newConfig; }; @@ -99,7 +115,7 @@ export const moveCardToIndex = ( const { cardIndex } = parseLovelaceCardPath(path); const containerPath = getLovelaceContainerPath(path); - const cards = findLovelaceCards(config, containerPath); + const cards = findLovelaceItems("cards", config, containerPath); const newCards = cards ? [...cards] : []; @@ -110,7 +126,12 @@ export const moveCardToIndex = ( newCards.splice(oldIndex, 1); newCards.splice(newIndex, 0, card); - const newConfig = updateLovelaceCards(config, containerPath, newCards); + const newConfig = updateLovelaceItems( + "cards", + config, + containerPath, + newCards + ); return newConfig; }; @@ -132,7 +153,7 @@ export const moveCardToContainer = ( } const fromContainerPath = getLovelaceContainerPath(fromPath); - const cards = findLovelaceCards(config, fromContainerPath); + const cards = findLovelaceItems("cards", config, fromContainerPath); const card = cards![fromCardIndex]; let newConfig = addCard(config, toPath, card); @@ -148,7 +169,7 @@ export const moveCard = ( ): LovelaceConfig => { const { cardIndex: fromCardIndex } = parseLovelaceCardPath(fromPath); const fromContainerPath = getLovelaceContainerPath(fromPath); - const cards = findLovelaceCards(config, fromContainerPath); + const cards = findLovelaceItems("cards", config, fromContainerPath); const card = cards![fromCardIndex]; let newConfig = deleteCard(config, fromPath); @@ -298,3 +319,98 @@ export const moveSection = ( return newConfig; }; + +export const addBadge = ( + config: LovelaceConfig, + path: LovelaceContainerPath, + badgeConfig: LovelaceBadgeConfig +): LovelaceConfig => { + const badges = findLovelaceItems("badges", config, path); + const newBadges = badges ? [...badges, badgeConfig] : [badgeConfig]; + const newConfig = updateLovelaceItems("badges", config, path, newBadges); + return newConfig; +}; + +export const replaceBadge = ( + config: LovelaceConfig, + path: LovelaceCardPath, + cardConfig: LovelaceBadgeConfig +): LovelaceConfig => { + const { cardIndex } = parseLovelaceCardPath(path); + const containerPath = getLovelaceContainerPath(path); + + const badges = findLovelaceItems("badges", config, containerPath); + + const newBadges = (badges ?? []).map((origConf, ind) => + ind === cardIndex ? cardConfig : origConf + ); + + const newConfig = updateLovelaceItems( + "badges", + config, + containerPath, + newBadges + ); + return newConfig; +}; + +export const deleteBadge = ( + config: LovelaceConfig, + path: LovelaceCardPath +): LovelaceConfig => { + const { cardIndex } = parseLovelaceCardPath(path); + const containerPath = getLovelaceContainerPath(path); + + const badges = findLovelaceItems("badges", config, containerPath); + + const newBadges = (badges ?? []).filter( + (_origConf, ind) => ind !== cardIndex + ); + + const newConfig = updateLovelaceItems( + "badges", + config, + containerPath, + newBadges + ); + return newConfig; +}; + +export const insertBadge = ( + config: LovelaceConfig, + path: LovelaceCardPath, + badgeConfig: LovelaceBadgeConfig +) => { + const { cardIndex } = parseLovelaceCardPath(path); + const containerPath = getLovelaceContainerPath(path); + + const badges = findLovelaceItems("badges", config, containerPath); + + const newBadges = badges + ? [...badges.slice(0, cardIndex), badgeConfig, ...badges.slice(cardIndex)] + : [badgeConfig]; + + const newConfig = updateLovelaceItems( + "badges", + config, + containerPath, + newBadges + ); + return newConfig; +}; + +export const moveBadge = ( + config: LovelaceConfig, + fromPath: LovelaceCardPath, + toPath: LovelaceCardPath +): LovelaceConfig => { + const { cardIndex: fromCardIndex } = parseLovelaceCardPath(fromPath); + const fromContainerPath = getLovelaceContainerPath(fromPath); + const badges = findLovelaceItems("badges", config, fromContainerPath); + const badge = badges![fromCardIndex]; + + let newConfig = deleteBadge(config, fromPath); + newConfig = insertBadge(newConfig, toPath, badge); + + return newConfig; +}; diff --git a/src/panels/lovelace/editor/get-badge-stub-config.ts b/src/panels/lovelace/editor/get-badge-stub-config.ts new file mode 100644 index 0000000000..63c64e57a8 --- /dev/null +++ b/src/panels/lovelace/editor/get-badge-stub-config.ts @@ -0,0 +1,26 @@ +import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import { HomeAssistant } from "../../../types"; +import { getBadgeElementClass } from "../create-element/create-badge-element"; + +export const getBadgeStubConfig = async ( + hass: HomeAssistant, + type: string, + entities: string[], + entitiesFallback: string[] +): Promise => { + let badgeConfig: LovelaceCardConfig = { type }; + + const elClass = await getBadgeElementClass(type); + + if (elClass && elClass.getStubConfig) { + const classStubConfig = await elClass.getStubConfig( + hass, + entities, + entitiesFallback + ); + + badgeConfig = { ...badgeConfig, ...classStubConfig }; + } + + return badgeConfig; +}; diff --git a/src/panels/lovelace/editor/get-card-documentation-url.ts b/src/panels/lovelace/editor/get-dashboard-documentation-url.ts similarity index 55% rename from src/panels/lovelace/editor/get-card-documentation-url.ts rename to src/panels/lovelace/editor/get-dashboard-documentation-url.ts index e312463d4b..aada4e5978 100644 --- a/src/panels/lovelace/editor/get-card-documentation-url.ts +++ b/src/panels/lovelace/editor/get-dashboard-documentation-url.ts @@ -1,4 +1,5 @@ import { + getCustomBadgeEntry, getCustomCardEntry, isCustomType, stripCustomPrefix, @@ -14,5 +15,16 @@ export const getCardDocumentationURL = ( return getCustomCardEntry(stripCustomPrefix(type))?.documentationURL; } - return `${documentationUrl(hass, "/lovelace/")}${type}`; + return `${documentationUrl(hass, "/dashboards/")}${type}`; +}; + +export const getBadgeDocumentationURL = ( + hass: HomeAssistant, + type: string +): string | undefined => { + if (isCustomType(type)) { + return getCustomBadgeEntry(stripCustomPrefix(type))?.documentationURL; + } + + return `${documentationUrl(hass, "/dashboards/badges")}`; }; diff --git a/src/panels/lovelace/editor/lovelace-path.ts b/src/panels/lovelace/editor/lovelace-path.ts index d4527126ba..c27716d050 100644 --- a/src/panels/lovelace/editor/lovelace-path.ts +++ b/src/panels/lovelace/editor/lovelace-path.ts @@ -1,3 +1,4 @@ +import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import { LovelaceSectionRawConfig, @@ -80,35 +81,6 @@ export const findLovelaceContainer: FindLovelaceContainer = ( return section; }; -export const findLovelaceCards = ( - config: LovelaceConfig, - path: LovelaceContainerPath -): LovelaceCardConfig[] | undefined => { - const { viewIndex, sectionIndex } = parseLovelaceContainerPath(path); - - const view = config.views[viewIndex]; - - if (!view) { - throw new Error("View does not exist"); - } - if (isStrategyView(view)) { - throw new Error("Can not find cards in a strategy view"); - } - if (sectionIndex === undefined) { - return view.cards; - } - - const section = view.sections?.[sectionIndex]; - - if (!section) { - throw new Error("Section does not exist"); - } - if (isStrategySection(section)) { - throw new Error("Can not find cards in a strategy section"); - } - return section.cards; -}; - export const updateLovelaceContainer = ( config: LovelaceConfig, path: LovelaceContainerPath, @@ -153,10 +125,16 @@ export const updateLovelaceContainer = ( }; }; -export const updateLovelaceCards = ( +type LovelaceItemKeys = { + cards: LovelaceCardConfig[]; + badges: LovelaceBadgeConfig[]; +}; + +export const updateLovelaceItems = ( + key: T, config: LovelaceConfig, path: LovelaceContainerPath, - cards: LovelaceCardConfig[] + items: LovelaceItemKeys[T] ): LovelaceConfig => { const { viewIndex, sectionIndex } = parseLovelaceContainerPath(path); @@ -164,13 +142,13 @@ export const updateLovelaceCards = ( const newViews = config.views.map((view, vIndex) => { if (vIndex !== viewIndex) return view; if (isStrategyView(view)) { - throw new Error("Can not update cards in a strategy view"); + throw new Error(`Can not update ${key} in a strategy view`); } if (sectionIndex === undefined) { updated = true; return { ...view, - cards, + [key]: items, }; } @@ -181,12 +159,12 @@ export const updateLovelaceCards = ( const newSections = view.sections.map((section, sIndex) => { if (sIndex !== sectionIndex) return section; if (isStrategySection(section)) { - throw new Error("Can not update cards in a strategy section"); + throw new Error(`Can not update ${key} in a strategy section`); } updated = true; return { ...section, - cards, + [key]: items, }; }); return { @@ -196,10 +174,43 @@ export const updateLovelaceCards = ( }); if (!updated) { - throw new Error("Can not update cards in a non-existing view/section"); + throw new Error(`Can not update ${key} in a non-existing view/section`); } return { ...config, views: newViews, }; }; + +export const findLovelaceItems = ( + key: T, + config: LovelaceConfig, + path: LovelaceContainerPath +): LovelaceItemKeys[T] | undefined => { + const { viewIndex, sectionIndex } = parseLovelaceContainerPath(path); + + const view = config.views[viewIndex]; + + if (!view) { + throw new Error("View does not exist"); + } + if (isStrategyView(view)) { + throw new Error("Can not find cards in a strategy view"); + } + if (sectionIndex === undefined) { + return view[key] as LovelaceItemKeys[T] | undefined; + } + + const section = view.sections?.[sectionIndex]; + + if (!section) { + throw new Error("Section does not exist"); + } + if (isStrategySection(section)) { + throw new Error("Can not find cards in a strategy section"); + } + if (key === "cards") { + return section[key as "cards"] as LovelaceItemKeys[T] | undefined; + } + throw new Error(`${key} is not supported in section`); +}; diff --git a/src/panels/lovelace/editor/structs/base-badge-struct.ts b/src/panels/lovelace/editor/structs/base-badge-struct.ts new file mode 100644 index 0000000000..b738119cef --- /dev/null +++ b/src/panels/lovelace/editor/structs/base-badge-struct.ts @@ -0,0 +1,6 @@ +import { object, string, any } from "superstruct"; + +export const baseLovelaceBadgeConfig = object({ + type: string(), + visibility: any(), +}); diff --git a/src/panels/lovelace/types.ts b/src/panels/lovelace/types.ts index 4c7ae4d164..5a522ea156 100644 --- a/src/panels/lovelace/types.ts +++ b/src/panels/lovelace/types.ts @@ -82,6 +82,16 @@ export interface LovelaceCardConstructor extends Constructor { getConfigForm?: () => LovelaceConfigForm; } +export interface LovelaceBadgeConstructor extends Constructor { + getStubConfig?: ( + hass: HomeAssistant, + entities: string[], + entitiesFallback: string[] + ) => LovelaceBadgeConfig; + getConfigElement?: () => LovelaceBadgeEditor; + getConfigForm?: () => LovelaceConfigForm; +} + export interface LovelaceHeaderFooterConstructor extends Constructor { getStubConfig?: ( @@ -107,6 +117,10 @@ export interface LovelaceCardEditor extends LovelaceGenericElementEditor { setConfig(config: LovelaceCardConfig): void; } +export interface LovelaceBadgeEditor extends LovelaceGenericElementEditor { + setConfig(config: LovelaceBadgeConfig): void; +} + export interface LovelaceHeaderFooterEditor extends LovelaceGenericElementEditor { setConfig(config: LovelaceHeaderFooterConfig): void; diff --git a/src/panels/lovelace/views/hui-masonry-view.ts b/src/panels/lovelace/views/hui-masonry-view.ts index 520856ad7b..500bc441cf 100644 --- a/src/panels/lovelace/views/hui-masonry-view.ts +++ b/src/panels/lovelace/views/hui-masonry-view.ts @@ -15,9 +15,11 @@ import "../../../components/ha-svg-icon"; import type { LovelaceViewElement } from "../../../data/lovelace"; import type { LovelaceViewConfig } from "../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../types"; +import { HuiBadge } from "../badges/hui-badge"; +import "../badges/hui-view-badges"; import { HuiCard } from "../cards/hui-card"; import { computeCardSize } from "../common/compute-card-size"; -import type { Lovelace, LovelaceBadge } from "../types"; +import type { Lovelace } from "../types"; // Find column with < 5 size, else smallest column const getColumnIndex = (columnSizes: number[], size: number) => { @@ -50,7 +52,7 @@ export class MasonryView extends LitElement implements LovelaceViewElement { @property({ attribute: false }) public cards: HuiCard[] = []; - @property({ attribute: false }) public badges: LovelaceBadge[] = []; + @property({ attribute: false }) public badges: HuiBadge[] = []; @state() private _columns?: number; @@ -78,9 +80,12 @@ export class MasonryView extends LitElement implements LovelaceViewElement { protected render(): TemplateResult { return html` - ${this.badges.length > 0 - ? html`
    ${this.badges}
    ` - : ""} +
    (); - private _getKey(sectionConfig: HuiSection) { - if (!this._sectionConfigKeys.has(sectionConfig)) { - this._sectionConfigKeys.set(sectionConfig, Math.random().toString()); + private _getSectionKey(section: HuiSection) { + if (!this._sectionConfigKeys.has(section)) { + this._sectionConfigKeys.set(section, Math.random().toString()); } - return this._sectionConfigKeys.get(sectionConfig)!; + return this._sectionConfigKeys.get(section)!; } private _computeSectionsCount() { @@ -83,9 +88,12 @@ export class SectionsView extends LitElement implements LovelaceViewElement { const maxColumnsCount = this._config?.max_columns; return html` - ${this.badges.length > 0 - ? html`
    ${this.badges}
    ` - : ""} + ${repeat( sections, - (section) => this._getKey(section), + (section) => this._getSectionKey(section), (section, idx) => { (section as any).itemPath = [idx]; return html` @@ -141,7 +149,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement { ${editMode ? html`
    `; @@ -190,19 +216,42 @@ export class HaIntegrationCard extends LitElement { } ); - private _getEntities = memoizeOne( + private _getEntityCount = memoizeOne( ( configEntry: ConfigEntry[], - entityRegistryEntries: EntityRegistryEntry[] - ): EntityRegistryEntry[] => { + entityRegistryEntries: EntityRegistryEntry[], + domainEntities: string[] + ): number => { if (!entityRegistryEntries) { - return []; + return domainEntities.length; } - const entryIds = configEntry.map((entry) => entry.entry_id); - return entityRegistryEntries.filter( + + const entryIds = configEntry + .map((entry) => entry.entry_id) + .filter(Boolean); + + if (!entryIds.length) { + return domainEntities.length; + } + + const entityRegEntities = entityRegistryEntries.filter( (entity) => entity.config_entry_id && entryIds.includes(entity.config_entry_id) ); + + if (entityRegEntities.length === domainEntities.length) { + return domainEntities.length; + } + + const entityIds = new Set( + entityRegEntities.map((reg) => reg.entity_id) + ); + + for (const entity of domainEntities) { + entityIds.add(entity); + } + + return entityIds.size; } ); @@ -308,6 +357,9 @@ export class HaIntegrationCard extends LitElement { .icon.custom { background: var(--warning-color); } + .icon.yaml { + background: var(--label-badge-grey); + } .icon ha-svg-icon { width: 16px; height: 16px; @@ -316,6 +368,9 @@ export class HaIntegrationCard extends LitElement { simple-tooltip { white-space: nowrap; } + .spacer { + height: 36px; + } `, ]; } diff --git a/src/translations/en.json b/src/translations/en.json index 5eb543eb0c..4f69e79e79 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4270,6 +4270,7 @@ "entries_system": "[%key:ui::panel::config::integrations::integration_page::entries%]", "entries_entity": "[%key:ui::panel::config::integrations::integration_page::entries%]", "no_entries": "No entries", + "yaml_entry": "This integration was not setup via the UI, you have either set it up in YAML or it is a dependency set up by another integration. If you want to configure it, you will need to do so in your configuration.yaml file.", "attention_entries": "Needs attention", "add_entry": "Add entry", "add_device": "Add device", @@ -4340,6 +4341,7 @@ "custom_integration": "Custom integration", "depends_on_cloud": "Depends on the cloud", "yaml_only": "Needs manual configuration", + "no_config_flow": "This integration was not set up from the UI", "disabled_polling": "Automatic polling for updated data disabled", "debug_logging_enabled": "Debug logging enabled", "state": { From dd22ae446ab3dfa4710c3073795ea71949b00794 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 22 Jul 2024 15:45:06 +0200 Subject: [PATCH 58/97] Don't show badge container if all badges are hidden (#21449) --- src/panels/lovelace/badges/hui-badge.ts | 2 + src/panels/lovelace/badges/hui-view-badges.ts | 45 ++++++++++++++++++- .../lovelace/views/hui-sections-view.ts | 19 ++++++-- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/panels/lovelace/badges/hui-badge.ts b/src/panels/lovelace/badges/hui-badge.ts index 9b362fd173..7637d2c464 100644 --- a/src/panels/lovelace/badges/hui-badge.ts +++ b/src/panels/lovelace/badges/hui-badge.ts @@ -15,6 +15,7 @@ import type { LovelaceBadge } from "../types"; declare global { interface HASSDomEvents { + "badge-visibility-changed": { value: boolean }; "badge-updated": undefined; } } @@ -183,6 +184,7 @@ export class HuiBadge extends ReactiveElement { if (this.hidden !== !visible) { this.style.setProperty("display", visible ? "" : "none"); this.toggleAttribute("hidden", !visible); + fireEvent(this, "badge-visibility-changed", { value: visible }); } if (!visible && this._element.parentElement) { diff --git a/src/panels/lovelace/badges/hui-view-badges.ts b/src/panels/lovelace/badges/hui-view-badges.ts index 64bfa106b8..b1736b97dd 100644 --- a/src/panels/lovelace/badges/hui-view-badges.ts +++ b/src/panels/lovelace/badges/hui-view-badges.ts @@ -1,5 +1,12 @@ import { mdiPlus } from "@mdi/js"; -import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; +import { + css, + CSSResultGroup, + html, + LitElement, + nothing, + PropertyValues, +} from "lit"; import { customElement, property, state } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; import { fireEvent } from "../../../common/dom/fire_event"; @@ -33,6 +40,38 @@ export class HuiViewBadges extends LitElement { private _badgeConfigKeys = new WeakMap(); + private _checkAllHidden() { + const allHidden = + !this.lovelace.editMode && this.badges.every((section) => section.hidden); + this.toggleAttribute("hidden", allHidden); + } + + private _badgeVisibilityChanged = () => { + this._checkAllHidden(); + }; + + connectedCallback(): void { + super.connectedCallback(); + this.addEventListener( + "badge-visibility-changed", + this._badgeVisibilityChanged + ); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + this.removeEventListener( + "badge-visibility-changed", + this._badgeVisibilityChanged + ); + } + + willUpdate(changedProperties: PropertyValues): void { + if (changedProperties.has("badges") || changedProperties.has("lovelace")) { + this._checkAllHidden(); + } + } + private _getBadgeKey(badge: HuiBadge) { if (!this._badgeConfigKeys.has(badge)) { this._badgeConfigKeys.set(badge, Math.random().toString()); @@ -130,6 +169,10 @@ export class HuiViewBadges extends LitElement { static get styles(): CSSResultGroup { return css` + :host([hidden]) { + display: none !important; + } + .badges { display: flex; align-items: flex-start; diff --git a/src/panels/lovelace/views/hui-sections-view.ts b/src/panels/lovelace/views/hui-sections-view.ts index 7199cd2b7a..722210f48d 100644 --- a/src/panels/lovelace/views/hui-sections-view.ts +++ b/src/panels/lovelace/views/hui-sections-view.ts @@ -65,11 +65,24 @@ export class SectionsView extends LitElement implements LovelaceViewElement { ).length; } + private _sectionVisibilityChanged = () => { + this._computeSectionsCount(); + }; + connectedCallback(): void { super.connectedCallback(); - this.addEventListener("section-visibility-changed", () => { - this._computeSectionsCount(); - }); + this.addEventListener( + "section-visibility-changed", + this._sectionVisibilityChanged + ); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + this.removeEventListener( + "section-visibility-changed", + this._sectionVisibilityChanged + ); } willUpdate(changedProperties: PropertyValues): void { From 811c34b489f25ba54cb1cef29b6d2b1d40365803 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Mon, 22 Jul 2024 09:06:48 -0700 Subject: [PATCH 59/97] Button to copy service response as json for templates (#21226) * Button to copy service response as json for templates --- src/components/ha-yaml-editor.ts | 17 +++++++++------ .../action/developer-tools-action.ts | 21 ++++++++++++++++++- src/translations/en.json | 1 + 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/components/ha-yaml-editor.ts b/src/components/ha-yaml-editor.ts index 882a12cd73..0d9d2c4b85 100644 --- a/src/components/ha-yaml-editor.ts +++ b/src/components/ha-yaml-editor.ts @@ -49,6 +49,8 @@ export class HaYamlEditor extends LitElement { @property({ type: Boolean }) public copyClipboard = false; + @property({ type: Boolean }) public hasExtraActions = false; + @state() private _yaml = ""; public setValue(value): void { @@ -100,13 +102,16 @@ export class HaYamlEditor extends LitElement { @value-changed=${this._onChange} dir="ltr" > - ${this.copyClipboard + ${this.copyClipboard || this.hasExtraActions ? html`
    - - ${this.hass.localize( - "ui.components.yaml-editor.copy_to_clipboard" - )} - + ${this.copyClipboard + ? html` + ${this.hass.localize( + "ui.components.yaml-editor.copy_to_clipboard" + )} + ` + : nothing} +
    ` : nothing} `; diff --git a/src/panels/developer-tools/action/developer-tools-action.ts b/src/panels/developer-tools/action/developer-tools-action.ts index 497b47e4c2..2bbb69d5bf 100644 --- a/src/panels/developer-tools/action/developer-tools-action.ts +++ b/src/panels/developer-tools/action/developer-tools-action.ts @@ -11,10 +11,13 @@ import { hasTemplate } from "../../../common/string/has-template"; import { extractSearchParam } from "../../../common/url/search-params"; import { HaProgressButton } from "../../../components/buttons/ha-progress-button"; import { LocalizeFunc } from "../../../common/translations/localize"; +import { showToast } from "../../../util/toast"; +import { copyToClipboard } from "../../../common/util/copy-clipboard"; import "../../../components/entity/ha-entity-picker"; import "../../../components/ha-card"; import "../../../components/ha-alert"; +import "../../../components/ha-button"; import "../../../components/ha-expansion-panel"; import "../../../components/ha-icon-button"; import "../../../components/ha-service-control"; @@ -189,8 +192,15 @@ class HaPanelDevAction extends LitElement { copyClipboard readOnly autoUpdate + hasExtraActions .value=${this._response} - > + > + ${this.hass.localize( + "ui.panel.developer-tools.tabs.actions.copy_clipboard_template" + )} +
    ` @@ -292,6 +302,15 @@ class HaPanelDevAction extends LitElement { `; } + private async _copyTemplate(): Promise { + await copyToClipboard( + `{% set action_response = ${JSON.stringify(this._response)} %}` + ); + showToast(this, { + message: this.hass.localize("ui.common.copied_clipboard"), + }); + } + private _filterSelectorFields = memoizeOne((fields) => fields.filter((field) => !field.selector) ); diff --git a/src/translations/en.json b/src/translations/en.json index 4f69e79e79..df4945647e 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6771,6 +6771,7 @@ "all_parameters": "All available parameters", "accepts_target": "This action accepts a target, for example: `entity_id: light.bed_light`", "no_template_ui_support": "The UI does not support templates, you can still use the YAML editor.", + "copy_clipboard_template": "Copy to clipboard (template)", "errors": { "ui": { "no_service": "No action selected, please select an action", From 567a2ea0191344d33f620f37eea286ad6cd76e28 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:23:46 +0200 Subject: [PATCH 60/97] Update dependency @braintree/sanitize-url to v7.1.0 (#21451) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 585f18fadf..53f28db8a3 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "type": "module", "dependencies": { "@babel/runtime": "7.24.8", - "@braintree/sanitize-url": "7.0.4", + "@braintree/sanitize-url": "7.1.0", "@codemirror/autocomplete": "6.17.0", "@codemirror/commands": "6.6.0", "@codemirror/language": "6.10.2", diff --git a/yarn.lock b/yarn.lock index a970400217..df524c471b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1453,10 +1453,10 @@ __metadata: languageName: node linkType: hard -"@braintree/sanitize-url@npm:7.0.4": - version: 7.0.4 - resolution: "@braintree/sanitize-url@npm:7.0.4" - checksum: 10/80ea0080776a0305d697d12042acac287675e88a2abd9d294464f70ec57c1b00242d8d02a110c98ef8ea1731e512d67273ff5532c4bf01a78ab8b046fabb53d9 +"@braintree/sanitize-url@npm:7.1.0": + version: 7.1.0 + resolution: "@braintree/sanitize-url@npm:7.1.0" + checksum: 10/b25cc5358bedfd97d8378d23ab43493e56a805bd82fdb092088bdd9db6aa3f6c32859d36526f570fb2c67a5a4f9ce579aacd52c3872db4285e4c34fb9947dfc0 languageName: node linkType: hard @@ -8964,7 +8964,7 @@ __metadata: "@babel/preset-env": "npm:7.24.8" "@babel/preset-typescript": "npm:7.24.7" "@babel/runtime": "npm:7.24.8" - "@braintree/sanitize-url": "npm:7.0.4" + "@braintree/sanitize-url": "npm:7.1.0" "@bundle-stats/plugin-webpack-filter": "npm:4.13.3" "@codemirror/autocomplete": "npm:6.17.0" "@codemirror/commands": "npm:6.6.0" From 87ba0e73ddc14205bade962c773570649b11c2f4 Mon Sep 17 00:00:00 2001 From: Quentame Date: Tue, 23 Jul 2024 09:50:34 +0200 Subject: [PATCH 61/97] picture cards: add person image support (#20593) * picture cards: add person image support * fix: person attributes typing * review: apply comment from @coderabbitai * fix lint:types * review: put person domain in image_entity config * add picture card compatibility & exemple in gallery * fix lint * Allow only image or person domains on image_entity editor config Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com> * fix domain type * gracefully use the default config.image if the person don't have an image * gracefully use the default config.image if the person don't have an image (that works) --------- Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com> --- gallery/public/images/paulus.jpg | Bin 0 -> 112613 bytes .../src/pages/lovelace/picture-card.markdown | 3 + gallery/src/pages/lovelace/picture-card.ts | 61 ++++++++++++++++++ .../pages/lovelace/picture-elements-card.ts | 22 +++++++ .../src/pages/lovelace/picture-entity-card.ts | 11 ++++ .../src/pages/lovelace/picture-glance-card.ts | 18 ++++++ src/data/person.ts | 18 ++++++ src/panels/lovelace/cards/hui-picture-card.ts | 25 +++++-- .../cards/hui-picture-elements-card.ts | 20 +++++- .../lovelace/cards/hui-picture-entity-card.ts | 24 +++++-- .../lovelace/cards/hui-picture-glance-card.ts | 19 +++++- src/panels/lovelace/cards/types.ts | 1 + .../common/generate-lovelace-config.ts | 2 +- .../hui-picture-card-editor.ts | 5 +- .../hui-picture-glance-card-editor.ts | 5 +- 15 files changed, 214 insertions(+), 20 deletions(-) create mode 100644 gallery/public/images/paulus.jpg create mode 100644 gallery/src/pages/lovelace/picture-card.markdown create mode 100644 gallery/src/pages/lovelace/picture-card.ts diff --git a/gallery/public/images/paulus.jpg b/gallery/public/images/paulus.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6ffa0fcdaaede9309c0f1bd40288b4ebcadf1aa8 GIT binary patch literal 112613 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 + } + + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); + hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); + hass.addEntities(ENTITIES); + mockIcons(hass); + } +} + +declare global { + interface HTMLElementTagNameMap { + "demo-lovelace-picture-card": DemoPicture; + } +} diff --git a/gallery/src/pages/lovelace/picture-elements-card.ts b/gallery/src/pages/lovelace/picture-elements-card.ts index 7f6b0c99cb..117e6c6bee 100644 --- a/gallery/src/pages/lovelace/picture-elements-card.ts +++ b/gallery/src/pages/lovelace/picture-elements-card.ts @@ -25,6 +25,15 @@ const ENTITIES = [ friendly_name: "Movement Backyard", device_class: "motion", }), + getEntity("person", "paulus", "home", { + friendly_name: "Paulus", + entity_picture: "/images/paulus.jpg", + }), + getEntity("sensor", "battery", 35, { + device_class: "battery", + friendly_name: "Battery", + unit_of_measurement: "%", + }), ]; const CONFIGS = [ @@ -123,6 +132,19 @@ const CONFIGS = [ left: 35% `, }, + { + heading: "Person entity", + config: ` +- type: picture-elements + image_entity: person.paulus + elements: + - type: state-icon + entity: sensor.battery + style: + top: 8% + left: 8% + `, + }, ]; @customElement("demo-lovelace-picture-elements-card") diff --git a/gallery/src/pages/lovelace/picture-entity-card.ts b/gallery/src/pages/lovelace/picture-entity-card.ts index 1573f0dbec..d97f417746 100644 --- a/gallery/src/pages/lovelace/picture-entity-card.ts +++ b/gallery/src/pages/lovelace/picture-entity-card.ts @@ -12,6 +12,10 @@ const ENTITIES = [ getEntity("light", "bed_light", "off", { friendly_name: "Bed Light", }), + getEntity("person", "paulus", "home", { + friendly_name: "Paulus", + entity_picture: "/images/paulus.jpg", + }), ]; const CONFIGS = [ @@ -50,6 +54,13 @@ const CONFIGS = [ entity: camera.demo_camera `, }, + { + heading: "Person entity", + config: ` +- type: picture-entity + entity: person.paulus + `, + }, { heading: "Hidden name", config: ` diff --git a/gallery/src/pages/lovelace/picture-glance-card.ts b/gallery/src/pages/lovelace/picture-glance-card.ts index dccc05e09b..91f2e4dca5 100644 --- a/gallery/src/pages/lovelace/picture-glance-card.ts +++ b/gallery/src/pages/lovelace/picture-glance-card.ts @@ -20,6 +20,15 @@ const ENTITIES = [ friendly_name: "Basement Floor Wet", device_class: "moisture", }), + getEntity("person", "paulus", "home", { + friendly_name: "Paulus", + entity_picture: "/images/paulus.jpg", + }), + getEntity("sensor", "battery", 35, { + device_class: "battery", + friendly_name: "Battery", + unit_of_measurement: "%", + }), ]; const CONFIGS = [ @@ -90,6 +99,15 @@ const CONFIGS = [ - light.ceiling_lights `, }, + { + heading: "Person entity", + config: ` +- type: picture-glance + image_entity: person.paulus + entities: + - sensor.battery + `, + }, { heading: "Custom icon", config: ` diff --git a/src/data/person.ts b/src/data/person.ts index d1a0cbdd6a..e8ee0ba25c 100644 --- a/src/data/person.ts +++ b/src/data/person.ts @@ -1,3 +1,7 @@ +import { + HassEntityAttributeBase, + HassEntityBase, +} from "home-assistant-js-websocket"; import { HomeAssistant } from "../types"; export interface BasePerson { @@ -18,6 +22,20 @@ export interface PersonMutableParams { picture: string | null; } +interface PersonEntityAttributes extends HassEntityAttributeBase { + id?: string; + user_id?: string; + device_trackers?: string[]; + editable?: boolean; + gps_accuracy?: number; + latitude?: number; + longitude?: number; +} + +export interface PersonEntity extends HassEntityBase { + attributes: PersonEntityAttributes; +} + export const fetchPersons = (hass: HomeAssistant) => hass.callWS<{ storage: Person[]; diff --git a/src/panels/lovelace/cards/hui-picture-card.ts b/src/panels/lovelace/cards/hui-picture-card.ts index be5902ba84..f5abf64b58 100644 --- a/src/panels/lovelace/cards/hui-picture-card.ts +++ b/src/panels/lovelace/cards/hui-picture-card.ts @@ -10,6 +10,7 @@ import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { ifDefined } from "lit/directives/if-defined"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; +import { computeDomain } from "../../../common/entity/compute_domain"; import "../../../components/ha-card"; import { computeImageUrl, ImageEntity } from "../../../data/image"; import { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; @@ -21,6 +22,7 @@ import { hasConfigChanged } from "../common/has-changed"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import { LovelaceCard, LovelaceCardEditor } from "../types"; import { PictureCardConfig } from "./types"; +import { PersonEntity } from "../../../data/person"; @customElement("hui-picture-card") export class HuiPictureCard extends LitElement implements LovelaceCard { @@ -95,10 +97,10 @@ export class HuiPictureCard extends LitElement implements LovelaceCard { return nothing; } - let stateObj: ImageEntity | undefined; + let stateObj: ImageEntity | PersonEntity | undefined; if (this._config.image_entity) { - stateObj = this.hass.states[this._config.image_entity] as ImageEntity; + stateObj = this.hass.states[this._config.image_entity]; if (!stateObj) { return html` ${createEntityNotFoundWarning(this.hass, this._config.image_entity)} @@ -106,6 +108,21 @@ export class HuiPictureCard extends LitElement implements LovelaceCard { } } + let image: string | undefined = this._config.image; + if (this._config.image_entity) { + const domain: string = computeDomain(this._config.image_entity); + switch (domain) { + case "image": + image = computeImageUrl(stateObj as ImageEntity); + break; + case "person": + if ((stateObj as PersonEntity).attributes.entity_picture) { + image = (stateObj as PersonEntity).attributes.entity_picture; + } + break; + } + } + return html` `; diff --git a/src/panels/lovelace/cards/hui-picture-elements-card.ts b/src/panels/lovelace/cards/hui-picture-elements-card.ts index 09f0455c2a..143a2fde58 100644 --- a/src/panels/lovelace/cards/hui-picture-elements-card.ts +++ b/src/panels/lovelace/cards/hui-picture-elements-card.ts @@ -8,6 +8,7 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; +import { computeDomain } from "../../../common/entity/compute_domain"; import "../../../components/ha-card"; import { ImageEntity, computeImageUrl } from "../../../data/image"; import { HomeAssistant } from "../../../types"; @@ -16,6 +17,7 @@ import { LovelaceElement, LovelaceElementConfig } from "../elements/types"; import { LovelaceCard } from "../types"; import { createStyledHuiElement } from "./picture-elements/create-styled-hui-element"; import { PictureElementsCardConfig } from "./types"; +import { PersonEntity } from "../../../data/person"; @customElement("hui-picture-elements-card") class HuiPictureElementsCard extends LitElement implements LovelaceCard { @@ -116,9 +118,21 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard { return nothing; } - let stateObj: ImageEntity | undefined; + let image: string | undefined = this._config.image; if (this._config.image_entity) { - stateObj = this.hass.states[this._config.image_entity] as ImageEntity; + const stateObj: ImageEntity | PersonEntity | undefined = + this.hass.states[this._config.image_entity]; + const domain: string = computeDomain(this._config.image_entity); + switch (domain) { + case "image": + image = computeImageUrl(stateObj as ImageEntity); + break; + case "person": + if ((stateObj as PersonEntity).attributes.entity_picture) { + image = (stateObj as PersonEntity).attributes.entity_picture; + } + break; + } } return html` @@ -126,7 +140,7 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {

    ${entityState}
    `; } - const domain = computeDomain(this._config.entity); + const domain: string = computeDomain(this._config.entity); + let image: string | undefined = this._config.image; + switch (domain) { + case "image": + image = computeImageUrl(stateObj as ImageEntity); + break; + case "person": + if ((stateObj as PersonEntity).attributes.entity_picture) { + image = (stateObj as PersonEntity).attributes.entity_picture; + } + break; + } return html` ; title?: string; image?: string; + image_entity?: string; camera_image?: string; camera_view?: HuiImage["cameraView"]; state_image?: Record; diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index f065215ce9..c2e8920fde 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -115,7 +115,7 @@ export const computeSection = ( type: "tile", entity, show_entity_picture: - ["person", "camera", "image"].includes(computeDomain(entity)) || + ["camera", "image", "person"].includes(computeDomain(entity)) || undefined, }) as TileCardConfig ), diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts index 20ff12e88b..6bd9d93cba 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts @@ -25,7 +25,10 @@ const cardConfigStruct = assign( const SCHEMA = [ { name: "image", selector: { image: {} } }, - { name: "image_entity", selector: { entity: { domain: "image" } } }, + { + name: "image_entity", + selector: { entity: { domain: ["image", "person"] } }, + }, { name: "alt_text", selector: { text: {} } }, { name: "theme", selector: { theme: {} } }, { diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts index 1c74428d38..8ed4b388db 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts @@ -36,7 +36,10 @@ const cardConfigStruct = assign( const SCHEMA = [ { name: "title", selector: { text: {} } }, { name: "image", selector: { image: {} } }, - { name: "image_entity", selector: { entity: { domain: "image" } } }, + { + name: "image_entity", + selector: { entity: { domain: ["image", "person"] } }, + }, { name: "camera_image", selector: { entity: { domain: "camera" } } }, { name: "", From 79618ce114fa730f4e2eabe8cccc366814132b99 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 23 Jul 2024 11:47:47 +0200 Subject: [PATCH 62/97] Fix offline db migration support (#21452) * Fix offline db migration support * Add error handling --- src/layouts/home-assistant.ts | 38 ++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/layouts/home-assistant.ts b/src/layouts/home-assistant.ts index 5fe9df6fbd..e9cbe62888 100644 --- a/src/layouts/home-assistant.ts +++ b/src/layouts/home-assistant.ts @@ -200,24 +200,26 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) { } protected async checkDataBaseMigration() { - if (this.hass?.config?.components.includes("recorder")) { - let recorderInfoProm: Promise | undefined; - const preloadWindow = window as WindowWithPreloads; - // On first load, we speed up loading page by having recorderInfoProm ready - if (preloadWindow.recorderInfoProm) { - recorderInfoProm = preloadWindow.recorderInfoProm; - preloadWindow.recorderInfoProm = undefined; - } - const info = await (recorderInfoProm || - getRecorderInfo(this.hass.connection)); - this._databaseMigration = - info.migration_in_progress && !info.migration_is_live; - if (this._databaseMigration) { - // check every 5 seconds if the migration is done - setTimeout(() => this.checkDataBaseMigration(), 5000); - } - } else { - this._databaseMigration = false; + let recorderInfoProm: Promise | undefined; + const preloadWindow = window as WindowWithPreloads; + // On first load, we speed up loading page by having recorderInfoProm ready + if (preloadWindow.recorderInfoProm) { + recorderInfoProm = preloadWindow.recorderInfoProm; + preloadWindow.recorderInfoProm = undefined; + } + const info = await ( + recorderInfoProm || getRecorderInfo(this.hass!.connection) + ).catch((err) => { + // If the command failed with code unknown_command, recorder is not enabled, + // otherwise re-throw the error + if (err.code !== "unknown_command") throw err; + return { migration_in_progress: false, migration_is_live: false }; + }); + this._databaseMigration = + info.migration_in_progress && !info.migration_is_live; + if (this._databaseMigration) { + // check every 5 seconds if the migration is done + setTimeout(() => this.checkDataBaseMigration(), 5000); } } From 6791e856254db1ee60840bfbdacf312df8c2c349 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 23 Jul 2024 13:13:35 +0200 Subject: [PATCH 63/97] Set 56px row height for new section button and title (#21456) --- src/panels/lovelace/sections/hui-grid-section.ts | 7 ++++++- src/panels/lovelace/views/hui-sections-view.ts | 5 +++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/panels/lovelace/sections/hui-grid-section.ts b/src/panels/lovelace/sections/hui-grid-section.ts index c29334cd51..ea011df222 100644 --- a/src/panels/lovelace/sections/hui-grid-section.ts +++ b/src/panels/lovelace/sections/hui-grid-section.ts @@ -246,7 +246,12 @@ export class GridSection extends LitElement implements LovelaceSectionElement { text-align: var(--ha-view-sections-title-text-align, start); min-height: 32px; display: block; - padding: 24px 10px 10px; + height: var(--row-height); + box-sizing: border-box; + padding: 0 10px 10px; + display: flex; + flex-direction: column; + justify-content: flex-end; } .title.placeholder { diff --git a/src/panels/lovelace/views/hui-sections-view.ts b/src/panels/lovelace/views/hui-sections-view.ts index 722210f48d..4dd2a89ac8 100644 --- a/src/panels/lovelace/views/hui-sections-view.ts +++ b/src/panels/lovelace/views/hui-sections-view.ts @@ -249,6 +249,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement { static get styles(): CSSResultGroup { return css` :host { + --row-height: var(--ha-view-sections-row-height, 56px); --row-gap: var(--ha-view-sections-row-gap, 8px); --column-gap: var(--ha-view-sections-column-gap, 32px); --column-min-width: var(--ha-view-sections-column-min-width, 320px); @@ -327,14 +328,14 @@ export class SectionsView extends LitElement implements LovelaceViewElement { } .create-section { - margin-top: calc(66px + var(--row-gap)); + margin-top: calc(var(--row-height) + var(--row-gap)); outline: none; background: none; cursor: pointer; border-radius: var(--ha-card-border-radius, 12px); border: 2px dashed var(--primary-color); order: 1; - height: calc(66px + 2 * (var(--row-gap) + 2px)); + height: calc(var(--row-height) + 2 * (var(--row-gap) + 2px)); padding: 8px; box-sizing: border-box; } From 8911b55316bfb2f398d19c56c80f049ff7800967 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 24 Jul 2024 13:32:27 +0200 Subject: [PATCH 64/97] Adjust message about offline database migration (#21460) Tweak message about offline database migration --- src/layouts/ha-init-page.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/layouts/ha-init-page.ts b/src/layouts/ha-init-page.ts index 43b6eb77d5..21c7e50fdb 100644 --- a/src/layouts/ha-init-page.ts +++ b/src/layouts/ha-init-page.ts @@ -39,7 +39,13 @@ class HaInitPage extends LitElement {
    ${this.migration - ? "Database migration in progress, please wait this might take some time" + ? html` + Database upgrade is in progress, Home Assistant will not start + until the upgrade is completed. +

    + The upgrade may need a long time to complete, please be + patient. + ` : "Loading data"}
    `; From 89d842c2a86e64d2c1949c3470d5dcc225c84470 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 18:44:04 +0200 Subject: [PATCH 65/97] Update dependency @bundle-stats/plugin-webpack-filter to v4.13.4 (#21457) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 53f28db8a3..5bd67bbccf 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "@babel/plugin-transform-runtime": "7.24.7", "@babel/preset-env": "7.24.8", "@babel/preset-typescript": "7.24.7", - "@bundle-stats/plugin-webpack-filter": "4.13.3", + "@bundle-stats/plugin-webpack-filter": "4.13.4", "@koa/cors": "5.0.0", "@lokalise/node-api": "12.7.0", "@octokit/auth-oauth-device": "7.1.1", diff --git a/yarn.lock b/yarn.lock index df524c471b..344aeff027 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1460,12 +1460,12 @@ __metadata: languageName: node linkType: hard -"@bundle-stats/plugin-webpack-filter@npm:4.13.3": - version: 4.13.3 - resolution: "@bundle-stats/plugin-webpack-filter@npm:4.13.3" +"@bundle-stats/plugin-webpack-filter@npm:4.13.4": + version: 4.13.4 + resolution: "@bundle-stats/plugin-webpack-filter@npm:4.13.4" peerDependencies: core-js: ^3.0.0 - checksum: 10/5de079362fe592d29a32598646164aeea329e81697b51356d2f760932bf95f0ca2e5ac8c45324e2850f8450c12e8a8ec95d851fd15ab25522832ebd3e64aacfe + checksum: 10/2f21d8125043256bc110da3e7742467f6d3e51a9303852845238ea8873198312846385c1c58138d3e15ca297ead228a93bdb138a2e7e78924a7e09a483a565f7 languageName: node linkType: hard @@ -8965,7 +8965,7 @@ __metadata: "@babel/preset-typescript": "npm:7.24.7" "@babel/runtime": "npm:7.24.8" "@braintree/sanitize-url": "npm:7.1.0" - "@bundle-stats/plugin-webpack-filter": "npm:4.13.3" + "@bundle-stats/plugin-webpack-filter": "npm:4.13.4" "@codemirror/autocomplete": "npm:6.17.0" "@codemirror/commands": "npm:6.6.0" "@codemirror/language": "npm:6.10.2" From 0358fe5614cd0d972db071771627745e2ed989ee Mon Sep 17 00:00:00 2001 From: schelv <13403863+schelv@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:05:59 +0200 Subject: [PATCH 66/97] Clearable time selector (#18590) * initial attempt at clearable time selector * only show clear button if there is a value * use null instead of undefined. * leave event value undefined * move clear button into input Co-authored-by: Bram Kragten --------- Co-authored-by: J. Nick Koston Co-authored-by: Bram Kragten --- src/components/ha-base-time-input.ts | 238 ++++++++++-------- .../ha-selector/ha-selector-time.ts | 1 + src/components/ha-time-input.ts | 16 +- 3 files changed, 151 insertions(+), 104 deletions(-) diff --git a/src/components/ha-base-time-input.ts b/src/components/ha-base-time-input.ts index 54490e047b..71870d2749 100644 --- a/src/components/ha-base-time-input.ts +++ b/src/components/ha-base-time-input.ts @@ -1,10 +1,12 @@ import "@material/mwc-list/mwc-list-item"; -import { css, html, LitElement, TemplateResult } from "lit"; +import { css, html, LitElement, TemplateResult, nothing } from "lit"; import { customElement, property } from "lit/decorators"; +import { mdiClose } from "@mdi/js"; import { ifDefined } from "lit/directives/if-defined"; import { fireEvent } from "../common/dom/fire_event"; import { stopPropagation } from "../common/dom/stop_propagation"; import "./ha-select"; +import "./ha-icon-button"; import { HaTextField } from "./ha-textfield"; import "./ha-input-helper-text"; @@ -124,116 +126,128 @@ export class HaBaseTimeInput extends LitElement { */ @property() amPm: "AM" | "PM" = "AM"; + @property({ type: Boolean, reflect: true }) public clearable?: boolean; + protected render(): TemplateResult { return html` ${this.label ? html`` : ""} -
    - ${this.enableDay - ? html` - +
    + ${this.enableDay + ? html` + + + ` + : ""} + + + + + + ${this.enableSecond + ? html` - - ` - : ""} + ` + : ""} + ${this.enableMillisecond + ? html` + ` + : ""} + ${this.clearable && !this.required && !this.disabled + ? html`` + : nothing} +
    - - - - - ${this.enableSecond - ? html` - ` - : ""} - ${this.enableMillisecond - ? html` - ` - : ""} ${this.format === 24 ? "" : html`AM PM `} + ${this.helper + ? html`${this.helper}` + : ""}
    - ${this.helper - ? html`${this.helper}` - : ""} `; } + private _clearValue(): void { + fireEvent(this, "value-changed"); + } + private _valueChanged(ev: InputEvent) { const textField = ev.currentTarget as HaTextField; this[textField.name] = @@ -302,18 +320,25 @@ export class HaBaseTimeInput extends LitElement { } static styles = css` + :host([clearable]) { + position: relative; + } :host { display: block; } + .time-input-wrap-wrap { + display: flex; + } .time-input-wrap { display: flex; border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0; overflow: hidden; position: relative; direction: ltr; + padding-right: 3px; } ha-textfield { - width: 40px; + width: 55px; text-align: center; --mdc-shape-small: 0; --text-field-appearance: none; @@ -335,6 +360,21 @@ export class HaBaseTimeInput extends LitElement { --mdc-shape-small: 0; width: 85px; } + :host([clearable]) .mdc-select__anchor { + padding-inline-end: var(--select-selected-text-padding-end, 12px); + } + ha-icon-button { + position: relative + --mdc-icon-button-size: 36px; + --mdc-icon-size: 20px; + color: var(--secondary-text-color); + direction: var(--direction); + display: flex; + align-items: center; + background-color:var(--mdc-text-field-fill-color, whitesmoke); + border-bottom-style: solid; + border-bottom-width: 1px; + } label { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; diff --git a/src/components/ha-selector/ha-selector-time.ts b/src/components/ha-selector/ha-selector-time.ts index ceb8d9fe93..bacba114fc 100644 --- a/src/components/ha-selector/ha-selector-time.ts +++ b/src/components/ha-selector/ha-selector-time.ts @@ -27,6 +27,7 @@ export class HaTimeSelector extends LitElement { .locale=${this.hass.locale} .disabled=${this.disabled} .required=${this.required} + clearable .helper=${this.helper} .label=${this.label} enable-second diff --git a/src/components/ha-time-input.ts b/src/components/ha-time-input.ts index 29893298dc..696dbef4aa 100644 --- a/src/components/ha-time-input.ts +++ b/src/components/ha-time-input.ts @@ -23,6 +23,8 @@ export class HaTimeInput extends LitElement { @property({ type: Boolean, attribute: "enable-second" }) public enableSecond = false; + @property({ type: Boolean, reflect: true }) public clearable?: boolean; + protected render() { const useAMPM = useAmPm(this.locale); @@ -48,22 +50,26 @@ export class HaTimeInput extends LitElement { @value-changed=${this._timeChanged} .enableSecond=${this.enableSecond} .required=${this.required} + .clearable=${this.clearable && this.value !== undefined} .helper=${this.helper} > `; } - private _timeChanged(ev: CustomEvent<{ value: TimeChangedEvent }>) { + private _timeChanged(ev: CustomEvent<{ value?: TimeChangedEvent }>) { ev.stopPropagation(); const eventValue = ev.detail.value; const useAMPM = useAmPm(this.locale); - let value; + let value: string | undefined; + // An undefined eventValue means the time selector is being cleared, + // the `value` variable will (intentionally) be left undefined. if ( - !isNaN(eventValue.hours) || - !isNaN(eventValue.minutes) || - !isNaN(eventValue.seconds) + eventValue !== undefined && + (!isNaN(eventValue.hours) || + !isNaN(eventValue.minutes) || + !isNaN(eventValue.seconds)) ) { let hours = eventValue.hours || 0; if (eventValue && useAMPM) { From 885ccb84cbe891b3891bc8a5b7479f4eaa56128e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:06:19 +0200 Subject: [PATCH 67/97] Update dependency eslint-plugin-unused-imports to v4.0.1 (#21468) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 5bd67bbccf..7e89ef1765 100644 --- a/package.json +++ b/package.json @@ -202,7 +202,7 @@ "eslint-plugin-import": "2.29.1", "eslint-plugin-lit": "1.14.0", "eslint-plugin-lit-a11y": "4.1.4", - "eslint-plugin-unused-imports": "4.0.0", + "eslint-plugin-unused-imports": "4.0.1", "eslint-plugin-wc": "2.1.0", "fancy-log": "2.0.0", "fs-extra": "11.2.0", diff --git a/yarn.lock b/yarn.lock index 344aeff027..a48b6a635c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7662,18 +7662,18 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-unused-imports@npm:4.0.0": - version: 4.0.0 - resolution: "eslint-plugin-unused-imports@npm:4.0.0" +"eslint-plugin-unused-imports@npm:4.0.1": + version: 4.0.1 + resolution: "eslint-plugin-unused-imports@npm:4.0.1" dependencies: eslint-rule-composer: "npm:^0.3.0" peerDependencies: - "@typescript-eslint/eslint-plugin": 8 - eslint: 9 + "@typescript-eslint/eslint-plugin": ^8.0.0-0 + eslint: ^9.0.0 peerDependenciesMeta: "@typescript-eslint/eslint-plugin": optional: true - checksum: 10/3fe6255140f393096d6f8384090db1fbbf5861835fa46baa51c207f479152161df7507687fa4eae51dc55bb261ed826c511d709945289b4eb9c1d7e2b33f48a2 + checksum: 10/ca6265c899c4ea36ac37d694f4356e57e2f6f93f835e2a1aecf405813a196db71b8e67e75a2b3433525e1b80d89714f1e27e6d67daa0d9b630dc602aa247f466 languageName: node linkType: hard @@ -9092,7 +9092,7 @@ __metadata: eslint-plugin-import: "npm:2.29.1" eslint-plugin-lit: "npm:1.14.0" eslint-plugin-lit-a11y: "npm:4.1.4" - eslint-plugin-unused-imports: "npm:4.0.0" + eslint-plugin-unused-imports: "npm:4.0.1" eslint-plugin-wc: "npm:2.1.0" fancy-log: "npm:2.0.0" fs-extra: "npm:11.2.0" From dd331173ad509f5d57bf32f6eb2de14a775a376d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:22:04 +0000 Subject: [PATCH 68/97] Update dependency tar to v7.4.1 (#21469) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 7e89ef1765..42d9d06c3f 100644 --- a/package.json +++ b/package.json @@ -233,7 +233,7 @@ "serve-handler": "6.1.5", "sinon": "18.0.0", "systemjs": "6.15.1", - "tar": "7.4.0", + "tar": "7.4.1", "terser-webpack-plugin": "5.3.10", "transform-async-modules-webpack-plugin": "1.1.1", "ts-lit-plugin": "2.0.2", diff --git a/yarn.lock b/yarn.lock index a48b6a635c..92eefd8b27 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9146,7 +9146,7 @@ __metadata: stacktrace-js: "npm:2.0.2" superstruct: "npm:2.0.2" systemjs: "npm:6.15.1" - tar: "npm:7.4.0" + tar: "npm:7.4.1" terser-webpack-plugin: "npm:5.3.10" tinykeys: "npm:2.1.0" transform-async-modules-webpack-plugin: "npm:1.1.1" @@ -13808,9 +13808,9 @@ __metadata: languageName: node linkType: hard -"tar@npm:7.4.0": - version: 7.4.0 - resolution: "tar@npm:7.4.0" +"tar@npm:7.4.1": + version: 7.4.1 + resolution: "tar@npm:7.4.1" dependencies: "@isaacs/fs-minipass": "npm:^4.0.0" chownr: "npm:^3.0.0" @@ -13818,7 +13818,7 @@ __metadata: minizlib: "npm:^3.0.1" mkdirp: "npm:^3.0.1" yallist: "npm:^5.0.0" - checksum: 10/fd0e366c0da823ac66cd0d2f52ff2b7963945a4252db88b5887053b6ab54a489380dfad64b2b26ae4a08bb2f5d66de04432d45e039a37bc0d442546ef29c6ee6 + checksum: 10/62585453020b1d63bbe1c046fbfb91591751dd86e24f27c6642767c91d9b8d2b0742cdb2416cc0421ba42befd3982ff72f3b151bad3d724efd448441ed27a3ca languageName: node linkType: hard From 35ec9af23f8fa535fbfba37a9bb843d21e6530d4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jul 2024 02:07:16 -0400 Subject: [PATCH 69/97] Update typescript-eslint monorepo to v7.17.0 (#21472) --- package.json | 4 +- yarn.lock | 104 +++++++++++++++++++++++++-------------------------- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 42d9d06c3f..5a23645cf8 100644 --- a/package.json +++ b/package.json @@ -185,8 +185,8 @@ "@types/tar": "6.1.13", "@types/ua-parser-js": "0.7.39", "@types/webspeechapi": "0.0.29", - "@typescript-eslint/eslint-plugin": "7.16.1", - "@typescript-eslint/parser": "7.16.1", + "@typescript-eslint/eslint-plugin": "7.17.0", + "@typescript-eslint/parser": "7.17.0", "@web/dev-server": "0.1.38", "@web/dev-server-rollup": "0.4.1", "babel-loader": "9.1.3", diff --git a/yarn.lock b/yarn.lock index 92eefd8b27..6f6f9d2b10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4584,15 +4584,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:7.16.1": - version: 7.16.1 - resolution: "@typescript-eslint/eslint-plugin@npm:7.16.1" +"@typescript-eslint/eslint-plugin@npm:7.17.0": + version: 7.17.0 + resolution: "@typescript-eslint/eslint-plugin@npm:7.17.0" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:7.16.1" - "@typescript-eslint/type-utils": "npm:7.16.1" - "@typescript-eslint/utils": "npm:7.16.1" - "@typescript-eslint/visitor-keys": "npm:7.16.1" + "@typescript-eslint/scope-manager": "npm:7.17.0" + "@typescript-eslint/type-utils": "npm:7.17.0" + "@typescript-eslint/utils": "npm:7.17.0" + "@typescript-eslint/visitor-keys": "npm:7.17.0" graphemer: "npm:^1.4.0" ignore: "npm:^5.3.1" natural-compare: "npm:^1.4.0" @@ -4603,44 +4603,44 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/fddbfe461f85d10ee3967b89efa3c704806074af6806833f982915b21754567a98c5a486627174cc6b0ac4cb5f1282865d64ae251a5cbf6dbbbe191d0268520a + checksum: 10/f3caba81b7ea4d1b4b097b3de1c51054424ad3d5e37f7af7df64f1c29b6448c535b61e0956f76bfa450b38917923f919a9bab081224c2b5577596caffa6e288a languageName: node linkType: hard -"@typescript-eslint/parser@npm:7.16.1": - version: 7.16.1 - resolution: "@typescript-eslint/parser@npm:7.16.1" +"@typescript-eslint/parser@npm:7.17.0": + version: 7.17.0 + resolution: "@typescript-eslint/parser@npm:7.17.0" dependencies: - "@typescript-eslint/scope-manager": "npm:7.16.1" - "@typescript-eslint/types": "npm:7.16.1" - "@typescript-eslint/typescript-estree": "npm:7.16.1" - "@typescript-eslint/visitor-keys": "npm:7.16.1" + "@typescript-eslint/scope-manager": "npm:7.17.0" + "@typescript-eslint/types": "npm:7.17.0" + "@typescript-eslint/typescript-estree": "npm:7.17.0" + "@typescript-eslint/visitor-keys": "npm:7.17.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.56.0 peerDependenciesMeta: typescript: optional: true - checksum: 10/7af36bacc2c38e9fb367edf886a04fde292ff28b49adfc3f4fc0dd456364c5e18444346112ae52557f2f32fe2e5abd144b87b4db89b6960b4957d69a9d390f91 + checksum: 10/91971e5d95fec798a456ec72d9d67c28eee72d0d1c52e682dbff2eef134e149799f69324ea8d42bd2cfa290eec763073b26fb343ce0632e4fa64c3b8a854d124 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:7.16.1": - version: 7.16.1 - resolution: "@typescript-eslint/scope-manager@npm:7.16.1" +"@typescript-eslint/scope-manager@npm:7.17.0": + version: 7.17.0 + resolution: "@typescript-eslint/scope-manager@npm:7.17.0" dependencies: - "@typescript-eslint/types": "npm:7.16.1" - "@typescript-eslint/visitor-keys": "npm:7.16.1" - checksum: 10/57ce02c2624e49988b01666b3e13d1adb44ab78f2dafc47a56800d57bff624779b348928a905393fa5f2cce94a5844173ab81f32b81f0bb2897f10bbaf9cab6a + "@typescript-eslint/types": "npm:7.17.0" + "@typescript-eslint/visitor-keys": "npm:7.17.0" + checksum: 10/aec72538a92d8a82ca39f60c34b0d0e00f2f8fb74f584aee90b6d1ef28f30a415b507f28aa27a536898992ad4b9b5af58671c743cd50439b21e67bee03a59c88 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:7.16.1": - version: 7.16.1 - resolution: "@typescript-eslint/type-utils@npm:7.16.1" +"@typescript-eslint/type-utils@npm:7.17.0": + version: 7.17.0 + resolution: "@typescript-eslint/type-utils@npm:7.17.0" dependencies: - "@typescript-eslint/typescript-estree": "npm:7.16.1" - "@typescript-eslint/utils": "npm:7.16.1" + "@typescript-eslint/typescript-estree": "npm:7.17.0" + "@typescript-eslint/utils": "npm:7.17.0" debug: "npm:^4.3.4" ts-api-utils: "npm:^1.3.0" peerDependencies: @@ -4648,23 +4648,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/38a72a3de8a2c3455d19e6d43e67ac6e1dc23e93b2d84571282b0323fadadcab33df1a89787c76fc99e45514e41a08bc9f5cb51287a7da48f56c64b512a3269b + checksum: 10/1405c626cd59a1fb29b897d22dce0b2f5b793e5d1cba228a119e58e7392c385c9131c332e744888b7d6ad41eee0abbd8099651664cafaed24229da2cd768e032 languageName: node linkType: hard -"@typescript-eslint/types@npm:7.16.1": - version: 7.16.1 - resolution: "@typescript-eslint/types@npm:7.16.1" - checksum: 10/cfb48821ffb5a5307e67ce05b9ec2f4775c560dc53011e313d4fa75d033e0130ce0d364ac92ad3634d325c16a889ddc3201e8a742217c73be8d34385da85620b +"@typescript-eslint/types@npm:7.17.0": + version: 7.17.0 + resolution: "@typescript-eslint/types@npm:7.17.0" + checksum: 10/92e571f794f51a1f110714a9de661f9a76781c8c3e53d8fe025a88be947ae30d1c18964083467db31001ce7910f1a1459b8f6b039c270bdb6d1de47eba5dfa7f languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:7.16.1": - version: 7.16.1 - resolution: "@typescript-eslint/typescript-estree@npm:7.16.1" +"@typescript-eslint/typescript-estree@npm:7.17.0": + version: 7.17.0 + resolution: "@typescript-eslint/typescript-estree@npm:7.17.0" dependencies: - "@typescript-eslint/types": "npm:7.16.1" - "@typescript-eslint/visitor-keys": "npm:7.16.1" + "@typescript-eslint/types": "npm:7.17.0" + "@typescript-eslint/visitor-keys": "npm:7.17.0" debug: "npm:^4.3.4" globby: "npm:^11.1.0" is-glob: "npm:^4.0.3" @@ -4674,31 +4674,31 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/7f88176f2d25779ec2d40df4c6bd0a26aa41494ee0302d4895b4d0cb4e284385c1e218ac2ad67ed90b5e1bf82b78b8aa4b903b5906fbf7101b08c409ce778e9c + checksum: 10/419c4ad3b470ea4d654c414bbc66269ba7a6504e10bf2a2a87f9214aad4358b670f60e89ae7e4b2a24fa7c0c4542ebdd3711b8964917c026a5eef27d861e23fb languageName: node linkType: hard -"@typescript-eslint/utils@npm:7.16.1": - version: 7.16.1 - resolution: "@typescript-eslint/utils@npm:7.16.1" +"@typescript-eslint/utils@npm:7.17.0": + version: 7.17.0 + resolution: "@typescript-eslint/utils@npm:7.17.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:7.16.1" - "@typescript-eslint/types": "npm:7.16.1" - "@typescript-eslint/typescript-estree": "npm:7.16.1" + "@typescript-eslint/scope-manager": "npm:7.17.0" + "@typescript-eslint/types": "npm:7.17.0" + "@typescript-eslint/typescript-estree": "npm:7.17.0" peerDependencies: eslint: ^8.56.0 - checksum: 10/b3c279d706ff1b3a0002c8e0f0fcf559b63f4296e218199a25863054bda5b28d5a7ab6ad4ad1d0b7fa2c6cd9f2d0dcd7f784c3f75026fae7b58846695481ec45 + checksum: 10/44d6bfcda4b03a7bec82939dd975579f40705cf4128e40f747bf96b81e8fae0c384434999334a9ac42990e2864266c8067ca0e4b27d736ce2f6b8667115f7a1d languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:7.16.1": - version: 7.16.1 - resolution: "@typescript-eslint/visitor-keys@npm:7.16.1" +"@typescript-eslint/visitor-keys@npm:7.17.0": + version: 7.17.0 + resolution: "@typescript-eslint/visitor-keys@npm:7.17.0" dependencies: - "@typescript-eslint/types": "npm:7.16.1" + "@typescript-eslint/types": "npm:7.17.0" eslint-visitor-keys: "npm:^3.4.3" - checksum: 10/f5088d72b6ca48f4e525b7b5d6c6c9254d0d039d2959fd91200691218e8ac8f3e56287ec8bc411a79609e9d85ed5fc6c4f7d2edd80fadf734aeb6f6bfc833322 + checksum: 10/a8bef372e212baab67ec4e074a8b4983348fc554874d40d1fc22c10ce2693609cdef4a215391e8b428a67b3e2dcbda12d821b4ed668394b0b001ba03a08c5145 languageName: node linkType: hard @@ -9056,8 +9056,8 @@ __metadata: "@types/tar": "npm:6.1.13" "@types/ua-parser-js": "npm:0.7.39" "@types/webspeechapi": "npm:0.0.29" - "@typescript-eslint/eslint-plugin": "npm:7.16.1" - "@typescript-eslint/parser": "npm:7.16.1" + "@typescript-eslint/eslint-plugin": "npm:7.17.0" + "@typescript-eslint/parser": "npm:7.17.0" "@vaadin/combo-box": "npm:24.4.4" "@vaadin/vaadin-themable-mixin": "npm:24.4.4" "@vibrant/color": "npm:3.2.1-alpha.1" From 88a33bee14ef835963d843b58466660d29c0e100 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jul 2024 02:09:07 -0400 Subject: [PATCH 70/97] Update dependency typescript to v5.5.4 (#21474) --- package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 5a23645cf8..04e9beb81f 100644 --- a/package.json +++ b/package.json @@ -237,7 +237,7 @@ "terser-webpack-plugin": "5.3.10", "transform-async-modules-webpack-plugin": "1.1.1", "ts-lit-plugin": "2.0.2", - "typescript": "5.5.3", + "typescript": "5.5.4", "webpack": "5.93.0", "webpack-cli": "5.1.4", "webpack-dev-server": "5.0.4", diff --git a/yarn.lock b/yarn.lock index 6f6f9d2b10..888d5f3cfd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9153,7 +9153,7 @@ __metadata: ts-lit-plugin: "npm:2.0.2" tsparticles-engine: "npm:2.12.0" tsparticles-preset-links: "npm:2.12.0" - typescript: "npm:5.5.3" + typescript: "npm:5.5.4" ua-parser-js: "npm:1.0.38" unfetch: "npm:5.0.0" vis-data: "npm:7.1.9" @@ -14325,13 +14325,13 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.5.3": - version: 5.5.3 - resolution: "typescript@npm:5.5.3" +"typescript@npm:5.5.4": + version: 5.5.4 + resolution: "typescript@npm:5.5.4" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/11a867312419ed497929aafd2f1d28b2cd41810a5eb6c6e9e169559112e9ea073d681c121a29102e67cd4478d0a4ae37a306a5800f3717f59c4337e6a9bd5e8d + checksum: 10/1689ccafef894825481fc3d856b4834ba3cc185a9c2878f3c76a9a1ef81af04194849840f3c69e7961e2312771471bb3b460ca92561e1d87599b26c37d0ffb6f languageName: node linkType: hard @@ -14345,13 +14345,13 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.5.3#optional!builtin": - version: 5.5.3 - resolution: "typescript@patch:typescript@npm%3A5.5.3#optional!builtin::version=5.5.3&hash=379a07" +"typescript@patch:typescript@npm%3A5.5.4#optional!builtin": + version: 5.5.4 + resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin::version=5.5.4&hash=379a07" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/7cf7acb78a80f749b82842f2ffe01e90e7b3e709a6f4268588e0b7599c41dca1059be217f47778fe1a380bfaf60933021ef20d002c426d4d7745e1b36c11467b + checksum: 10/746fdd0865c5ce4f15e494c57ede03a9e12ede59cfdb40da3a281807853fe63b00ef1c912d7222143499aa82f18b8b472baa1830df8804746d09b55f6cf5b1cc languageName: node linkType: hard From 5d794e7e8857da7aa7f11f3e4f32a52542408c16 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 28 Jul 2024 08:02:34 +0200 Subject: [PATCH 71/97] Update dependency tar to v7.4.2 (#21482) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 04e9beb81f..0c39ed9bef 100644 --- a/package.json +++ b/package.json @@ -233,7 +233,7 @@ "serve-handler": "6.1.5", "sinon": "18.0.0", "systemjs": "6.15.1", - "tar": "7.4.1", + "tar": "7.4.2", "terser-webpack-plugin": "5.3.10", "transform-async-modules-webpack-plugin": "1.1.1", "ts-lit-plugin": "2.0.2", diff --git a/yarn.lock b/yarn.lock index 888d5f3cfd..8895bfacd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9146,7 +9146,7 @@ __metadata: stacktrace-js: "npm:2.0.2" superstruct: "npm:2.0.2" systemjs: "npm:6.15.1" - tar: "npm:7.4.1" + tar: "npm:7.4.2" terser-webpack-plugin: "npm:5.3.10" tinykeys: "npm:2.1.0" transform-async-modules-webpack-plugin: "npm:1.1.1" @@ -13808,9 +13808,9 @@ __metadata: languageName: node linkType: hard -"tar@npm:7.4.1": - version: 7.4.1 - resolution: "tar@npm:7.4.1" +"tar@npm:7.4.2": + version: 7.4.2 + resolution: "tar@npm:7.4.2" dependencies: "@isaacs/fs-minipass": "npm:^4.0.0" chownr: "npm:^3.0.0" @@ -13818,7 +13818,7 @@ __metadata: minizlib: "npm:^3.0.1" mkdirp: "npm:^3.0.1" yallist: "npm:^5.0.0" - checksum: 10/62585453020b1d63bbe1c046fbfb91591751dd86e24f27c6642767c91d9b8d2b0742cdb2416cc0421ba42befd3982ff72f3b151bad3d724efd448441ed27a3ca + checksum: 10/d3d806d388e51a24a16b3920add65dbfe9521fb6065195dd1584de966d2a0309398004743e7df467571a7b7a97bfd25996e61067cc844cbe8a7e929e7e9c8c97 languageName: node linkType: hard From 945c4a66b10199a9de223e35f7b8a2bdce4f7359 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:43:43 +0200 Subject: [PATCH 72/97] Update dependency tar to v7.4.3 (#21491) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 0c39ed9bef..fb7982da84 100644 --- a/package.json +++ b/package.json @@ -233,7 +233,7 @@ "serve-handler": "6.1.5", "sinon": "18.0.0", "systemjs": "6.15.1", - "tar": "7.4.2", + "tar": "7.4.3", "terser-webpack-plugin": "5.3.10", "transform-async-modules-webpack-plugin": "1.1.1", "ts-lit-plugin": "2.0.2", diff --git a/yarn.lock b/yarn.lock index 8895bfacd6..0c6312f614 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9146,7 +9146,7 @@ __metadata: stacktrace-js: "npm:2.0.2" superstruct: "npm:2.0.2" systemjs: "npm:6.15.1" - tar: "npm:7.4.2" + tar: "npm:7.4.3" terser-webpack-plugin: "npm:5.3.10" tinykeys: "npm:2.1.0" transform-async-modules-webpack-plugin: "npm:1.1.1" @@ -13808,9 +13808,9 @@ __metadata: languageName: node linkType: hard -"tar@npm:7.4.2": - version: 7.4.2 - resolution: "tar@npm:7.4.2" +"tar@npm:7.4.3": + version: 7.4.3 + resolution: "tar@npm:7.4.3" dependencies: "@isaacs/fs-minipass": "npm:^4.0.0" chownr: "npm:^3.0.0" @@ -13818,7 +13818,7 @@ __metadata: minizlib: "npm:^3.0.1" mkdirp: "npm:^3.0.1" yallist: "npm:^5.0.0" - checksum: 10/d3d806d388e51a24a16b3920add65dbfe9521fb6065195dd1584de966d2a0309398004743e7df467571a7b7a97bfd25996e61067cc844cbe8a7e929e7e9c8c97 + checksum: 10/12a2a4fc6dee23e07cc47f1aeb3a14a1afd3f16397e1350036a8f4cdfee8dcac7ef5978337a4e7b2ac2c27a9a6d46388fc2088ea7c80cb6878c814b1425f8ecf languageName: node linkType: hard From 2faa8fec176ca0eb0ca912ffc450cca61ab972b6 Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Mon, 29 Jul 2024 11:09:33 -0400 Subject: [PATCH 73/97] Music Assistant repository is now built in (#21496) --- hassio/src/dialogs/repositories/dialog-hassio-repositories.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts b/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts index 47f107a6c5..f61dcf8070 100644 --- a/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts +++ b/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts @@ -66,7 +66,8 @@ class HassioRepositoriesDialog extends LitElement { repo.slug !== "core" && // The core add-ons repository repo.slug !== "local" && // Locally managed add-ons repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons - repo.slug !== "5c53de3b" // The ESPHome repository + repo.slug !== "5c53de3b" && // The ESPHome repository + repo.slug !== "d5369777" // Music Assistant repository ) .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language) From 09f0da1ead27029acc403b8a14cb722a6ae24a02 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:38:14 +0200 Subject: [PATCH 74/97] Update dependency @codemirror/view to v6.29.0 (#21488) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index fb7982da84..8132145f64 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@codemirror/legacy-modes": "6.4.0", "@codemirror/search": "6.5.6", "@codemirror/state": "6.4.1", - "@codemirror/view": "6.28.6", + "@codemirror/view": "6.29.0", "@egjs/hammerjs": "2.0.17", "@formatjs/intl-datetimeformat": "6.12.5", "@formatjs/intl-displaynames": "6.6.8", diff --git a/yarn.lock b/yarn.lock index 0c6312f614..76087ad968 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1539,14 +1539,14 @@ __metadata: languageName: node linkType: hard -"@codemirror/view@npm:6.28.6, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0": - version: 6.28.6 - resolution: "@codemirror/view@npm:6.28.6" +"@codemirror/view@npm:6.29.0, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0": + version: 6.29.0 + resolution: "@codemirror/view@npm:6.29.0" dependencies: "@codemirror/state": "npm:^6.4.0" style-mod: "npm:^4.1.0" w3c-keyname: "npm:^2.2.4" - checksum: 10/be4a524bf3a78b71474a8e51554deaa8f4a1fd576b579dbadb25b61eb6182fe760be076b599ea9d6549fcb3f0a3d3502b324ac4752fa3330a9528f3fa0e1aad2 + checksum: 10/c9ef04ca7b8a4ff08bd8534fe208f9a3304f6b94b276f732ec89c775c841e7340db453579a41e21cf839a568a599660e45be405056e036e31600dd1939eac2bd languageName: node linkType: hard @@ -8972,7 +8972,7 @@ __metadata: "@codemirror/legacy-modes": "npm:6.4.0" "@codemirror/search": "npm:6.5.6" "@codemirror/state": "npm:6.4.1" - "@codemirror/view": "npm:6.28.6" + "@codemirror/view": "npm:6.29.0" "@egjs/hammerjs": "npm:2.0.17" "@formatjs/intl-datetimeformat": "npm:6.12.5" "@formatjs/intl-displaynames": "npm:6.6.8" From e578904ff7a1b0d7a0940cd5a0fb58ce64f10e21 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:56:22 +0200 Subject: [PATCH 75/97] Update dependency @material/web to v2 (#21489) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 8132145f64..091ecc63df 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "@material/mwc-top-app-bar": "0.27.0", "@material/mwc-top-app-bar-fixed": "0.27.0", "@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0", - "@material/web": "1.5.1", + "@material/web": "2.0.0", "@mdi/js": "7.4.47", "@mdi/svg": "7.4.47", "@polymer/paper-item": "3.0.1", diff --git a/yarn.lock b/yarn.lock index 76087ad968..78df343305 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3235,13 +3235,13 @@ __metadata: languageName: node linkType: hard -"@material/web@npm:1.5.1": - version: 1.5.1 - resolution: "@material/web@npm:1.5.1" +"@material/web@npm:2.0.0": + version: 2.0.0 + resolution: "@material/web@npm:2.0.0" dependencies: lit: "npm:^2.7.4 || ^3.0.0" tslib: "npm:^2.4.0" - checksum: 10/9be6019068fbc4ed6873837ad549fd672c24beaacd9123bc9f3d72b7dfd67a1acdafab43bd484b50d40300e27e3742314915f4d6e6723b38be223b4547ae206f + checksum: 10/22979a902dccf456ab9555026165ef5e221822e7782981d5a65ad5238971959a3cdf301b643bf74819d3ad6426d45088ba4b1975dc21e632d6d0a933299f0c03 languageName: node linkType: hard @@ -9021,7 +9021,7 @@ __metadata: "@material/mwc-top-app-bar": "npm:0.27.0" "@material/mwc-top-app-bar-fixed": "npm:0.27.0" "@material/top-app-bar": "npm:=14.0.0-canary.53b3cad2f.0" - "@material/web": "npm:1.5.1" + "@material/web": "npm:2.0.0" "@mdi/js": "npm:7.4.47" "@mdi/svg": "npm:7.4.47" "@octokit/auth-oauth-device": "npm:7.1.1" From a85dda33659e70c969ead8ac4bc8db16df094317 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Jul 2024 07:44:43 +0000 Subject: [PATCH 76/97] Update dependency husky to v9.1.3 (#21443) * Update dependency husky to v9.1.3 * Follow migration guide (options removed in 10.x, but deprecation notice right now) --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com> --- .husky/pre-commit | 3 --- package.json | 4 ++-- yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index cf5c994491..f4ae2f354a 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - yarn run lint-staged --relative --shell "/bin/bash" diff --git a/package.json b/package.json index 091ecc63df..3cde49c4ed 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "lint:lit": "lit-analyzer \"{.,*}/src/**/*.ts\"", "lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types && yarn run lint:lit", "format": "yarn run format:eslint && yarn run format:prettier", - "postinstall": "husky install", + "postinstall": "husky", "prepack": "pinst --disable", "postpack": "pinst --enable", "test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.cjs \"test/**/*.ts\"" @@ -212,7 +212,7 @@ "gulp-rename": "2.0.0", "gulp-zopfli-green": "6.0.2", "html-minifier-terser": "7.2.0", - "husky": "9.0.11", + "husky": "9.1.3", "instant-mocha": "1.5.2", "jszip": "3.10.1", "lint-staged": "15.2.7", diff --git a/yarn.lock b/yarn.lock index 78df343305..1113edf627 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9106,7 +9106,7 @@ __metadata: hls.js: "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch" home-assistant-js-websocket: "npm:9.4.0" html-minifier-terser: "npm:7.2.0" - husky: "npm:9.0.11" + husky: "npm:9.1.3" idb-keyval: "npm:6.2.1" instant-mocha: "npm:1.5.2" intl-messageformat: "npm:10.5.14" @@ -9379,12 +9379,12 @@ __metadata: languageName: node linkType: hard -"husky@npm:9.0.11": - version: 9.0.11 - resolution: "husky@npm:9.0.11" +"husky@npm:9.1.3": + version: 9.1.3 + resolution: "husky@npm:9.1.3" bin: - husky: bin.mjs - checksum: 10/8a9b7cb9dc8494b470b3b47b386e65d579608c6206da80d3cc8b71d10e37947264af3dfe00092368dad9673b51d2a5ee87afb4b2291e77ba9e7ec1ac36e56cd1 + husky: bin.js + checksum: 10/35d7ad85a247fb130659ae60b05bca9461820d261d6ff181b55c3dc6f2ae5da5ae3f3807367b90cc85d3bb915a2de8295aa9950e3cba3309994b7763dfd70cb1 languageName: node linkType: hard From 4ade39543dd335faa97d1818ec26df39fc6cd252 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 30 Jul 2024 11:18:50 +0200 Subject: [PATCH 77/97] Add created/modified to registry tables (#21494) --- demo/src/ha-demo.ts | 4 + gallery/src/pages/components/ha-form.ts | 15 +++- gallery/src/pages/components/ha-selector.ts | 25 +++++- gallery/src/pages/misc/entity-state.ts | 4 - gallery/src/pages/misc/integration-card.ts | 4 + hassio/src/backups/hassio-backups.ts | 5 +- src/components/data-table/ha-data-table.ts | 78 ++++++++++++++----- src/components/ha-area-picker.ts | 8 ++ src/components/ha-floor-picker.ts | 8 ++ src/components/ha-label-picker.ts | 4 + src/data/area_registry.ts | 3 +- src/data/device_registry.ts | 5 +- src/data/entity_registry.ts | 3 +- src/data/floor_registry.ts | 3 +- src/data/label_registry.ts | 5 +- src/data/registry.ts | 4 + .../ha-config-application-credentials.ts | 5 +- .../config/automation/ha-automation-picker.ts | 7 +- src/panels/config/backup/ha-config-backup.ts | 5 +- .../config/blueprint/ha-blueprint-overview.ts | 6 +- .../devices/ha-config-devices-dashboard.ts | 63 +++++++++++---- .../config/entities/ha-config-entities.ts | 39 ++++++++-- .../config/helpers/ha-config-helpers.ts | 5 +- .../zha/zha-clusters-data-table.ts | 6 +- .../zha/zha-device-endpoint-data-table.ts | 6 +- .../zha/zha-device-neighbors.ts | 8 +- .../zha/zha-groups-dashboard.ts | 6 +- .../zwave_js/zwave_js-provisioned.ts | 5 +- src/panels/config/labels/ha-config-labels.ts | 61 ++++++++++++--- .../ha-config-lovelace-dashboards.ts | 7 +- .../resources/ha-config-lovelace-resources.ts | 3 +- src/panels/config/scene/ha-scene-dashboard.ts | 6 +- src/panels/config/script/ha-script-picker.ts | 4 +- src/panels/config/tags/ha-config-tags.ts | 3 +- src/panels/config/users/ha-config-users.ts | 10 +-- ...-config-voice-assistants-assist-devices.ts | 4 +- .../ha-config-voice-assistants-expose.ts | 7 +- .../statistics/developer-tools-statistics.ts | 14 ++-- .../card-editor/hui-entity-picker-table.ts | 6 +- src/translations/en.json | 7 ++ 40 files changed, 320 insertions(+), 151 deletions(-) create mode 100644 src/data/registry.ts diff --git a/demo/src/ha-demo.ts b/demo/src/ha-demo.ts index c1b059ebb5..8d2fe2b0e4 100644 --- a/demo/src/ha-demo.ts +++ b/demo/src/ha-demo.ts @@ -82,6 +82,8 @@ export class HaDemo extends HomeAssistantAppEl { has_entity_name: false, unique_id: "co2_intensity", options: null, + created_at: 0, + modified_at: 0, }, { config_entry_id: "co2signal", @@ -100,6 +102,8 @@ export class HaDemo extends HomeAssistantAppEl { has_entity_name: false, unique_id: "grid_fossil_fuel_percentage", options: null, + created_at: 0, + modified_at: 0, }, ]); diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts index 722bfefe8e..ccc287fd85 100644 --- a/gallery/src/pages/components/ha-form.ts +++ b/gallery/src/pages/components/ha-form.ts @@ -15,6 +15,7 @@ import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import { HomeAssistant } from "../../../../src/types"; import "../../components/demo-black-white-row"; +import { DeviceRegistryEntry } from "../../../../src/data/device_registry"; const ENTITIES = [ getEntity("alarm_control_panel", "alarm", "disarmed", { @@ -41,7 +42,7 @@ const ENTITIES = [ }), ]; -const DEVICES = [ +const DEVICES: DeviceRegistryEntry[] = [ { area_id: "bedroom", configuration_url: null, @@ -61,6 +62,8 @@ const DEVICES = [ via_device_id: null, serial_number: null, labels: [], + created_at: 0, + modified_at: 0, }, { area_id: "backyard", @@ -81,6 +84,8 @@ const DEVICES = [ via_device_id: null, serial_number: null, labels: [], + created_at: 0, + modified_at: 0, }, { area_id: null, @@ -101,6 +106,8 @@ const DEVICES = [ via_device_id: null, serial_number: null, labels: [], + created_at: 0, + modified_at: 0, }, ]; @@ -113,6 +120,8 @@ const AREAS: AreaRegistryEntry[] = [ picture: null, aliases: [], labels: [], + created_at: 0, + modified_at: 0, }, { area_id: "bedroom", @@ -122,6 +131,8 @@ const AREAS: AreaRegistryEntry[] = [ picture: null, aliases: [], labels: [], + created_at: 0, + modified_at: 0, }, { area_id: "livingroom", @@ -131,6 +142,8 @@ const AREAS: AreaRegistryEntry[] = [ picture: null, aliases: [], labels: [], + created_at: 0, + modified_at: 0, }, ]; diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index 8657bc4e7b..cb3a1f2f49 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -21,6 +21,7 @@ import { FloorRegistryEntry } from "../../../../src/data/floor_registry"; import { LabelRegistryEntry } from "../../../../src/data/label_registry"; import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry"; import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry"; +import { DeviceRegistryEntry } from "../../../../src/data/device_registry"; const ENTITIES = [ getEntity("alarm_control_panel", "alarm", "disarmed", { @@ -41,7 +42,7 @@ const ENTITIES = [ }), ]; -const DEVICES = [ +const DEVICES: DeviceRegistryEntry[] = [ { area_id: "bedroom", configuration_url: null, @@ -61,6 +62,8 @@ const DEVICES = [ via_device_id: null, serial_number: null, labels: [], + created_at: 0, + modified_at: 0, }, { area_id: "backyard", @@ -81,6 +84,8 @@ const DEVICES = [ via_device_id: null, serial_number: null, labels: [], + created_at: 0, + modified_at: 0, }, { area_id: null, @@ -101,6 +106,8 @@ const DEVICES = [ via_device_id: null, serial_number: null, labels: [], + created_at: 0, + modified_at: 0, }, ]; @@ -113,6 +120,8 @@ const AREAS: AreaRegistryEntry[] = [ picture: null, aliases: [], labels: [], + created_at: 0, + modified_at: 0, }, { area_id: "bedroom", @@ -122,6 +131,8 @@ const AREAS: AreaRegistryEntry[] = [ picture: null, aliases: [], labels: [], + created_at: 0, + modified_at: 0, }, { area_id: "livingroom", @@ -131,6 +142,8 @@ const AREAS: AreaRegistryEntry[] = [ picture: null, aliases: [], labels: [], + created_at: 0, + modified_at: 0, }, ]; @@ -141,6 +154,8 @@ const FLOORS: FloorRegistryEntry[] = [ level: 0, icon: null, aliases: [], + created_at: 0, + modified_at: 0, }, { floor_id: "first", @@ -148,6 +163,8 @@ const FLOORS: FloorRegistryEntry[] = [ level: 1, icon: "mdi:numeric-1", aliases: [], + created_at: 0, + modified_at: 0, }, { floor_id: "second", @@ -155,6 +172,8 @@ const FLOORS: FloorRegistryEntry[] = [ level: 2, icon: "mdi:numeric-2", aliases: [], + created_at: 0, + modified_at: 0, }, ]; @@ -165,6 +184,8 @@ const LABELS: LabelRegistryEntry[] = [ icon: null, color: "yellow", description: null, + created_at: 0, + modified_at: 0, }, { label_id: "entertainment", @@ -172,6 +193,8 @@ const LABELS: LabelRegistryEntry[] = [ icon: "mdi:popcorn", color: "blue", description: null, + created_at: 0, + modified_at: 0, }, ]; diff --git a/gallery/src/pages/misc/entity-state.ts b/gallery/src/pages/misc/entity-state.ts index 8d38fe9413..68375c60d8 100644 --- a/gallery/src/pages/misc/entity-state.ts +++ b/gallery/src/pages/misc/entity-state.ts @@ -358,13 +358,11 @@ export class DemoEntityState extends LitElement { }, entity_id: { title: "Entity ID", - width: "30%", filterable: true, sortable: true, }, state: { title: "State", - width: "20%", sortable: true, template: (entry) => html`${computeStateDisplay( @@ -379,14 +377,12 @@ export class DemoEntityState extends LitElement { device_class: { title: "Device class", template: (entry) => html`${entry.device_class ?? "-"}`, - width: "20%", filterable: true, sortable: true, }, domain: { title: "Domain", template: (entry) => html`${computeDomain(entry.entity_id)}`, - width: "20%", filterable: true, sortable: true, }, diff --git a/gallery/src/pages/misc/integration-card.ts b/gallery/src/pages/misc/integration-card.ts index 3144d99aa6..cab686e210 100644 --- a/gallery/src/pages/misc/integration-card.ts +++ b/gallery/src/pages/misc/integration-card.ts @@ -203,6 +203,8 @@ const createEntityRegistryEntries = ( options: null, labels: [], categories: {}, + created_at: 0, + modified_at: 0, }, ]; @@ -228,6 +230,8 @@ const createDeviceRegistryEntries = ( disabled_by: null, configuration_url: null, labels: [], + created_at: 0, + modified_at: 0, }, ]; diff --git a/hassio/src/backups/hassio-backups.ts b/hassio/src/backups/hassio-backups.ts index 64c735d977..095bd922f4 100644 --- a/hassio/src/backups/hassio-backups.ts +++ b/hassio/src/backups/hassio-backups.ts @@ -127,14 +127,13 @@ export class HassioBackups extends LitElement { main: true, sortable: true, filterable: true, - grows: true, + flex: 2, template: (backup) => html`${backup.name || backup.slug}
    ${backup.secondary}
    `, }, size: { title: this.supervisor.localize("backup.size"), - width: "15%", hidden: narrow, filterable: true, sortable: true, @@ -142,7 +141,6 @@ export class HassioBackups extends LitElement { }, location: { title: this.supervisor.localize("backup.location"), - width: "15%", hidden: narrow, filterable: true, sortable: true, @@ -151,7 +149,6 @@ export class HassioBackups extends LitElement { }, date: { title: this.supervisor.localize("backup.created"), - width: "15%", direction: "desc", hidden: narrow, filterable: true, diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index 9deadd898a..5dcd3c8983 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -85,9 +85,9 @@ export interface DataTableColumnData extends DataTableSortColumnData { | "flex"; template?: (row: T) => TemplateResult | string | typeof nothing; extraTemplate?: (row: T) => TemplateResult | string | typeof nothing; - width?: string; + minWidth?: string; maxWidth?: string; - grows?: boolean; + flex?: number; forceLTR?: boolean; hidden?: boolean; } @@ -216,6 +216,18 @@ export class HaDataTable extends LitElement { this.updateComplete.then(() => this._calcTableHeight()); } + protected updated() { + const header = this.renderRoot.querySelector(".mdc-data-table__header-row"); + if (!header) { + return; + } + if (header.scrollWidth > header.clientWidth) { + this.style.setProperty("--table-row-width", `${header.scrollWidth}px`); + } else { + this.style.removeProperty("--table-row-width"); + } + } + public willUpdate(properties: PropertyValues) { super.willUpdate(properties); @@ -355,7 +367,12 @@ export class HaDataTable extends LitElement { : `calc(100% - ${this._headerHeight}px)`, })} > -
    +
    ${this.selectable ? html` @@ -398,18 +415,16 @@ export class HaDataTable extends LitElement { column.type === "overflow", sortable: Boolean(column.sortable), "not-sorted": Boolean(column.sortable && !sorted), - grows: Boolean(column.grows), }; return html`
    ${column.template ? column.template(row) @@ -815,6 +828,17 @@ export class HaDataTable extends LitElement { @eventOptions({ passive: true }) private _saveScrollPos(e: Event) { this._savedScrollPos = (e.target as HTMLDivElement).scrollTop; + + this.renderRoot.querySelector(".mdc-data-table__header-row")!.scrollLeft = ( + e.target as HTMLDivElement + ).scrollLeft; + } + + @eventOptions({ passive: true }) + private _scrollContent(e: Event) { + this.renderRoot.querySelector("lit-virtualizer")!.scrollLeft = ( + e.target as HTMLDivElement + ).scrollLeft; } private _collapseGroup = (ev: Event) => { @@ -889,8 +913,8 @@ export class HaDataTable extends LitElement { .mdc-data-table__row { display: flex; - width: 100%; height: var(--data-table-row-height, 52px); + width: var(--table-row-width, 100%); } .mdc-data-table__row ~ .mdc-data-table__row { @@ -914,18 +938,26 @@ export class HaDataTable extends LitElement { .mdc-data-table__header-row { height: 56px; display: flex; - width: 100%; border-bottom: 1px solid var(--divider-color); + overflow: auto; } + /* Hide scrollbar for Chrome, Safari and Opera */ .mdc-data-table__header-row::-webkit-scrollbar { display: none; } + /* Hide scrollbar for IE, Edge and Firefox */ + .mdc-data-table__header-row { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } + .mdc-data-table__cell, .mdc-data-table__header-cell { padding-right: 16px; padding-left: 16px; + min-width: 150px; align-self: center; overflow: hidden; text-overflow: ellipsis; @@ -973,6 +1005,8 @@ export class HaDataTable extends LitElement { letter-spacing: 0.0178571429em; text-decoration: inherit; text-transform: inherit; + flex-grow: 0; + flex-shrink: 0; } .mdc-data-table__cell a { @@ -991,7 +1025,8 @@ export class HaDataTable extends LitElement { .mdc-data-table__header-cell--icon, .mdc-data-table__cell--icon { - width: 54px; + min-width: 64px; + flex: 0 0 64px !important; } .mdc-data-table__cell--icon img { @@ -1031,11 +1066,14 @@ export class HaDataTable extends LitElement { .mdc-data-table__header-cell--overflow-menu, .mdc-data-table__header-cell--icon-button, .mdc-data-table__cell--icon-button { + min-width: 64px; + flex: 0 0 64px !important; padding: 8px; } .mdc-data-table__header-cell--icon-button, .mdc-data-table__cell--icon-button { + min-width: 56px; width: 56px; } diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index 5fe9b63d5e..9001e71eb2 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -279,6 +279,8 @@ export class HaAreaPicker extends LitElement { icon: null, aliases: [], labels: [], + created_at: 0, + modified_at: 0, }, ]; } @@ -295,6 +297,8 @@ export class HaAreaPicker extends LitElement { icon: "mdi:plus", aliases: [], labels: [], + created_at: 0, + modified_at: 0, }, ]; } @@ -377,6 +381,8 @@ export class HaAreaPicker extends LitElement { picture: null, labels: [], aliases: [], + created_at: 0, + modified_at: 0, }, ] as AreaRegistryEntry[]; } else { @@ -393,6 +399,8 @@ export class HaAreaPicker extends LitElement { picture: null, labels: [], aliases: [], + created_at: 0, + modified_at: 0, }, ] as AreaRegistryEntry[]; } diff --git a/src/components/ha-floor-picker.ts b/src/components/ha-floor-picker.ts index c810292677..d790ec1a46 100644 --- a/src/components/ha-floor-picker.ts +++ b/src/components/ha-floor-picker.ts @@ -295,6 +295,8 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) { icon: null, level: null, aliases: [], + created_at: 0, + modified_at: 0, }, ]; } @@ -309,6 +311,8 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) { icon: "mdi:plus", level: null, aliases: [], + created_at: 0, + modified_at: 0, }, ]; } @@ -391,6 +395,8 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) { icon: null, level: null, aliases: [], + created_at: 0, + modified_at: 0, }, ] as FloorRegistryEntry[]; } else { @@ -405,6 +411,8 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) { icon: "mdi:plus", level: null, aliases: [], + created_at: 0, + modified_at: 0, }, ] as FloorRegistryEntry[]; } diff --git a/src/components/ha-label-picker.ts b/src/components/ha-label-picker.ts index fd0239d176..dba720d94b 100644 --- a/src/components/ha-label-picker.ts +++ b/src/components/ha-label-picker.ts @@ -303,6 +303,8 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) { icon: null, color: null, description: null, + created_at: 0, + modified_at: 0, }, ]; } @@ -317,6 +319,8 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) { icon: "mdi:plus", color: null, description: null, + created_at: 0, + modified_at: 0, }, ]; } diff --git a/src/data/area_registry.ts b/src/data/area_registry.ts index 46e16b50cc..6220d60d62 100644 --- a/src/data/area_registry.ts +++ b/src/data/area_registry.ts @@ -2,10 +2,11 @@ import { stringCompare } from "../common/string/compare"; import { HomeAssistant } from "../types"; import { DeviceRegistryEntry } from "./device_registry"; import { EntityRegistryEntry } from "./entity_registry"; +import { RegistryEntry } from "./registry"; export { subscribeAreaRegistry } from "./ws-area_registry"; -export interface AreaRegistryEntry { +export interface AreaRegistryEntry extends RegistryEntry { area_id: string; floor_id: string | null; name: string; diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index 7772febaf2..f758f19b8d 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -1,19 +1,20 @@ import { computeStateName } from "../common/entity/compute_state_name"; import { caseInsensitiveStringCompare } from "../common/string/compare"; import type { HomeAssistant } from "../types"; +import { ConfigEntry } from "./config_entries"; import type { EntityRegistryDisplayEntry, EntityRegistryEntry, } from "./entity_registry"; -import { ConfigEntry } from "./config_entries"; import type { EntitySources } from "./entity_sources"; +import { RegistryEntry } from "./registry"; export { fetchDeviceRegistry, subscribeDeviceRegistry, } from "./ws-device_registry"; -export interface DeviceRegistryEntry { +export interface DeviceRegistryEntry extends RegistryEntry { id: string; config_entries: string[]; connections: Array<[string, string]>; diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index 02b72bf477..ae370892f1 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -7,6 +7,7 @@ import { debounce } from "../common/util/debounce"; import { HomeAssistant } from "../types"; import { LightColor } from "./light"; import { computeDomain } from "../common/entity/compute_domain"; +import { RegistryEntry } from "./registry"; export { subscribeEntityRegistryDisplay } from "./ws-entity_registry_display"; @@ -43,7 +44,7 @@ export interface EntityRegistryDisplayEntryResponse { entity_categories: Record; } -export interface EntityRegistryEntry { +export interface EntityRegistryEntry extends RegistryEntry { id: string; entity_id: string; name: string | null; diff --git a/src/data/floor_registry.ts b/src/data/floor_registry.ts index c69e31cc51..1a31cbf09b 100644 --- a/src/data/floor_registry.ts +++ b/src/data/floor_registry.ts @@ -4,10 +4,11 @@ import { stringCompare } from "../common/string/compare"; import { debounce } from "../common/util/debounce"; import { HomeAssistant } from "../types"; import { AreaRegistryEntry } from "./area_registry"; +import { RegistryEntry } from "./registry"; export { subscribeAreaRegistry } from "./ws-area_registry"; -export interface FloorRegistryEntry { +export interface FloorRegistryEntry extends RegistryEntry { floor_id: string; name: string; level: number | null; diff --git a/src/data/label_registry.ts b/src/data/label_registry.ts index cc6b318354..8c8f481e57 100644 --- a/src/data/label_registry.ts +++ b/src/data/label_registry.ts @@ -1,10 +1,11 @@ import { Connection, createCollection } from "home-assistant-js-websocket"; import { Store } from "home-assistant-js-websocket/dist/store"; import { stringCompare } from "../common/string/compare"; -import { HomeAssistant } from "../types"; import { debounce } from "../common/util/debounce"; +import { HomeAssistant } from "../types"; +import { RegistryEntry } from "./registry"; -export interface LabelRegistryEntry { +export interface LabelRegistryEntry extends RegistryEntry { label_id: string; name: string; icon: string | null; diff --git a/src/data/registry.ts b/src/data/registry.ts new file mode 100644 index 0000000000..985d66a0ec --- /dev/null +++ b/src/data/registry.ts @@ -0,0 +1,4 @@ +export interface RegistryEntry { + created_at: number; + modified_at: number; +} diff --git a/src/panels/config/application_credentials/ha-config-application-credentials.ts b/src/panels/config/application_credentials/ha-config-application-credentials.ts index d31c8b274d..f18966c6f8 100644 --- a/src/panels/config/application_credentials/ha-config-application-credentials.ts +++ b/src/panels/config/application_credentials/ha-config-application-credentials.ts @@ -87,14 +87,13 @@ export class HaConfigApplicationCredentials extends LitElement { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, }, client_id: { title: localize( "ui.panel.config.application_credentials.picker.headers.client_id" ), filterable: true, - width: "30%", }, localizedDomain: { title: localize( @@ -102,12 +101,10 @@ export class HaConfigApplicationCredentials extends LitElement { ), sortable: true, filterable: true, - width: "30%", direction: "asc", }, actions: { title: "", - width: "64px", type: "overflow-menu", showNarrow: true, hideable: false, diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index 7bef54309e..a5523eb3f4 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -288,7 +288,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, extraTemplate: (automation) => automation.labels.length ? html` { if (!automation.last_triggered) { @@ -337,7 +336,8 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { }, }, formatted_state: { - width: "82px", + minWidth: "82px", + maxWidth: "82px", sortable: true, groupable: true, hidden: narrow, @@ -353,7 +353,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { }, actions: { title: "", - width: "64px", type: "icon-button", showNarrow: true, moveable: false, diff --git a/src/panels/config/backup/ha-config-backup.ts b/src/panels/config/backup/ha-config-backup.ts index bc4bf616cf..eb3ffdec56 100644 --- a/src/panels/config/backup/ha-config-backup.ts +++ b/src/panels/config/backup/ha-config-backup.ts @@ -59,7 +59,7 @@ class HaConfigBackup extends LitElement { main: true, sortable: true, filterable: true, - grows: true, + flex: 2, template: narrow ? undefined : (backup) => @@ -72,14 +72,12 @@ class HaConfigBackup extends LitElement { }, size: { title: localize("ui.panel.config.backup.size"), - width: "15%", filterable: true, sortable: true, template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB", }, date: { title: localize("ui.panel.config.backup.created"), - width: "15%", direction: "desc", filterable: true, sortable: true, @@ -89,7 +87,6 @@ class HaConfigBackup extends LitElement { actions: { title: "", - width: "15%", type: "overflow-menu", showNarrow: true, hideable: false, diff --git a/src/panels/config/blueprint/ha-blueprint-overview.ts b/src/panels/config/blueprint/ha-blueprint-overview.ts index 292a267bd7..a7b32ea051 100644 --- a/src/panels/config/blueprint/ha-blueprint-overview.ts +++ b/src/panels/config/blueprint/ha-blueprint-overview.ts @@ -176,7 +176,7 @@ class HaBlueprintOverview extends LitElement { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, }, translated_type: { title: localize("ui.panel.config.blueprint.overview.headers.type"), @@ -184,14 +184,13 @@ class HaBlueprintOverview extends LitElement { filterable: true, groupable: true, direction: "asc", - width: "10%", }, path: { title: localize("ui.panel.config.blueprint.overview.headers.file_name"), sortable: true, filterable: true, direction: "asc", - width: "25%", + flex: 2, }, fullpath: { title: "fullpath", @@ -199,7 +198,6 @@ class HaBlueprintOverview extends LitElement { }, actions: { title: "", - width: this.narrow ? undefined : "10%", type: "overflow-menu", showNarrow: true, moveable: false, diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts index 6a5b857530..d7cfd5498b 100644 --- a/src/panels/config/devices/ha-config-devices-dashboard.ts +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -22,6 +22,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { computeCssColor } from "../../../common/color/compute-color"; +import { formatShortDateTime } from "../../../common/datetime/format_date_time"; import { storage } from "../../../common/decorators/storage"; import { HASSDomEvent } from "../../../common/dom/fire_event"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; @@ -58,6 +59,11 @@ import "../../../components/ha-sub-menu"; import { createAreaRegistryEntry } from "../../../data/area_registry"; import { ConfigEntry, sortConfigEntries } from "../../../data/config_entries"; import { fullEntitiesContext } from "../../../data/context"; +import { + DataTableFilters, + deserializeFilters, + serializeFilters, +} from "../../../data/data_table_filters"; import { DeviceEntityLookup, DeviceRegistryEntry, @@ -75,6 +81,7 @@ import { createLabelRegistryEntry, subscribeLabelRegistry, } from "../../../data/label_registry"; +import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-tabs-subpage-data-table"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { haStyle } from "../../../resources/styles"; @@ -85,12 +92,6 @@ import { configSections } from "../ha-panel-config"; import "../integrations/ha-integration-overflow-menu"; import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; -import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; -import { - serializeFilters, - deserializeFilters, - DataTableFilters, -} from "../../../data/data_table_filters"; interface DeviceRowData extends DeviceRegistryEntry { device?: DeviceRowData; @@ -443,7 +444,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { } ); - private _columns = memoizeOne((localize: LocalizeFunc, narrow: boolean) => { + private _columns = memoizeOne((localize: LocalizeFunc) => { type DeviceItem = ReturnType< typeof this._devicesAndFilterDomains >["devicesOutput"][number]; @@ -476,6 +477,8 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { filterable: true, direction: "asc", grows: true, + flex: 2, + minWidth: "150px", extraTemplate: (device) => html` ${device.label_entries.length ? html` @@ -491,27 +494,27 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { sortable: true, filterable: true, groupable: true, - width: "15%", + minWidth: "120px", }, model: { title: localize("ui.panel.config.devices.data_table.model"), sortable: true, filterable: true, - width: "15%", + minWidth: "120px", }, area: { title: localize("ui.panel.config.devices.data_table.area"), sortable: true, filterable: true, groupable: true, - width: "15%", + minWidth: "120px", }, integration: { title: localize("ui.panel.config.devices.data_table.integration"), sortable: true, filterable: true, groupable: true, - width: "15%", + minWidth: "120px", }, battery_entity: { title: localize("ui.panel.config.devices.data_table.battery"), @@ -519,8 +522,8 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { sortable: true, filterable: true, type: "numeric", - width: narrow ? "105px" : "15%", - maxWidth: "105px", + maxWidth: "101px", + minWidth: "101px", valueColumn: "battery_level", template: (device) => { const batteryEntityPair = device.battery_entity; @@ -548,9 +551,39 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { .batteryChargingStateObj=${batteryCharging} > ` - : html`—`; + : "—"; }, }, + created_at: { + title: localize("ui.panel.config.generic.headers.created_at"), + defaultHidden: true, + sortable: true, + filterable: true, + minWidth: "128px", + template: (entry) => + entry.created_at + ? formatShortDateTime( + new Date(entry.created_at * 1000), + this.hass.locale, + this.hass.config + ) + : "—", + }, + modified_at: { + title: localize("ui.panel.config.generic.headers.modified_at"), + defaultHidden: true, + sortable: true, + filterable: true, + minWidth: "128px", + template: (entry) => + entry.modified_at + ? formatShortDateTime( + new Date(entry.modified_at * 1000), + this.hass.locale, + this.hass.config + ) + : "—", + }, disabled_by: { title: "", label: localize("ui.panel.config.devices.data_table.disabled_by"), @@ -675,7 +708,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { "ui.panel.config.devices.picker.search", { number: devicesOutput.length } )} - .columns=${this._columns(this.hass.localize, this.narrow)} + .columns=${this._columns(this.hass.localize)} .data=${devicesOutput} selectable .selected=${this._selected.length} diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 7c48cd1605..13745e6d22 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -103,6 +103,7 @@ import { deserializeFilters, DataTableFilters, } from "../../../data/data_table_filters"; +import { formatShortDateTime } from "../../../common/datetime/format_date_time"; export interface StateEntity extends Omit { @@ -294,7 +295,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, extraTemplate: (entry) => entry.label_entries.length ? html` @@ -308,14 +309,12 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { title: localize("ui.panel.config.entities.picker.headers.entity_id"), sortable: true, filterable: true, - width: "25%", }, localized_platform: { title: localize("ui.panel.config.entities.picker.headers.integration"), sortable: true, groupable: true, filterable: true, - width: "20%", }, domain: { title: localize("ui.panel.config.entities.picker.headers.domain"), @@ -329,7 +328,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { sortable: true, filterable: true, groupable: true, - width: "15%", }, disabled_by: { title: localize("ui.panel.config.entities.picker.headers.disabled_by"), @@ -348,7 +346,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { showNarrow: true, sortable: true, filterable: true, - width: "68px", template: (entry) => entry.unavailable || entry.disabled_by || @@ -398,6 +395,36 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { ` : "—", }, + created_at: { + title: localize("ui.panel.config.generic.headers.created_at"), + defaultHidden: true, + sortable: true, + filterable: true, + minWidth: "128px", + template: (entry) => + entry.created_at + ? formatShortDateTime( + new Date(entry.created_at * 1000), + this.hass.locale, + this.hass.config + ) + : "—", + }, + modified_at: { + title: localize("ui.panel.config.generic.headers.modified_at"), + defaultHidden: true, + sortable: true, + filterable: true, + minWidth: "128px", + template: (entry) => + entry.modified_at + ? formatShortDateTime( + new Date(entry.modified_at * 1000), + this.hass.locale, + this.hass.config + ) + : "—", + }, available: { title: localize("ui.panel.config.entities.picker.headers.availability"), sortable: true, @@ -1081,6 +1108,8 @@ ${ options: null, labels: [], categories: {}, + created_at: 0, + modified_at: 0, }); } if (changed) { diff --git a/src/panels/config/helpers/ha-config-helpers.ts b/src/panels/config/helpers/ha-config-helpers.ts index 1046866244..5a5d07d957 100644 --- a/src/panels/config/helpers/ha-config-helpers.ts +++ b/src/panels/config/helpers/ha-config-helpers.ts @@ -280,7 +280,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { main: true, sortable: true, filterable: true, - grows: true, + flex: 2, direction: "asc", extraTemplate: (helper) => helper.label_entries.length @@ -295,7 +295,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { title: localize("ui.panel.config.helpers.picker.headers.entity_id"), sortable: true, filterable: true, - width: "25%", }, category: { title: localize("ui.panel.config.helpers.picker.headers.category"), @@ -314,7 +313,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { localized_type: { title: localize("ui.panel.config.helpers.picker.headers.type"), sortable: true, - width: "25%", filterable: true, groupable: true, }, @@ -344,7 +342,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { actions: { title: "", label: "Actions", - width: "64px", type: "overflow-menu", hideable: false, moveable: false, diff --git a/src/panels/config/integrations/integration-panels/zha/zha-clusters-data-table.ts b/src/panels/config/integrations/integration-panels/zha/zha-clusters-data-table.ts index 1b14b676a8..2d97719771 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-clusters-data-table.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-clusters-data-table.ts @@ -44,7 +44,7 @@ export class ZHAClustersDataTable extends LitElement { title: "Name", sortable: true, direction: "asc", - grows: true, + flex: 2, }, } : { @@ -52,18 +52,16 @@ export class ZHAClustersDataTable extends LitElement { title: "Name", sortable: true, direction: "asc", - grows: true, + flex: 2, }, id: { title: "ID", template: (cluster) => html` ${formatAsPaddedHex(cluster.id)} `, sortable: true, - width: "25%", }, endpoint_id: { title: "Endpoint ID", sortable: true, - width: "15%", }, } ); diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-endpoint-data-table.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-endpoint-data-table.ts index d68fdb0b1e..18a265c16c 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-device-endpoint-data-table.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-endpoint-data-table.ts @@ -65,7 +65,7 @@ export class ZHADeviceEndpointDataTable extends LitElement { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, template: (device) => html` ${device.name} @@ -84,7 +84,7 @@ export class ZHADeviceEndpointDataTable extends LitElement { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, template: (device) => html` ${device.name} @@ -100,7 +100,7 @@ export class ZHADeviceEndpointDataTable extends LitElement { title: "Associated Entities", sortable: false, filterable: false, - width: "50%", + flex: 2, template: (device) => html` ${device.entities.length ? device.entities.length > 3 diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-neighbors.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-neighbors.ts index 0774f8b343..98435bae27 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-device-neighbors.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-neighbors.ts @@ -69,14 +69,13 @@ class ZHADeviceNeighbors extends LitElement { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, }, lqi: { title: this.hass.localize("ui.panel.config.zha.neighbors.lqi"), sortable: true, filterable: true, type: "numeric", - width: "75px", }, } : { @@ -85,14 +84,13 @@ class ZHADeviceNeighbors extends LitElement { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, }, lqi: { title: this.hass.localize("ui.panel.config.zha.neighbors.lqi"), sortable: true, filterable: true, type: "numeric", - width: "75px", }, relationship: { title: this.hass.localize( @@ -100,14 +98,12 @@ class ZHADeviceNeighbors extends LitElement { ), sortable: true, filterable: true, - width: "150px", }, depth: { title: this.hass.localize("ui.panel.config.zha.neighbors.depth"), sortable: true, filterable: true, type: "numeric", - width: "75px", }, } ); diff --git a/src/panels/config/integrations/integration-panels/zha/zha-groups-dashboard.ts b/src/panels/config/integrations/integration-panels/zha/zha-groups-dashboard.ts index d21ae02aea..e6559f63b0 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-groups-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-groups-dashboard.ts @@ -79,7 +79,7 @@ export class ZHAGroupsDashboard extends LitElement { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, }, } : { @@ -88,19 +88,17 @@ export class ZHAGroupsDashboard extends LitElement { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, }, group_id: { title: this.hass.localize("ui.panel.config.zha.groups.group_id"), type: "numeric", - width: "15%", template: (group) => html` ${formatAsPaddedHex(group.group_id)} `, sortable: true, }, members: { title: this.hass.localize("ui.panel.config.zha.groups.members"), type: "numeric", - width: "15%", template: (group) => html` ${group.members.length} `, sortable: true, }, diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-provisioned.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-provisioned.ts index 93539a507e..df0600d788 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-provisioned.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-provisioned.ts @@ -47,7 +47,6 @@ class ZWaveJSProvisioned extends LitElement { "ui.panel.config.zwave_js.provisioned.included" ), type: "icon", - width: "100px", template: (entry) => entry.nodeId ? html` @@ -71,13 +70,12 @@ class ZWaveJSProvisioned extends LitElement { title: this.hass.localize("ui.panel.config.zwave_js.provisioned.dsk"), sortable: true, filterable: true, - grows: true, + flex: 2, }, security_classes: { title: this.hass.localize( "ui.panel.config.zwave_js.provisioned.security_classes" ), - width: "30%", hidden: narrow, filterable: true, sortable: true, @@ -97,7 +95,6 @@ class ZWaveJSProvisioned extends LitElement { "ui.panel.config.zwave_js.provisioned.unprovison" ), type: "icon-button", - width: "100px", template: (entry) => html` { + private _columns = memoizeOne((localize: LocalizeFunc, narrow: boolean) => { const columns: DataTableColumnContainer = { icon: { title: "", @@ -110,22 +111,60 @@ export class HaConfigLabels extends LitElement { name: { title: localize("ui.panel.config.labels.headers.name"), main: true, + flex: 2, sortable: true, filterable: true, - grows: true, - template: (label) => html` -
    ${label.name}
    - ${label.description - ? html`
    ${label.description}
    ` - : nothing} - `, + template: narrow + ? undefined + : (label) => html` +
    ${label.name}
    + ${label.description + ? html`
    ${label.description}
    ` + : nothing} + `, + }, + description: { + title: localize("ui.panel.config.labels.headers.description"), + hidden: !narrow, + filterable: true, + hideable: true, + }, + created_at: { + title: localize("ui.panel.config.generic.headers.created_at"), + defaultHidden: true, + sortable: true, + filterable: true, + minWidth: "128px", + template: (label) => + label.created_at + ? formatShortDateTime( + new Date(label.created_at * 1000), + this.hass.locale, + this.hass.config + ) + : "—", + }, + modified_at: { + title: localize("ui.panel.config.generic.headers.modified_at"), + defaultHidden: true, + sortable: true, + filterable: true, + minWidth: "128px", + template: (label) => + label.modified_at + ? formatShortDateTime( + new Date(label.modified_at * 1000), + this.hass.locale, + this.hass.config + ) + : "—", }, actions: { title: "", + label: localize("ui.panel.config.generic.headers.actions"), showNarrow: true, moveable: false, hideable: false, - width: "64px", type: "overflow-menu", template: (label) => html` html` @@ -171,7 +171,6 @@ export class HaConfigLovelaceDashboards extends LitElement { ), sortable: true, filterable: true, - width: "20%", template: (dashboard) => html` ${this.hass.localize( `ui.panel.config.lovelace.dashboards.conf_mode.${dashboard.mode}` @@ -183,7 +182,6 @@ export class HaConfigLovelaceDashboards extends LitElement { title: localize( "ui.panel.config.lovelace.dashboards.picker.headers.filename" ), - width: "15%", sortable: true, filterable: true, }; @@ -195,7 +193,6 @@ export class HaConfigLovelaceDashboards extends LitElement { sortable: true, type: "icon", hidden: narrow, - width: "100px", template: (dashboard) => dashboard.require_admin ? html`` @@ -207,7 +204,6 @@ export class HaConfigLovelaceDashboards extends LitElement { ), type: "icon", hidden: narrow, - width: "121px", template: (dashboard) => dashboard.show_in_sidebar ? html`` @@ -221,7 +217,6 @@ export class HaConfigLovelaceDashboards extends LitElement { ), filterable: true, showNarrow: true, - width: "100px", template: (dashboard) => narrow ? html` diff --git a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts index 2c91a30a88..d43d572cfd 100644 --- a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts +++ b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts @@ -94,7 +94,7 @@ export class HaConfigLovelaceRescources extends LitElement { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, forceLTR: true, }, type: { @@ -103,7 +103,6 @@ export class HaConfigLovelaceRescources extends LitElement { ), sortable: true, filterable: true, - width: "30%", template: (resource) => html` ${this.hass.localize( `ui.panel.config.lovelace.resources.types.${resource.type}` diff --git a/src/panels/config/scene/ha-scene-dashboard.ts b/src/panels/config/scene/ha-scene-dashboard.ts index b889dc1094..179ee15fac 100644 --- a/src/panels/config/scene/ha-scene-dashboard.ts +++ b/src/panels/config/scene/ha-scene-dashboard.ts @@ -260,7 +260,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, extraTemplate: (scene) => scene.labels.length ? html` { const lastActivated = scene.state; if (!lastActivated || isUnavailableState(lastActivated)) { @@ -312,7 +311,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { }, only_editable: { title: "", - width: "56px", + type: "icon", showNarrow: true, template: (scene) => !scene.attributes.id @@ -331,7 +330,6 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { }, actions: { title: "", - width: "64px", type: "overflow-menu", showNarrow: true, moveable: false, diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index d35aed5f90..8e1767a2c9 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -270,7 +270,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, extraTemplate: (script) => script.labels.length ? html` { const date = new Date(script.last_triggered); @@ -322,7 +321,6 @@ class HaScriptPicker extends SubscribeMixin(LitElement) { }, actions: { title: "", - width: "64px", type: "overflow-menu", showNarrow: true, moveable: false, diff --git a/src/panels/config/tags/ha-config-tags.ts b/src/panels/config/tags/ha-config-tags.ts index 933490adfe..66eded944c 100644 --- a/src/panels/config/tags/ha-config-tags.ts +++ b/src/panels/config/tags/ha-config-tags.ts @@ -81,13 +81,12 @@ export class HaConfigTags extends SubscribeMixin(LitElement) { main: true, sortable: true, filterable: true, - grows: true, + flex: 2, }, last_scanned_datetime: { title: localize("ui.panel.config.tag.headers.last_scanned"), sortable: true, direction: "desc", - width: "20%", template: (tag) => html` ${tag.last_scanned_datetime ? html` html`${user.username || "—"}`, }, @@ -100,7 +98,6 @@ export class HaConfigUsers extends LitElement { sortable: true, filterable: true, groupable: true, - width: "20%", direction: "asc", }, is_active: { @@ -110,7 +107,6 @@ export class HaConfigUsers extends LitElement { type: "icon", sortable: true, filterable: true, - width: "80px", hidden: narrow, template: (user) => user.is_active @@ -124,7 +120,6 @@ export class HaConfigUsers extends LitElement { type: "icon", sortable: true, filterable: true, - width: "80px", hidden: narrow, template: (user) => user.system_generated @@ -138,7 +133,6 @@ export class HaConfigUsers extends LitElement { type: "icon", sortable: true, filterable: true, - width: "80px", hidden: narrow, template: (user) => user.local_only @@ -153,7 +147,7 @@ export class HaConfigUsers extends LitElement { type: "icon", sortable: false, filterable: false, - width: "104px", + minWidth: "104px", hidden: !narrow, showNarrow: true, template: (user) => { diff --git a/src/panels/config/voice-assistants/assist/ha-config-voice-assistants-assist-devices.ts b/src/panels/config/voice-assistants/assist/ha-config-voice-assistants-assist-devices.ts index 9c9e32d2e0..5e3f250d90 100644 --- a/src/panels/config/voice-assistants/assist/ha-config-voice-assistants-assist-devices.ts +++ b/src/panels/config/voice-assistants/assist/ha-config-voice-assistants-assist-devices.ts @@ -42,13 +42,12 @@ class AssistDevicesPage extends LitElement { ), filterable: true, sortable: true, - grows: true, + flex: 2, }, pipeline: { title: localize( "ui.panel.config.voice_assistants.assistants.pipeline.devices.pipeline" ), - width: "30%", filterable: true, sortable: true, }, @@ -58,7 +57,6 @@ class AssistDevicesPage extends LitElement { ), filterable: true, sortable: true, - width: "30%", }, }; diff --git a/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts b/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts index 7a2b5e9c49..dfa40a6f83 100644 --- a/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts +++ b/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts @@ -167,7 +167,7 @@ export class VoiceAssistantsExpose extends LitElement { sortable: true, filterable: true, direction: "asc", - grows: true, + flex: 2, template: narrow ? undefined : (entry) => html` @@ -197,7 +197,6 @@ export class VoiceAssistantsExpose extends LitElement { sortable: true, groupable: true, filterable: true, - width: "15%", }, assistants: { title: localize( @@ -206,7 +205,8 @@ export class VoiceAssistantsExpose extends LitElement { showNarrow: true, sortable: true, filterable: true, - width: "160px", + minWidth: "160px", + maxWidth: "160px", type: "flex", template: (entry) => html`${availableAssistants.map((key) => { @@ -233,7 +233,6 @@ export class VoiceAssistantsExpose extends LitElement { ), sortable: true, filterable: true, - width: "15%", template: (entry) => entry.aliases.length === 0 ? "-" diff --git a/src/panels/developer-tools/statistics/developer-tools-statistics.ts b/src/panels/developer-tools/statistics/developer-tools-statistics.ts index 518dce24b9..8d40dc0b42 100644 --- a/src/panels/developer-tools/statistics/developer-tools-statistics.ts +++ b/src/panels/developer-tools/statistics/developer-tools-statistics.ts @@ -89,9 +89,10 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) { title: localize( "ui.panel.developer-tools.tabs.statistics.data_table.name" ), + main: true, sortable: true, filterable: true, - grows: true, + flex: 2, }, statistic_id: { title: localize( @@ -100,7 +101,6 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) { sortable: true, filterable: true, hidden: this.narrow, - width: "20%", }, statistics_unit_of_measurement: { title: localize( @@ -108,7 +108,6 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) { ), sortable: true, filterable: true, - width: "10%", forceLTR: true, }, source: { @@ -117,7 +116,6 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) { ), sortable: true, filterable: true, - width: "10%", }, issues_string: { title: localize( @@ -126,7 +124,7 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) { sortable: true, filterable: true, direction: "asc", - width: "30%", + flex: 2, template: (statistic) => html`${statistic.issues_string ?? localize("ui.panel.developer-tools.tabs.statistics.no_issue")}`, @@ -147,12 +145,15 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) { )} ` : "—"}`, - width: "113px", + minWidth: "113px", + maxWidth: "113px", + showNarrow: true, }, actions: { title: "", label: localize("ui.panel.developer-tools.tabs.statistics.adjust_sum"), type: "icon-button", + showNarrow: true, template: (statistic) => statistic.has_sum ? html` @@ -179,6 +180,7 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) { .noDataText=${this.hass.localize( "ui.panel.developer-tools.tabs.statistics.data_table.no_statistics" )} + .narrow=${this.narrow} id="statistic_id" clickable @row-click=${this._rowClicked} diff --git a/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts b/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts index 51f42252c1..8caff95723 100644 --- a/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts +++ b/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts @@ -64,7 +64,8 @@ export class HuiEntityPickerTable extends LitElement { title: this.hass!.localize("ui.panel.lovelace.unused_entities.entity"), sortable: true, filterable: true, - grows: true, + flex: 2, + main: true, direction: "asc", template: (entity: any) => html`
    @@ -81,7 +82,6 @@ export class HuiEntityPickerTable extends LitElement { title: this.hass!.localize("ui.panel.lovelace.unused_entities.entity_id"), sortable: true, filterable: true, - width: "30%", hidden: narrow, }; @@ -89,7 +89,6 @@ export class HuiEntityPickerTable extends LitElement { title: this.hass!.localize("ui.panel.lovelace.unused_entities.domain"), sortable: true, filterable: true, - width: "15%", hidden: narrow, }; @@ -99,7 +98,6 @@ export class HuiEntityPickerTable extends LitElement { ), type: "numeric", sortable: true, - width: "15%", hidden: narrow, template: (entity) => html` Date: Tue, 30 Jul 2024 12:48:28 +0200 Subject: [PATCH 78/97] Fix translation loading and manifest fetching for integration page (#21501) --- src/layouts/hass-tabs-subpage-data-table.ts | 3 ++- .../config/integrations/ha-config-integration-page.ts | 4 +++- .../config/integrations/ha-config-integrations-dashboard.ts | 6 ++++++ src/panels/config/integrations/ha-config-integrations.ts | 5 ++++- src/panels/config/integrations/ha-integration-card.ts | 2 +- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index 79db1674e5..8d88db443a 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -266,7 +266,8 @@ export class HaTabsSubpageDataTable extends LitElement { ${normalEntries.length === 0 ? html`
    - ${this.hass.config.components.find( + ${this._manifest && + !this._manifest.config_flow && + this.hass.config.components.find( (comp) => comp.split(".")[0] === this.domain ) ? this.hass.localize( diff --git a/src/panels/config/integrations/ha-config-integrations-dashboard.ts b/src/panels/config/integrations/ha-config-integrations-dashboard.ts index 938a9f9c2d..bdb1dce719 100644 --- a/src/panels/config/integrations/ha-config-integrations-dashboard.ts +++ b/src/panels/config/integrations/ha-config-integrations-dashboard.ts @@ -172,6 +172,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) { if ( !entryDomains.has(componentDomain) && manifests[componentDomain] && + !manifests[componentDomain].config_flow && (!manifests[componentDomain].integration_type || ["device", "hub", "service", "integration"].includes( manifests[componentDomain].integration_type! @@ -314,6 +315,11 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) { this.configEntriesInProgress.map((flow) => flow.handler) ); } + if (changed.has("configEntries") && this.configEntries) { + this._fetchIntegrationManifests( + this.configEntries.map((entry) => entry.domain) + ); + } } protected render() { diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index aa218486ff..4a80dbb807 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -151,7 +151,10 @@ class HaConfigIntegrations extends SubscribeMixin(HassRouterPage) { if (this.hasUpdated) { return; } - this._loadTranslationsPromise = this.hass.loadBackendTranslation("title"); + this._loadTranslationsPromise = this.hass.loadBackendTranslation( + "title", + this.hass.config.components.map((comp) => comp.split(".")[0]) + ); } protected updatePageEl(pageEl) { diff --git a/src/panels/config/integrations/ha-integration-card.ts b/src/panels/config/integrations/ha-integration-card.ts index 0ea39700d6..3e7cd7568f 100644 --- a/src/panels/config/integrations/ha-integration-card.ts +++ b/src/panels/config/integrations/ha-integration-card.ts @@ -182,7 +182,7 @@ export class HaIntegrationCard extends LitElement { >
    ` : nothing} - ${!this.manifest?.config_flow + ${this.manifest && !this.manifest?.config_flow ? html`
    Date: Tue, 30 Jul 2024 15:13:05 +0200 Subject: [PATCH 79/97] Show helpers setup in YAML also in the UI (#21500) --- .../config/helpers/ha-config-helpers.ts | 78 ++++++++++++++----- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/src/panels/config/helpers/ha-config-helpers.ts b/src/panels/config/helpers/ha-config-helpers.ts index 5a5d07d957..eff9674f9b 100644 --- a/src/panels/config/helpers/ha-config-helpers.ts +++ b/src/panels/config/helpers/ha-config-helpers.ts @@ -71,7 +71,11 @@ import { subscribeEntityRegistry, updateEntityRegistryEntry, } from "../../../data/entity_registry"; -import { domainToName } from "../../../data/integration"; +import { + IntegrationManifest, + domainToName, + fetchIntegrationManifests, +} from "../../../data/integration"; import { LabelRegistryEntry, createLabelRegistryEntry, @@ -101,6 +105,7 @@ import { deserializeFilters, DataTableFilters, } from "../../../data/data_table_filters"; +import { fetchEntitySourcesWithCache } from "../../../data/entity_sources"; type HelperItem = { id: string; @@ -187,6 +192,8 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { @state() private _configEntries?: Record; + @state() private _entitySource: Record = {}; + @state() private _selected: string[] = []; @state() private _activeFilters?: string[]; @@ -409,7 +416,8 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { configEntry !== undefined || entityState.attributes.editable, type: configEntry ? configEntry.domain - : computeStateDomain(entityState), + : this._entitySource[entityState.entity_id] || + computeStateDomain(entityState), configEntry, entity: entityState, }; @@ -441,11 +449,12 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { const category = entityRegEntry?.categories.helpers; return { ...item, - localized_type: item.configEntry - ? domainToName(localize, item.type) - : localize( - `ui.panel.config.helpers.types.${item.type}` as LocalizeKeys - ) || item.type, + localized_type: + domainToName(localize, item.type) || + localize( + `ui.panel.config.helpers.types.${item.type}` as LocalizeKeys + ) || + item.type, label_entries: (labels || []).map( (lbl) => labelReg!.find((label) => label.label_id === lbl)! ), @@ -464,7 +473,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { this._entityEntries === undefined || this._configEntries === undefined ) { - return html` `; + return html``; } const categoryItems = html`${this._categories?.map( @@ -923,11 +932,48 @@ ${rejected protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); + + this._fetchEntitySources(); + if (this.route.path === "/add") { this._handleAdd(); } } + private async _fetchEntitySources() { + const [entitySources, fetchedManifests] = await Promise.all([ + fetchEntitySourcesWithCache(this.hass), + fetchIntegrationManifests(this.hass), + ]); + + const manifests: { [domain: string]: IntegrationManifest } = {}; + + for (const manifest of fetchedManifests) { + manifests[manifest.domain] = manifest; + } + + const entityDomains = {}; + const domains = new Set(); + + for (const [entity, source] of Object.entries(entitySources)) { + const domain = source.domain; + if ( + !(domain in manifests) || + manifests[domain].integration_type !== "helper" + ) { + continue; + } + entityDomains[entity] = domain; + domains.add(domain); + } + + if (domains.size) { + this.hass.loadBackendTranslation("title", [...domains]); + } + + this._entitySource = entityDomains; + } + private async _handleAdd() { const domain = extractSearchParam("domain"); navigate("/config/helpers", { replace: true }); @@ -994,7 +1040,8 @@ ${rejected let changed = !this._stateItems || changedProps.has("_entityEntries") || - changedProps.has("_configEntries"); + changedProps.has("_configEntries") || + changedProps.has("_entitySource"); if (!changed && changedProps.has("hass")) { const oldHass = changedProps.get("hass") as HomeAssistant | undefined; @@ -1004,20 +1051,11 @@ ${rejected return; } - const extraEntities = new Set(); - - for (const entityEntry of Object.values(this._entityEntries)) { - if ( - entityEntry.config_entry_id && - entityEntry.config_entry_id in this._configEntries - ) { - extraEntities.add(entityEntry.entity_id); - } - } + const entityIds = Object.keys(this._entitySource); const newStates = Object.values(this.hass!.states).filter( (entity) => - extraEntities.has(entity.entity_id) || + entityIds.includes(entity.entity_id) || isHelperDomain(computeStateDomain(entity)) ); From 7a6491a901ade1ed5069f2d26ac9a48c05cf4385 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Jul 2024 16:59:34 +0200 Subject: [PATCH 80/97] Update babel monorepo to v7.25.0 (#21497) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 +- yarn.lock | 297 ++++++++++++++++++++++++++------------------------- 2 files changed, 155 insertions(+), 146 deletions(-) diff --git a/package.json b/package.json index 3cde49c4ed..aceca0fb1b 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "license": "Apache-2.0", "type": "module", "dependencies": { - "@babel/runtime": "7.24.8", + "@babel/runtime": "7.25.0", "@braintree/sanitize-url": "7.1.0", "@codemirror/autocomplete": "6.17.0", "@codemirror/commands": "6.6.0", @@ -153,7 +153,7 @@ "@babel/helper-define-polyfill-provider": "0.6.2", "@babel/plugin-proposal-decorators": "7.24.7", "@babel/plugin-transform-runtime": "7.24.7", - "@babel/preset-env": "7.24.8", + "@babel/preset-env": "7.25.0", "@babel/preset-typescript": "7.24.7", "@bundle-stats/plugin-webpack-filter": "4.13.4", "@koa/cors": "5.0.0", diff --git a/yarn.lock b/yarn.lock index 1113edf627..2284cd4afb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -48,10 +48,10 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.24.8": - version: 7.24.9 - resolution: "@babel/compat-data@npm:7.24.9" - checksum: 10/fcdbf3dd978305880f06ae20a23f4f68a8eddbe64fc5d2fbc98dfe4cdf15c174cff41e3a8eb9d935f9f3a68d3a23fa432044082ee9768a2ed4b15f769b8f6853 +"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.24.8, @babel/compat-data@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/compat-data@npm:7.25.0" + checksum: 10/35cb500c85084bc09d4385134c64cb0030df750c502e1d78d674e7b059c3e545286e3449163b3812e94098096982f5162f72fb13afd2d2161f4da5076cf2194e languageName: node linkType: hard @@ -78,15 +78,15 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.24.8, @babel/generator@npm:^7.24.9": - version: 7.24.10 - resolution: "@babel/generator@npm:7.24.10" +"@babel/generator@npm:^7.24.9, @babel/generator@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/generator@npm:7.25.0" dependencies: - "@babel/types": "npm:^7.24.9" + "@babel/types": "npm:^7.25.0" "@jridgewell/gen-mapping": "npm:^0.3.5" "@jridgewell/trace-mapping": "npm:^0.3.25" jsesc: "npm:^2.5.1" - checksum: 10/c2491fb7d985527a165546cbcf9e5f6a2518f2a968c7564409c012acce1019056b21e67a152af89b3f4d4a295ca2e75a1a16858152f750efbc4b5087f0cb7253 + checksum: 10/de3ce2ae7aa0c9585260556ca5a81ce2ce6b8269e3260d7bb4e47a74661af715184ca6343e9906c22e4dd3eed5ce39977dfaf6cded4d2d8968fa096c7cf66697 languageName: node linkType: hard @@ -141,16 +141,16 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.24.7" +"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7, @babel/helper-create-regexp-features-plugin@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.0" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.24.7" regexpu-core: "npm:^5.3.1" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/dd7238af30ea6b26a627192422822ae810873fd899150dd8d4348eb107045721a849abcfa2bd04f917493784a93724b8caf6994c31afd16f9347a8a9b9862425 + checksum: 10/591a860396911b4264a82afe43c2bedbc35660570ae1891074d86153785b660bb42b1b075aa996ade70fc4b99e5cbfd7bb52c0c8ba00856a04a0a69dfb4b9ed9 languageName: node linkType: hard @@ -188,16 +188,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-hoist-variables@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-hoist-variables@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10/6cfdcf2289cd12185dcdbdf2435fa8d3447b797ac75851166de9fc8503e2fd0021db6baf8dfbecad3753e582c08e6a3f805c8d00cbed756060a877d705bd8d8d - languageName: node - linkType: hard - -"@babel/helper-member-expression-to-functions@npm:^7.24.7, @babel/helper-member-expression-to-functions@npm:^7.24.8": +"@babel/helper-member-expression-to-functions@npm:^7.24.8": version: 7.24.8 resolution: "@babel/helper-member-expression-to-functions@npm:7.24.8" dependencies: @@ -217,18 +208,17 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.24.9": - version: 7.24.9 - resolution: "@babel/helper-module-transforms@npm:7.24.9" +"@babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.24.9, @babel/helper-module-transforms@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/helper-module-transforms@npm:7.25.0" dependencies: - "@babel/helper-environment-visitor": "npm:^7.24.7" "@babel/helper-module-imports": "npm:^7.24.7" "@babel/helper-simple-access": "npm:^7.24.7" - "@babel/helper-split-export-declaration": "npm:^7.24.7" "@babel/helper-validator-identifier": "npm:^7.24.7" + "@babel/traverse": "npm:^7.25.0" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/eaed9cb93edb11626758f76bfb482f9c3b6583f6756813c5ef849d6d52bbe7c2cb39f61646758e860732d14c2588b60eb4e2af78d7751450649a8d3d7ca41697 + checksum: 10/c1668f96d13815780b7e146faff67061d24f16c16e923894bfa2eb0cd8c051ece49e0e41bdcaba9660a744a6ee496b7de0c92f205961c0dba710b851a805477f languageName: node linkType: hard @@ -248,29 +238,29 @@ __metadata: languageName: node linkType: hard -"@babel/helper-remap-async-to-generator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-remap-async-to-generator@npm:7.24.7" +"@babel/helper-remap-async-to-generator@npm:^7.24.7, @babel/helper-remap-async-to-generator@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/helper-remap-async-to-generator@npm:7.25.0" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-wrap-function": "npm:^7.24.7" + "@babel/helper-wrap-function": "npm:^7.25.0" + "@babel/traverse": "npm:^7.25.0" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/4b7c925e71811902c8aa57904044921027eae10ac9b5b029df491ed4abc1ea18b450a7923fd0feb1248ae37703889e72b6c27f2a0e2d5811103c7655c49ad355 + checksum: 10/6b1ab73a067008c92e2fe5b7a9f39aab32e7f5a8c5eaf0a864436c21791f708ad8619d4a509febdfe934aeb373af4baa7c7d9f41181b385e09f39eaf11ca108e languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-replace-supers@npm:7.24.7" +"@babel/helper-replace-supers@npm:^7.24.7, @babel/helper-replace-supers@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/helper-replace-supers@npm:7.25.0" dependencies: - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-member-expression-to-functions": "npm:^7.24.7" + "@babel/helper-member-expression-to-functions": "npm:^7.24.8" "@babel/helper-optimise-call-expression": "npm:^7.24.7" + "@babel/traverse": "npm:^7.25.0" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/18b7c3709819d008a14953e885748f3e197537f131d8f7ae095fec245506d854ff40b236edb1754afb6467f795aa90ae42a1d961a89557702249bacfc3fdad19 + checksum: 10/97c6c17780cb9692132f7243f5a21fb6420104cb8ff8752dc03cfc9a1912a243994c0290c77ff096637ab6f2a7363b63811cfc68c2bad44e6b39460ac2f6a63f languageName: node linkType: hard @@ -324,15 +314,14 @@ __metadata: languageName: node linkType: hard -"@babel/helper-wrap-function@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-wrap-function@npm:7.24.7" +"@babel/helper-wrap-function@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/helper-wrap-function@npm:7.25.0" dependencies: - "@babel/helper-function-name": "npm:^7.24.7" - "@babel/template": "npm:^7.24.7" - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10/1c248accfbb09a891293840506e3fbfc807b524abf16fc32115a6e73f760387d2dc7935282b48caa281c8033bf93dc80eca7649250524cfb95da8643771bca02 + "@babel/template": "npm:^7.25.0" + "@babel/traverse": "npm:^7.25.0" + "@babel/types": "npm:^7.25.0" + checksum: 10/08724128b9c540c02a59f02f9c1c9940fe5363d85d0f30ec826a4f926afdb26fa4ec33ca2b88b4aa745fe3dbe1f44be2969b8a03af259af7945d8cd3262168d3 languageName: node linkType: hard @@ -358,35 +347,46 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.23.5, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/parser@npm:7.24.8" +"@babel/parser@npm:^7.23.5, @babel/parser@npm:^7.24.8, @babel/parser@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/parser@npm:7.25.0" bin: parser: ./bin/babel-parser.js - checksum: 10/e44b8327da46e8659bc9fb77f66e2dc4364dd66495fb17d046b96a77bf604f0446f1e9a89cf2f011d78fc3f5cdfbae2e9e0714708e1c985988335683b2e781ef + checksum: 10/1860179256b5e04259a1d567dc43470306757f51c515bedd6fc92dc5f8b4c4a6582c0b1f89a90fd4e981430195b727348d50c890b21c7eb84871248884771aaf languageName: node linkType: hard -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.24.7" +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.0" dependencies: - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/traverse": "npm:^7.25.0" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/d5091ca6b58c54316c4d3b6e8120a1bb70cfe2e61cb7ec11f5fdc8ba3ff5124de21e527fabc28f239bf6efc0660046aa416e8fc1e3d920d0e57b78edb507ec3f + checksum: 10/9befa15787d9dd0abba6a84e2c8a40d798241cbe00d186ad453bcf91342fe5dd0f6882a246bb209c9bd5d2f0b914d83850e1dcf99e144a45fe7918538ef40020 languageName: node linkType: hard -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.24.7" +"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.25.0" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.8" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/f0e0e9bdcf5479f8c5b4494353dc64dee37205e5ffd30920e649e75537a8f795cdcf32dfb40a00e908469a5d61cf62806bc359294cb2a6f2e604bf4efe086301 + checksum: 10/5e504bba884a4500e71224d344efb1e70ebbeabd621e07a58f2d3c0d14a71a49c97b4989259a288cdbbfacebfea224397acf1217d26c77aebf9aa35bdd988249 + languageName: node + linkType: hard + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.25.0" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.8" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/f574beb1d4f723bb9b913ce379259a55b50a308364585ccb83e00d933465c26c04cbbc85a06e6d4c829279eb1021b3236133d486b3ff11cfd90ad815c8b478d2 languageName: node linkType: hard @@ -403,15 +403,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.24.7" +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.25.0" dependencies: - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/traverse": "npm:^7.25.0" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/ad63317eb72ca7e160394e9223768b1f826287eaf65297f2794d0203510225f20dd9858bce217af4a050754abf94565841617b45b35a2de355c4e2bba546b39c + checksum: 10/de04a9342e9a0db1673683112c83cdc52173f489f45aeed864ceba72dfba8c8588e565171e64cb2a408a09269e5fb35c6ab4ef50e3e649c4f8c0c787feb5c048 languageName: node linkType: hard @@ -680,17 +680,17 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.24.7" +"@babel/plugin-transform-async-generator-functions@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.0" dependencies: - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-remap-async-to-generator": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-remap-async-to-generator": "npm:^7.25.0" "@babel/plugin-syntax-async-generators": "npm:^7.8.4" + "@babel/traverse": "npm:^7.25.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/cf0a4b5ffc6d7f3f3bf12d4792535e8a46332714211326fd5058a6e45988891ee402b26cb9cc6c7121b2c8283ebd160e431827f885bdfa51d6127f934bd9ba7f + checksum: 10/c65757490005234719a9614dbaf5004ca815612eff251edf95d4149fb74f42ebf91ff079f6b3594b6aa93eec6f4b6d2cda9f2c924f6217bb0422896be58ed0fe languageName: node linkType: hard @@ -718,14 +718,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-block-scoping@npm:7.24.7" +"@babel/plugin-transform-block-scoping@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-transform-block-scoping@npm:7.25.0" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.8" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/9656e7bb0673279e18d9f9408027786f1b20d657e2cc106456e0bd7826bd12d81813299adbef2b2a5837b05740f2295fe8fb62389122d38c9e961b3005270777 + checksum: 10/981e565a8ff1e1f8d539b5ff067328517233142b131329d11e6c60405204e2a4a993828c367f7dc729a9608aabebdada869616563816e5f8f1385e91ac0fa4d6 languageName: node linkType: hard @@ -754,21 +754,19 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-classes@npm:7.24.8" +"@babel/plugin-transform-classes@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-transform-classes@npm:7.25.0" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.24.7" "@babel/helper-compilation-targets": "npm:^7.24.8" - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-function-name": "npm:^7.24.7" "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-replace-supers": "npm:^7.24.7" - "@babel/helper-split-export-declaration": "npm:^7.24.7" + "@babel/helper-replace-supers": "npm:^7.25.0" + "@babel/traverse": "npm:^7.25.0" globals: "npm:^11.1.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/3d586018691423ed1fbcb4589cc29001226c96e5e060932bf99379568c684a4a230cca7871e7c825335336ef0326066ba6e3bf5e6d0209425b0f5ceeda3eaed2 + checksum: 10/59aeb33b91e462a9b01cc9691c6a27e6601c5b76d83e3e4f95fef4086c6561e3557597847fe5243006542723fe4288d8fa6824544b1d94bb3104438f4fd96ebc languageName: node linkType: hard @@ -818,6 +816,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.25.0" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.25.0" + "@babel/helper-plugin-utils": "npm:^7.24.8" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/869c08def8eb80e3619c77e7af962dd82323a8447697298f461624077593c7b7082fc2238989880a0c0ba94bc6442300fd23e33255ac225760bc8bb755268941 + languageName: node + linkType: hard + "@babel/plugin-transform-dynamic-import@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-dynamic-import@npm:7.24.7" @@ -866,16 +876,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-function-name@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-function-name@npm:7.24.7" +"@babel/plugin-transform-function-name@npm:^7.25.0": + version: 7.25.1 + resolution: "@babel/plugin-transform-function-name@npm:7.25.1" dependencies: - "@babel/helper-compilation-targets": "npm:^7.24.7" - "@babel/helper-function-name": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-compilation-targets": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/traverse": "npm:^7.25.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/9d4dcffea45acd255fed4a97e372ada234579f9bae01a4d0ced657091f159edf1635ff2a666508a08f8e59390def09ae6ce8372679faad894aa6f3247728ebe1 + checksum: 10/1b4cd214c8523f7fa024fcda540ffe5503eda0e0be08b7c21405c96a870b5fe8bb1bda9e23a43a31467bf3dfc3a08edca250cf7f55f09dc40759a1ca6c6d6a4a languageName: node linkType: hard @@ -950,17 +960,17 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.24.7" +"@babel/plugin-transform-modules-systemjs@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.25.0" dependencies: - "@babel/helper-hoist-variables": "npm:^7.24.7" - "@babel/helper-module-transforms": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-module-transforms": "npm:^7.25.0" + "@babel/helper-plugin-utils": "npm:^7.24.8" "@babel/helper-validator-identifier": "npm:^7.24.7" + "@babel/traverse": "npm:^7.25.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/14f0ed1a252a2a04e075cd9051b809e33cd45374a2495dc0a428517893b8e951819acc8343c61d348c51ba54e42660bc93990a77aa3460d16a1c21d52d9c2cf1 + checksum: 10/2c38efdbaf6faf730cdcb0c5e42d2d15bb114eecf184db078319de496b5e3ce68d499e531265a0e13e29f0dcaa001f240773db5c4c078eac7f4456d6c8bddd88 languageName: node linkType: hard @@ -1278,18 +1288,19 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:7.24.8, @babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.13.0": - version: 7.24.8 - resolution: "@babel/preset-env@npm:7.24.8" +"@babel/preset-env@npm:7.25.0, @babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.13.0": + version: 7.25.0 + resolution: "@babel/preset-env@npm:7.25.0" dependencies: - "@babel/compat-data": "npm:^7.24.8" + "@babel/compat-data": "npm:^7.25.0" "@babel/helper-compilation-targets": "npm:^7.24.8" "@babel/helper-plugin-utils": "npm:^7.24.8" "@babel/helper-validator-option": "npm:^7.24.8" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.24.7" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.24.7" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.25.0" + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.25.0" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.25.0" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.24.7" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.24.7" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.25.0" "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-async-generators": "npm:^7.8.4" "@babel/plugin-syntax-class-properties": "npm:^7.12.13" @@ -1310,29 +1321,30 @@ __metadata: "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" "@babel/plugin-transform-arrow-functions": "npm:^7.24.7" - "@babel/plugin-transform-async-generator-functions": "npm:^7.24.7" + "@babel/plugin-transform-async-generator-functions": "npm:^7.25.0" "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" "@babel/plugin-transform-block-scoped-functions": "npm:^7.24.7" - "@babel/plugin-transform-block-scoping": "npm:^7.24.7" + "@babel/plugin-transform-block-scoping": "npm:^7.25.0" "@babel/plugin-transform-class-properties": "npm:^7.24.7" "@babel/plugin-transform-class-static-block": "npm:^7.24.7" - "@babel/plugin-transform-classes": "npm:^7.24.8" + "@babel/plugin-transform-classes": "npm:^7.25.0" "@babel/plugin-transform-computed-properties": "npm:^7.24.7" "@babel/plugin-transform-destructuring": "npm:^7.24.8" "@babel/plugin-transform-dotall-regex": "npm:^7.24.7" "@babel/plugin-transform-duplicate-keys": "npm:^7.24.7" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.25.0" "@babel/plugin-transform-dynamic-import": "npm:^7.24.7" "@babel/plugin-transform-exponentiation-operator": "npm:^7.24.7" "@babel/plugin-transform-export-namespace-from": "npm:^7.24.7" "@babel/plugin-transform-for-of": "npm:^7.24.7" - "@babel/plugin-transform-function-name": "npm:^7.24.7" + "@babel/plugin-transform-function-name": "npm:^7.25.0" "@babel/plugin-transform-json-strings": "npm:^7.24.7" "@babel/plugin-transform-literals": "npm:^7.24.7" "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" "@babel/plugin-transform-member-expression-literals": "npm:^7.24.7" "@babel/plugin-transform-modules-amd": "npm:^7.24.7" "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" - "@babel/plugin-transform-modules-systemjs": "npm:^7.24.7" + "@babel/plugin-transform-modules-systemjs": "npm:^7.25.0" "@babel/plugin-transform-modules-umd": "npm:^7.24.7" "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" "@babel/plugin-transform-new-target": "npm:^7.24.7" @@ -1365,7 +1377,7 @@ __metadata: semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/6d32d4554b34230031c0fb0c0e636e7e78e2219a26d5145209d9417cabcd2bd09637b1470187d2613a0b0d2128ed4a6e27a40ea268e44a62fc13b5d242e2cf82 + checksum: 10/df061ad7e18245a631fc161ba8a1e9c6ec51216e09d373ba5d395bc52c84e48ca7e8b0571328705a0e7daae78738a2e57f265b265f42f0b774e8e277eab31995 languageName: node linkType: hard @@ -1404,52 +1416,49 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.24.8, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.4": - version: 7.24.8 - resolution: "@babel/runtime@npm:7.24.8" +"@babel/runtime@npm:7.25.0, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.4": + version: 7.25.0 + resolution: "@babel/runtime@npm:7.25.0" dependencies: regenerator-runtime: "npm:^0.14.0" - checksum: 10/e6f335e472a8a337379effc15815dd0eddf6a7d0c00b50deb4f9e9585819b45431d0ff3c2d3d0fa58c227a9b04dcc4a85e7245fb57493adb2863b5208c769cbd + checksum: 10/6870e9e0e9125075b3aeba49a266f442b10820bfc693019eb6c1785c5a0edbe927e98b8238662cdcdba17842107c040386c3b69f39a0a3b217f9d00ffe685b27 languageName: node linkType: hard -"@babel/template@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/template@npm:7.24.7" +"@babel/template@npm:^7.24.7, @babel/template@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/template@npm:7.25.0" dependencies: "@babel/code-frame": "npm:^7.24.7" - "@babel/parser": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10/5975d404ef51cf379515eb0f80b115981d0b9dff5539e53a47516644abb8c83d7559f5b083eb1d4977b20d8359ebb2f911ccd4f729143f8958fdc465f976d843 + "@babel/parser": "npm:^7.25.0" + "@babel/types": "npm:^7.25.0" + checksum: 10/07ebecf6db8b28244b7397628e09c99e7a317b959b926d90455c7253c88df3677a5a32d1501d9749fe292a263ff51a4b6b5385bcabd5dadd3a48036f4d4949e0 languageName: node linkType: hard -"@babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/traverse@npm:7.24.8" +"@babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1": + version: 7.25.1 + resolution: "@babel/traverse@npm:7.25.1" dependencies: "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.24.8" - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-function-name": "npm:^7.24.7" - "@babel/helper-hoist-variables": "npm:^7.24.7" - "@babel/helper-split-export-declaration": "npm:^7.24.7" - "@babel/parser": "npm:^7.24.8" - "@babel/types": "npm:^7.24.8" + "@babel/generator": "npm:^7.25.0" + "@babel/parser": "npm:^7.25.0" + "@babel/template": "npm:^7.25.0" + "@babel/types": "npm:^7.25.0" debug: "npm:^4.3.1" globals: "npm:^11.1.0" - checksum: 10/47d8ecf8cfff58fe621fc4d8454b82c97c407816d8f9c435caa0c849ea7c357b91119a06f3c69f21a0228b5d06ac0b44f49d1f78cff032d6266317707f1fe615 + checksum: 10/319397a4d32c76c28dba8d446ed3297df6c910e086ee766fb8d2024051bd1d6fbcb74718035c3d9cfd54c7aaa2f1eb48e404b9caac400fe065657071287f927e languageName: node linkType: hard -"@babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.24.9, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": - version: 7.24.9 - resolution: "@babel/types@npm:7.24.9" +"@babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.24.9, @babel/types@npm:^7.25.0, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": + version: 7.25.0 + resolution: "@babel/types@npm:7.25.0" dependencies: "@babel/helper-string-parser": "npm:^7.24.8" "@babel/helper-validator-identifier": "npm:^7.24.7" to-fast-properties: "npm:^2.0.0" - checksum: 10/21873a08a124646824aa230de06af52149ab88206dca59849dcb3003990a6306ec2cdaa4147ec1127c0cfc5f133853cfc18f80d7f6337b6662a3c378ed565f15 + checksum: 10/07bd6079e64a8c4038367188390b7e51403fe8b43ff7cf651154ce3202c733cda6616bab9f866b89a2b740b640b9fbab37c5b5c94cc18ec9f9b348dadfa73dff languageName: node linkType: hard @@ -8961,9 +8970,9 @@ __metadata: "@babel/helper-define-polyfill-provider": "npm:0.6.2" "@babel/plugin-proposal-decorators": "npm:7.24.7" "@babel/plugin-transform-runtime": "npm:7.24.7" - "@babel/preset-env": "npm:7.24.8" + "@babel/preset-env": "npm:7.25.0" "@babel/preset-typescript": "npm:7.24.7" - "@babel/runtime": "npm:7.24.8" + "@babel/runtime": "npm:7.25.0" "@braintree/sanitize-url": "npm:7.1.0" "@bundle-stats/plugin-webpack-filter": "npm:4.13.4" "@codemirror/autocomplete": "npm:6.17.0" From cd4af674a33a062decae35dff1b96d3f26ad93f9 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Wed, 31 Jul 2024 10:44:52 +0200 Subject: [PATCH 81/97] Move some polymer paper-tabs to mwc-tabs (#21390) * dev-tools * entity card editor * edit section dialog * edit view * Set the page from the router back in dev tools * Remove changes to developer tools * Prettier --- .../card-editor/hui-card-element-editor.ts | 48 +++++++------- .../section-editor/hui-dialog-edit-section.ts | 39 ++++++------ .../view-editor/hui-dialog-edit-view.ts | 62 ++++++++----------- 3 files changed, 68 insertions(+), 81 deletions(-) diff --git a/src/panels/lovelace/editor/card-editor/hui-card-element-editor.ts b/src/panels/lovelace/editor/card-editor/hui-card-element-editor.ts index aa4806689a..d7a264aaea 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-element-editor.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-element-editor.ts @@ -9,18 +9,18 @@ import { HuiElementEditor } from "../hui-element-editor"; import "./hui-card-layout-editor"; import "./hui-card-visibility-editor"; -type Tab = "config" | "visibility" | "layout"; +const tabs = ["config", "visibility", "layout"] as const; @customElement("hui-card-element-editor") export class HuiCardElementEditor extends HuiElementEditor { - @state() private _curTab: Tab = "config"; - @property({ type: Boolean, attribute: "show-visibility-tab" }) public showVisibilityTab = false; @property({ type: Boolean, attribute: "show-layout-tab" }) public showLayoutTab = false; + @state() private _currTab: (typeof tabs)[number] = tabs[0]; + protected async getConfigElement(): Promise { const elClass = await getCardElementClass(this.configElementType!); @@ -43,20 +43,13 @@ export class HuiCardElementEditor extends HuiElementEditor { return undefined; } - private _handleTabSelected(ev: CustomEvent): void { - if (!ev.detail.value) { - return; - } - this._curTab = ev.detail.value.id; - } - private _configChanged(ev: CustomEvent): void { ev.stopPropagation(); this.value = ev.detail.value; } protected renderConfigElement(): TemplateResult { - const displayedTabs: Tab[] = ["config"]; + const displayedTabs: string[] = ["config"]; if (this.showVisibilityTab) displayedTabs.push("visibility"); if (this.showLayoutTab) displayedTabs.push("layout"); @@ -64,7 +57,7 @@ export class HuiCardElementEditor extends HuiElementEditor { let content: TemplateResult<1> | typeof nothing = nothing; - switch (this._curTab) { + switch (this._currTab) { case "config": content = html`${super.renderConfigElement()}`; break; @@ -88,33 +81,38 @@ export class HuiCardElementEditor extends HuiElementEditor { `; } return html` - ${displayedTabs.map( - (tab, index) => html` - - ${this.hass.localize( + (tab) => html` + + > + ` )} - + ${content} `; } + private _handleTabChanged(ev: CustomEvent): void { + const newTab = tabs[ev.detail.index]; + if (newTab === this._currTab) { + return; + } + this._currTab = newTab; + } + static get styles(): CSSResultGroup { return [ HuiElementEditor.styles, css` - paper-tabs { - --paper-tabs-selection-bar-color: var(--primary-color); - color: var(--primary-text-color); + mwc-tab-bar { text-transform: uppercase; margin-bottom: 16px; border-bottom: 1px solid var(--divider-color); diff --git a/src/panels/lovelace/editor/section-editor/hui-dialog-edit-section.ts b/src/panels/lovelace/editor/section-editor/hui-dialog-edit-section.ts index de52e96888..91ab6c3f2f 100644 --- a/src/panels/lovelace/editor/section-editor/hui-dialog-edit-section.ts +++ b/src/panels/lovelace/editor/section-editor/hui-dialog-edit-section.ts @@ -1,7 +1,5 @@ import { ActionDetail } from "@material/mwc-list"; import { mdiCheck, mdiClose, mdiDotsVertical } from "@mdi/js"; -import "@polymer/paper-tabs/paper-tab"; -import "@polymer/paper-tabs/paper-tabs"; import { CSSResultGroup, LitElement, @@ -35,6 +33,8 @@ import { import "./hui-section-settings-editor"; import "./hui-section-visibility-editor"; import type { EditSectionDialogParams } from "./show-edit-section-dialog"; +import "@material/mwc-tab-bar/mwc-tab-bar"; +import "@material/mwc-tab/mwc-tab"; const TABS = ["tab-settings", "tab-visibility"] as const; @@ -51,7 +51,7 @@ export class HuiDialogEditSection @state() private _yamlMode = false; - @state() private _curTab: (typeof TABS)[number] = TABS[0]; + @state() private _currTab: (typeof TABS)[number] = TABS[0]; @query("ha-yaml-editor") private _editor?: HaYamlEditor; @@ -77,7 +77,7 @@ export class HuiDialogEditSection this._params = undefined; this._yamlMode = false; this._config = undefined; - this._curTab = TABS[0]; + this._currTab = TABS[0]; fireEvent(this, "dialog-closed", { dialog: this.localName }); } @@ -101,7 +101,7 @@ export class HuiDialogEditSection > `; } else { - switch (this._curTab) { + switch (this._currTab) { case "tab-settings": content = html` ${!this._yamlMode ? html` - ${TABS.map( - (tab, index) => html` - - ${this.hass!.localize( + (tab) => html` + + > + ` )} - + ` : nothing} @@ -221,11 +220,12 @@ export class HuiDialogEditSection this._config = ev.detail.value; } - private _handleTabSelected(ev: CustomEvent): void { - if (!ev.detail.value) { + private _handleTabChanged(ev: CustomEvent): void { + const newTab = TABS[ev.detail.index]; + if (newTab === this._currTab) { return; } - this._curTab = ev.detail.value.id; + this._currTab = newTab; } private async _handleAction(ev: CustomEvent) { @@ -293,8 +293,7 @@ export class HuiDialogEditSection ha-dialog.yaml-mode { --dialog-content-padding: 0; } - paper-tabs { - --paper-tabs-selection-bar-color: var(--primary-color); + mwc-tab-bar { color: var(--primary-text-color); text-transform: uppercase; padding: 0 20px; diff --git a/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts b/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts index 3454ea3a15..a14e5b35a6 100644 --- a/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts +++ b/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts @@ -1,8 +1,6 @@ import "@material/mwc-button"; import { ActionDetail } from "@material/mwc-list"; import { mdiCheck, mdiClose, mdiDotsVertical } from "@mdi/js"; -import "@polymer/paper-tabs/paper-tab"; -import "@polymer/paper-tabs/paper-tabs"; import { CSSResultGroup, LitElement, @@ -45,6 +43,10 @@ import "./hui-view-editor"; import "./hui-view-background-editor"; import "./hui-view-visibility-editor"; import { EditViewDialogParams } from "./show-edit-view-dialog"; +import "@material/mwc-tab-bar/mwc-tab-bar"; +import "@material/mwc-tab/mwc-tab"; + +const TABS = ["tab-settings", "tab-background", "tab-visibility"] as const; @customElement("hui-dialog-edit-view") export class HuiDialogEditView extends LitElement { @@ -56,7 +58,7 @@ export class HuiDialogEditView extends LitElement { @state() private _saving = false; - @state() private _curTab?: string; + @state() private _currTab: (typeof TABS)[number] = TABS[0]; @state() private _dirty = false; @@ -64,8 +66,6 @@ export class HuiDialogEditView extends LitElement { @query("ha-yaml-editor") private _editor?: HaYamlEditor; - private _curTabIndex = 0; - get _type(): string { if (!this._config) { return DEFAULT_VIEW_LAYOUT; @@ -103,11 +103,11 @@ export class HuiDialogEditView extends LitElement { } public closeDialog(): void { - this._curTabIndex = 0; this._params = undefined; this._config = {}; this._yamlMode = false; this._dirty = false; + this._currTab = TABS[0]; fireEvent(this, "dialog-closed", { dialog: this.localName }); } @@ -138,7 +138,7 @@ export class HuiDialogEditView extends LitElement { > `; } else { - switch (this._curTab) { + switch (this._currTab) { case "tab-settings": content = html` `; break; - case "tab-cards": - content = html` Cards `; - break; } } @@ -252,28 +249,21 @@ export class HuiDialogEditView extends LitElement { ` : nothing} ${!this._yamlMode - ? html` - ${this.hass!.localize( - "ui.panel.lovelace.editor.edit_view.tab_settings" - )} - ${this.hass!.localize( - "ui.panel.lovelace.editor.edit_view.tab_background" - )} - ${this.hass!.localize( - "ui.panel.lovelace.editor.edit_view.tab_visibility" - )} - ` + ${TABS.map( + (tab) => html` + + + ` + )} + ` : nothing} ${content} @@ -365,11 +355,12 @@ export class HuiDialogEditView extends LitElement { this._delete(); } - private _handleTabSelected(ev: CustomEvent): void { - if (!ev.detail.value) { + private _handleTabChanged(ev: CustomEvent): void { + const newTab = TABS[ev.detail.index]; + if (newTab === this._currTab) { return; } - this._curTab = ev.detail.value.id; + this._currTab = newTab; } private async _save(): Promise { @@ -494,8 +485,7 @@ export class HuiDialogEditView extends LitElement { font-size: inherit; font-weight: inherit; } - paper-tabs { - --paper-tabs-selection-bar-color: var(--primary-color); + mwc-tab-bar { color: var(--primary-text-color); text-transform: uppercase; padding: 0 20px; From b1d8ec0fe417b8bb422382d9bac3624fc6b0a583 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Wed, 31 Jul 2024 10:46:00 +0200 Subject: [PATCH 82/97] Call a service: Split remaining service_data's into data and target (#21440) * service_data into data and target * Add other possible targets --- .../buttons/ha-call-service-button.ts | 18 ++++++++++++++---- .../zha/zha-cluster-attributes.ts | 2 +- .../zha/zha-cluster-commands.ts | 2 +- .../integration-panels/zha/zha-device-card.ts | 1 - .../elements/hui-service-button-element.ts | 13 ++++++++++++- src/panels/lovelace/elements/types.ts | 4 ++++ 6 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/components/buttons/ha-call-service-button.ts b/src/components/buttons/ha-call-service-button.ts index 3cea93de0d..3fc835e14a 100644 --- a/src/components/buttons/ha-call-service-button.ts +++ b/src/components/buttons/ha-call-service-button.ts @@ -1,5 +1,6 @@ import { LitElement, TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators"; +import { HassServiceTarget } from "home-assistant-js-websocket"; import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box"; import "./ha-progress-button"; import { HomeAssistant } from "../../types"; @@ -17,7 +18,9 @@ class HaCallServiceButton extends LitElement { @property() public service!: string; - @property({ type: Object }) public serviceData = {}; + @property({ type: Object }) public target!: HassServiceTarget; + + @property({ type: Object }) public data = {}; @property() public confirmation?; @@ -39,7 +42,8 @@ class HaCallServiceButton extends LitElement { const eventData = { domain: this.domain, service: this.service, - serviceData: this.serviceData, + data: this.data, + target: this.target, success: false, }; @@ -47,7 +51,12 @@ class HaCallServiceButton extends LitElement { this.shadowRoot!.querySelector("ha-progress-button")!; try { - await this.hass.callService(this.domain, this.service, this.serviceData); + await this.hass.callService( + this.domain, + this.service, + this.data, + this.target + ); this.progress = false; progressElement.actionSuccess(); eventData.success = true; @@ -85,7 +94,8 @@ declare global { "hass-service-called": { domain: string; service: string; - serviceData: object; + target: HassServiceTarget; + data: object; success: boolean; }; } diff --git a/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts b/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts index e3462fd69e..9b628d4699 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts @@ -134,7 +134,7 @@ export class ZHAClusterAttributes extends LitElement { .hass=${this.hass} domain="zha" service="set_zigbee_cluster_attribute" - .serviceData=${this._setAttributeServiceData} + .data=${this._setAttributeServiceData} > ${this.hass!.localize( "ui.panel.config.zha.cluster_attributes.write_zigbee_attribute" diff --git a/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts b/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts index d6932b10ff..4ea3417261 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts @@ -115,7 +115,7 @@ export class ZHAClusterCommands extends LitElement { .hass=${this.hass} domain="zha" service="issue_zigbee_cluster_command" - .serviceData=${this._issueClusterCommandServiceData} + .data=${this._issueClusterCommandServiceData} .disabled=${!this._canIssueCommand} > ${this.hass!.localize( diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-card.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-card.ts index 695a9bd9dc..78a0ae9ef4 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-device-card.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-card.ts @@ -6,7 +6,6 @@ import { fireEvent } from "../../../../../common/dom/fire_event"; import { computeStateName } from "../../../../../common/entity/compute_state_name"; import { stringCompare } from "../../../../../common/string/compare"; import { slugify } from "../../../../../common/string/slugify"; -import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/entity/state-badge"; import "../../../../../components/ha-area-picker"; import "../../../../../components/ha-card"; diff --git a/src/panels/lovelace/elements/hui-service-button-element.ts b/src/panels/lovelace/elements/hui-service-button-element.ts index 91c8abcb75..418758540d 100644 --- a/src/panels/lovelace/elements/hui-service-button-element.ts +++ b/src/panels/lovelace/elements/hui-service-button-element.ts @@ -40,12 +40,23 @@ export class HuiServiceButtonElement return nothing; } + const { entity_id, label_id, floor_id, device_id, area_id } = + this._config.service_data ?? {}; + const updatedTarget = this._config.target ?? { + entity_id, + label_id, + floor_id, + device_id, + area_id, + }; + return html` ${this._config.title} `; diff --git a/src/panels/lovelace/elements/types.ts b/src/panels/lovelace/elements/types.ts index 2cddae5721..35b2c453a8 100644 --- a/src/panels/lovelace/elements/types.ts +++ b/src/panels/lovelace/elements/types.ts @@ -1,3 +1,4 @@ +import { HassServiceTarget } from "home-assistant-js-websocket"; import { ActionConfig } from "../../../data/lovelace/config/action"; import { HomeAssistant } from "../../../types"; import { Condition } from "../common/validate-condition"; @@ -56,7 +57,10 @@ export interface ImageElementConfig extends LovelaceElementConfigBase { export interface ServiceButtonElementConfig extends LovelaceElementConfigBase { title?: string; service?: string; + target?: HassServiceTarget; + // "service_data" is kept for backwards compatibility. Replaced by "data". service_data?: Record; + data?: Record; } export interface StateBadgeElementConfig extends LovelaceElementConfigBase { From dd74a35d3fdea20026b4be4dd75dcae2a9f86029 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Wed, 31 Jul 2024 04:57:52 -0400 Subject: [PATCH 83/97] Use Brotli compression for modern build (#17906) --- build-scripts/gulp/compress.js | 49 +++++++++++++++++++++++++++++----- package.json | 1 + yarn.lock | 29 +++++++++++++++++--- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/build-scripts/gulp/compress.js b/build-scripts/gulp/compress.js index aa1567b721..ce68c1a108 100644 --- a/build-scripts/gulp/compress.js +++ b/build-scripts/gulp/compress.js @@ -1,19 +1,54 @@ // Tasks to compress +import { constants } from "node:zlib"; import gulp from "gulp"; +import brotli from "gulp-brotli"; import zopfli from "gulp-zopfli-green"; import paths from "../paths.cjs"; +const filesGlob = "*.{js,json,css,svg,xml}"; +const brotliOptions = { + skipLarger: true, + params: { + [constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY, + }, +}; const zopfliOptions = { threshold: 150 }; -const compressDist = (rootDir) => +const compressDistBrotli = (rootDir, modernDir) => gulp - .src([ - `${rootDir}/**/*.{js,json,css,svg,xml}`, - `${rootDir}/{authorize,onboarding}.html`, - ]) + .src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], { + base: rootDir, + }) + .pipe(brotli(brotliOptions)) + .pipe(gulp.dest(rootDir)); + +const compressDistZopfli = (rootDir, modernDir) => + gulp + .src( + [ + `${rootDir}/**/${filesGlob}`, + `!${modernDir}/**/${filesGlob}`, + `!${rootDir}/sw-modern.js`, + `${rootDir}/{authorize,onboarding}.html`, + ], + { base: rootDir } + ) .pipe(zopfli(zopfliOptions)) .pipe(gulp.dest(rootDir)); -gulp.task("compress-app", () => compressDist(paths.app_output_root)); -gulp.task("compress-hassio", () => compressDist(paths.hassio_output_root)); +const compressAppBrotli = () => + compressDistBrotli(paths.app_output_root, paths.app_output_latest); +const compressHassioBrotli = () => + compressDistBrotli(paths.hassio_output_root, paths.hassio_output_latest); + +const compressAppZopfli = () => + compressDistZopfli(paths.app_output_root, paths.app_output_latest); +const compressHassioZopfli = () => + compressDistZopfli(paths.hassio_output_root, paths.hassio_output_latest); + +gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli)); +gulp.task( + "compress-hassio", + gulp.parallel(compressHassioBrotli, compressHassioZopfli) +); diff --git a/package.json b/package.json index aceca0fb1b..905bac737b 100644 --- a/package.json +++ b/package.json @@ -208,6 +208,7 @@ "fs-extra": "11.2.0", "glob": "11.0.0", "gulp": "5.0.0", + "gulp-brotli": "3.0.0", "gulp-json-transform": "0.5.0", "gulp-rename": "2.0.0", "gulp-zopfli-green": "6.0.2", diff --git a/yarn.lock b/yarn.lock index 2284cd4afb..b49be286f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8790,6 +8790,16 @@ __metadata: languageName: node linkType: hard +"gulp-brotli@npm:3.0.0": + version: 3.0.0 + resolution: "gulp-brotli@npm:3.0.0" + dependencies: + plugin-error: "npm:^1.0.1" + through2: "npm:^3.0.1" + checksum: 10/0eea1fc60ae7f256184155b61a30a916007d21d37234698d8cdb299f64f71b4d68ca3182528e7da5d71290079c32c0228573578b76f5af7af7230c31537ef9d2 + languageName: node + linkType: hard + "gulp-cli@npm:^3.0.0": version: 3.0.0 resolution: "gulp-cli@npm:3.0.0" @@ -9109,6 +9119,7 @@ __metadata: glob: "npm:11.0.0" google-timezones-json: "npm:1.2.0" gulp: "npm:5.0.0" + gulp-brotli: "npm:3.0.0" gulp-json-transform: "npm:0.5.0" gulp-rename: "npm:2.0.0" gulp-zopfli-green: "npm:6.0.2" @@ -11523,9 +11534,9 @@ __metadata: linkType: hard "object-inspect@npm:^1.13.1": - version: 1.13.2 - resolution: "object-inspect@npm:1.13.2" - checksum: 10/7ef65583b6397570a17c56f0c1841e0920e83900f2c94638927abb7b81ac08a19c7aae135bd9dcca96208cac0c7332b4650fb927f027b0cf92d71df2990d0561 + version: 1.13.1 + resolution: "object-inspect@npm:1.13.1" + checksum: 10/92f4989ed83422d56431bc39656d4c780348eb15d397ce352ade6b7fec08f973b53744bd41b94af021901e61acaf78fcc19e65bf464ecc0df958586a672700f0 languageName: node linkType: hard @@ -12416,7 +12427,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:3, readable-stream@npm:^3.0.6, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:2 || 3, readable-stream@npm:3, readable-stream@npm:^3.0.6, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -13957,6 +13968,16 @@ __metadata: languageName: node linkType: hard +"through2@npm:^3.0.1": + version: 3.0.2 + resolution: "through2@npm:3.0.2" + dependencies: + inherits: "npm:^2.0.4" + readable-stream: "npm:2 || 3" + checksum: 10/98bdffba8e877fd8beb2154adc4eb0d52fad281130f56f6e5d18f85d1e1aa528a7b27317b302eb5443f6636ab045d3c272e6dffc61d984775db284823b90532d + languageName: node + linkType: hard + "through2@npm:^4.0.2": version: 4.0.2 resolution: "through2@npm:4.0.2" From 0aa25dbed9ad593d262bec97a8069d5586705f4f Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Wed, 31 Jul 2024 10:58:39 +0200 Subject: [PATCH 84/97] Don't eat spaces while searching (#21479) --- src/components/data-table/ha-data-table.ts | 2 +- src/components/search-input-outlined.ts | 2 +- src/components/search-input.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index 5dcd3c8983..60faa4dcaf 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -610,7 +610,7 @@ export class HaDataTable extends LitElement { filteredData = await this._memFilterData( this.data, this._sortColumns, - this._filter + this._filter.trim() ); } diff --git a/src/components/search-input-outlined.ts b/src/components/search-input-outlined.ts index 06fef1cd0a..693be1feac 100644 --- a/src/components/search-input-outlined.ts +++ b/src/components/search-input-outlined.ts @@ -79,7 +79,7 @@ class SearchInputOutlined extends LitElement { } private async _filterInputChanged(e) { - this._filterChanged(e.target.value?.trim()); + this._filterChanged(e.target.value); } private async _clearSearch() { diff --git a/src/components/search-input.ts b/src/components/search-input.ts index 2d5180a6e6..2f4298ecaa 100644 --- a/src/components/search-input.ts +++ b/src/components/search-input.ts @@ -67,7 +67,7 @@ class SearchInput extends LitElement { } private async _filterInputChanged(e) { - this._filterChanged(e.target.value?.trim()); + this._filterChanged(e.target.value); } private async _clearSearch() { From dbd84901f822d263c8cec5b8ae20371b36fe7b7f Mon Sep 17 00:00:00 2001 From: illuzn <57167030+illuzn@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:28:45 +0930 Subject: [PATCH 85/97] Implement show_empty functionality for the markdown card (#21379) * Implement show_empty functionality * Implement show_empty functionality * import fireEvent * Order imports correctly * Lint with prettier * Unhide card when appropriate * only run show code if card is hidden * Commit coderabbit code cleanup Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * linting --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/panels/lovelace/cards/hui-markdown-card.ts | 11 ++++++++++- src/panels/lovelace/cards/types.ts | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/panels/lovelace/cards/hui-markdown-card.ts b/src/panels/lovelace/cards/hui-markdown-card.ts index ec78139c22..5ab0227f18 100644 --- a/src/panels/lovelace/cards/hui-markdown-card.ts +++ b/src/panels/lovelace/cards/hui-markdown-card.ts @@ -9,6 +9,7 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; +import { fireEvent } from "../../../common/dom/fire_event"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import "../../../components/ha-card"; import "../../../components/ha-markdown"; @@ -113,7 +114,15 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard { if (changedProps.has("_config")) { this._tryConnect(); } - + const shouldBeHidden = + this._templateResult && + this._config.show_empty === false && + this._templateResult.result.length === 0; + if (shouldBeHidden !== this.hidden) { + this.style.display = shouldBeHidden ? "none" : "block"; + this.toggleAttribute("hidden", shouldBeHidden); + fireEvent(this, "card-visibility-changed", { value: !shouldBeHidden }); + } const oldHass = changedProps.get("hass") as HomeAssistant | undefined; const oldConfig = changedProps.get("_config") as | MarkdownCardConfig diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index f911d48afb..38a94f1ce5 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -316,6 +316,7 @@ export interface MarkdownCardConfig extends LovelaceCardConfig { card_size?: number; entity_ids?: string | string[]; theme?: string; + show_empty?: boolean; } export interface MediaControlCardConfig extends LovelaceCardConfig { From 1f90a0c2e5208377263cd5d5e613477293e8d6c2 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Wed, 31 Jul 2024 11:05:14 +0200 Subject: [PATCH 86/97] Add translation for unknown entity (#21484) * unknown entity * prettier --- src/data/device_automation.ts | 12 ++++++++++-- src/translations/en.json | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/data/device_automation.ts b/src/data/device_automation.ts index eb0ec9ebdc..b984ac9001 100644 --- a/src/data/device_automation.ts +++ b/src/data/device_automation.ts @@ -178,7 +178,11 @@ const getEntityName = ( entityId: string | undefined ): string => { if (!entityId) { - return ""; + return ( + "<" + + hass.localize("ui.panel.config.automation.editor.unknown_entity") + + ">" + ); } if (entityId.includes(".")) { const state = hass.states[entityId]; @@ -191,7 +195,11 @@ const getEntityName = ( if (entityReg) { return computeEntityRegistryName(hass, entityReg) || entityId; } - return ""; + return ( + "<" + + hass.localize("ui.panel.config.automation.editor.unknown_entity") + + ">" + ); }; export const localizeDeviceAutomationAction = ( diff --git a/src/translations/en.json b/src/translations/en.json index 833cdf2eb7..3cc624eac6 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2837,6 +2837,7 @@ "edit_ui": "Edit in visual editor", "copy_to_clipboard": "Copy to clipboard", "search_in": "Search · {group}", + "unknown_entity": "unknown entity", "triggers": { "name": "Triggers", "header": "When", From d0e61ca31a4ce7d3617be52812a02a87d125a406 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 31 Jul 2024 11:12:11 +0200 Subject: [PATCH 87/97] Show OHF logo on loading and about screen (#21504) * Show OHF logo on loading screen * Also add OHF logo to about page --- demo/src/html/index.html.template | 4 +++ public/static/icons/ohf.svg | 38 ++++++++++++++++++++++++ src/html/index.html.template | 4 +++ src/panels/config/info/ha-config-info.ts | 21 +++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 public/static/icons/ohf.svg diff --git a/demo/src/html/index.html.template b/demo/src/html/index.html.template index 4f4353d633..bd1daaf1c5 100644 --- a/demo/src/html/index.html.template +++ b/demo/src/html/index.html.template @@ -69,6 +69,9 @@ #ha-launch-screen .ha-launch-screen-spacer { flex: 1; } + .ohf-logo { + height: 80px; + } @@ -79,6 +82,7 @@
    +
    <%= renderTemplate("../../../src/html/_js_base.html.template") %> diff --git a/public/static/icons/ohf.svg b/public/static/icons/ohf.svg new file mode 100644 index 0000000000..64fd2eccd3 --- /dev/null +++ b/public/static/icons/ohf.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/html/index.html.template b/src/html/index.html.template index ef34659178..95f064ca24 100644 --- a/src/html/index.html.template +++ b/src/html/index.html.template @@ -45,6 +45,9 @@ #ha-launch-screen .ha-launch-screen-spacer { flex: 1; } + .ohf-logo { + height: 80px; + } @@ -55,6 +58,7 @@
    +
    <%= renderTemplate("_js_base.html.template") %> diff --git a/src/panels/config/info/ha-config-info.ts b/src/panels/config/info/ha-config-info.ts index b7289a7eed..c4c49f3197 100644 --- a/src/panels/config/info/ha-config-info.ts +++ b/src/panels/config/info/ha-config-info.ts @@ -162,6 +162,17 @@ class HaConfigInfo extends LitElement { + +
    Proud part of
    +
    + Open Home Foundation + + + ${PAGES.map( @@ -272,6 +283,16 @@ class HaConfigInfo extends LitElement { margin: 24px; } + .ohf { + text-align: center; + padding-bottom: 0; + } + + .ohf img { + width: 100%; + max-width: 250px; + } + .versions { display: flex; flex-direction: column; From e05c66444a796f52480ad42896cc5a335455efc1 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Wed, 31 Jul 2024 02:16:57 -0700 Subject: [PATCH 88/97] Picture Elements Visual editor (#19718) * preliminary edits * more functional prototype * all types implemented * mostly style and localization updates * fix empty conditional case * Move getConfigElement to elements themselves * drop unneeded imports * move struct validation to individual element editors * description for unknown types * Update src/panels/lovelace/editor/config-elements/elements/hui-service-button-element-editor.ts Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com> * Update src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com> * Update src/panels/lovelace/editor/config-elements/elements/hui-service-button-element-editor.ts Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com> * Update src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com> * Update hui-picture-elements-card-row-editor.ts * Fix merge mistake * Update src/panels/lovelace/create-element/create-picture-element.ts remove comment Co-authored-by: Bram Kragten --------- Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com> Co-authored-by: Bram Kragten --- .../ha-selector/ha-selector-boolean.ts | 4 +- .../cards/hui-picture-elements-card.ts | 7 +- .../create-element/create-element-base.ts | 3 +- .../create-element/create-picture-element.ts | 42 +++ .../hui-conditional-element-editor.ts | 167 ++++++++++++ .../elements/hui-icon-element-editor.ts | 88 ++++++ .../elements/hui-image-element-editor.ts | 103 +++++++ .../hui-service-button-element-editor.ts | 79 ++++++ .../hui-state-badge-element-editor.ts | 86 ++++++ .../elements/hui-state-icon-element-editor.ts | 98 +++++++ .../hui-state-label-element-editor.ts | 98 +++++++ .../hui-picture-elements-card-editor.ts | 214 +++++++++++++++ .../lovelace/editor/hui-element-editor.ts | 2 + .../hui-picture-elements-card-row-editor.ts | 255 ++++++++++++++++++ .../lovelace/editor/hui-sub-element-editor.ts | 23 +- .../hui-picture-element-element-editor.ts | 31 +++ src/panels/lovelace/editor/types.ts | 6 +- .../elements/hui-conditional-element.ts | 8 + .../lovelace/elements/hui-icon-element.ts | 6 + .../lovelace/elements/hui-image-element.ts | 6 + .../elements/hui-service-button-element.ts | 8 + .../elements/hui-state-badge-element.ts | 10 +- .../elements/hui-state-icon-element.ts | 10 +- .../elements/hui-state-label-element.ts | 10 +- src/panels/lovelace/elements/types.ts | 13 +- src/panels/lovelace/types.ts | 11 + src/translations/en.json | 28 +- 27 files changed, 1400 insertions(+), 16 deletions(-) create mode 100644 src/panels/lovelace/create-element/create-picture-element.ts create mode 100644 src/panels/lovelace/editor/config-elements/elements/hui-conditional-element-editor.ts create mode 100644 src/panels/lovelace/editor/config-elements/elements/hui-icon-element-editor.ts create mode 100644 src/panels/lovelace/editor/config-elements/elements/hui-image-element-editor.ts create mode 100644 src/panels/lovelace/editor/config-elements/elements/hui-service-button-element-editor.ts create mode 100644 src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts create mode 100644 src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts create mode 100644 src/panels/lovelace/editor/config-elements/elements/hui-state-label-element-editor.ts create mode 100644 src/panels/lovelace/editor/config-elements/hui-picture-elements-card-editor.ts create mode 100644 src/panels/lovelace/editor/hui-picture-elements-card-row-editor.ts create mode 100644 src/panels/lovelace/editor/picture-element-editor/hui-picture-element-element-editor.ts diff --git a/src/components/ha-selector/ha-selector-boolean.ts b/src/components/ha-selector/ha-selector-boolean.ts index a6e0584f07..18ce85b912 100644 --- a/src/components/ha-selector/ha-selector-boolean.ts +++ b/src/components/ha-selector/ha-selector-boolean.ts @@ -12,6 +12,8 @@ export class HaBooleanSelector extends LitElement { @property({ type: Boolean }) public value = false; + @property() public placeholder?: any; + @property() public label?: string; @property() public helper?: string; @@ -22,7 +24,7 @@ export class HaBooleanSelector extends LitElement { return html` diff --git a/src/panels/lovelace/cards/hui-picture-elements-card.ts b/src/panels/lovelace/cards/hui-picture-elements-card.ts index 143a2fde58..9ddf4d65e3 100644 --- a/src/panels/lovelace/cards/hui-picture-elements-card.ts +++ b/src/panels/lovelace/cards/hui-picture-elements-card.ts @@ -14,13 +14,18 @@ import { ImageEntity, computeImageUrl } from "../../../data/image"; import { HomeAssistant } from "../../../types"; import { findEntities } from "../common/find-entities"; import { LovelaceElement, LovelaceElementConfig } from "../elements/types"; -import { LovelaceCard } from "../types"; +import { LovelaceCard, LovelaceCardEditor } from "../types"; import { createStyledHuiElement } from "./picture-elements/create-styled-hui-element"; import { PictureElementsCardConfig } from "./types"; import { PersonEntity } from "../../../data/person"; @customElement("hui-picture-elements-card") class HuiPictureElementsCard extends LitElement implements LovelaceCard { + public static async getConfigElement(): Promise { + await import("../editor/config-elements/hui-picture-elements-card-editor"); + return document.createElement("hui-picture-elements-card-editor"); + } + @property({ attribute: false }) public hass?: HomeAssistant; @state() private _elements?: LovelaceElement[]; diff --git a/src/panels/lovelace/create-element/create-element-base.ts b/src/panels/lovelace/create-element/create-element-base.ts index 65f3ecbcc5..33aaecb0af 100644 --- a/src/panels/lovelace/create-element/create-element-base.ts +++ b/src/panels/lovelace/create-element/create-element-base.ts @@ -23,6 +23,7 @@ import { LovelaceCardConstructor, LovelaceCardFeature, LovelaceCardFeatureConstructor, + LovelaceElementConstructor, LovelaceHeaderFooter, LovelaceHeaderFooterConstructor, LovelaceRowConstructor, @@ -44,7 +45,7 @@ interface CreateElementConfigTypes { element: { config: LovelaceElementConfig; element: LovelaceElement; - constructor: unknown; + constructor: LovelaceElementConstructor; }; row: { config: LovelaceRowConfig; diff --git a/src/panels/lovelace/create-element/create-picture-element.ts b/src/panels/lovelace/create-element/create-picture-element.ts new file mode 100644 index 0000000000..ce848b3bd0 --- /dev/null +++ b/src/panels/lovelace/create-element/create-picture-element.ts @@ -0,0 +1,42 @@ +import "../elements/hui-conditional-element"; +import "../elements/hui-icon-element"; +import "../elements/hui-image-element"; +import "../elements/hui-service-button-element"; +import "../elements/hui-state-badge-element"; +import "../elements/hui-state-icon-element"; +import "../elements/hui-state-label-element"; +import { LovelaceElementConfig } from "../elements/types"; +import { + createLovelaceElement, + getLovelaceElementClass, +} from "./create-element-base"; + +const ALWAYS_LOADED_TYPES = new Set([ + "conditional", + "icon", + "image", + "service-button", + "state-badge", + "state-icon", + "state-label", +]); + +const LAZY_LOAD_TYPES = {}; + +export const createPictureElementElement = (config: LovelaceElementConfig) => + createLovelaceElement( + "element", + config, + ALWAYS_LOADED_TYPES, + LAZY_LOAD_TYPES, + undefined, + undefined + ); + +export const getPictureElementClass = (type: string) => + getLovelaceElementClass( + type, + "element", + ALWAYS_LOADED_TYPES, + LAZY_LOAD_TYPES + ); diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-conditional-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-conditional-element-editor.ts new file mode 100644 index 0000000000..94809079c6 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/elements/hui-conditional-element-editor.ts @@ -0,0 +1,167 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { + any, + array, + assert, + literal, + object, + optional, + string, +} from "superstruct"; +import { HASSDomEvent, fireEvent } from "../../../../../common/dom/fire_event"; +import type { HomeAssistant } from "../../../../../types"; +import "../../../../../components/ha-form/ha-form"; +import { LovelacePictureElementEditor } from "../../../types"; +import { + ConditionalElementConfig, + LovelaceElementConfig, +} from "../../../elements/types"; +import "../../conditions/ha-card-conditions-editor"; +import "../../hui-picture-elements-card-row-editor"; +import { LovelaceCardConfig } from "../../../../../data/lovelace/config/card"; +import { EditSubElementEvent, SubElementEditorConfig } from "../../types"; +import "../../hui-sub-element-editor"; +import { SchemaUnion } from "../../../../../components/ha-form/types"; + +const conditionalElementConfigStruct = object({ + type: literal("conditional"), + conditions: optional(array(any())), + elements: optional(array(any())), + title: optional(string()), +}); + +const SCHEMA = [{ name: "title", selector: { text: {} } }] as const; + +@customElement("hui-conditional-element-editor") +export class HuiConditionalElementEditor + extends LitElement + implements LovelacePictureElementEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: ConditionalElementConfig; + + @state() private _subElementEditorConfig?: SubElementEditorConfig; + + public setConfig(config: ConditionalElementConfig): void { + assert(config, conditionalElementConfigStruct); + this._config = config; + } + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + if (this._subElementEditorConfig) { + return html` + + + `; + } + + return html` + + + + + `; + } + + private _formChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); + } + + private _conditionChanged(ev: CustomEvent) { + ev.stopPropagation(); + if (!this._config) { + return; + } + const conditions = ev.detail.value; + this._config = { ...this._config, conditions }; + fireEvent(this, "config-changed", { config: this._config }); + } + + private _elementsChanged(ev: CustomEvent): void { + ev.stopPropagation(); + + const config = { + ...this._config, + elements: ev.detail.elements as LovelaceElementConfig[], + } as LovelaceCardConfig; + + fireEvent(this, "config-changed", { config }); + } + + private _handleSubElementChanged(ev: CustomEvent): void { + ev.stopPropagation(); + if (!this._config || !this.hass) { + return; + } + + const configValue = this._subElementEditorConfig?.type; + const value = ev.detail.config; + + if (configValue === "element") { + const newConfigElements = this._config.elements!.concat(); + if (!value) { + newConfigElements.splice(this._subElementEditorConfig!.index!, 1); + this._goBack(); + } else { + newConfigElements[this._subElementEditorConfig!.index!] = value; + } + + this._config = { ...this._config!, elements: newConfigElements }; + } + + this._subElementEditorConfig = { + ...this._subElementEditorConfig!, + elementConfig: value, + }; + + fireEvent(this, "config-changed", { config: this._config }); + } + + private _editDetailElement(ev: HASSDomEvent): void { + this._subElementEditorConfig = ev.detail.subElementConfig; + } + + private _goBack(ev?): void { + ev?.stopPropagation(); + this._subElementEditorConfig = undefined; + } + + private _computeLabelCallback = (schema: SchemaUnion) => + this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ) || + this.hass!.localize(`ui.panel.lovelace.editor.elements.${schema.name}`) || + schema.name; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-conditional-element-editor": HuiConditionalElementEditor; + } +} diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-icon-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-icon-element-editor.ts new file mode 100644 index 0000000000..bbf89f7477 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/elements/hui-icon-element-editor.ts @@ -0,0 +1,88 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { any, assert, literal, object, optional, string } from "superstruct"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../../types"; +import "../../../../../components/ha-form/ha-form"; +import { LovelacePictureElementEditor } from "../../../types"; +import { IconElementConfig } from "../../../elements/types"; +import { actionConfigStruct } from "../../structs/action-struct"; + +const iconElementConfigStruct = object({ + type: literal("icon"), + entity: optional(string()), + icon: optional(string()), + style: optional(any()), + title: optional(string()), + tap_action: optional(actionConfigStruct), + hold_action: optional(actionConfigStruct), + double_tap_action: optional(actionConfigStruct), +}); + +const SCHEMA = [ + { name: "icon", selector: { icon: {} } }, + { name: "title", selector: { text: {} } }, + { name: "entity", selector: { entity: {} } }, + { + name: "tap_action", + selector: { + ui_action: {}, + }, + }, + { + name: "hold_action", + selector: { + ui_action: {}, + }, + }, + { name: "style", selector: { object: {} } }, +] as const; + +@customElement("hui-icon-element-editor") +export class HuiIconElementEditor + extends LitElement + implements LovelacePictureElementEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: IconElementConfig; + + public setConfig(config: IconElementConfig): void { + assert(config, iconElementConfigStruct); + this._config = config; + } + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); + } + + private _computeLabelCallback = (schema: SchemaUnion) => + this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ) || + this.hass!.localize(`ui.panel.lovelace.editor.elements.${schema.name}`) || + schema.name; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-icon-element-editor": HuiIconElementEditor; + } +} diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-image-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-image-element-editor.ts new file mode 100644 index 0000000000..06c9ee68d6 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/elements/hui-image-element-editor.ts @@ -0,0 +1,103 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { any, assert, literal, object, optional, string } from "superstruct"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../../types"; +import "../../../../../components/ha-form/ha-form"; +import { LovelacePictureElementEditor } from "../../../types"; +import { ImageElementConfig } from "../../../elements/types"; +import { actionConfigStruct } from "../../structs/action-struct"; + +const imageElementConfigStruct = object({ + type: literal("image"), + entity: optional(string()), + image: optional(string()), + style: optional(any()), + title: optional(string()), + tap_action: optional(actionConfigStruct), + hold_action: optional(actionConfigStruct), + double_tap_action: optional(actionConfigStruct), + camera_image: optional(string()), + camera_view: optional(string()), + state_image: optional(any()), + filter: optional(string()), + state_filter: optional(any()), + aspect_ratio: optional(string()), +}); + +const SCHEMA = [ + { name: "entity", selector: { entity: {} } }, + { name: "title", selector: { text: {} } }, + { + name: "tap_action", + selector: { + ui_action: {}, + }, + }, + { + name: "hold_action", + selector: { + ui_action: {}, + }, + }, + { name: "image", selector: { text: {} } }, + { name: "camera_image", selector: { entity: { domain: "camera" } } }, + { + name: "camera_view", + selector: { select: { options: ["auto", "live"] } }, + }, + { name: "state_image", selector: { object: {} } }, + { name: "filter", selector: { text: {} } }, + { name: "state_filter", selector: { object: {} } }, + { name: "aspect_ratio", selector: { text: {} } }, + { name: "style", selector: { object: {} } }, +] as const; + +@customElement("hui-image-element-editor") +export class HuiImageElementEditor + extends LitElement + implements LovelacePictureElementEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: ImageElementConfig; + + public setConfig(config: ImageElementConfig): void { + assert(config, imageElementConfigStruct); + this._config = config; + } + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); + } + + private _computeLabelCallback = (schema: SchemaUnion) => + this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ) || + this.hass!.localize(`ui.panel.lovelace.editor.elements.${schema.name}`) || + schema.name; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-image-element-editor": HuiImageElementEditor; + } +} diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-service-button-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-service-button-element-editor.ts new file mode 100644 index 0000000000..7f6afd193e --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/elements/hui-service-button-element-editor.ts @@ -0,0 +1,79 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { any, assert, literal, object, optional, string } from "superstruct"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../../types"; +import "../../../../../components/ha-form/ha-form"; +import { LovelacePictureElementEditor } from "../../../types"; +import { ServiceButtonElementConfig } from "../../../elements/types"; +// import { UiAction } from "../../components/hui-action-editor"; + +const serviceButtonElementConfigStruct = object({ + type: literal("service-button"), + style: optional(any()), + title: optional(string()), + service: optional(string()), + service_data: optional(any()), +}); + +const SCHEMA = [ + { name: "title", required: true, selector: { text: {} } }, + /* { + name: "service", + selector: { + ui_action: { actions: ["call-service"] as UiAction[] }, + }, + }, */ + { name: "service", required: true, selector: { text: {} } }, + { name: "service_data", selector: { object: {} } }, + { name: "style", selector: { object: {} } }, +] as const; + +@customElement("hui-service-button-element-editor") +export class HuiServiceButtonElementEditor + extends LitElement + implements LovelacePictureElementEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: ServiceButtonElementConfig; + + public setConfig(config: ServiceButtonElementConfig): void { + assert(config, serviceButtonElementConfigStruct); + this._config = config; + } + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); + } + + private _computeLabelCallback = (schema: SchemaUnion) => + this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ) || + this.hass!.localize(`ui.panel.lovelace.editor.elements.${schema.name}`) || + schema.name; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-service-button-element-editor": HuiServiceButtonElementEditor; + } +} diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts new file mode 100644 index 0000000000..f5cf03d0fb --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts @@ -0,0 +1,86 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { any, assert, literal, object, optional, string } from "superstruct"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../../types"; +import "../../../../../components/ha-form/ha-form"; +import { LovelacePictureElementEditor } from "../../../types"; +import { StateBadgeElementConfig } from "../../../elements/types"; +import { actionConfigStruct } from "../../structs/action-struct"; + +const stateBadgeElementConfigStruct = object({ + type: literal("state-badge"), + entity: optional(string()), + style: optional(any()), + title: optional(string()), + tap_action: optional(actionConfigStruct), + hold_action: optional(actionConfigStruct), + double_tap_action: optional(actionConfigStruct), +}); + +const SCHEMA = [ + { name: "entity", required: true, selector: { entity: {} } }, + { name: "title", selector: { text: {} } }, + { + name: "tap_action", + selector: { + ui_action: {}, + }, + }, + { + name: "hold_action", + selector: { + ui_action: {}, + }, + }, + { name: "style", selector: { object: {} } }, +] as const; + +@customElement("hui-state-badge-element-editor") +export class HuiStateBadgeElementEditor + extends LitElement + implements LovelacePictureElementEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: StateBadgeElementConfig; + + public setConfig(config: StateBadgeElementConfig): void { + assert(config, stateBadgeElementConfigStruct); + this._config = config; + } + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); + } + + private _computeLabelCallback = (schema: SchemaUnion) => + this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ) || + this.hass!.localize(`ui.panel.lovelace.editor.elements.${schema.name}`) || + schema.name; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-state-badge-element-editor": HuiStateBadgeElementEditor; + } +} diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts new file mode 100644 index 0000000000..c5ebb70a19 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts @@ -0,0 +1,98 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { + any, + assert, + boolean, + literal, + object, + optional, + string, +} from "superstruct"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../../types"; +import "../../../../../components/ha-form/ha-form"; +import { LovelacePictureElementEditor } from "../../../types"; +import { StateIconElementConfig } from "../../../elements/types"; +import { actionConfigStruct } from "../../structs/action-struct"; + +const stateIconElementConfigStruct = object({ + type: literal("state-icon"), + entity: optional(string()), + icon: optional(string()), + state_color: optional(boolean()), + style: optional(any()), + title: optional(string()), + tap_action: optional(actionConfigStruct), + hold_action: optional(actionConfigStruct), + double_tap_action: optional(actionConfigStruct), +}); + +const SCHEMA = [ + { name: "entity", required: true, selector: { entity: {} } }, + { name: "icon", selector: { icon: {} } }, + { name: "title", selector: { text: {} } }, + { name: "state_color", default: true, selector: { boolean: {} } }, + { + name: "tap_action", + selector: { + ui_action: {}, + }, + }, + { + name: "hold_action", + selector: { + ui_action: {}, + }, + }, + { name: "style", selector: { object: {} } }, +] as const; + +@customElement("hui-state-icon-element-editor") +export class HuiStateIconElementEditor + extends LitElement + implements LovelacePictureElementEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: StateIconElementConfig; + + public setConfig(config: StateIconElementConfig): void { + assert(config, stateIconElementConfigStruct); + this._config = config; + } + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); + } + + private _computeLabelCallback = (schema: SchemaUnion) => + this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ) || + this.hass!.localize(`ui.panel.lovelace.editor.elements.${schema.name}`) || + schema.name; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-state-icon-element-editor": HuiStateIconElementEditor; + } +} diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-state-label-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-state-label-element-editor.ts new file mode 100644 index 0000000000..af2043801d --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/elements/hui-state-label-element-editor.ts @@ -0,0 +1,98 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { any, assert, literal, object, optional, string } from "superstruct"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../../types"; +import "../../../../../components/ha-form/ha-form"; +import { LovelacePictureElementEditor } from "../../../types"; +import { StateLabelElementConfig } from "../../../elements/types"; +import { actionConfigStruct } from "../../structs/action-struct"; + +const stateLabelElementConfigStruct = object({ + type: literal("state-label"), + entity: optional(string()), + attribute: optional(string()), + prefix: optional(string()), + suffix: optional(string()), + style: optional(any()), + title: optional(string()), + tap_action: optional(actionConfigStruct), + hold_action: optional(actionConfigStruct), + double_tap_action: optional(actionConfigStruct), +}); + +const SCHEMA = [ + { name: "entity", required: true, selector: { entity: {} } }, + { + name: "attribute", + selector: { attribute: {} }, + context: { + filter_entity: "entity", + }, + }, + { name: "prefix", selector: { text: {} } }, + { name: "suffix", selector: { text: {} } }, + { name: "title", selector: { text: {} } }, + { + name: "tap_action", + selector: { + ui_action: {}, + }, + }, + { + name: "hold_action", + selector: { + ui_action: {}, + }, + }, + { name: "style", selector: { object: {} } }, +] as const; + +@customElement("hui-state-label-element-editor") +export class HuiStateLabelElementEditor + extends LitElement + implements LovelacePictureElementEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: StateLabelElementConfig; + + public setConfig(config: StateLabelElementConfig): void { + assert(config, stateLabelElementConfigStruct); + this._config = config; + } + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); + } + + private _computeLabelCallback = (schema: SchemaUnion) => + this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ) || + this.hass!.localize(`ui.panel.lovelace.editor.elements.${schema.name}`) || + schema.name; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-state-label-element-editor": HuiStateLabelElementEditor; + } +} diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-elements-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-elements-card-editor.ts new file mode 100644 index 0000000000..329a19ab8e --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-picture-elements-card-editor.ts @@ -0,0 +1,214 @@ +import { CSSResultGroup, html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { + any, + array, + assert, + assign, + object, + optional, + string, + type, +} from "superstruct"; +import memoizeOne from "memoize-one"; +import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-card"; +import "../../../../components/ha-form/ha-form"; +import "../../../../components/ha-icon"; +import "../../../../components/ha-switch"; +import type { HomeAssistant } from "../../../../types"; +import type { PictureElementsCardConfig } from "../../cards/types"; +import type { LovelaceCardEditor } from "../../types"; +import "../hui-sub-element-editor"; +import { baseLovelaceCardConfig } from "../structs/base-card-struct"; +import { EditSubElementEvent, SubElementEditorConfig } from "../types"; +import { configElementStyle } from "./config-elements-style"; +import "../hui-picture-elements-card-row-editor"; +import { LovelaceElementConfig } from "../../elements/types"; +import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; +import { LocalizeFunc } from "../../../../common/translations/localize"; + +const genericElementConfigStruct = type({ + type: string(), +}); + +const cardConfigStruct = assign( + baseLovelaceCardConfig, + object({ + image: optional(string()), + camera_image: optional(string()), + camera_view: optional(string()), + elements: array(genericElementConfigStruct), + title: optional(string()), + state_filter: optional(any()), + theme: optional(string()), + dark_mode_image: optional(string()), + dark_mode_filter: optional(any()), + }) +); + +@customElement("hui-picture-elements-card-editor") +export class HuiPictureElementsCardEditor + extends LitElement + implements LovelaceCardEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: PictureElementsCardConfig; + + @state() private _subElementEditorConfig?: SubElementEditorConfig; + + public setConfig(config: PictureElementsCardConfig): void { + assert(config, cardConfigStruct); + this._config = config; + } + + private _schema = memoizeOne( + (localize: LocalizeFunc) => + [ + { + name: "", + type: "expandable", + title: localize( + "ui.panel.lovelace.editor.card.picture-elements.card_options" + ), + schema: [ + { name: "title", selector: { text: {} } }, + { name: "image", selector: { text: {} } }, + { name: "dark_mode_image", selector: { text: {} } }, + { + name: "camera_image", + selector: { entity: { domain: "camera" } }, + }, + { + name: "camera_view", + selector: { select: { options: ["auto", "live"] } }, + }, + { name: "theme", selector: { theme: {} } }, + { name: "state_filter", selector: { object: {} } }, + { name: "dark_mode_filter", selector: { object: {} } }, + ], + }, + ] as const + ); + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + if (this._subElementEditorConfig) { + return html` + + + `; + } + + return html` + + + `; + } + + private _formChanged(ev: CustomEvent): void { + ev.stopPropagation(); + if (!this._config || !this.hass) { + return; + } + + fireEvent(this, "config-changed", { config: ev.detail.value }); + } + + private _elementsChanged(ev: CustomEvent): void { + ev.stopPropagation(); + + const config = { + ...this._config, + elements: ev.detail.elements as LovelaceElementConfig[], + } as LovelaceCardConfig; + + fireEvent(this, "config-changed", { config }); + } + + private _handleSubElementChanged(ev: CustomEvent): void { + ev.stopPropagation(); + if (!this._config || !this.hass) { + return; + } + + const configValue = this._subElementEditorConfig?.type; + const value = ev.detail.config; + + if (configValue === "element") { + const newConfigElements = this._config.elements!.concat(); + if (!value) { + newConfigElements.splice(this._subElementEditorConfig!.index!, 1); + this._goBack(); + } else { + newConfigElements[this._subElementEditorConfig!.index!] = value; + } + + this._config = { ...this._config!, elements: newConfigElements }; + } + + this._subElementEditorConfig = { + ...this._subElementEditorConfig!, + elementConfig: value, + }; + + fireEvent(this, "config-changed", { config: this._config }); + } + + private _editDetailElement(ev: HASSDomEvent): void { + this._subElementEditorConfig = ev.detail.subElementConfig; + } + + private _goBack(): void { + this._subElementEditorConfig = undefined; + } + + private _computeLabelCallback = (schema) => { + switch (schema.name) { + case "dark_mode_image": + case "state_filter": + case "dark_mode_filter": + return ( + this.hass!.localize( + `ui.panel.lovelace.editor.card.picture-elements.${schema.name}` + ) || schema.name + ); + default: + return ( + this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ) || schema.name + ); + } + }; + + static get styles(): CSSResultGroup { + return [configElementStyle]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-picture-elements-card-editor": HuiPictureElementsCardEditor; + } +} diff --git a/src/panels/lovelace/editor/hui-element-editor.ts b/src/panels/lovelace/editor/hui-element-editor.ts index 58c0337334..b3be3fb3bd 100644 --- a/src/panels/lovelace/editor/hui-element-editor.ts +++ b/src/panels/lovelace/editor/hui-element-editor.ts @@ -23,6 +23,7 @@ import type { HomeAssistant } from "../../../types"; import { LovelaceCardFeatureConfig } from "../card-features/types"; import type { LovelaceRowConfig } from "../entity-rows/types"; import { LovelaceHeaderFooterConfig } from "../header-footer/types"; +import { LovelaceElementConfig } from "../elements/types"; import type { LovelaceConfigForm, LovelaceGenericElementEditor, @@ -41,6 +42,7 @@ export interface ConfigChangedEvent { | LovelaceHeaderFooterConfig | LovelaceCardFeatureConfig | LovelaceStrategyConfig + | LovelaceElementConfig | LovelaceBadgeConfig; error?: string; guiModeAvailable?: boolean; diff --git a/src/panels/lovelace/editor/hui-picture-elements-card-row-editor.ts b/src/panels/lovelace/editor/hui-picture-elements-card-row-editor.ts new file mode 100644 index 0000000000..dfff3aea16 --- /dev/null +++ b/src/panels/lovelace/editor/hui-picture-elements-card-row-editor.ts @@ -0,0 +1,255 @@ +import { mdiClose, mdiPencil, mdiContentDuplicate } from "@mdi/js"; +import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; +import { customElement, property, query } from "lit/decorators"; +import { fireEvent } from "../../../common/dom/fire_event"; +import { stopPropagation } from "../../../common/dom/stop_propagation"; +import "../../../components/ha-icon-button"; +import "../../../components/ha-svg-icon"; +import { HomeAssistant } from "../../../types"; +import "../../../components/ha-select"; +import type { HaSelect } from "../../../components/ha-select"; +import { + ConditionalElementConfig, + IconElementConfig, + ImageElementConfig, + LovelaceElementConfig, + ServiceButtonElementConfig, + StateBadgeElementConfig, + StateIconElementConfig, + StateLabelElementConfig, +} from "../elements/types"; + +declare global { + interface HASSDomEvents { + "elements-changed": { + elements: any[]; + }; + } +} + +const elementTypes: string[] = [ + "state-badge", + "state-icon", + "state-label", + "service-button", + "icon", + "image", + "conditional", +]; + +@customElement("hui-picture-elements-card-row-editor") +export class HuiPictureElementsCardRowEditor extends LitElement { + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public elements?: LovelaceElementConfig[]; + + @query("ha-select") private _select!: HaSelect; + + protected render() { + if (!this.elements || !this.hass) { + return nothing; + } + + return html` +

    + ${this.hass.localize( + "ui.panel.lovelace.editor.card.picture-elements.elements" + )} +

    +
    + ${this.elements.map( + (element, index) => html` +
    + ${element.type + ? html` +
    +
    + + ${this.hass?.localize( + `ui.panel.lovelace.editor.card.picture-elements.element_types.${element.type}` + ) || element.type} + + ${this._getSecondaryDescription(element)} +
    +
    + ` + : nothing} + + + +
    + ` + )} + + ${elementTypes.map( + (element) => html` + ${this.hass?.localize( + `ui.panel.lovelace.editor.card.picture-elements.element_types.${element}` + )} + ` + )} + +
    + `; + } + + private _getSecondaryDescription(element: LovelaceElementConfig): string { + switch (element.type) { + case "icon": + return element.title ?? (element as IconElementConfig).icon ?? ""; + case "state-badge": + case "state-icon": + case "state-label": + return ( + element.title ?? + ( + element as + | StateBadgeElementConfig + | StateIconElementConfig + | StateLabelElementConfig + ).entity ?? + "" + ); + case "service-button": + return ( + element.title ?? (element as ServiceButtonElementConfig).service ?? "" + ); + case "image": + return ( + element.title ?? + (element as ImageElementConfig).image ?? + (element as ImageElementConfig).camera_image ?? + "" + ); + case "conditional": + return ( + element.title ?? + `${((element as ConditionalElementConfig).elements || []).length.toString()} ${this.hass?.localize("ui.panel.lovelace.editor.card.picture-elements.elements")}` + ); + } + return "Unknown type"; + } + + private async _addElement(ev): Promise { + const value = ev.target!.value; + if (value === "") { + return; + } + const newElements = this.elements!.concat({ + type: value! as string, + ...(value !== "conditional" + ? { + style: { + top: "50%", + left: "50%", + }, + } + : {}), + } as LovelaceElementConfig); + fireEvent(this, "elements-changed", { elements: newElements }); + this._select.select(-1); + } + + private _removeRow(ev: CustomEvent): void { + const index = (ev.currentTarget as any).index; + const newElements = this.elements!.concat(); + + newElements.splice(index, 1); + + fireEvent(this, "elements-changed", { elements: newElements }); + } + + private _editRow(ev: CustomEvent): void { + const index = (ev.currentTarget as any).index; + fireEvent(this, "edit-detail-element", { + subElementConfig: { + index, + type: "element", + elementConfig: this.elements![index], + }, + }); + } + + private _duplicateRow(ev: CustomEvent): void { + const index = (ev.currentTarget as any).index; + const newElements = [...this.elements!, this.elements![index]]; + + fireEvent(this, "elements-changed", { elements: newElements }); + } + + static get styles(): CSSResultGroup { + return css` + .element { + display: flex; + align-items: center; + } + + .element-row { + height: 60px; + font-size: 16px; + display: flex; + align-items: center; + justify-content: space-between; + flex-grow: 1; + } + + .element-row div { + display: flex; + flex-direction: column; + } + + .remove-icon, + .edit-icon, + .duplicate-icon { + --mdc-icon-button-size: 36px; + color: var(--secondary-text-color); + } + + .secondary { + font-size: 12px; + color: var(--secondary-text-color); + } + + ha-select { + width: 100%; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-picture-elements-card-row-editor": HuiPictureElementsCardRowEditor; + } +} diff --git a/src/panels/lovelace/editor/hui-sub-element-editor.ts b/src/panels/lovelace/editor/hui-sub-element-editor.ts index 62848734dd..df6ed4308d 100644 --- a/src/panels/lovelace/editor/hui-sub-element-editor.ts +++ b/src/panels/lovelace/editor/hui-sub-element-editor.ts @@ -1,6 +1,13 @@ import "@material/mwc-button"; import { mdiCodeBraces, mdiListBoxOutline } from "@mdi/js"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { + css, + CSSResultGroup, + html, + LitElement, + nothing, + TemplateResult, +} from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; import "../../../components/ha-icon-button"; @@ -13,6 +20,7 @@ import "./header-footer-editor/hui-header-footer-element-editor"; import type { HuiElementEditor } from "./hui-element-editor"; import "./feature-editor/hui-card-feature-element-editor"; import type { GUIModeChangedEvent, SubElementEditorConfig } from "./types"; +import "./picture-element-editor/hui-picture-element-element-editor"; declare global { interface HASSDomEvents { @@ -95,7 +103,18 @@ export class HuiSubElementEditor extends LitElement { @GUImode-changed=${this._handleGUIModeChanged} > ` - : ""} + : this.config.type === "element" + ? html` + + ` + : nothing} `; } diff --git a/src/panels/lovelace/editor/picture-element-editor/hui-picture-element-element-editor.ts b/src/panels/lovelace/editor/picture-element-editor/hui-picture-element-element-editor.ts new file mode 100644 index 0000000000..79aa163318 --- /dev/null +++ b/src/panels/lovelace/editor/picture-element-editor/hui-picture-element-element-editor.ts @@ -0,0 +1,31 @@ +import { customElement } from "lit/decorators"; +import { LovelaceElementConfig } from "../../elements/types"; +import type { LovelacePictureElementEditor } from "../../types"; +import { HuiElementEditor } from "../hui-element-editor"; +import { getPictureElementClass } from "../../create-element/create-picture-element"; + +@customElement("hui-picture-element-element-editor") +export class HuiPictureElementElementEditor extends HuiElementEditor { + protected get configElementType(): string | undefined { + return this.value?.type; + } + + protected async getConfigElement(): Promise< + LovelacePictureElementEditor | undefined + > { + const elClass = await getPictureElementClass(this.configElementType!); + + // Check if a GUI editor exists + if (elClass && elClass.getConfigElement) { + return elClass.getConfigElement(); + } + + return undefined; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-picture-element-element-editor": HuiPictureElementElementEditor; + } +} diff --git a/src/panels/lovelace/editor/types.ts b/src/panels/lovelace/editor/types.ts index aaa8f4225f..0aacf3c406 100644 --- a/src/panels/lovelace/editor/types.ts +++ b/src/panels/lovelace/editor/types.ts @@ -7,6 +7,7 @@ import { import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types"; import { LovelaceHeaderFooterConfig } from "../header-footer/types"; import { LovelaceCardFeatureConfig } from "../card-features/types"; +import { LovelaceElementConfig } from "../elements/types"; import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; export interface YamlChangedEvent extends Event { @@ -93,8 +94,9 @@ export interface SubElementEditorConfig { elementConfig?: | LovelaceRowConfig | LovelaceHeaderFooterConfig - | LovelaceCardFeatureConfig; - type: "header" | "footer" | "row" | "feature"; + | LovelaceCardFeatureConfig + | LovelaceElementConfig; + type: "header" | "footer" | "row" | "feature" | "element"; } export interface EditSubElementEvent { diff --git a/src/panels/lovelace/elements/hui-conditional-element.ts b/src/panels/lovelace/elements/hui-conditional-element.ts index 3f515e5870..838bb70874 100644 --- a/src/panels/lovelace/elements/hui-conditional-element.ts +++ b/src/panels/lovelace/elements/hui-conditional-element.ts @@ -4,6 +4,7 @@ import { checkConditionsMet, validateConditionalConfig, } from "../common/validate-condition"; +import { LovelacePictureElementEditor } from "../types"; import { ConditionalElementConfig, LovelaceElement, @@ -11,6 +12,13 @@ import { } from "./types"; class HuiConditionalElement extends HTMLElement implements LovelaceElement { + public static async getConfigElement(): Promise { + await import( + "../editor/config-elements/elements/hui-conditional-element-editor" + ); + return document.createElement("hui-conditional-element-editor"); + } + public _hass?: HomeAssistant; private _config?: ConditionalElementConfig; diff --git a/src/panels/lovelace/elements/hui-icon-element.ts b/src/panels/lovelace/elements/hui-icon-element.ts index 0dcd51fdd7..4daadc94ed 100644 --- a/src/panels/lovelace/elements/hui-icon-element.ts +++ b/src/panels/lovelace/elements/hui-icon-element.ts @@ -8,10 +8,16 @@ import { actionHandler } from "../common/directives/action-handler-directive"; import { handleAction } from "../common/handle-action"; import { hasAction } from "../common/has-action"; import { IconElementConfig, LovelaceElement } from "./types"; +import { LovelacePictureElementEditor } from "../types"; import { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; @customElement("hui-icon-element") export class HuiIconElement extends LitElement implements LovelaceElement { + public static async getConfigElement(): Promise { + await import("../editor/config-elements/elements/hui-icon-element-editor"); + return document.createElement("hui-icon-element-editor"); + } + public hass?: HomeAssistant; @state() private _config?: IconElementConfig; diff --git a/src/panels/lovelace/elements/hui-image-element.ts b/src/panels/lovelace/elements/hui-image-element.ts index 4fc9159c72..104461661a 100644 --- a/src/panels/lovelace/elements/hui-image-element.ts +++ b/src/panels/lovelace/elements/hui-image-element.ts @@ -10,9 +10,15 @@ import { handleAction } from "../common/handle-action"; import { hasAction } from "../common/has-action"; import "../components/hui-image"; import { ImageElementConfig, LovelaceElement } from "./types"; +import { LovelacePictureElementEditor } from "../types"; @customElement("hui-image-element") export class HuiImageElement extends LitElement implements LovelaceElement { + public static async getConfigElement(): Promise { + await import("../editor/config-elements/elements/hui-image-element-editor"); + return document.createElement("hui-image-element-editor"); + } + @property({ attribute: false }) public hass?: HomeAssistant; @state() private _config?: ImageElementConfig; diff --git a/src/panels/lovelace/elements/hui-service-button-element.ts b/src/panels/lovelace/elements/hui-service-button-element.ts index 418758540d..6c7d3508e1 100644 --- a/src/panels/lovelace/elements/hui-service-button-element.ts +++ b/src/panels/lovelace/elements/hui-service-button-element.ts @@ -3,12 +3,20 @@ import { customElement, state } from "lit/decorators"; import "../../../components/buttons/ha-call-service-button"; import { HomeAssistant } from "../../../types"; import { LovelaceElement, ServiceButtonElementConfig } from "./types"; +import { LovelacePictureElementEditor } from "../types"; @customElement("hui-service-button-element") export class HuiServiceButtonElement extends LitElement implements LovelaceElement { + public static async getConfigElement(): Promise { + await import( + "../editor/config-elements/elements/hui-service-button-element-editor" + ); + return document.createElement("hui-service-button-element-editor"); + } + public hass?: HomeAssistant; @state() private _config?: ServiceButtonElementConfig; diff --git a/src/panels/lovelace/elements/hui-state-badge-element.ts b/src/panels/lovelace/elements/hui-state-badge-element.ts index 446bae8cdc..78d69a31ca 100644 --- a/src/panels/lovelace/elements/hui-state-badge-element.ts +++ b/src/panels/lovelace/elements/hui-state-badge-element.ts @@ -12,12 +12,20 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import "../components/hui-warning-element"; import { LovelaceElement, StateBadgeElementConfig } from "./types"; +import { LovelacePictureElementEditor } from "../types"; @customElement("hui-state-badge-element") export class HuiStateBadgeElement extends LitElement implements LovelaceElement { + public static async getConfigElement(): Promise { + await import( + "../editor/config-elements/elements/hui-state-badge-element-editor" + ); + return document.createElement("hui-state-badge-element-editor"); + } + @property({ attribute: false }) public hass?: HomeAssistant; @state() private _config?: StateBadgeElementConfig; @@ -44,7 +52,7 @@ export class HuiStateBadgeElement if (!stateObj) { return html` `; } diff --git a/src/panels/lovelace/elements/hui-state-icon-element.ts b/src/panels/lovelace/elements/hui-state-icon-element.ts index 1f21100479..84e0a7add6 100644 --- a/src/panels/lovelace/elements/hui-state-icon-element.ts +++ b/src/panels/lovelace/elements/hui-state-icon-element.ts @@ -18,10 +18,18 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import "../components/hui-warning-element"; import { LovelaceElement, StateIconElementConfig } from "./types"; +import { LovelacePictureElementEditor } from "../types"; import { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; @customElement("hui-state-icon-element") export class HuiStateIconElement extends LitElement implements LovelaceElement { + public static async getConfigElement(): Promise { + await import( + "../editor/config-elements/elements/hui-state-icon-element-editor" + ); + return document.createElement("hui-state-icon-element-editor"); + } + @property({ attribute: false }) public hass?: HomeAssistant; @state() private _config?: StateIconElementConfig; @@ -52,7 +60,7 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement { if (!stateObj) { return html` `; } diff --git a/src/panels/lovelace/elements/hui-state-label-element.ts b/src/panels/lovelace/elements/hui-state-label-element.ts index a8176fdb4a..ef699bf6a6 100644 --- a/src/panels/lovelace/elements/hui-state-label-element.ts +++ b/src/panels/lovelace/elements/hui-state-label-element.ts @@ -18,9 +18,17 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import "../components/hui-warning-element"; import { LovelaceElement, StateLabelElementConfig } from "./types"; +import { LovelacePictureElementEditor } from "../types"; @customElement("hui-state-label-element") class HuiStateLabelElement extends LitElement implements LovelaceElement { + public static async getConfigElement(): Promise { + await import( + "../editor/config-elements/elements/hui-state-label-element-editor" + ); + return document.createElement("hui-state-label-element-editor"); + } + @property({ attribute: false }) public hass?: HomeAssistant; @state() private _config?: StateLabelElementConfig; @@ -47,7 +55,7 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement { if (!stateObj) { return html` `; } diff --git a/src/panels/lovelace/elements/types.ts b/src/panels/lovelace/elements/types.ts index 35b2c453a8..1b433b5bd9 100644 --- a/src/panels/lovelace/elements/types.ts +++ b/src/panels/lovelace/elements/types.ts @@ -26,6 +26,7 @@ export interface LovelaceElement extends HTMLElement { export interface ConditionalElementConfig extends LovelaceElementConfigBase { conditions: Condition[]; elements: LovelaceElementConfigBase[]; + title?: string; } export interface IconElementConfig extends LovelaceElementConfigBase { @@ -34,7 +35,8 @@ export interface IconElementConfig extends LovelaceElementConfigBase { tap_action?: ActionConfig; hold_action?: ActionConfig; double_tap_action?: ActionConfig; - icon: string; + icon?: string; + title?: string; } export interface ImageElementConfig extends LovelaceElementConfigBase { @@ -52,6 +54,7 @@ export interface ImageElementConfig extends LovelaceElementConfigBase { filter?: string; state_filter?: string; aspect_ratio?: string; + title?: string; } export interface ServiceButtonElementConfig extends LovelaceElementConfigBase { @@ -64,7 +67,7 @@ export interface ServiceButtonElementConfig extends LovelaceElementConfigBase { } export interface StateBadgeElementConfig extends LovelaceElementConfigBase { - entity: string; + entity?: string; title?: string; tap_action?: ActionConfig; hold_action?: ActionConfig; @@ -72,20 +75,22 @@ export interface StateBadgeElementConfig extends LovelaceElementConfigBase { } export interface StateIconElementConfig extends LovelaceElementConfigBase { - entity: string; + entity?: string; tap_action?: ActionConfig; hold_action?: ActionConfig; double_tap_action?: ActionConfig; icon?: string; state_color?: boolean; + title?: string; } export interface StateLabelElementConfig extends LovelaceElementConfigBase { - entity: string; + entity?: string; attribute?: string; prefix?: string; suffix?: string; tap_action?: ActionConfig; hold_action?: ActionConfig; double_tap_action?: ActionConfig; + title?: string; } diff --git a/src/panels/lovelace/types.ts b/src/panels/lovelace/types.ts index ab452b891f..d99f52112f 100644 --- a/src/panels/lovelace/types.ts +++ b/src/panels/lovelace/types.ts @@ -12,6 +12,7 @@ import { Constructor, HomeAssistant } from "../../types"; import { LovelaceRow, LovelaceRowConfig } from "./entity-rows/types"; import { LovelaceHeaderFooterConfig } from "./header-footer/types"; import { LovelaceCardFeatureConfig } from "./card-features/types"; +import { LovelaceElement, LovelaceElementConfig } from "./elements/types"; declare global { // eslint-disable-next-line @@ -105,6 +106,11 @@ export interface LovelaceRowConstructor extends Constructor { getConfigElement?: () => LovelaceRowEditor; } +export interface LovelaceElementConstructor + extends Constructor { + getConfigElement?: () => LovelacePictureElementEditor; +} + export interface LovelaceHeaderFooter extends HTMLElement { hass?: HomeAssistant; type: "header" | "footer"; @@ -129,6 +135,11 @@ export interface LovelaceRowEditor extends LovelaceGenericElementEditor { setConfig(config: LovelaceRowConfig): void; } +export interface LovelacePictureElementEditor + extends LovelaceGenericElementEditor { + setConfig(config: LovelaceElementConfig): void; +} + export interface LovelaceGenericElementEditor extends HTMLElement { hass?: HomeAssistant; lovelace?: LovelaceConfig; diff --git a/src/translations/en.json b/src/translations/en.json index 3cc624eac6..6d5434ab77 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -5984,7 +5984,22 @@ }, "picture-elements": { "name": "Picture elements", - "description": "The Picture elements card is one of the most versatile types of cards. The cards allow you to position icons or text and even services! On an image based on coordinates." + "description": "The Picture elements card is one of the most versatile types of cards. The cards allow you to position icons or text and even services! On an image based on coordinates.", + "card_options": "Card Options", + "elements": "Elements", + "new_element": "Add new element", + "dark_mode_image": "Dark mode image path", + "state_filter": "State filter", + "dark_mode_filter": "Dark mode state filter", + "element_types": { + "state-badge": "State badge", + "state-icon": "State icon", + "state-label": "State label", + "service-button": "Service call button", + "icon": "Icon", + "image": "Image", + "conditional": "Conditional" + } }, "picture-entity": { "name": "Picture entity", @@ -6046,6 +6061,14 @@ "twice_daily": "Twice daily" } }, + "elements": { + "style": "Style", + "prefix": "Prefix", + "suffix": "Suffix", + "state_image": "State image", + "filter": "Filter", + "state_filter": "[%key:ui::panel::lovelace::editor::card::picture-elements::state_filter%]" + }, "badge": { "entity": { "name": "Entity", @@ -6290,7 +6313,8 @@ "header": "Header editor", "footer": "Footer editor", "row": "Entity row editor", - "feature": "Feature editor" + "feature": "Feature editor", + "element": "Element editor" } } }, From e8dd835eeb79567120554c57f63b3487f47b735e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 31 Jul 2024 11:39:32 +0200 Subject: [PATCH 89/97] rename actions in UI editors to interactions (#21505) --- .../editor/config-elements/hui-entity-badge-editor.ts | 2 +- .../lovelace/editor/config-elements/hui-tile-card-editor.ts | 2 +- src/translations/en.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/panels/lovelace/editor/config-elements/hui-entity-badge-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entity-badge-editor.ts index 73f5b65c07..5d8ebad738 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entity-badge-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entity-badge-editor.ts @@ -131,7 +131,7 @@ export class HuiEntityBadgeEditor { name: "", type: "expandable", - title: localize(`ui.panel.lovelace.editor.badge.entity.actions`), + title: localize(`ui.panel.lovelace.editor.badge.entity.interactions`), iconPath: mdiGestureTap, schema: [ { diff --git a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts index 172d2c95ed..587bb85fe4 100644 --- a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts @@ -138,7 +138,7 @@ export class HuiTileCardEditor { name: "", type: "expandable", - title: localize(`ui.panel.lovelace.editor.card.tile.actions`), + title: localize(`ui.panel.lovelace.editor.card.tile.interactions`), iconPath: mdiGestureTap, schema: [ { diff --git a/src/translations/en.json b/src/translations/en.json index 6d5434ab77..5733deffc1 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6035,7 +6035,7 @@ "description": "The tile card gives you a quick overview of your entity. The card allow you to toggle the entity, show the more info dialog or custom actions.", "color": "Color", "icon_tap_action": "Icon tap behavior", - "actions": "Actions", + "interactions": "Interactions", "appearance": "Appearance", "show_entity_picture": "Show entity picture", "vertical": "Vertical", @@ -6074,7 +6074,7 @@ "name": "Entity", "description": "The Entity badge gives you a quick overview of your entity.", "color": "Color", - "actions": "Actions", + "interactions": "Interactions", "appearance": "Appearance", "show_entity_picture": "Show entity picture", "state_content": "State content", From 0a095c6f21e3924f0dcc73749403a3a08309c8c0 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Wed, 31 Jul 2024 03:34:57 -0700 Subject: [PATCH 90/97] Add enable_millisecond to duration selector (#21498) Add enable_milliseconds to duration selector --- src/components/ha-selector/ha-selector-duration.ts | 1 + src/components/ha-selector/ha-selector-selector.ts | 4 ++++ src/data/selector.ts | 1 + 3 files changed, 6 insertions(+) diff --git a/src/components/ha-selector/ha-selector-duration.ts b/src/components/ha-selector/ha-selector-duration.ts index cbf60b4f66..66f2e8d50d 100644 --- a/src/components/ha-selector/ha-selector-duration.ts +++ b/src/components/ha-selector/ha-selector-duration.ts @@ -30,6 +30,7 @@ export class HaTimeDuration extends LitElement { .disabled=${this.disabled} .required=${this.required} ?enableDay=${this.selector.duration?.enable_day} + ?enableMillisecond=${this.selector.duration?.enable_millisecond} > `; } diff --git a/src/components/ha-selector/ha-selector-selector.ts b/src/components/ha-selector/ha-selector-selector.ts index 831fe05f73..2602fc675d 100644 --- a/src/components/ha-selector/ha-selector-selector.ts +++ b/src/components/ha-selector/ha-selector-selector.ts @@ -57,6 +57,10 @@ const SELECTOR_SCHEMAS = { name: "enable_day", selector: { boolean: {} }, }, + { + name: "enable_millisecond", + selector: { boolean: {} }, + }, ] as const, entity: [ { diff --git a/src/data/selector.ts b/src/data/selector.ts index 7e4ccc8f38..c4b385b136 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -203,6 +203,7 @@ export interface LegacyDeviceSelector { export interface DurationSelector { duration: { enable_day?: boolean; + enable_millisecond?: boolean; } | null; } From a88a7c5236c50cca485b27e7335d9605249e666d Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 31 Jul 2024 14:34:43 +0200 Subject: [PATCH 91/97] Change yaml config of UI service call actions (#21508) --- gallery/src/pages/lovelace/entities-card.ts | 4 +-- src/data/lovelace/config/action.ts | 8 +++-- .../lovelace/cards/hui-entities-card.ts | 4 ++- src/panels/lovelace/cards/types.ts | 2 ++ src/panels/lovelace/common/handle-action.ts | 14 ++++++--- .../lovelace/components/hui-action-editor.ts | 30 ++++++++++++------- .../hui-entities-card-editor.ts | 6 ++-- .../hui-row-element-editor.ts | 4 +++ .../lovelace/editor/structs/action-struct.ts | 9 ++++-- src/panels/lovelace/entity-rows/types.ts | 8 +++-- .../special-rows/hui-call-service-row.ts | 11 +++---- src/translations/en.json | 3 +- 12 files changed, 71 insertions(+), 32 deletions(-) diff --git a/gallery/src/pages/lovelace/entities-card.ts b/gallery/src/pages/lovelace/entities-card.ts index a7131557e6..45842de2a6 100644 --- a/gallery/src/pages/lovelace/entities-card.ts +++ b/gallery/src/pages/lovelace/entities-card.ts @@ -287,11 +287,11 @@ const CONFIGS = [ config: ` - type: entities entities: - - type: call-service + - type: perform-action icon: mdi:power name: Bed light action_name: Toggle light - service: light.toggle + action: light.toggle data: entity_id: light.bed_light - type: section diff --git a/src/data/lovelace/config/action.ts b/src/data/lovelace/config/action.ts index 94f781a209..9ed065bc8d 100644 --- a/src/data/lovelace/config/action.ts +++ b/src/data/lovelace/config/action.ts @@ -5,10 +5,12 @@ export interface ToggleActionConfig extends BaseActionConfig { } export interface CallServiceActionConfig extends BaseActionConfig { - action: "call-service"; - service: string; + action: "call-service" | "perform-action"; + /** @deprecated "service" is kept for backwards compatibility. Replaced by "perform_action". */ + service?: string; + perform_action: string; target?: HassServiceTarget; - // "service_data" is kept for backwards compatibility. Replaced by "data". + /** @deprecated "service_data" is kept for backwards compatibility. Replaced by "data". */ service_data?: Record; data?: Record; } diff --git a/src/panels/lovelace/cards/hui-entities-card.ts b/src/panels/lovelace/cards/hui-entities-card.ts index 4fb12c4661..152c8b1ca4 100644 --- a/src/panels/lovelace/cards/hui-entities-card.ts +++ b/src/panels/lovelace/cards/hui-entities-card.ts @@ -302,7 +302,9 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard { state_color: this._config.state_color, ...(entityConf as EntityConfig), } as EntityConfig) - : entityConf + : entityConf.type === "perform-action" + ? { ...entityConf, type: "call-service" } + : entityConf ); if (this._hass) { element.hass = this._hass; diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 38a94f1ce5..8bf731f56d 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -66,6 +66,8 @@ export interface EntitiesCardEntityConfig extends EntityConfig { | "tilt-position" | "brightness"; action_name?: string; + action?: string; + /** @deprecated use "action" instead */ service?: string; // "service_data" is kept for backwards compatibility. Replaced by "data". service_data?: Record; diff --git a/src/panels/lovelace/common/handle-action.ts b/src/panels/lovelace/common/handle-action.ts index f4fdd90653..609e0c162b 100644 --- a/src/panels/lovelace/common/handle-action.ts +++ b/src/panels/lovelace/common/handle-action.ts @@ -56,8 +56,12 @@ export const handleAction = async ( forwardHaptic("warning"); let serviceName; - if (actionConfig.action === "call-service") { - const [domain, service] = actionConfig.service.split(".", 2); + if ( + actionConfig.action === "call-service" || + actionConfig.action === "perform-action" + ) { + const [domain, service] = (actionConfig.perform_action || + actionConfig.service)!.split(".", 2); const serviceDomains = hass.services; if (domain in serviceDomains && service in serviceDomains[domain]) { await hass.loadBackendTranslation("title"); @@ -145,15 +149,17 @@ export const handleAction = async ( } break; } + case "perform-action": case "call-service": { - if (!actionConfig.service) { + if (!actionConfig.perform_action && !actionConfig.service) { showToast(node, { message: hass.localize("ui.panel.lovelace.cards.actions.no_action"), }); forwardHaptic("failure"); return; } - const [domain, service] = actionConfig.service.split(".", 2); + const [domain, service] = (actionConfig.perform_action || + actionConfig.service)!.split(".", 2); hass.callService( domain, service, diff --git a/src/panels/lovelace/components/hui-action-editor.ts b/src/panels/lovelace/components/hui-action-editor.ts index 92fa589987..86ef25eb05 100644 --- a/src/panels/lovelace/components/hui-action-editor.ts +++ b/src/panels/lovelace/components/hui-action-editor.ts @@ -33,7 +33,7 @@ const DEFAULT_ACTIONS: UiAction[] = [ "toggle", "navigate", "url", - "call-service", + "perform-action", "assist", "none", ]; @@ -98,7 +98,7 @@ export class HuiActionEditor extends LitElement { get _service(): string { const config = this.config as CallServiceActionConfig; - return config?.service || ""; + return config?.perform_action || config?.service || ""; } private _serviceAction = memoizeOne( @@ -127,13 +127,19 @@ export class HuiActionEditor extends LitElement { const actions = this.actions ?? DEFAULT_ACTIONS; + let action = this.config?.action || "default"; + + if (action === "call-service") { + action = "perform-action"; + } + return html` <%= renderTemplate("_js_base.html.template") %> From 3a83cb36a1d99ffdd8ac917d1e4ab4c32493dd4e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 31 Jul 2024 15:27:19 +0200 Subject: [PATCH 96/97] Bumped version to 20240731.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 28e4ce2b29..9f8d6c3fe9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20240719.0" +version = "20240731.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From 574fc998893941785380c2eac9d5aa34a9f8a042 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 31 Jul 2024 16:19:32 +0200 Subject: [PATCH 97/97] Rename service button to action button (#21511) --- src/components/ha-service-control.ts | 2 +- src/panels/lovelace/create-element/create-hui-element.ts | 8 ++++++-- .../elements/hui-service-button-element-editor.ts | 4 ++-- .../editor/hui-picture-elements-card-row-editor.ts | 8 ++++++-- .../hui-picture-element-element-editor.ts | 4 +++- src/translations/en.json | 1 + 6 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/components/ha-service-control.ts b/src/components/ha-service-control.ts index 20cf293874..744549b254 100644 --- a/src/components/ha-service-control.ts +++ b/src/components/ha-service-control.ts @@ -717,7 +717,7 @@ export class HaServiceControl extends LitElement { } const value = { - service: newService, + action: newService, target, }; diff --git a/src/panels/lovelace/create-element/create-hui-element.ts b/src/panels/lovelace/create-element/create-hui-element.ts index aacf1614cd..59c411829f 100644 --- a/src/panels/lovelace/create-element/create-hui-element.ts +++ b/src/panels/lovelace/create-element/create-hui-element.ts @@ -18,5 +18,9 @@ const ALWAYS_LOADED_TYPES = new Set([ "state-label", ]); -export const createHuiElement = (config: LovelaceElementConfig) => - createLovelaceElement("element", config, ALWAYS_LOADED_TYPES); +export const createHuiElement = (config: LovelaceElementConfig) => { + if (config.type === "action-button") { + config = { ...config, type: "service-button" }; + } + return createLovelaceElement("element", config, ALWAYS_LOADED_TYPES); +}; diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-service-button-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-service-button-element-editor.ts index 30110619fe..ae3039d327 100644 --- a/src/panels/lovelace/editor/config-elements/elements/hui-service-button-element-editor.ts +++ b/src/panels/lovelace/editor/config-elements/elements/hui-service-button-element-editor.ts @@ -1,7 +1,7 @@ import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; -import { any, assert, literal, object, optional, string } from "superstruct"; +import { any, assert, enums, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../../components/ha-form/types"; @@ -12,7 +12,7 @@ import { ServiceButtonElementConfig } from "../../../elements/types"; import { LovelacePictureElementEditor } from "../../../types"; const serviceButtonElementConfigStruct = object({ - type: literal("service-button"), + type: enums(["service-button", "action-button"]), style: optional(any()), title: optional(string()), action: optional(string()), diff --git a/src/panels/lovelace/editor/hui-picture-elements-card-row-editor.ts b/src/panels/lovelace/editor/hui-picture-elements-card-row-editor.ts index dfff3aea16..8c3d20e772 100644 --- a/src/panels/lovelace/editor/hui-picture-elements-card-row-editor.ts +++ b/src/panels/lovelace/editor/hui-picture-elements-card-row-editor.ts @@ -31,7 +31,7 @@ const elementTypes: string[] = [ "state-badge", "state-icon", "state-label", - "service-button", + "action-button", "icon", "image", "conditional", @@ -141,9 +141,13 @@ export class HuiPictureElementsCardRowEditor extends LitElement { ).entity ?? "" ); + case "action-button": case "service-button": return ( - element.title ?? (element as ServiceButtonElementConfig).service ?? "" + element.title ?? + (element as ServiceButtonElementConfig).action ?? + (element as ServiceButtonElementConfig).service ?? + "" ); case "image": return ( diff --git a/src/panels/lovelace/editor/picture-element-editor/hui-picture-element-element-editor.ts b/src/panels/lovelace/editor/picture-element-editor/hui-picture-element-element-editor.ts index 79aa163318..946061d366 100644 --- a/src/panels/lovelace/editor/picture-element-editor/hui-picture-element-element-editor.ts +++ b/src/panels/lovelace/editor/picture-element-editor/hui-picture-element-element-editor.ts @@ -7,7 +7,9 @@ import { getPictureElementClass } from "../../create-element/create-picture-elem @customElement("hui-picture-element-element-editor") export class HuiPictureElementElementEditor extends HuiElementEditor { protected get configElementType(): string | undefined { - return this.value?.type; + return this.value?.type === "action-button" + ? "service-button" + : this.value?.type; } protected async getConfigElement(): Promise< diff --git a/src/translations/en.json b/src/translations/en.json index 804fad507d..8510a8045f 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -5997,6 +5997,7 @@ "state-icon": "State icon", "state-label": "State label", "service-button": "Perform action button", + "action-button": "Perform action button", "icon": "Icon", "image": "Image", "conditional": "Conditional"