From 62a0a64554262ea8d8e0011da82c6592ec7043f7 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 27 Jul 2022 00:08:06 -0400 Subject: [PATCH 001/134] Update zwave_js WS API commands --- src/data/zwave_js.ts | 8 ++++---- .../integration-elements/zwave_js/device-actions.ts | 10 +++++----- .../zwave_js/dialog-zwave_js-update-firmware-node.ts | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index a99244541d..d6898c5f98 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -665,21 +665,21 @@ export const subscribeZwaveNodeStatistics = ( } ); -export const fetchZwaveNodeIsFirmwareUpdateInProgress = ( +export const fetchZwaveIsNodeFirmwareUpdateInProgress = ( hass: HomeAssistant, device_id: string ): Promise => hass.callWS({ - type: "zwave_js/get_firmware_update_progress", + type: "zwave_js/is_node_firmware_update_in_progress", device_id, }); -export const fetchZwaveIsAnyFirmwareUpdateInProgress = ( +export const fetchZwaveIsAnyOTAFirmwareUpdateInProgress = ( hass: HomeAssistant, entry_id: string ): Promise => hass.callWS({ - type: "zwave_js/get_any_firmware_update_progress", + type: "zwave_js/is_any_ota_firmware_update_in_progress", entry_id, }); diff --git a/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts b/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts index 2a5de14122..5201a4ae89 100644 --- a/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts +++ b/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts @@ -1,8 +1,8 @@ import { getConfigEntries } from "../../../../../../data/config_entries"; import { DeviceRegistryEntry } from "../../../../../../data/device_registry"; import { - fetchZwaveIsAnyFirmwareUpdateInProgress, - fetchZwaveNodeIsFirmwareUpdateInProgress, + fetchZwaveIsAnyOTAFirmwareUpdateInProgress, + fetchZwaveIsNodeFirmwareUpdateInProgress, fetchZwaveNodeStatus, } from "../../../../../../data/zwave_js"; import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box"; @@ -88,8 +88,8 @@ export const getZwaveDeviceActions = async ( const [isAnyFirmwareUpdateInProgress, isNodeFirmwareUpdateInProgress] = await Promise.all([ - fetchZwaveIsAnyFirmwareUpdateInProgress(hass, entryId), - fetchZwaveNodeIsFirmwareUpdateInProgress(hass, device.id), + fetchZwaveIsAnyOTAFirmwareUpdateInProgress(hass, entryId), + fetchZwaveIsNodeFirmwareUpdateInProgress(hass, device.id), ]); if (!isAnyFirmwareUpdateInProgress || isNodeFirmwareUpdateInProgress) { @@ -100,7 +100,7 @@ export const getZwaveDeviceActions = async ( action: async () => { if ( isNodeFirmwareUpdateInProgress || - (await fetchZwaveNodeIsFirmwareUpdateInProgress(hass, device.id)) || + (await fetchZwaveIsNodeFirmwareUpdateInProgress(hass, device.id)) || (await showConfirmationDialog(el, { text: hass.localize( "ui.panel.config.zwave_js.update_firmware.warning" diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts index 00e4bfb437..768d2d5a82 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts @@ -15,7 +15,7 @@ import { } from "../../../../../data/device_registry"; import { abortZwaveNodeFirmwareUpdate, - fetchZwaveNodeIsFirmwareUpdateInProgress, + fetchZwaveIsNodeFirmwareUpdateInProgress, fetchZwaveNodeStatus, FirmwareUpdateStatus, NodeStatus, @@ -272,7 +272,7 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement { private async _fetchData(): Promise { [this._nodeStatus, this._updateInProgress] = await Promise.all([ fetchZwaveNodeStatus(this.hass, this.device!.id), - fetchZwaveNodeIsFirmwareUpdateInProgress(this.hass, this.device!.id), + fetchZwaveIsNodeFirmwareUpdateInProgress(this.hass, this.device!.id), ]); if (this._updateInProgress) { this._subscribeNodeFirmwareUpdate(); From 150bc00c3120f9044fa40c5b02204cf4aaecac1b Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Fri, 5 Aug 2022 09:37:54 -0400 Subject: [PATCH 002/134] Fix localize key types related to form schemas (Group 1) (#13258) --- src/components/ha-form/ha-form.ts | 6 +- src/components/ha-form/types.ts | 17 ++- .../types/ha-automation-action-repeat.ts | 2 +- .../ha-automation-action-wait_template.ts | 10 +- .../ha-automation-condition-editor.ts | 6 +- .../ha-automation-condition-numeric_state.ts | 35 +++--- .../types/ha-automation-condition-state.ts | 27 +++-- .../types/ha-automation-condition-sun.ts | 93 ++++++++-------- .../types/ha-automation-condition-time.ts | 62 ++++++----- .../types/ha-automation-condition-zone.ts | 2 +- .../trigger/ha-automation-trigger-row.ts | 4 +- .../types/ha-automation-trigger-calendar.ts | 102 +++++++++--------- .../ha-automation-trigger-geo_location.ts | 55 +++++----- .../ha-automation-trigger-homeassistant.ts | 53 ++++----- .../types/ha-automation-trigger-mqtt.ts | 13 ++- .../ha-automation-trigger-numeric_state.ts | 39 ++++--- .../types/ha-automation-trigger-state.ts | 39 ++++--- .../types/ha-automation-trigger-sun.ts | 54 +++++----- .../types/ha-automation-trigger-time.ts | 12 ++- .../ha-automation-trigger-time_pattern.ts | 13 ++- .../hui-alarm-panel-card-editor.ts | 100 ++++++++--------- .../config-elements/hui-area-card-editor.ts | 8 +- .../config-elements/hui-button-card-editor.ts | 93 ++++++++-------- .../hui-calendar-card-editor.ts | 53 ++++----- .../config-elements/hui-entity-card-editor.ts | 61 ++++++----- 25 files changed, 516 insertions(+), 443 deletions(-) diff --git a/src/components/ha-form/ha-form.ts b/src/components/ha-form/ha-form.ts index 579a7ab7d1..a42f90d773 100644 --- a/src/components/ha-form/ha-form.ts +++ b/src/components/ha-form/ha-form.ts @@ -35,7 +35,7 @@ export class HaForm extends LitElement implements HaFormElement { @property({ attribute: false }) public data!: HaFormDataContainer; - @property({ attribute: false }) public schema!: HaFormSchema[]; + @property({ attribute: false }) public schema!: readonly HaFormSchema[]; @property() public error?: Record; @@ -44,7 +44,7 @@ export class HaForm extends LitElement implements HaFormElement { @property() public computeError?: (schema: HaFormSchema, error) => string; @property() public computeLabel?: ( - schema: HaFormSchema, + schema: any, data?: HaFormDataContainer ) => string; @@ -168,7 +168,7 @@ export class HaForm extends LitElement implements HaFormElement { return this.computeHelper ? this.computeHelper(schema) : ""; } - private _computeError(error, schema: HaFormSchema | HaFormSchema[]) { + private _computeError(error, schema: HaFormSchema | readonly HaFormSchema[]) { return this.computeError ? this.computeError(error, schema) : error; } diff --git a/src/components/ha-form/types.ts b/src/components/ha-form/types.ts index d8e380cca4..6f0f2fd8b4 100644 --- a/src/components/ha-form/types.ts +++ b/src/components/ha-form/types.ts @@ -31,7 +31,7 @@ export interface HaFormGridSchema extends HaFormBaseSchema { type: "grid"; name: ""; column_min_width?: string; - schema: HaFormSchema[]; + schema: readonly HaFormSchema[]; } export interface HaFormSelector extends HaFormBaseSchema { @@ -53,12 +53,15 @@ export interface HaFormIntegerSchema extends HaFormBaseSchema { export interface HaFormSelectSchema extends HaFormBaseSchema { type: "select"; - options: Array<[string, string]>; + options: ReadonlyArray; } export interface HaFormMultiSelectSchema extends HaFormBaseSchema { type: "multi_select"; - options: Record | string[] | Array<[string, string]>; + options: + | Record + | readonly string[] + | ReadonlyArray; } export interface HaFormFloatSchema extends HaFormBaseSchema { @@ -78,6 +81,12 @@ export interface HaFormTimeSchema extends HaFormBaseSchema { type: "positive_time_period_dict"; } +// Type utility to unionize a schema array by flattening any grid schemas +export type SchemaUnion< + SchemaArray extends readonly HaFormSchema[], + Schema = SchemaArray[number] +> = Schema extends HaFormGridSchema ? SchemaUnion : Schema; + export interface HaFormDataContainer { [key: string]: HaFormData; } @@ -100,7 +109,7 @@ export type HaFormMultiSelectData = string[]; export type HaFormTimeData = HaDurationData; export interface HaFormElement extends LitElement { - schema: HaFormSchema | HaFormSchema[]; + schema: HaFormSchema | readonly HaFormSchema[]; data?: HaFormDataContainer | HaFormData; label?: string; } diff --git a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts index ed8b536bba..6aaf19f8f1 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts @@ -15,7 +15,7 @@ import "../ha-automation-action"; import "../../../../../components/ha-textfield"; import type { ActionElement } from "../ha-automation-action-row"; -const OPTIONS = ["count", "while", "until"]; +const OPTIONS = ["count", "while", "until"] as const; const getType = (action) => OPTIONS.find((option) => option in action); diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts index 06dbab6c26..7d42efc309 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts @@ -1,12 +1,12 @@ import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; -import type { HaFormSchema } from "../../../../../components/ha-form/types"; import type { WaitAction } from "../../../../../data/script"; import type { HomeAssistant } from "../../../../../types"; import type { ActionElement } from "../ha-automation-action-row"; import "../../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "wait_template", selector: { @@ -24,7 +24,7 @@ const SCHEMA: HaFormSchema[] = [ name: "continue_on_timeout", selector: { boolean: {} }, }, -]; +] as const; @customElement("ha-automation-action-wait_template") export class HaWaitAction extends LitElement implements ActionElement { @@ -47,7 +47,9 @@ export class HaWaitAction extends LitElement implements ActionElement { `; } - private _computeLabelCallback = (schema: HaFormSchema): string => + private _computeLabelCallback = ( + schema: SchemaUnion + ): string => this.hass.localize( `ui.panel.config.automation.editor.actions.type.wait_template.${ schema.name === "continue_on_timeout" ? "continue_timeout" : schema.name diff --git a/src/panels/config/automation/condition/ha-automation-condition-editor.ts b/src/panels/config/automation/condition/ha-automation-condition-editor.ts index bea73fd2c5..b694b3536c 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -36,15 +36,15 @@ const OPTIONS = [ "time", "trigger", "zone", -]; +] as const; @customElement("ha-automation-condition-editor") export default class HaAutomationConditionEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() condition!: Condition; + @property({ attribute: false }) condition!: Condition; - @property() public yamlMode = false; + @property({ type: Boolean }) public yamlMode = false; private _processedCondition = memoizeOne((condition) => expandConditionWithShorthand(condition) diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts index 56944306d0..47b1c9919b 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts @@ -3,9 +3,9 @@ import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import type { HaFormSchema } from "../../../../../components/ha-form/types"; import { NumericStateCondition } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; @customElement("ha-automation-condition-numeric_state") export default class HaNumericStateCondition extends LitElement { @@ -19,19 +19,22 @@ export default class HaNumericStateCondition extends LitElement { }; } - private _schema = memoizeOne((entityId): HaFormSchema[] => [ - { name: "entity_id", required: true, selector: { entity: {} } }, - { - name: "attribute", - selector: { attribute: { entity_id: entityId } }, - }, - { name: "above", selector: { text: {} } }, - { name: "below", selector: { text: {} } }, - { - name: "value_template", - selector: { text: { multiline: true } }, - }, - ]); + private _schema = memoizeOne( + (entityId) => + [ + { name: "entity_id", required: true, selector: { entity: {} } }, + { + name: "attribute", + selector: { attribute: { entity_id: entityId } }, + }, + { name: "above", selector: { text: {} } }, + { name: "below", selector: { text: {} } }, + { + name: "value_template", + selector: { text: { multiline: true } }, + }, + ] as const + ); public render() { const schema = this._schema(this.condition.entity_id); @@ -53,7 +56,9 @@ export default class HaNumericStateCondition extends LitElement { fireEvent(this, "value-changed", { value: newTrigger }); } - private _computeLabelCallback = (schema: HaFormSchema): string => { + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => { switch (schema.name) { case "entity_id": return this.hass.localize("ui.components.entity.entity-picker.entity"); diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts index 61e064ff4c..ad0f86c146 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts @@ -4,12 +4,12 @@ import memoizeOne from "memoize-one"; import { assert, literal, object, optional, string, union } from "superstruct"; import { createDurationData } from "../../../../../common/datetime/create_duration_data"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import type { HaFormSchema } from "../../../../../components/ha-form/types"; import type { StateCondition } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; import { forDictStruct } from "../../structs"; import type { ConditionElement } from "../ha-automation-condition-row"; import "../../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; const stateConditionStruct = object({ condition: literal("state"), @@ -29,15 +29,18 @@ export class HaStateCondition extends LitElement implements ConditionElement { return { entity_id: "", state: "" }; } - private _schema = memoizeOne((entityId) => [ - { name: "entity_id", required: true, selector: { entity: {} } }, - { - name: "attribute", - selector: { attribute: { entity_id: entityId } }, - }, - { name: "state", selector: { text: {} } }, - { name: "for", selector: { duration: {} } }, - ]); + private _schema = memoizeOne( + (entityId) => + [ + { name: "entity_id", required: true, selector: { entity: {} } }, + { + name: "attribute", + selector: { attribute: { entity_id: entityId } }, + }, + { name: "state", selector: { text: {} } }, + { name: "for", selector: { duration: {} } }, + ] as const + ); public shouldUpdate(changedProperties: PropertyValues) { if (changedProperties.has("condition")) { @@ -80,7 +83,9 @@ export class HaStateCondition extends LitElement implements ConditionElement { fireEvent(this, "value-changed", { value: newTrigger }); } - private _computeLabelCallback = (schema: HaFormSchema): string => { + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => { switch (schema.name) { case "entity_id": return this.hass.localize("ui.components.entity.entity-picker.entity"); diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-sun.ts b/src/panels/config/automation/condition/types/ha-automation-condition-sun.ts index e8966b6969..269f9e072b 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-sun.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-sun.ts @@ -6,8 +6,8 @@ import type { SunCondition } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; import type { ConditionElement } from "../ha-automation-condition-row"; import type { LocalizeFunc } from "../../../../../common/translations/localize"; -import type { HaFormSchema } from "../../../../../components/ha-form/types"; import "../../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; @customElement("ha-automation-condition-sun") export class HaSunCondition extends LitElement implements ConditionElement { @@ -19,48 +19,51 @@ export class HaSunCondition extends LitElement implements ConditionElement { return {}; } - private _schema = memoizeOne((localize: LocalizeFunc) => [ - { - name: "before", - type: "select", - required: true, - options: [ - [ - "sunrise", - localize( - "ui.panel.config.automation.editor.conditions.type.sun.sunrise" - ), - ], - [ - "sunset", - localize( - "ui.panel.config.automation.editor.conditions.type.sun.sunset" - ), - ], - ], - }, - { name: "before_offset", selector: { text: {} } }, - { - name: "after", - type: "select", - required: true, - options: [ - [ - "sunrise", - localize( - "ui.panel.config.automation.editor.conditions.type.sun.sunrise" - ), - ], - [ - "sunset", - localize( - "ui.panel.config.automation.editor.conditions.type.sun.sunset" - ), - ], - ], - }, - { name: "after_offset", selector: { text: {} } }, - ]); + private _schema = memoizeOne( + (localize: LocalizeFunc) => + [ + { + name: "before", + type: "select", + required: true, + options: [ + [ + "sunrise", + localize( + "ui.panel.config.automation.editor.conditions.type.sun.sunrise" + ), + ], + [ + "sunset", + localize( + "ui.panel.config.automation.editor.conditions.type.sun.sunset" + ), + ], + ], + }, + { name: "before_offset", selector: { text: {} } }, + { + name: "after", + type: "select", + required: true, + options: [ + [ + "sunrise", + localize( + "ui.panel.config.automation.editor.conditions.type.sun.sunrise" + ), + ], + [ + "sunset", + localize( + "ui.panel.config.automation.editor.conditions.type.sun.sunset" + ), + ], + ], + }, + { name: "after_offset", selector: { text: {} } }, + ] as const + ); protected render() { const schema = this._schema(this.hass.localize); @@ -81,7 +84,9 @@ export class HaSunCondition extends LitElement implements ConditionElement { fireEvent(this, "value-changed", { value: newTrigger }); } - private _computeLabelCallback = (schema: HaFormSchema): string => + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => this.hass.localize( `ui.panel.config.automation.editor.conditions.type.sun.${schema.name}` ); diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-time.ts b/src/panels/config/automation/condition/types/ha-automation-condition-time.ts index 88277292d8..9901b4fc22 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-time.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-time.ts @@ -6,18 +6,10 @@ import type { TimeCondition } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; import type { ConditionElement } from "../ha-automation-condition-row"; import type { LocalizeFunc } from "../../../../../common/translations/localize"; -import type { HaFormSchema } from "../../../../../components/ha-form/types"; import "../../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; -const DAYS = { - mon: 1, - tue: 2, - wed: 3, - thu: 4, - fri: 5, - sat: 6, - sun: 7, -}; +const DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] as const; @customElement("ha-automation-condition-time") export class HaTimeCondition extends LitElement implements ConditionElement { @@ -38,16 +30,8 @@ export class HaTimeCondition extends LitElement implements ConditionElement { localize: LocalizeFunc, inputModeAfter?: boolean, inputModeBefore?: boolean - ): HaFormSchema[] => { - const modeAfterSchema = inputModeAfter - ? { name: "after", selector: { entity: { domain: "input_datetime" } } } - : { name: "after", selector: { time: {} } }; - - const modeBeforeSchema = inputModeBefore - ? { name: "before", selector: { entity: { domain: "input_datetime" } } } - : { name: "before", selector: { time: {} } }; - - return [ + ) => + [ { name: "mode_after", type: "select", @@ -67,7 +51,12 @@ export class HaTimeCondition extends LitElement implements ConditionElement { ], ], }, - modeAfterSchema, + { + name: "after", + selector: inputModeAfter + ? { entity: { domain: "input_datetime" } } + : { time: {} }, + }, { name: "mode_before", type: "select", @@ -87,19 +76,26 @@ export class HaTimeCondition extends LitElement implements ConditionElement { ], ], }, - modeBeforeSchema, + { + name: "before", + selector: inputModeBefore + ? { entity: { domain: "input_datetime" } } + : { time: {} }, + }, { type: "multi_select", name: "weekday", - options: Object.keys(DAYS).map((day) => [ - day, - localize( - `ui.panel.config.automation.editor.conditions.type.time.weekdays.${day}` - ), - ]), + options: DAYS.map( + (day) => + [ + day, + localize( + `ui.panel.config.automation.editor.conditions.type.time.weekdays.${day}` + ), + ] as const + ), }, - ]; - } + ] as const ); protected render() { @@ -110,7 +106,7 @@ export class HaTimeCondition extends LitElement implements ConditionElement { this._inputModeAfter ?? this.condition.after?.startsWith("input_datetime."); - const schema: HaFormSchema[] = this._schema( + const schema = this._schema( this.hass.localize, inputModeAfter, inputModeBefore @@ -152,7 +148,9 @@ export class HaTimeCondition extends LitElement implements ConditionElement { fireEvent(this, "value-changed", { value: newValue }); } - private _computeLabelCallback = (schema: HaFormSchema): string => + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => this.hass.localize( `ui.panel.config.automation.editor.conditions.type.time.${schema.name}` ); diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-zone.ts b/src/panels/config/automation/condition/types/ha-automation-condition-zone.ts index 6c748351a9..f1825750c1 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-zone.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-zone.ts @@ -18,7 +18,7 @@ const includeDomains = ["zone"]; export class HaZoneCondition extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public condition!: ZoneCondition; + @property({ attribute: false }) public condition!: ZoneCondition; public static get defaultConfig() { return { diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index b7a2a6de2e..0894164036 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -60,7 +60,7 @@ const OPTIONS = [ "time_pattern", "webhook", "zone", -]; +] as const; export interface TriggerElement extends LitElement { trigger: Trigger; @@ -92,7 +92,7 @@ export const handleChangeEvent = (element: TriggerElement, ev: CustomEvent) => { export default class HaAutomationTriggerRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public trigger!: Trigger; + @property({ attribute: false }) public trigger!: Trigger; @state() private _warnings?: string[]; diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts index 7d205322cd..6ce9382efb 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts @@ -6,9 +6,10 @@ import type { CalendarTrigger } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; import type { TriggerElement } from "../ha-automation-trigger-row"; import type { HaDurationData } from "../../../../../components/ha-duration-input"; -import type { HaFormSchema } from "../../../../../components/ha-form/types"; +import "../../../../../components/ha-form/ha-form"; import { createDurationData } from "../../../../../common/datetime/create_duration_data"; import type { LocalizeFunc } from "../../../../../common/translations/localize"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; @customElement("ha-automation-trigger-calendar") export class HaCalendarTrigger extends LitElement implements TriggerElement { @@ -16,52 +17,55 @@ export class HaCalendarTrigger extends LitElement implements TriggerElement { @property({ attribute: false }) public trigger!: CalendarTrigger; - private _schema = memoizeOne((localize: LocalizeFunc) => [ - { - name: "entity_id", - required: true, - selector: { entity: { domain: "calendar" } }, - }, - { - name: "event", - type: "select", - required: true, - options: [ - [ - "start", - localize( - "ui.panel.config.automation.editor.triggers.type.calendar.start" - ), - ], - [ - "end", - localize( - "ui.panel.config.automation.editor.triggers.type.calendar.end" - ), - ], - ], - }, - { name: "offset", selector: { duration: {} } }, - { - name: "offset_type", - type: "select", - required: true, - options: [ - [ - "before", - localize( - "ui.panel.config.automation.editor.triggers.type.calendar.before" - ), - ], - [ - "after", - localize( - "ui.panel.config.automation.editor.triggers.type.calendar.after" - ), - ], - ], - }, - ]); + private _schema = memoizeOne( + (localize: LocalizeFunc) => + [ + { + name: "entity_id", + required: true, + selector: { entity: { domain: "calendar" } }, + }, + { + name: "event", + type: "select", + required: true, + options: [ + [ + "start", + localize( + "ui.panel.config.automation.editor.triggers.type.calendar.start" + ), + ], + [ + "end", + localize( + "ui.panel.config.automation.editor.triggers.type.calendar.end" + ), + ], + ], + }, + { name: "offset", selector: { duration: {} } }, + { + name: "offset_type", + type: "select", + required: true, + options: [ + [ + "before", + localize( + "ui.panel.config.automation.editor.triggers.type.calendar.before" + ), + ], + [ + "after", + localize( + "ui.panel.config.automation.editor.triggers.type.calendar.after" + ), + ], + ], + }, + ] as const + ); public static get defaultConfig() { return { @@ -114,7 +118,9 @@ export class HaCalendarTrigger extends LitElement implements TriggerElement { fireEvent(this, "value-changed", { value: newTrigger }); } - private _computeLabelCallback = (schema: HaFormSchema): string => + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => this.hass.localize( `ui.panel.config.automation.editor.triggers.type.calendar.${schema.name}` ); diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location.ts index 3b8dcb8e71..c2b8020696 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location.ts @@ -3,10 +3,10 @@ import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import { HaFormSchema } from "../../../../../components/ha-form/types"; import type { GeoLocationTrigger } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; import type { LocalizeFunc } from "../../../../../common/translations/localize"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; @customElement("ha-automation-trigger-geo_location") export class HaGeolocationTrigger extends LitElement { @@ -14,29 +14,32 @@ export class HaGeolocationTrigger extends LitElement { @property({ attribute: false }) public trigger!: GeoLocationTrigger; - private _schema = memoizeOne((localize: LocalizeFunc) => [ - { name: "source", selector: { text: {} } }, - { name: "zone", selector: { entity: { domain: "zone" } } }, - { - name: "event", - type: "select", - required: true, - options: [ - [ - "enter", - localize( - "ui.panel.config.automation.editor.triggers.type.geo_location.enter" - ), - ], - [ - "leave", - localize( - "ui.panel.config.automation.editor.triggers.type.geo_location.leave" - ), - ], - ], - }, - ]); + private _schema = memoizeOne( + (localize: LocalizeFunc) => + [ + { name: "source", selector: { text: {} } }, + { name: "zone", selector: { entity: { domain: "zone" } } }, + { + name: "event", + type: "select", + required: true, + options: [ + [ + "enter", + localize( + "ui.panel.config.automation.editor.triggers.type.geo_location.enter" + ), + ], + [ + "leave", + localize( + "ui.panel.config.automation.editor.triggers.type.geo_location.leave" + ), + ], + ], + }, + ] as const + ); public static get defaultConfig() { return { @@ -64,7 +67,9 @@ export class HaGeolocationTrigger extends LitElement { fireEvent(this, "value-changed", { value: newTrigger }); } - private _computeLabelCallback = (schema: HaFormSchema): string => + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => this.hass.localize( `ui.panel.config.automation.editor.triggers.type.geo_location.${schema.name}` ); diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant.ts index fceb9bd515..eebd3b0a6c 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant.ts @@ -5,8 +5,8 @@ import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; import type { HassTrigger } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; -import type { HaFormSchema } from "../../../../../components/ha-form/types"; import type { LocalizeFunc } from "../../../../../common/translations/localize"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; @customElement("ha-automation-trigger-homeassistant") export class HaHassTrigger extends LitElement { @@ -14,27 +14,30 @@ export class HaHassTrigger extends LitElement { @property({ attribute: false }) public trigger!: HassTrigger; - private _schema = memoizeOne((localize: LocalizeFunc) => [ - { - name: "event", - type: "select", - required: true, - options: [ - [ - "start", - localize( - "ui.panel.config.automation.editor.triggers.type.homeassistant.start" - ), - ], - [ - "shutdown", - localize( - "ui.panel.config.automation.editor.triggers.type.homeassistant.shutdown" - ), - ], - ], - }, - ]); + private _schema = memoizeOne( + (localize: LocalizeFunc) => + [ + { + name: "event", + type: "select", + required: true, + options: [ + [ + "start", + localize( + "ui.panel.config.automation.editor.triggers.type.homeassistant.start" + ), + ], + [ + "shutdown", + localize( + "ui.panel.config.automation.editor.triggers.type.homeassistant.shutdown" + ), + ], + ], + }, + ] as const + ); public static get defaultConfig() { return { @@ -60,9 +63,11 @@ export class HaHassTrigger extends LitElement { fireEvent(this, "value-changed", { value: newTrigger }); } - private _computeLabelCallback = (schema: HaFormSchema): string => + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => this.hass.localize( - `ui.panel.config.automation.editor.triggers.type.geo_location.${schema.name}` + `ui.panel.config.automation.editor.triggers.type.homeassistant.${schema.name}` ); static styles = css` diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt.ts index 95941c85b2..85bd81aa3e 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt.ts @@ -1,21 +1,22 @@ import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import type { HaFormSchema } from "../../../../../components/ha-form/types"; +import "../../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; import { MqttTrigger } from "../../../../../data/automation"; import { HomeAssistant } from "../../../../../types"; import type { TriggerElement } from "../ha-automation-trigger-row"; -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "topic", required: true, selector: { text: {} } }, { name: "payload", selector: { text: {} } }, -]; +] as const; @customElement("ha-automation-trigger-mqtt") export class HaMQTTTrigger extends LitElement implements TriggerElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public trigger!: MqttTrigger; + @property({ attribute: false }) public trigger!: MqttTrigger; public static get defaultConfig() { return { topic: "" }; @@ -39,7 +40,9 @@ export class HaMQTTTrigger extends LitElement implements TriggerElement { fireEvent(this, "value-changed", { value: newTrigger }); } - private _computeLabelCallback = (schema: HaFormSchema): string => + private _computeLabelCallback = ( + schema: SchemaUnion + ): string => this.hass.localize( `ui.panel.config.automation.editor.triggers.type.mqtt.${schema.name}` ); diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts index c156d2b9aa..88be9564a5 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts @@ -2,33 +2,36 @@ import "../../../../../components/ha-form/ha-form"; import { html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; -import type { HaFormSchema } from "../../../../../components/ha-form/types"; import { createDurationData } from "../../../../../common/datetime/create_duration_data"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { hasTemplate } from "../../../../../common/string/has-template"; import type { NumericStateTrigger } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; @customElement("ha-automation-trigger-numeric_state") export class HaNumericStateTrigger extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public trigger!: NumericStateTrigger; + @property({ attribute: false }) public trigger!: NumericStateTrigger; - private _schema = memoizeOne((entityId): HaFormSchema[] => [ - { name: "entity_id", required: true, selector: { entity: {} } }, - { - name: "attribute", - selector: { attribute: { entity_id: entityId } }, - }, - { name: "above", selector: { text: {} } }, - { name: "below", selector: { text: {} } }, - { - name: "value_template", - selector: { text: { multiline: true } }, - }, - { name: "for", selector: { duration: {} } }, - ]); + private _schema = memoizeOne( + (entityId) => + [ + { name: "entity_id", required: true, selector: { entity: {} } }, + { + name: "attribute", + selector: { attribute: { entity_id: entityId } }, + }, + { name: "above", selector: { text: {} } }, + { name: "below", selector: { text: {} } }, + { + name: "value_template", + selector: { text: { multiline: true } }, + }, + { name: "for", selector: { duration: {} } }, + ] as const + ); public willUpdate(changedProperties: PropertyValues) { if (!changedProperties.has("trigger")) { @@ -73,7 +76,9 @@ export class HaNumericStateTrigger extends LitElement { fireEvent(this, "value-changed", { value: newTrigger }); } - private _computeLabelCallback = (schema: HaFormSchema): string => { + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => { switch (schema.name) { case "entity_id": return this.hass.localize("ui.components.entity.entity-picker.entity"); diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts index 60997afcde..00ded53fac 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts @@ -20,7 +20,7 @@ import { baseTriggerStruct, forDictStruct } from "../../structs"; import { TriggerElement } from "../ha-automation-trigger-row"; import "../../../../../components/ha-form/ha-form"; import { createDurationData } from "../../../../../common/datetime/create_duration_data"; -import { HaFormSchema } from "../../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; const stateTriggerStruct = assign( baseTriggerStruct, @@ -38,26 +38,29 @@ const stateTriggerStruct = assign( export class HaStateTrigger extends LitElement implements TriggerElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public trigger!: StateTrigger; + @property({ attribute: false }) public trigger!: StateTrigger; public static get defaultConfig() { return { entity_id: [] }; } - private _schema = memoizeOne((entityId) => [ - { - name: "entity_id", - required: true, - selector: { entity: { multiple: true } }, - }, - { - name: "attribute", - selector: { attribute: { entity_id: entityId } }, - }, - { name: "from", selector: { text: {} } }, - { name: "to", selector: { text: {} } }, - { name: "for", selector: { duration: {} } }, - ]); + private _schema = memoizeOne( + (entityId) => + [ + { + name: "entity_id", + required: true, + selector: { entity: { multiple: true } }, + }, + { + name: "attribute", + selector: { attribute: { entity_id: entityId } }, + }, + { name: "from", selector: { text: {} } }, + { name: "to", selector: { text: {} } }, + { name: "for", selector: { duration: {} } }, + ] as const + ); public shouldUpdate(changedProperties: PropertyValues) { if (!changedProperties.has("trigger")) { @@ -122,7 +125,9 @@ export class HaStateTrigger extends LitElement implements TriggerElement { fireEvent(this, "value-changed", { value: newTrigger }); } - private _computeLabelCallback = (schema: HaFormSchema): string => + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => this.hass.localize( schema.name === "entity_id" ? "ui.components.entity.entity-picker.entity" diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-sun.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-sun.ts index 0be06923b8..d50de420ca 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-sun.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-sun.ts @@ -5,8 +5,9 @@ import { fireEvent } from "../../../../../common/dom/fire_event"; import type { SunTrigger } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; import type { TriggerElement } from "../ha-automation-trigger-row"; -import type { HaFormSchema } from "../../../../../components/ha-form/types"; +import "../../../../../components/ha-form/ha-form"; import type { LocalizeFunc } from "../../../../../common/translations/localize"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; @customElement("ha-automation-trigger-sun") export class HaSunTrigger extends LitElement implements TriggerElement { @@ -14,28 +15,31 @@ export class HaSunTrigger extends LitElement implements TriggerElement { @property({ attribute: false }) public trigger!: SunTrigger; - private _schema = memoizeOne((localize: LocalizeFunc) => [ - { - name: "event", - type: "select", - required: true, - options: [ - [ - "sunrise", - localize( - "ui.panel.config.automation.editor.triggers.type.sun.sunrise" - ), - ], - [ - "sunset", - localize( - "ui.panel.config.automation.editor.triggers.type.sun.sunset" - ), - ], - ], - }, - { name: "offset", selector: { text: {} } }, - ]); + private _schema = memoizeOne( + (localize: LocalizeFunc) => + [ + { + name: "event", + type: "select", + required: true, + options: [ + [ + "sunrise", + localize( + "ui.panel.config.automation.editor.triggers.type.sun.sunrise" + ), + ], + [ + "sunset", + localize( + "ui.panel.config.automation.editor.triggers.type.sun.sunset" + ), + ], + ], + }, + { name: "offset", selector: { text: {} } }, + ] as const + ); public static get defaultConfig() { return { @@ -63,7 +67,9 @@ export class HaSunTrigger extends LitElement implements TriggerElement { fireEvent(this, "value-changed", { value: newTrigger }); } - private _computeLabelCallback = (schema: HaFormSchema): string => + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => this.hass.localize( `ui.panel.config.automation.editor.triggers.type.sun.${schema.name}` ); diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-time.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-time.ts index 61a9c46a1e..a25b1ee1ca 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-time.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-time.ts @@ -5,9 +5,9 @@ import type { TimeTrigger } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; import type { TriggerElement } from "../ha-automation-trigger-row"; import type { LocalizeFunc } from "../../../../../common/translations/localize"; -import type { HaFormSchema } from "../../../../../components/ha-form/types"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; @customElement("ha-automation-trigger-time") export class HaTimeTrigger extends LitElement implements TriggerElement { @@ -22,7 +22,7 @@ export class HaTimeTrigger extends LitElement implements TriggerElement { } private _schema = memoizeOne( - (localize: LocalizeFunc, inputMode?: boolean): HaFormSchema[] => { + (localize: LocalizeFunc, inputMode?: boolean) => { const atSelector = inputMode ? { entity: { domain: "input_datetime" } } : { time: {} }; @@ -48,7 +48,7 @@ export class HaTimeTrigger extends LitElement implements TriggerElement { ], }, { name: "at", selector: atSelector }, - ]; + ] as const; } ); @@ -77,7 +77,7 @@ export class HaTimeTrigger extends LitElement implements TriggerElement { this._inputMode ?? (at?.startsWith("input_datetime.") || at?.startsWith("sensor.")); - const schema: HaFormSchema[] = this._schema(this.hass.localize, inputMode); + const schema = this._schema(this.hass.localize, inputMode); const data = { mode: inputMode ? "input" : "value", @@ -111,7 +111,9 @@ export class HaTimeTrigger extends LitElement implements TriggerElement { fireEvent(this, "value-changed", { value: newValue }); } - private _computeLabelCallback = (schema: HaFormSchema): string => + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => this.hass.localize( `ui.panel.config.automation.editor.triggers.type.time.${schema.name}` ); diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-time_pattern.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-time_pattern.ts index dbb91a0567..da24934ec2 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-time_pattern.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-time_pattern.ts @@ -1,22 +1,23 @@ import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import type { HaFormSchema } from "../../../../../components/ha-form/types"; +import "../../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; import type { TimePatternTrigger } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; import type { TriggerElement } from "../ha-automation-trigger-row"; -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "hours", selector: { text: {} } }, { name: "minutes", selector: { text: {} } }, { name: "seconds", selector: { text: {} } }, -]; +] as const; @customElement("ha-automation-trigger-time_pattern") export class HaTimePatternTrigger extends LitElement implements TriggerElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public trigger!: TimePatternTrigger; + @property({ attribute: false }) public trigger!: TimePatternTrigger; public static get defaultConfig() { return {}; @@ -40,7 +41,9 @@ export class HaTimePatternTrigger extends LitElement implements TriggerElement { fireEvent(this, "value-changed", { value: newTrigger }); } - private _computeLabelCallback = (schema: HaFormSchema): string => + private _computeLabelCallback = ( + schema: SchemaUnion + ): string => this.hass.localize( `ui.panel.config.automation.editor.triggers.type.time_pattern.${schema.name}` ); diff --git a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts index 96f98fbbc2..97f5593057 100644 --- a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts @@ -8,7 +8,7 @@ import type { HomeAssistant } from "../../../../types"; import type { AlarmPanelCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { LocalizeFunc } from "../../../../common/translations/localize"; const cardConfigStruct = assign( @@ -27,7 +27,7 @@ const states = [ "arm_night", "arm_vacation", "arm_custom_bypass", -]; +] as const; @customElement("hui-alarm-panel-card-editor") export class HuiAlarmPanelCardEditor @@ -43,30 +43,32 @@ export class HuiAlarmPanelCardEditor this._config = config; } - private _schema = memoizeOne((localize: LocalizeFunc): HaFormSchema[] => [ - { - name: "entity", - required: true, - selector: { entity: { domain: "alarm_control_panel" } }, - }, - { - type: "grid", - name: "", - schema: [ - { name: "name", selector: { text: {} } }, - { name: "theme", selector: { theme: {} } }, - ], - }, - - { - type: "multi_select", - name: "states", - options: states.map((s) => [ - s, - localize(`ui.card.alarm_control_panel.${s}`), - ]) as [string, string][], - }, - ]); + private _schema = memoizeOne( + (localize: LocalizeFunc) => + [ + { + name: "entity", + required: true, + selector: { entity: { domain: "alarm_control_panel" } }, + }, + { + type: "grid", + name: "", + schema: [ + { name: "name", selector: { text: {} } }, + { name: "theme", selector: { theme: {} } }, + ], + }, + { + type: "multi_select", + name: "states", + options: states.map((s) => [ + s, + localize(`ui.card.alarm_control_panel.${s}`), + ]) as [string, string][], + }, + ] as const + ); protected render(): TemplateResult { if (!this.hass || !this._config) { @@ -88,30 +90,30 @@ export class HuiAlarmPanelCardEditor fireEvent(this, "config-changed", { config: ev.detail.value }); } - private _computeLabelCallback = (schema: HaFormSchema) => { - if (schema.name === "entity") { - return this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.entity" - ); + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "entity": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.entity" + ); + case "name": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.name" + ); + case "theme": + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.theme" + )} (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.optional" + )})`; + default: + // "states" + return this.hass!.localize( + "ui.panel.lovelace.editor.card.alarm-panel.available_states" + ); } - - if (schema.name === "name") { - return this.hass!.localize(`ui.panel.lovelace.editor.card.generic.name`); - } - - if (schema.name === "theme") { - return `${this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.theme" - )} (${this.hass!.localize( - "ui.panel.lovelace.editor.card.config.optional" - )})`; - } - - return this.hass!.localize( - `ui.panel.lovelace.editor.card.alarm-panel.${ - schema.name === "states" ? "available_states" : schema.name - }` - ); }; } diff --git a/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts index ed4114bda5..c1731bf95f 100644 --- a/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts @@ -3,7 +3,7 @@ import { customElement, property, state } from "lit/decorators"; import { assert, assign, boolean, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-form/ha-form"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { AreaCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; @@ -19,7 +19,7 @@ const cardConfigStruct = assign( }) ); -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "area", selector: { area: {} } }, { name: "show_camera", required: false, selector: { boolean: {} } }, { @@ -30,7 +30,7 @@ const SCHEMA: HaFormSchema[] = [ { name: "theme", required: false, selector: { theme: {} } }, ], }, -]; +] as const; @customElement("hui-area-card-editor") export class HuiAreaCardEditor @@ -67,7 +67,7 @@ export class HuiAreaCardEditor fireEvent(this, "config-changed", { config }); } - private _computeLabelCallback = (schema: HaFormSchema) => { + private _computeLabelCallback = (schema: SchemaUnion) => { switch (schema.name) { case "theme": return `${this.hass!.localize( diff --git a/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts index 90bc7e51d3..f6a63ca0e8 100644 --- a/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts @@ -7,7 +7,7 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; import "../../../../components/ha-form/ha-form"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import { ActionConfig } from "../../../../data/lovelace"; import type { HomeAssistant } from "../../../../types"; import type { ButtonCardConfig } from "../../cards/types"; @@ -58,53 +58,50 @@ export class HuiButtonCardEditor } private _schema = memoizeOne( - ( - entity?: string, - icon?: string, - entityState?: HassEntity - ): HaFormSchema[] => [ - { name: "entity", selector: { entity: {} } }, - { - name: "", - type: "grid", - schema: [ - { name: "name", selector: { text: {} } }, - { - name: "icon", - selector: { - icon: { - placeholder: icon || entityState?.attributes.icon, - fallbackPath: - !icon && - !entityState?.attributes.icon && - entityState && - entity - ? domainIcon(computeDomain(entity), entityState) - : undefined, + (entity?: string, icon?: string, entityState?: HassEntity) => + [ + { name: "entity", selector: { entity: {} } }, + { + name: "", + type: "grid", + schema: [ + { name: "name", selector: { text: {} } }, + { + name: "icon", + selector: { + icon: { + placeholder: icon || entityState?.attributes.icon, + fallbackPath: + !icon && + !entityState?.attributes.icon && + entityState && + entity + ? domainIcon(computeDomain(entity), entityState) + : undefined, + }, }, }, - }, - ], - }, - { - name: "", - type: "grid", - column_min_width: "100px", - schema: [ - { name: "show_name", selector: { boolean: {} } }, - { name: "show_state", selector: { boolean: {} } }, - { name: "show_icon", selector: { boolean: {} } }, - ], - }, - { - name: "", - type: "grid", - schema: [ - { name: "icon_height", selector: { text: { suffix: "px" } } }, - { name: "theme", selector: { theme: {} } }, - ], - }, - ] + ], + }, + { + name: "", + type: "grid", + column_min_width: "100px", + schema: [ + { name: "show_name", selector: { boolean: {} } }, + { name: "show_state", selector: { boolean: {} } }, + { name: "show_icon", selector: { boolean: {} } }, + ], + }, + { + name: "", + type: "grid", + schema: [ + { name: "icon_height", selector: { text: { suffix: "px" } } }, + { name: "theme", selector: { theme: {} } }, + ], + }, + ] as const ); get _tap_action(): ActionConfig | undefined { @@ -193,7 +190,9 @@ export class HuiButtonCardEditor fireEvent(this, "config-changed", { config }); } - private _computeLabelCallback = (schema: HaFormSchema) => { + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { if (schema.name === "entity") { return `${this.hass!.localize( "ui.panel.lovelace.editor.card.generic.entity" diff --git a/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts index b1e6baa3e5..ed25fd5201 100644 --- a/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts @@ -15,7 +15,7 @@ import { } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; import { LocalizeFunc } from "../../../../common/translations/localize"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { CalendarCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; @@ -31,7 +31,7 @@ const cardConfigStruct = assign( }) ); -const views = ["dayGridMonth", "dayGridDay", "listWeek"]; +const views = ["dayGridMonth", "dayGridDay", "listWeek"] as const; @customElement("hui-calendar-card-editor") export class HuiCalendarCardEditor @@ -47,30 +47,33 @@ export class HuiCalendarCardEditor this._config = config; } - private _schema = memoizeOne((localize: LocalizeFunc) => [ - { - name: "", - type: "grid", - schema: [ - { name: "title", required: false, selector: { text: {} } }, + private _schema = memoizeOne( + (localize: LocalizeFunc) => + [ { - name: "initial_view", - required: false, - selector: { - select: { - options: views.map((view) => [ - view, - localize( - `ui.panel.lovelace.editor.card.calendar.views.${view}` - ), - ]), + name: "", + type: "grid", + schema: [ + { name: "title", required: false, selector: { text: {} } }, + { + name: "initial_view", + required: false, + selector: { + select: { + options: views.map((view) => ({ + value: view, + label: localize( + `ui.panel.lovelace.editor.card.calendar.views.${view}` + ), + })), + }, + }, }, - }, + ], }, - ], - }, - { name: "theme", required: false, selector: { theme: {} } }, - ]); + { name: "theme", required: false, selector: { theme: {} } }, + ] as const + ); protected render(): TemplateResult { if (!this.hass || !this._config) { @@ -116,7 +119,9 @@ export class HuiCalendarCardEditor fireEvent(this, "config-changed", { config }); } - private _computeLabelCallback = (schema: HaFormSchema) => { + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { if (schema.name === "title") { return this.hass!.localize("ui.panel.lovelace.editor.card.generic.title"); } diff --git a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts index badd139786..334e84bc06 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts @@ -7,7 +7,7 @@ import type { HassEntity } from "home-assistant-js-websocket/dist/types"; import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { EntityCardConfig } from "../../cards/types"; import { headerFooterConfigStructs } from "../../header-footer/structs"; @@ -43,36 +43,37 @@ export class HuiEntityCardEditor } private _schema = memoizeOne( - (entity: string, icon: string, entityState: HassEntity): HaFormSchema[] => [ - { name: "entity", required: true, selector: { entity: {} } }, - { - type: "grid", - name: "", - schema: [ - { name: "name", selector: { text: {} } }, - { - name: "icon", - selector: { - icon: { - placeholder: icon || entityState?.attributes.icon, - fallbackPath: - !icon && !entityState?.attributes.icon && entityState - ? domainIcon(computeDomain(entity), entityState) - : undefined, + (entity: string, icon: string, entityState: HassEntity) => + [ + { name: "entity", required: true, selector: { entity: {} } }, + { + type: "grid", + name: "", + schema: [ + { name: "name", selector: { text: {} } }, + { + name: "icon", + selector: { + icon: { + placeholder: icon || entityState?.attributes.icon, + fallbackPath: + !icon && !entityState?.attributes.icon && entityState + ? domainIcon(computeDomain(entity), entityState) + : undefined, + }, }, }, - }, - { - name: "attribute", - selector: { attribute: { entity_id: entity } }, - }, - { name: "unit", selector: { text: {} } }, - { name: "theme", selector: { theme: {} } }, - { name: "state_color", selector: { boolean: {} } }, - ], - }, - ] + { + name: "attribute", + selector: { attribute: { entity_id: entity } }, + }, + { name: "unit", selector: { text: {} } }, + { name: "theme", selector: { theme: {} } }, + { name: "state_color", selector: { boolean: {} } }, + ], + }, + ] as const ); protected render(): TemplateResult { @@ -105,7 +106,9 @@ export class HuiEntityCardEditor fireEvent(this, "config-changed", { config }); } - private _computeLabelCallback = (schema: HaFormSchema) => { + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { if (schema.name === "entity") { return this.hass!.localize( "ui.panel.lovelace.editor.card.generic.entity" From 1d47303127b62c635b7fa7c29b287fb81c51ad56 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Fri, 5 Aug 2022 09:39:19 -0400 Subject: [PATCH 003/134] Separate supervisor localize key exceptions and disallow string (#13317) --- hassio/src/backups/hassio-backups.ts | 4 ++-- hassio/src/supervisor-base-element.ts | 5 +++-- src/common/translations/localize.ts | 18 +++++++----------- src/data/supervisor/supervisor.ts | 11 +++++++++-- src/translations/en.json | 1 + 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/hassio/src/backups/hassio-backups.ts b/hassio/src/backups/hassio-backups.ts index 99d2869f8b..f2b0466623 100644 --- a/hassio/src/backups/hassio-backups.ts +++ b/hassio/src/backups/hassio-backups.ts @@ -176,7 +176,7 @@ export class HassioBackups extends LitElement { : supervisorTabs(this.hass)} .hass=${this.hass} .localizeFunc=${this.supervisor.localize} - .searchLabel=${this.supervisor.localize("search")} + .searchLabel=${this.supervisor.localize("backup.search")} .noDataText=${this.supervisor.localize("backup.no_backups")} .narrow=${this.narrow} .route=${this.route} @@ -240,7 +240,7 @@ export class HassioBackups extends LitElement { : html` ( + localize: await computeLocalize( this.constructor.prototype, language, { diff --git a/src/common/translations/localize.ts b/src/common/translations/localize.ts index 06a16904f1..13c5a2e49b 100644 --- a/src/common/translations/localize.ts +++ b/src/common/translations/localize.ts @@ -9,18 +9,18 @@ import { getLocalLanguage } from "../../util/common-translation"; // Exclude some patterns from key type checking for now // These are intended to be removed as errors are fixed // Fixing component category will require tighter definition of types from backend and/or web socket -type LocalizeKeyExceptions = +export type LocalizeKeys = + | FlattenObjectKeys> | `${string}` | `panel.${string}` | `state.${string}` | `state_attributes.${string}` | `state_badge.${string}` | `ui.${string}` - | `${keyof TranslationDict["supervisor"]}.${string}` | `component.${string}`; // Tweaked from https://www.raygesualdo.com/posts/flattening-object-keys-with-typescript-types -type FlattenObjectKeys< +export type FlattenObjectKeys< T extends Record, Key extends keyof T = keyof T > = Key extends string @@ -29,10 +29,8 @@ type FlattenObjectKeys< : `${Key}` : never; -export type LocalizeFunc< - Dict extends Record = TranslationDict -> = ( - key: FlattenObjectKeys | LocalizeKeyExceptions, +export type LocalizeFunc = ( + key: Keys, ...args: any[] ) => string; @@ -94,14 +92,12 @@ export const polyfillsLoaded = * } */ -export const computeLocalize = async < - Dict extends Record = TranslationDict ->( +export const computeLocalize = async ( cache: any, language: string, resources: Resources, formats?: FormatsType -): Promise> => { +): Promise> => { if (polyfillsLoaded) { await polyfillsLoaded; } diff --git a/src/data/supervisor/supervisor.ts b/src/data/supervisor/supervisor.ts index 62812b2c59..2610c76fda 100644 --- a/src/data/supervisor/supervisor.ts +++ b/src/data/supervisor/supervisor.ts @@ -1,6 +1,9 @@ import { Connection, getCollection } from "home-assistant-js-websocket"; import { Store } from "home-assistant-js-websocket/dist/store"; -import { LocalizeFunc } from "../../common/translations/localize"; +import { + FlattenObjectKeys, + LocalizeFunc, +} from "../../common/translations/localize"; import { HomeAssistant, TranslationDict } from "../../types"; import { HassioAddonsInfo } from "../hassio/addon"; import { HassioHassOSInfo, HassioHostInfo } from "../hassio/host"; @@ -57,6 +60,10 @@ export interface SupervisorEvent { [key: string]: any; } +export type SupervisorKeys = + | FlattenObjectKeys + | `${keyof TranslationDict["supervisor"]}.${string}`; + export interface Supervisor { host: HassioHostInfo; supervisor: HassioSupervisorInfo; @@ -67,7 +74,7 @@ export interface Supervisor { os: HassioHassOSInfo; addon: HassioAddonsInfo; store: SupervisorStore; - localize: LocalizeFunc; + localize: LocalizeFunc; } export const supervisorApiWsRequest = ( diff --git a/src/translations/en.json b/src/translations/en.json index 13084e5d84..7c7cb97831 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4955,6 +4955,7 @@ } }, "backup": { + "search": "[%key:ui::panel::config::backup::picker::search%]", "no_backups": "You don't have any backups yet.", "create_blocked_not_running": "Creating a backup is not possible right now because the system is in {state} state.", "delete_selected": "Delete selected backups", From a79d6b6a4dc4a61638fd08268242b845b07bfb3e Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Fri, 5 Aug 2022 12:31:07 -0400 Subject: [PATCH 004/134] Upgrade vaadin to 23.1.5 for combobox accessibility --- package.json | 4 +- yarn.lock | 148 +++++++++++++++++++++++++++------------------------ 2 files changed, 81 insertions(+), 71 deletions(-) diff --git a/package.json b/package.json index 2b7bbf967a..3ee9e6a068 100644 --- a/package.json +++ b/package.json @@ -89,8 +89,8 @@ "@polymer/paper-tooltip": "^3.0.1", "@polymer/polymer": "3.4.1", "@thomasloven/round-slider": "0.5.4", - "@vaadin/combo-box": "^23.0.10", - "@vaadin/vaadin-themable-mixin": "^23.0.10", + "@vaadin/combo-box": "^23.1.5", + "@vaadin/vaadin-themable-mixin": "^23.1.5", "@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 d2ff85dab8..69f98fe8bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4196,86 +4196,96 @@ __metadata: languageName: node linkType: hard -"@vaadin/combo-box@npm:^23.0.10": - version: 23.0.10 - resolution: "@vaadin/combo-box@npm:23.0.10" +"@vaadin/combo-box@npm:^23.1.5": + version: 23.1.5 + resolution: "@vaadin/combo-box@npm:23.1.5" dependencies: "@open-wc/dedupe-mixin": ^1.3.0 "@polymer/polymer": ^3.0.0 - "@vaadin/component-base": ^23.0.10 - "@vaadin/field-base": ^23.0.10 - "@vaadin/input-container": ^23.0.10 - "@vaadin/item": ^23.0.10 - "@vaadin/vaadin-lumo-styles": ^23.0.10 - "@vaadin/vaadin-material-styles": ^23.0.10 - "@vaadin/vaadin-overlay": ^23.0.10 - "@vaadin/vaadin-themable-mixin": ^23.0.10 - checksum: daaa0bd0cc0e8f1622417bd311e389f860c76d31fe8885f23fcdd486ff966a0945d6fe504f8d7251ec1ec54b95ef66b52d8a9e9e03ec21791bb6ca8b7f24efeb + "@vaadin/component-base": ^23.1.5 + "@vaadin/field-base": ^23.1.5 + "@vaadin/input-container": ^23.1.5 + "@vaadin/item": ^23.1.5 + "@vaadin/lit-renderer": ^23.1.5 + "@vaadin/vaadin-lumo-styles": ^23.1.5 + "@vaadin/vaadin-material-styles": ^23.1.5 + "@vaadin/vaadin-overlay": ^23.1.5 + "@vaadin/vaadin-themable-mixin": ^23.1.5 + checksum: bb4204f6a95b5d52a2e04a030e25920f28bae6bc8d2018d422f4f960ec838cdcf0933f82e51f730e1b398ea04f10140a42f929aaf9638ec8a53439fba05a41a5 languageName: node linkType: hard -"@vaadin/component-base@npm:^23.0.10": - version: 23.0.10 - resolution: "@vaadin/component-base@npm:23.0.10" +"@vaadin/component-base@npm:^23.1.5": + version: 23.1.5 + resolution: "@vaadin/component-base@npm:23.1.5" dependencies: "@open-wc/dedupe-mixin": ^1.3.0 "@polymer/polymer": ^3.0.0 "@vaadin/vaadin-development-mode-detector": ^2.0.0 "@vaadin/vaadin-usage-statistics": ^2.1.0 lit: ^2.0.0 - checksum: 2b4d851999aad9dc8e1131d66924e26db64ce5a61d2c895bdf00c8daef774a651f5c2d5ade7274d3c7a1b8286170ee21a7ef873547895e0ceef4f7a7e07073a7 + checksum: b50f99006eb89c6b475d2a34f00ff48b8f3c135462eab6b589066193c5323bc7626a0ec3a39c1e7f976f58a862e71d200c70e61e01e1eca189a864b1aa11bc58 languageName: node linkType: hard -"@vaadin/field-base@npm:^23.0.10": - version: 23.0.10 - resolution: "@vaadin/field-base@npm:23.0.10" +"@vaadin/field-base@npm:^23.1.5": + version: 23.1.5 + resolution: "@vaadin/field-base@npm:23.1.5" dependencies: "@open-wc/dedupe-mixin": ^1.3.0 "@polymer/polymer": ^3.0.0 - "@vaadin/component-base": ^23.0.10 + "@vaadin/component-base": ^23.1.5 lit: ^2.0.0 - checksum: 0f8d631af00a8268997beed8bde3f6080482eaf044577fd0df0b518fe06b90cbfa6f5c1b2b6e054171de652914d5596749484e4eaecff9754aef70760f25f493 + checksum: ede58da8a93e29c5655d6a5993f9d6c7b86166035e5e766c3dad77b4f7e3478ac529462c31dd5aa42e0407942e4d29a817332636b44c74c96689776094417d5e languageName: node linkType: hard -"@vaadin/icon@npm:^23.0.10": - version: 23.0.10 - resolution: "@vaadin/icon@npm:23.0.10" +"@vaadin/icon@npm:^23.1.5": + version: 23.1.5 + resolution: "@vaadin/icon@npm:23.1.5" dependencies: "@polymer/polymer": ^3.0.0 - "@vaadin/component-base": ^23.0.10 - "@vaadin/vaadin-lumo-styles": ^23.0.10 - "@vaadin/vaadin-themable-mixin": ^23.0.10 + "@vaadin/component-base": ^23.1.5 + "@vaadin/vaadin-lumo-styles": ^23.1.5 + "@vaadin/vaadin-themable-mixin": ^23.1.5 lit: ^2.0.0 - checksum: 1b7da9116ac649796e7852572280be67176fd609c46f37cb1f35fdd1daba0d44ac4c81642de07104c16580b9165f1f05af15c15c3828c95bc9d795b3050b31a1 + checksum: 278e32f019616f336403410fd586bbf240c6681b7c6f173e1c860e8d4264d600240a918d79db9a6e42c153394ef8e066c228ca54492b66edec97b22ccdb294bd languageName: node linkType: hard -"@vaadin/input-container@npm:^23.0.10": - version: 23.0.10 - resolution: "@vaadin/input-container@npm:23.0.10" +"@vaadin/input-container@npm:^23.1.5": + version: 23.1.5 + resolution: "@vaadin/input-container@npm:23.1.5" dependencies: "@polymer/polymer": ^3.0.0 - "@vaadin/component-base": ^23.0.10 - "@vaadin/vaadin-lumo-styles": ^23.0.10 - "@vaadin/vaadin-material-styles": ^23.0.10 - "@vaadin/vaadin-themable-mixin": ^23.0.10 - checksum: 2a21486ef0d4618bf890823bae2471a4e476cf37d5b53059c80e0516acc34990dd0a627751b17ea3ab7eb3040dd343b222b9984d734bd07985a6b9f441015aa3 + "@vaadin/component-base": ^23.1.5 + "@vaadin/vaadin-lumo-styles": ^23.1.5 + "@vaadin/vaadin-material-styles": ^23.1.5 + "@vaadin/vaadin-themable-mixin": ^23.1.5 + checksum: e42e683d47883efbf7971ccdf8b7e68cdb080c53649a745a74a04d413e4ec215b1831de4cf61a7695a78f6982f0dde8d39f2ab9abb8e67f382210da91a98e639 languageName: node linkType: hard -"@vaadin/item@npm:^23.0.10": - version: 23.0.10 - resolution: "@vaadin/item@npm:23.0.10" +"@vaadin/item@npm:^23.1.5": + version: 23.1.5 + resolution: "@vaadin/item@npm:23.1.5" dependencies: "@open-wc/dedupe-mixin": ^1.3.0 "@polymer/polymer": ^3.0.0 - "@vaadin/component-base": ^23.0.10 - "@vaadin/vaadin-lumo-styles": ^23.0.10 - "@vaadin/vaadin-material-styles": ^23.0.10 - "@vaadin/vaadin-themable-mixin": ^23.0.10 - checksum: 790fb48a12c37ad20fbb8f498601585006e27e2b78164336932c64071b1a7d34e88541d4f190c5357c32694dc76676d63c22b4c9cee64debecef90ea5f9ebbc5 + "@vaadin/component-base": ^23.1.5 + "@vaadin/vaadin-lumo-styles": ^23.1.5 + "@vaadin/vaadin-material-styles": ^23.1.5 + "@vaadin/vaadin-themable-mixin": ^23.1.5 + checksum: 09c4bf705abae1ac4edf51c6527e5875e388f9cff4b1ffe6d70566a1ed731989cb7859ad013162e7b43de414feb28e905ffaaafd431facc7883dca1e988276c9 + languageName: node + linkType: hard + +"@vaadin/lit-renderer@npm:^23.1.5": + version: 23.1.5 + resolution: "@vaadin/lit-renderer@npm:23.1.5" + dependencies: + lit: ^2.0.0 + checksum: cba32fb9d89e3d1238cd66ef68de15fa6b4c43249d633185d2a50182eac8626e91bb460683176aa462b9d145ec813025baea4c70828a1d56182de26ee29aadf6 languageName: node linkType: hard @@ -4286,49 +4296,49 @@ __metadata: languageName: node linkType: hard -"@vaadin/vaadin-lumo-styles@npm:^23.0.10": - version: 23.0.10 - resolution: "@vaadin/vaadin-lumo-styles@npm:23.0.10" +"@vaadin/vaadin-lumo-styles@npm:^23.1.5": + version: 23.1.5 + resolution: "@vaadin/vaadin-lumo-styles@npm:23.1.5" dependencies: "@polymer/iron-icon": ^3.0.0 "@polymer/iron-iconset-svg": ^3.0.0 "@polymer/polymer": ^3.0.0 - "@vaadin/icon": ^23.0.10 - "@vaadin/vaadin-themable-mixin": ^23.0.10 - checksum: c2a9c9f9d441bc55f326b267c4953361d64fed4bfe08727e7c3ea1d1598711c18c4e78faedb7ae192ba474b72a6f1521f366be1a9ed80132f83bc400869275c7 + "@vaadin/icon": ^23.1.5 + "@vaadin/vaadin-themable-mixin": ^23.1.5 + checksum: f4b91d95112d91b2f06c5e859c724713640648b64de9ebef3666991b24ecea67710aad2da855fcde022575f198844ae714109d418ff23cb1873ee2f9f01fed50 languageName: node linkType: hard -"@vaadin/vaadin-material-styles@npm:^23.0.10": - version: 23.0.10 - resolution: "@vaadin/vaadin-material-styles@npm:23.0.10" +"@vaadin/vaadin-material-styles@npm:^23.1.5": + version: 23.1.5 + resolution: "@vaadin/vaadin-material-styles@npm:23.1.5" dependencies: "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-themable-mixin": ^23.0.10 - checksum: d2f47272ac6ec3c099a6bebb0d6d861837f5d506b4c1c1ce2fe7d691b1aa8dd944afaaa9c3d4fc51cbee2cfb2efb677a89428fc124a1a4c4cc2b98058a6d2ef7 + "@vaadin/vaadin-themable-mixin": ^23.1.5 + checksum: 4025992a2f85d6e7465791ca5058c02fc701bed062a941416cb7df1750586f7a74d577e1a42157a65064bc9a1cd6963f2ca0786a657a60bd7eeae9234ad8697d languageName: node linkType: hard -"@vaadin/vaadin-overlay@npm:^23.0.10": - version: 23.0.10 - resolution: "@vaadin/vaadin-overlay@npm:23.0.10" +"@vaadin/vaadin-overlay@npm:^23.1.5": + version: 23.1.5 + resolution: "@vaadin/vaadin-overlay@npm:23.1.5" dependencies: "@polymer/polymer": ^3.0.0 - "@vaadin/component-base": ^23.0.10 - "@vaadin/vaadin-lumo-styles": ^23.0.10 - "@vaadin/vaadin-material-styles": ^23.0.10 - "@vaadin/vaadin-themable-mixin": ^23.0.10 - checksum: 25bbca730426a87967ea0648db760b2669a257e46cd2e60cd9506bbfd8c0e7c353fc23063aa338b8feee9f6ed0a763c78a458b79c39d04c34591fdc8e476274c + "@vaadin/component-base": ^23.1.5 + "@vaadin/vaadin-lumo-styles": ^23.1.5 + "@vaadin/vaadin-material-styles": ^23.1.5 + "@vaadin/vaadin-themable-mixin": ^23.1.5 + checksum: 669de63ecf2d0675e11bc337015b32ab0299733bf7f1f43b5ce55f1573b71bfc10a4422bedf8c6c9fa20d98762509efb3eee14cdbcf1433aa3be09c011175072 languageName: node linkType: hard -"@vaadin/vaadin-themable-mixin@npm:^23.0.10": - version: 23.0.10 - resolution: "@vaadin/vaadin-themable-mixin@npm:23.0.10" +"@vaadin/vaadin-themable-mixin@npm:^23.1.5": + version: 23.1.5 + resolution: "@vaadin/vaadin-themable-mixin@npm:23.1.5" dependencies: "@open-wc/dedupe-mixin": ^1.3.0 lit: ^2.0.0 - checksum: 056bff097ebc7fe1756c53018272b8b3b9006a53e77bf5ba964cff2f3b43ede52d980352a3bd38bc9eb6889ab85e897dcdc67493e9c0380993afd4fa0f2e7796 + checksum: 500877b3bc1bd2be36a3a303fa9db1abeab1837b841a5edee28c94761acbc5f8428d376c74b4e29c87dfd725e55a82c79e2ba0534f056912cdc2a4e0f2cee9e8 languageName: node linkType: hard @@ -9083,8 +9093,8 @@ fsevents@^1.2.7: "@types/webspeechapi": ^0.0.29 "@typescript-eslint/eslint-plugin": ^4.32.0 "@typescript-eslint/parser": ^4.32.0 - "@vaadin/combo-box": ^23.0.10 - "@vaadin/vaadin-themable-mixin": ^23.0.10 + "@vaadin/combo-box": ^23.1.5 + "@vaadin/vaadin-themable-mixin": ^23.1.5 "@vibrant/color": ^3.2.1-alpha.1 "@vibrant/core": ^3.2.1-alpha.1 "@vibrant/quantizer-mmcq": ^3.2.1-alpha.1 From 825558c8db7f1f797f8958da26959a728b954314 Mon Sep 17 00:00:00 2001 From: ludeeus Date: Sun, 7 Aug 2022 16:53:09 +0000 Subject: [PATCH 005/134] Make sure we have supervisor.addon --- hassio/src/addon-view/hassio-addon-dashboard.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hassio/src/addon-view/hassio-addon-dashboard.ts b/hassio/src/addon-view/hassio-addon-dashboard.ts index 98a6c8a843..52bf98df4c 100644 --- a/hassio/src/addon-view/hassio-addon-dashboard.ts +++ b/hassio/src/addon-view/hassio-addon-dashboard.ts @@ -75,7 +75,7 @@ class HassioAddonDashboard extends LitElement { >`; } - if (!this.addon) { + if (!this.addon || !this.supervisor?.addon) { return html``; } @@ -263,6 +263,10 @@ class HassioAddonDashboard extends LitElement { return; } try { + if (!this.supervisor.addon) { + const addonsInfo = await fetchHassioAddonsInfo(this.hass); + fireEvent(this, "supervisor-update", { addon: addonsInfo }); + } this.addon = await fetchAddonInfo(this.hass, this.supervisor, addon); } catch (err: any) { this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`; From 0d5b86e2b6b091161c042e763317207b028194ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sun, 7 Aug 2022 18:56:44 +0200 Subject: [PATCH 006/134] Check store for addons when using my link (#13352) --- hassio/src/addon-view/hassio-addon-dashboard.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hassio/src/addon-view/hassio-addon-dashboard.ts b/hassio/src/addon-view/hassio-addon-dashboard.ts index 98a6c8a843..a65787cc03 100644 --- a/hassio/src/addon-view/hassio-addon-dashboard.ts +++ b/hassio/src/addon-view/hassio-addon-dashboard.ts @@ -14,7 +14,6 @@ import "../../../src/components/ha-circular-progress"; import { fetchAddonInfo, fetchHassioAddonInfo, - fetchHassioAddonsInfo, HassioAddonDetails, } from "../../../src/data/hassio/addon"; import { extractApiErrorMessage } from "../../../src/data/hassio/common"; @@ -209,8 +208,8 @@ class HassioAddonDashboard extends LitElement { } if (requestedAddon) { - const addonsInfo = await fetchHassioAddonsInfo(this.hass); - const validAddon = addonsInfo.addons.some( + const store = await fetchSupervisorStore(this.hass); + const validAddon = store.addons.some( (addon) => addon.slug === requestedAddon ); if (!validAddon) { From d23d774ec1e22617ab2ce941414814621140849c Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Sun, 10 Jul 2022 14:17:41 -0400 Subject: [PATCH 007/134] Fix some key type errors in cards --- src/data/media-player.ts | 4 ++-- .../lovelace/cards/energy/hui-energy-usage-graph-card.ts | 2 +- src/panels/lovelace/cards/hui-alarm-panel-card.ts | 8 +++++--- src/panels/lovelace/cards/types.ts | 6 +++--- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/data/media-player.ts b/src/data/media-player.ts index a5c780f582..2eca50dd0a 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -34,7 +34,7 @@ import type { } from "home-assistant-js-websocket"; import { supportsFeature } from "../common/entity/supports-feature"; import { MediaPlayerItemId } from "../components/media-player/ha-media-player-browse"; -import type { HomeAssistant } from "../types"; +import type { HomeAssistant, TranslationDict } from "../types"; import { UNAVAILABLE_STATES } from "./entity"; import { isTTSMediaSource } from "./tts"; @@ -170,7 +170,7 @@ export interface MediaPlayerThumbnail { export interface ControlButton { icon: string; // Used as key for action as well as tooltip and aria-label translation key - action: string; + action: keyof TranslationDict["ui"]["card"]["media_player"]; } export interface MediaPlayerItem { diff --git a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts index f8c76a0755..f761ed0a88 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts @@ -246,7 +246,7 @@ export class HuiEnergyUsageGraphCard : "", totalReturned ? this.hass.localize( - "ui.panel.lovelace.cards.energyenergy_usage_graph.total_returned", + "ui.panel.lovelace.cards.energy.energy_usage_graph.total_returned", { num: formatNumber(totalReturned, locale) } ) : "", diff --git a/src/panels/lovelace/cards/hui-alarm-panel-card.ts b/src/panels/lovelace/cards/hui-alarm-panel-card.ts index fe5abd4af6..84f1d3b103 100644 --- a/src/panels/lovelace/cards/hui-alarm-panel-card.ts +++ b/src/panels/lovelace/cards/hui-alarm-panel-card.ts @@ -85,7 +85,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { } const defaults = { - states: ["arm_away", "arm_home"], + states: ["arm_away", "arm_home"] as const, }; this._config = { ...defaults, ...config }; @@ -166,7 +166,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
${(stateObj.state === "disarmed" ? this._config.states! - : ["disarm"] + : (["disarm"] as const) ).map( (stateAction) => html` [number] + ): string { return this.hass!.localize(`ui.card.alarm_control_panel.${entityState}`); } diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 14616c2917..ffd0cc3555 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -1,6 +1,6 @@ import { StatisticType } from "../../../data/history"; import { ActionConfig, LovelaceCardConfig } from "../../../data/lovelace"; -import { FullCalendarView } from "../../../types"; +import { FullCalendarView, TranslationDict } from "../../../types"; import { Condition } from "../common/validate-condition"; import { HuiImage } from "../components/hui-image"; import { LovelaceElementConfig } from "../elements/types"; @@ -14,7 +14,7 @@ import { LovelaceHeaderFooterConfig } from "../header-footer/types"; export interface AlarmPanelCardConfig extends LovelaceCardConfig { entity: string; name?: string; - states?: string[]; + states?: readonly (keyof TranslationDict["ui"]["card"]["alarm_control_panel"])[]; theme?: string; } @@ -411,7 +411,7 @@ export interface WeatherForecastCardConfig extends LovelaceCardConfig { name?: string; show_current?: boolean; show_forecast?: boolean; - secondary_info_attribute?: string; + secondary_info_attribute?: keyof TranslationDict["ui"]["card"]["weather"]["attributes"]; theme?: string; tap_action?: ActionConfig; hold_action?: ActionConfig; From 0ebeec0db61c7b18f34e63ed961094d8789ecfe6 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Fri, 15 Jul 2022 10:21:00 -0400 Subject: [PATCH 008/134] Fix key type errors for media player --- src/data/media-player.ts | 2 +- src/panels/media-browser/ha-bar-media-player.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/data/media-player.ts b/src/data/media-player.ts index 2eca50dd0a..38a288d17e 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -177,7 +177,7 @@ export interface MediaPlayerItem { title: string; media_content_type: string; media_content_id: string; - media_class: string; + media_class: keyof TranslationDict["ui"]["components"]["media-browser"]["class"]; children_media_class?: string; can_play: boolean; can_expand: boolean; diff --git a/src/panels/media-browser/ha-bar-media-player.ts b/src/panels/media-browser/ha-bar-media-player.ts index 90d5c97de4..c93f0d47b3 100644 --- a/src/panels/media-browser/ha-bar-media-player.ts +++ b/src/panels/media-browser/ha-bar-media-player.ts @@ -39,6 +39,7 @@ import { cleanupMediaTitle, computeMediaControls, computeMediaDescription, + ControlButton, formatMediaTime, getCurrentProgress, handleMediaControlClick, @@ -179,7 +180,7 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) { return this._renderChoosePlayer(stateObj); } - const controls = !this.narrow + const controls: ControlButton[] | undefined = !this.narrow ? computeMediaControls(stateObj, true) : (stateObj.state === "playing" && (supportsFeature(stateObj, SUPPORT_PAUSE) || @@ -207,7 +208,7 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) { : "media_stop", }, ] - : [{}]; + : undefined; const mediaDescription = computeMediaDescription(stateObj); const mediaDuration = formatMediaTime(stateObj.attributes.media_duration); const mediaTitleClean = cleanupMediaTitle( From 3d236a8f49e0cf5062a98c032a7ad225ba18145c Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Fri, 15 Jul 2022 12:28:53 -0400 Subject: [PATCH 009/134] Fix key type errors for cloud TTS --- src/data/cloud/tts.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/cloud/tts.ts b/src/data/cloud/tts.ts index 02fe969e21..ec563a6087 100644 --- a/src/data/cloud/tts.ts +++ b/src/data/cloud/tts.ts @@ -59,9 +59,9 @@ export const getCloudTtsSupportedGenders = ( if (curLang === language) { genders.push([ gender, - localize(`ui.panel.media-browser.tts.gender_${gender}`) || - localize(`ui.panel.config.cloud.account.tts.${gender}`) || - gender, + gender === "male" || gender === "female" + ? localize(`ui.panel.config.cloud.account.tts.${gender}`) + : gender, ]); } } From ca28feca800bae2a155b0a539134e95da1470dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 8 Aug 2022 10:16:13 +0200 Subject: [PATCH 010/134] Missing import and refresh correct collection (#13358) --- hassio/src/addon-view/hassio-addon-dashboard.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hassio/src/addon-view/hassio-addon-dashboard.ts b/hassio/src/addon-view/hassio-addon-dashboard.ts index 2e8ac0427e..4fb8f46178 100644 --- a/hassio/src/addon-view/hassio-addon-dashboard.ts +++ b/hassio/src/addon-view/hassio-addon-dashboard.ts @@ -14,6 +14,7 @@ import "../../../src/components/ha-circular-progress"; import { fetchAddonInfo, fetchHassioAddonInfo, + fetchHassioAddonsInfo, HassioAddonDetails, } from "../../../src/data/hassio/addon"; import { extractApiErrorMessage } from "../../../src/data/hassio/common"; @@ -237,7 +238,7 @@ class HassioAddonDashboard extends LitElement { if (["uninstall", "install", "update", "start", "stop"].includes(path)) { fireEvent(this, "supervisor-collection-refresh", { - collection: "supervisor", + collection: "addon", }); } From b444d0030f0a3542b31da6763bedbbcc85d700cb Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Wed, 13 Jul 2022 13:19:43 -0400 Subject: [PATCH 011/134] Fix key type errors for blueprints --- src/components/ha-blueprint-picker.ts | 9 +++++++-- src/data/blueprint.ts | 10 ++++++---- src/panels/config/blueprint/ha-blueprint-overview.ts | 5 +++-- src/translations/en.json | 1 + 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/components/ha-blueprint-picker.ts b/src/components/ha-blueprint-picker.ts index b018d4c768..58a0f03f4a 100644 --- a/src/components/ha-blueprint-picker.ts +++ b/src/components/ha-blueprint-picker.ts @@ -5,7 +5,12 @@ import memoizeOne from "memoize-one"; import { fireEvent } from "../common/dom/fire_event"; import { stopPropagation } from "../common/dom/stop_propagation"; import { stringCompare } from "../common/string/compare"; -import { Blueprint, Blueprints, fetchBlueprints } from "../data/blueprint"; +import { + Blueprint, + BlueprintDomain, + Blueprints, + fetchBlueprints, +} from "../data/blueprint"; import { HomeAssistant } from "../types"; import "./ha-select"; @@ -17,7 +22,7 @@ class HaBluePrintPicker extends LitElement { @property() public value = ""; - @property() public domain = "automation"; + @property() public domain: BlueprintDomain = "automation"; @property() public blueprints?: Blueprints; diff --git a/src/data/blueprint.ts b/src/data/blueprint.ts index 2d98dc8c80..3f2b826953 100644 --- a/src/data/blueprint.ts +++ b/src/data/blueprint.ts @@ -1,6 +1,8 @@ import { HomeAssistant } from "../types"; import { Selector } from "./selector"; +export type BlueprintDomain = "automation" | "script"; + export type Blueprints = Record; export type BlueprintOrError = Blueprint | { error: string }; @@ -9,7 +11,7 @@ export interface Blueprint { } export interface BlueprintMetaData { - domain: string; + domain: BlueprintDomain; name: string; input?: Record; description?: string; @@ -30,7 +32,7 @@ export interface BlueprintImportResult { validation_errors: string[] | null; } -export const fetchBlueprints = (hass: HomeAssistant, domain: string) => +export const fetchBlueprints = (hass: HomeAssistant, domain: BlueprintDomain) => hass.callWS({ type: "blueprint/list", domain }); export const importBlueprint = (hass: HomeAssistant, url: string) => @@ -38,7 +40,7 @@ export const importBlueprint = (hass: HomeAssistant, url: string) => export const saveBlueprint = ( hass: HomeAssistant, - domain: string, + domain: BlueprintDomain, path: string, yaml: string, source_url?: string @@ -53,7 +55,7 @@ export const saveBlueprint = ( export const deleteBlueprint = ( hass: HomeAssistant, - domain: string, + domain: BlueprintDomain, path: string ) => hass.callWS({ diff --git a/src/panels/config/blueprint/ha-blueprint-overview.ts b/src/panels/config/blueprint/ha-blueprint-overview.ts index 0e65094222..3df0c44e41 100644 --- a/src/panels/config/blueprint/ha-blueprint-overview.ts +++ b/src/panels/config/blueprint/ha-blueprint-overview.ts @@ -25,6 +25,7 @@ import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; import { showAutomationEditor } from "../../../data/automation"; import { + BlueprintDomain, BlueprintMetaData, Blueprints, deleteBlueprint, @@ -124,7 +125,7 @@ class HaBlueprintOverview extends LitElement { title: this.hass.localize( "ui.panel.config.blueprint.overview.headers.type" ), - template: (type: string) => + template: (type: BlueprintDomain) => html`${this.hass.localize( `ui.panel.config.blueprint.overview.types.${type}` )}`, @@ -148,7 +149,7 @@ class HaBlueprintOverview extends LitElement { title: "", width: narrow ? undefined : "20%", type: narrow ? "icon-button" : undefined, - template: (_, blueprint: any) => + template: (_, blueprint: BlueprintMetaDataPath) => blueprint.error ? "" : narrow diff --git a/src/translations/en.json b/src/translations/en.json index 7c7cb97831..8c48ae19de 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2200,6 +2200,7 @@ "confirm_delete_header": "Delete this blueprint?", "confirm_delete_text": "Are you sure you want to delete this blueprint?", "add_blueprint": "Import blueprint", + "no_blueprints": "[%key:ui::panel::config::automation::editor::blueprint::no_blueprints%]", "create_automation": "Create automation", "create_script": "Create script", "delete_blueprint": "Delete blueprint", From 75d05cdb0eed59010ff44e8acb1d0814fe79a5b9 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Sat, 16 Jul 2022 00:26:14 -0400 Subject: [PATCH 012/134] Fix key type errors in target picker --- src/components/ha-target-picker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index 0243534269..0f6ee2f41e 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -258,7 +258,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { } private _renderChip( - type: string, + type: "area_id" | "device_id" | "entity_id", id: string, name: string, entityState?: HassEntity, From 1322ff929503df3d0caba05cdd86b4b83d61e749 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Aug 2022 23:36:46 +0200 Subject: [PATCH 013/134] Process description placeholders in titles for repairs flows (#13362) --- src/panels/config/repairs/show-dialog-repair-flow.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/panels/config/repairs/show-dialog-repair-flow.ts b/src/panels/config/repairs/show-dialog-repair-flow.ts index e16fa8af32..b21f6811ad 100644 --- a/src/panels/config/repairs/show-dialog-repair-flow.ts +++ b/src/panels/config/repairs/show-dialog-repair-flow.ts @@ -67,7 +67,8 @@ export const showRepairsFlowDialog = ( hass.localize( `component.${issue.domain}.issues.${ issue.translation_key || issue.issue_id - }.fix_flow.step.${step.step_id}.title` + }.fix_flow.step.${step.step_id}.title`, + step.description_placeholders ) || hass.localize(`ui.dialogs.issues_flow.form.header`) ); }, From 9eb81e2211e3228d152f52c1c3798fc55189d96d Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Mon, 8 Aug 2022 17:59:15 -0400 Subject: [PATCH 014/134] Add missing key for area not found --- src/translations/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/translations/en.json b/src/translations/en.json index 8c48ae19de..a1d347c50f 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1256,6 +1256,7 @@ "add_picture": "Add a picture", "assigned_to_area": "Assigned to this area", "targeting_area": "Targeting this area", + "area_not_found": "[%key:ui::card::area::area_not_found%]", "data_table": { "area": "Area", "devices": "Devices", From f3b543f46c2dda763d9c87265e5d402fb1f80063 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Mon, 8 Aug 2022 18:21:39 -0400 Subject: [PATCH 015/134] Fix bad key on error screen --- src/layouts/hass-error-screen.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts/hass-error-screen.ts b/src/layouts/hass-error-screen.ts index f7a181e0ab..bc9a4737f4 100644 --- a/src/layouts/hass-error-screen.ts +++ b/src/layouts/hass-error-screen.ts @@ -40,7 +40,7 @@ class HassErrorScreen extends LitElement {

${this.error}

- ${this.hass?.localize("ui.panel.error.go_back") || "go back"} + ${this.hass?.localize("ui.common.back")}
From 95231554d55cb34d0f57ac4ce5afeb60402a9c25 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Tue, 9 Aug 2022 11:01:51 -0400 Subject: [PATCH 016/134] Add missing key for required tag name --- src/translations/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/translations/en.json b/src/translations/en.json index a1d347c50f..88bb50cfaa 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1342,6 +1342,7 @@ "update": "Update", "create": "Create", "create_and_write": "Create and Write", + "required_error_msg": "[%key:ui::panel::config::zone::detail::required_error_msg%]", "usage": "A tag can trigger an automation when scanned, you can use NFC tags, QR codes or any other kind of tag. Use our {companion_link} to write this tag to a programmable NFC tag or create a QR code below.", "companion_apps": "companion apps" } From b0807cb80cc9536fc5fdcd0c9d8784e140321ef8 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Tue, 9 Aug 2022 11:55:10 -0400 Subject: [PATCH 017/134] Fix bad keys in repair flow dialog --- src/panels/config/repairs/show-dialog-repair-flow.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/panels/config/repairs/show-dialog-repair-flow.ts b/src/panels/config/repairs/show-dialog-repair-flow.ts index e16fa8af32..29f0dff1e8 100644 --- a/src/panels/config/repairs/show-dialog-repair-flow.ts +++ b/src/panels/config/repairs/show-dialog-repair-flow.ts @@ -68,7 +68,7 @@ export const showRepairsFlowDialog = ( `component.${issue.domain}.issues.${ issue.translation_key || issue.issue_id }.fix_flow.step.${step.step_id}.title` - ) || hass.localize(`ui.dialogs.issues_flow.form.header`) + ) || hass.localize("ui.dialogs.repair_flow.form.header") ); }, @@ -125,7 +125,7 @@ export const showRepairsFlowDialog = ( renderCreateEntryDescription(hass, _step) { return html` -

${hass.localize(`ui.dialogs.repairs.success.description`)}

+

${hass.localize("ui.dialogs.repair_flow.success.description")}

`; }, @@ -201,7 +201,7 @@ export const showRepairsFlowDialog = ( issue.translation_key || issue.issue_id }.fix_flow.loading` ) || - hass.localize(`ui.dialogs.repairs.loading.${reason}`, { + hass.localize(`ui.dialogs.repair_flow.loading.${reason}`, { integration: domainToName(hass.localize, issue.domain), }) ); From ae28eb38135d88eaa51982372a1c132e4e684d64 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Tue, 9 Aug 2022 12:04:06 -0400 Subject: [PATCH 018/134] Fix bad key in system information --- src/panels/config/repairs/dialog-system-information.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/repairs/dialog-system-information.ts b/src/panels/config/repairs/dialog-system-information.ts index d6e6851734..b62ec9f410 100644 --- a/src/panels/config/repairs/dialog-system-information.ts +++ b/src/panels/config/repairs/dialog-system-information.ts @@ -336,7 +336,7 @@ class DialogSystemInformation extends LitElement { rel="noreferrer noopener" > ${this.hass.localize( - "ui.panel.config.info.system_health.more_systemInfo" + "ui.panel.config.info.system_health.more_info" )} `} From 3fe5075ad4c7cfe2589fdd9b127c71ada5c80e05 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Wed, 10 Aug 2022 12:32:46 -0400 Subject: [PATCH 019/134] Add missing key in input_select helper dialog --- src/translations/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index 88bb50cfaa..3f2342f8b1 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -925,7 +925,8 @@ }, "input_select": { "options": "Options", - "add_option": "Add option", + "add_option": "[%key:ui::panel::config::automation::editor::actions::type::choose::add_option%]", + "remove_option": "[%key:ui::panel::config::automation::editor::actions::type::choose::remove_option%]", "no_options": "There are no options yet.", "add": "Add" }, From d21bdf2807addb3c8981c4c9ce551df4306046cb Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Wed, 10 Aug 2022 15:30:58 -0400 Subject: [PATCH 020/134] Add missing key in logbook card --- src/panels/lovelace/cards/hui-logbook-card.ts | 4 +++- src/translations/en.json | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/panels/lovelace/cards/hui-logbook-card.ts b/src/panels/lovelace/cards/hui-logbook-card.ts index 13aaf9a2ab..98772b37f9 100644 --- a/src/panels/lovelace/cards/hui-logbook-card.ts +++ b/src/panels/lovelace/cards/hui-logbook-card.ts @@ -105,7 +105,9 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard { return html` ${this.hass.localize( - "ui.components.logbook.component_not_loaded" + "ui.components.logbook.not_loaded", + "platform", + "logbook" )} `; diff --git a/src/translations/en.json b/src/translations/en.json index 3f2342f8b1..c7afc5ae3d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -357,6 +357,7 @@ "triggered_by_homeassistant_starting": "triggered by Home Assistant starting", "show_trace": "[%key:ui::panel::config::automation::editor::show_trace%]", "retrieval_error": "Could not load logbook", + "not_loaded": "[%key:ui::dialogs::helper_settings::platform_not_loaded%]", "messages": { "was_away": "was detected away", "was_at_state": "was detected at {state}", From dba9658658ea500621a2d06eeaac8f4db20d091b Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Wed, 10 Aug 2022 15:59:15 -0400 Subject: [PATCH 021/134] Fix bad key for add entities to view --- src/panels/lovelace/editor/add-entities-to-view.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/panels/lovelace/editor/add-entities-to-view.ts b/src/panels/lovelace/editor/add-entities-to-view.ts index 42ad5ac06d..cd1aed9d8b 100644 --- a/src/panels/lovelace/editor/add-entities-to-view.ts +++ b/src/panels/lovelace/editor/add-entities-to-view.ts @@ -97,9 +97,7 @@ export const addEntitiesToLovelaceView = async ( try { await saveConfig(hass!, null, newConfig); } catch (err: any) { - alert( - hass.localize("ui.panel.config.devices.add_entities.saving_failed") - ); + alert(hass.localize("ui.panel.lovelace.add_entities.saving_failed")); } }, path: [0], @@ -123,9 +121,7 @@ export const addEntitiesToLovelaceView = async ( await saveConfig(hass!, newUrlPath, newConfig); } catch { alert( - hass.localize( - "ui.panel.config.devices.add_entities.saving_failed" - ) + hass.localize("ui.panel.lovelace.add_entities.saving_failed") ); } }, From 589cec10f64ce2d417147b8470ddbc17e4b9af73 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Wed, 10 Aug 2022 16:25:01 -0400 Subject: [PATCH 022/134] Fix bad key for quick bar hint Also fixed a bunch of lit plugin errors. --- src/panels/lovelace/hui-root.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 89c5cfb5e6..3539e6f26f 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -31,6 +31,7 @@ import { } from "lit"; import { property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; +import { ifDefined } from "lit/directives/if-defined"; import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { fireEvent } from "../../common/dom/fire_event"; @@ -82,7 +83,10 @@ class HUIRoot extends LitElement { @property({ type: Boolean }) public narrow = false; - @property() public route?: { path: string; prefix: string }; + @property({ attribute: false }) public route?: { + path: string; + prefix: string; + }; @state() private _curView?: number | "hass-unused-entities"; @@ -240,7 +244,7 @@ class HUIRoot extends LitElement { ${this.lovelace!.config.views.map( (view) => html` ` @@ -472,7 +476,7 @@ class HUIRoot extends LitElement { ${this.lovelace!.config.views.map( (view) => html` ` @@ -712,7 +716,7 @@ class HUIRoot extends LitElement { private _showQuickBar(): void { showQuickBar(this, { commandMode: false, - hint: this.hass.localize("ui.dialogs.quick-bar.key_e_hint"), + hint: this.hass.localize("ui.tips.key_e_hint"), }); } From 38607a641019bf8ddef25c8ea4e816a7affb6413 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Fri, 12 Aug 2022 08:58:08 -0500 Subject: [PATCH 023/134] Add UI for Schedule Helper (#13375) --- package.json | 1 + src/common/datetime/format_time.ts | 15 +- src/data/helpers_crud.ts | 22 +- src/data/schedule.ts | 61 +++ src/panels/config/entities/const.ts | 1 + .../settings/entity-settings-helper-tab.ts | 3 +- src/panels/config/helpers/const.ts | 5 +- .../config/helpers/dialog-helper-detail.ts | 3 + .../config/helpers/forms/ha-schedule-form.ts | 379 ++++++++++++++++++ src/translations/en.json | 3 +- yarn.lock | 14 +- 11 files changed, 494 insertions(+), 13 deletions(-) create mode 100644 src/data/schedule.ts create mode 100644 src/panels/config/helpers/forms/ha-schedule-form.ts diff --git a/package.json b/package.json index 3ee9e6a068..c2a91ffd2e 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@fullcalendar/daygrid": "5.9.0", "@fullcalendar/interaction": "5.9.0", "@fullcalendar/list": "5.9.0", + "@fullcalendar/timegrid": "5.9.0", "@lit-labs/motion": "^1.0.2", "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch", "@material/chips": "14.0.0-canary.261f2db59.0", diff --git a/src/common/datetime/format_time.ts b/src/common/datetime/format_time.ts index c49afc7f56..082e7cb4da 100644 --- a/src/common/datetime/format_time.ts +++ b/src/common/datetime/format_time.ts @@ -1,7 +1,7 @@ import memoizeOne from "memoize-one"; import { FrontendLocaleData } from "../../data/translation"; -import { useAmPm } from "./use_am_pm"; import { polyfillsLoaded } from "../translations/localize"; +import { useAmPm } from "./use_am_pm"; if (__BUILD__ === "latest" && polyfillsLoaded) { await polyfillsLoaded; @@ -64,3 +64,16 @@ const formatTimeWeekdayMem = memoizeOne( } ) ); + +// 21:15 +export const formatTime24h = (dateObj: Date) => + formatTime24hMem().format(dateObj); + +const formatTime24hMem = memoizeOne( + () => + new Intl.DateTimeFormat(undefined, { + hour: "numeric", + minute: "2-digit", + hour12: false, + }) +); diff --git a/src/data/helpers_crud.ts b/src/data/helpers_crud.ts index c03ce664ac..47010e5ef4 100644 --- a/src/data/helpers_crud.ts +++ b/src/data/helpers_crud.ts @@ -1,31 +1,32 @@ -import { fetchCounter, updateCounter, deleteCounter } from "./counter"; +import { deleteCounter, fetchCounter, updateCounter } from "./counter"; import { + deleteInputBoolean, fetchInputBoolean, updateInputBoolean, - deleteInputBoolean, } from "./input_boolean"; import { + deleteInputButton, fetchInputButton, updateInputButton, - deleteInputButton, } from "./input_button"; import { + deleteInputDateTime, fetchInputDateTime, updateInputDateTime, - deleteInputDateTime, } from "./input_datetime"; import { + deleteInputNumber, fetchInputNumber, updateInputNumber, - deleteInputNumber, } from "./input_number"; import { + deleteInputSelect, fetchInputSelect, updateInputSelect, - deleteInputSelect, } from "./input_select"; -import { fetchInputText, updateInputText, deleteInputText } from "./input_text"; -import { fetchTimer, updateTimer, deleteTimer } from "./timer"; +import { deleteInputText, fetchInputText, updateInputText } from "./input_text"; +import { deleteSchedule, fetchSchedule, updateSchedule } from "./schedule"; +import { deleteTimer, fetchTimer, updateTimer } from "./timer"; export const HELPERS_CRUD = { input_boolean: { @@ -68,4 +69,9 @@ export const HELPERS_CRUD = { update: updateTimer, delete: deleteTimer, }, + schedule: { + fetch: fetchSchedule, + update: updateSchedule, + delete: deleteSchedule, + }, }; diff --git a/src/data/schedule.ts b/src/data/schedule.ts new file mode 100644 index 0000000000..c073795650 --- /dev/null +++ b/src/data/schedule.ts @@ -0,0 +1,61 @@ +import { HomeAssistant } from "../types"; + +export const weekdays = [ + "sunday", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", +] as const; + +export interface ScheduleDay { + from: string; + to: string; +} + +type ScheduleDays = { [K in typeof weekdays[number]]?: ScheduleDay[] }; + +export interface Schedule extends ScheduleDays { + id: string; + name: string; + icon?: string; +} + +export interface ScheduleMutableParams { + name: string; + icon: string; +} + +export const fetchSchedule = (hass: HomeAssistant) => + hass.callWS({ type: "schedule/list" }); + +export const createSchedule = ( + hass: HomeAssistant, + values: ScheduleMutableParams +) => + hass.callWS({ + type: "schedule/create", + ...values, + }); + +export const updateSchedule = ( + hass: HomeAssistant, + id: string, + updates: Partial +) => + hass.callWS({ + type: "schedule/update", + schedule_id: id, + ...updates, + }); + +export const deleteSchedule = (hass: HomeAssistant, id: string) => + hass.callWS({ + type: "schedule/delete", + schedule_id: id, + }); + +export const getScheduleTime = (date: Date): string => + `${("0" + date.getHours()).slice(-2)}:${("0" + date.getMinutes()).slice(-2)}`; diff --git a/src/panels/config/entities/const.ts b/src/panels/config/entities/const.ts index 11d2b2622b..f1979111d3 100644 --- a/src/panels/config/entities/const.ts +++ b/src/panels/config/entities/const.ts @@ -8,4 +8,5 @@ export const PLATFORMS_WITH_SETTINGS_TAB = { counter: "entity-settings-helper-tab", timer: "entity-settings-helper-tab", input_button: "entity-settings-helper-tab", + schedule: "entity-settings-helper-tab", }; diff --git a/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts b/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts index 210d931a6c..da462f9ca9 100644 --- a/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts +++ b/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts @@ -6,7 +6,7 @@ import { PropertyValues, TemplateResult, } from "lit"; -import { customElement, property, state, query } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import { isComponentLoaded } from "../../../../../common/config/is_component_loaded"; import { dynamicElement } from "../../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../../common/dom/fire_event"; @@ -26,6 +26,7 @@ import "../../../helpers/forms/ha-input_datetime-form"; import "../../../helpers/forms/ha-input_number-form"; import "../../../helpers/forms/ha-input_select-form"; import "../../../helpers/forms/ha-input_text-form"; +import "../../../helpers/forms/ha-schedule-form"; import "../../../helpers/forms/ha-timer-form"; import "../../entity-registry-basic-editor"; import type { HaEntityRegistryBasicEditor } from "../../entity-registry-basic-editor"; diff --git a/src/panels/config/helpers/const.ts b/src/panels/config/helpers/const.ts index c103332573..87b03eff10 100644 --- a/src/panels/config/helpers/const.ts +++ b/src/panels/config/helpers/const.ts @@ -5,6 +5,7 @@ import type { InputDateTime } from "../../../data/input_datetime"; import type { InputNumber } from "../../../data/input_number"; import type { InputSelect } from "../../../data/input_select"; import type { InputText } from "../../../data/input_text"; +import type { Schedule } from "../../../data/schedule"; import type { Timer } from "../../../data/timer"; export const HELPER_DOMAINS = [ @@ -16,6 +17,7 @@ export const HELPER_DOMAINS = [ "input_select", "counter", "timer", + "schedule", ]; export type Helper = @@ -26,4 +28,5 @@ export type Helper = | InputSelect | InputDateTime | Counter - | Timer; + | Timer + | Schedule; diff --git a/src/panels/config/helpers/dialog-helper-detail.ts b/src/panels/config/helpers/dialog-helper-detail.ts index 65daced845..7f4b0a488a 100644 --- a/src/panels/config/helpers/dialog-helper-detail.ts +++ b/src/panels/config/helpers/dialog-helper-detail.ts @@ -19,6 +19,7 @@ import { createInputNumber } from "../../../data/input_number"; import { createInputSelect } from "../../../data/input_select"; import { createInputText } from "../../../data/input_text"; import { domainToName } from "../../../data/integration"; +import { createSchedule } from "../../../data/schedule"; import { createTimer } from "../../../data/timer"; import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow"; import { haStyleDialog } from "../../../resources/styles"; @@ -32,6 +33,7 @@ import "./forms/ha-input_datetime-form"; import "./forms/ha-input_number-form"; import "./forms/ha-input_select-form"; import "./forms/ha-input_text-form"; +import "./forms/ha-schedule-form"; import "./forms/ha-timer-form"; import type { ShowDialogHelperDetailParams } from "./show-dialog-helper-detail"; @@ -44,6 +46,7 @@ const HELPERS = { input_select: createInputSelect, counter: createCounter, timer: createTimer, + schedule: createSchedule, }; @customElement("dialog-helper-detail") diff --git a/src/panels/config/helpers/forms/ha-schedule-form.ts b/src/panels/config/helpers/forms/ha-schedule-form.ts new file mode 100644 index 0000000000..ea0dc38c05 --- /dev/null +++ b/src/panels/config/helpers/forms/ha-schedule-form.ts @@ -0,0 +1,379 @@ +// @ts-ignore +import fullcalendarStyle from "@fullcalendar/common/main.css"; +import { Calendar, CalendarOptions } from "@fullcalendar/core"; +import allLocales from "@fullcalendar/core/locales-all"; +import interactionPlugin from "@fullcalendar/interaction"; +import timeGridPlugin from "@fullcalendar/timegrid"; +// @ts-ignore +import timegridStyle from "@fullcalendar/timegrid/main.css"; +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, + unsafeCSS, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { formatTime24h } from "../../../../common/datetime/format_time"; +import { useAmPm } from "../../../../common/datetime/use_am_pm"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-icon-picker"; +import "../../../../components/ha-textfield"; +import { Schedule, ScheduleDay, weekdays } from "../../../../data/schedule"; +import { haStyle } from "../../../../resources/styles"; +import { HomeAssistant } from "../../../../types"; + +const defaultFullCalendarConfig: CalendarOptions = { + plugins: [timeGridPlugin, interactionPlugin], + headerToolbar: false, + initialView: "timeGridWeek", + editable: true, + selectable: true, + selectMirror: true, + selectOverlap: false, + eventOverlap: false, + allDaySlot: false, + height: "parent", + locales: allLocales, + firstDay: 1, + dayHeaderFormat: { weekday: "short", month: undefined, day: undefined }, + slotLabelFormat: { hour: "numeric", minute: undefined, meridiem: "narrow" }, +}; + +@customElement("ha-schedule-form") +class HaScheduleForm extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public new?: boolean; + + @state() private _name!: string; + + @state() private _icon!: string; + + @state() private _monday!: ScheduleDay[]; + + @state() private _tuesday!: ScheduleDay[]; + + @state() private _wednesday!: ScheduleDay[]; + + @state() private _thursday!: ScheduleDay[]; + + @state() private _friday!: ScheduleDay[]; + + @state() private _saturday!: ScheduleDay[]; + + @state() private _sunday!: ScheduleDay[]; + + @state() private calendar?: Calendar; + + private _item?: Schedule; + + set item(item: Schedule) { + this._item = item; + if (item) { + this._name = item.name || ""; + this._icon = item.icon || ""; + this._monday = item.monday || []; + this._tuesday = item.tuesday || []; + this._wednesday = item.wednesday || []; + this._thursday = item.thursday || []; + this._friday = item.friday || []; + this._saturday = item.saturday || []; + this._sunday = item.sunday || []; + } else { + this._name = ""; + this._icon = ""; + this._monday = []; + this._tuesday = []; + this._wednesday = []; + this._thursday = []; + this._friday = []; + this._saturday = []; + this._sunday = []; + } + } + + public focus() { + this.updateComplete.then(() => + ( + this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement + )?.focus() + ); + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + const nameInvalid = !this._name || this._name.trim() === ""; + + return html` +
+ + +
+
+ `; + } + + public willUpdate(changedProps: PropertyValues): void { + super.willUpdate(changedProps); + if (!this.calendar) { + return; + } + + if ( + changedProps.has("_sunday") || + changedProps.has("_monday") || + changedProps.has("_tuesday") || + changedProps.has("_wednesday") || + changedProps.has("_thursday") || + changedProps.has("_friday") || + changedProps.has("_saturday") || + changedProps.has("calendar") + ) { + this.calendar.removeAllEventSources(); + this.calendar.addEventSource(this._events); + } + + const oldHass = changedProps.get("hass") as HomeAssistant; + + if (oldHass && oldHass.language !== this.hass.language) { + this.calendar.setOption("locale", this.hass.language); + } + } + + protected firstUpdated(): void { + const config: CalendarOptions = { + ...defaultFullCalendarConfig, + locale: this.hass.language, + eventTimeFormat: { + hour: useAmPm(this.hass.locale) ? "numeric" : "2-digit", + minute: useAmPm(this.hass.locale) ? "numeric" : "2-digit", + hour12: useAmPm(this.hass.locale), + }, + }; + + config.eventClick = (info) => this._handleEventClick(info); + config.select = (info) => this._handleSelect(info); + config.eventResize = (info) => this._handleEventResize(info); + config.eventDrop = (info) => this._handleEventDrop(info); + + this.calendar = new Calendar( + this.shadowRoot!.getElementById("calendar")!, + config + ); + + this.calendar!.render(); + + // Update size after fully rendered to avoid a bad render in the more info + this.updateComplete.then(() => + window.setTimeout(() => { + this.calendar!.updateSize(); + }, 500) + ); + } + + private get _events() { + const events: any[] = []; + const currentDay = new Date().getDay(); + + for (const [i, day] of weekdays.entries()) { + if (!this[`_${day}`].length) { + continue; + } + + this[`_${day}`].forEach((item: ScheduleDay, index: number) => { + const distance = i - currentDay; + + const start = new Date(); + start.setDate(start.getDate() + distance); + start.setHours( + parseInt(item.from.slice(0, 2)), + parseInt(item.from.slice(-2)) + ); + + const end = new Date(); + end.setDate(end.getDate() + distance); + end.setHours( + parseInt(item.to.slice(0, 2)), + parseInt(item.to.slice(-2)) + ); + + events.push({ + id: `${day}-${index}`, + start: start.toISOString(), + end: end.toISOString(), + }); + }); + } + + return events; + } + + private _handleSelect(info: { start: Date; end: Date }) { + const { start, end } = info; + + if (start.getDay() !== end.getDay()) { + this.calendar!.unselect(); + return; + } + + const day = weekdays[start.getDay()]; + const value = [...this[`_${day}`]]; + const newValue = { ...this._item }; + + value.push({ + from: formatTime24h(start), + to: formatTime24h(end), + }); + + newValue[day] = value; + + fireEvent(this, "value-changed", { + value: newValue, + }); + } + + private _handleEventResize(info: any) { + const { id, start, end } = info.event; + + if (start.getDay() !== end.getDay()) { + info.revert(); + return; + } + + const [day, index] = id.split("-"); + const value = this[`_${day}`][parseInt(index)]; + const newValue = { ...this._item }; + + newValue[day][index] = { + from: value.from, + to: formatTime24h(end), + }; + + fireEvent(this, "value-changed", { + value: newValue, + }); + } + + private _handleEventDrop(info: any) { + const { id, start, end } = info.event; + + if (start.getDay() !== end.getDay()) { + info.revert(); + return; + } + + const [day, index] = id.split("-"); + const newDay = weekdays[start.getDay()]; + const newValue = { ...this._item }; + + const event = { + from: formatTime24h(start), + to: formatTime24h(end), + }; + + if (newDay === day) { + newValue[day][index] = event; + } else { + newValue[day].splice(index, 1); + const value = [...this[`_${newDay}`]]; + value.push(event); + newValue[newDay] = value; + } + + fireEvent(this, "value-changed", { + value: newValue, + }); + } + + private _handleEventClick(info: any) { + const [day, index] = info.event.id.split("-"); + const value = [...this[`_${day}`]]; + + const newValue = { ...this._item }; + value.splice(parseInt(index), 1); + newValue[day] = value; + + fireEvent(this, "value-changed", { + value: newValue, + }); + } + + private _valueChanged(ev: CustomEvent) { + if (!this.new && !this._item) { + return; + } + + ev.stopPropagation(); + const configValue = (ev.target as any).configValue; + const value = ev.detail?.value || (ev.target as any).value; + if (this[`_${configValue}`] === value) { + return; + } + const newValue = { ...this._item }; + if (!value) { + delete newValue[configValue]; + } else { + newValue[configValue] = value; + } + fireEvent(this, "value-changed", { + value: newValue, + }); + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + css` + ${unsafeCSS(fullcalendarStyle)} + ${unsafeCSS(timegridStyle)} + .form { + color: var(--primary-text-color); + } + + ha-textfield { + display: block; + margin: 8px 0; + } + + #calendar { + margin: 8px 0; + height: 450px; + width: 100%; + } + .fc-scroller { + overflow-x: visible !important; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-schedule-form": HaScheduleForm; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index c7afc5ae3d..9e5b4d5a4e 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1538,7 +1538,8 @@ "input_button": "Button", "input_datetime": "Date and/or time", "counter": "Counter", - "timer": "Timer" + "timer": "Timer", + "schedule": "Schedule" }, "picker": { "headers": { diff --git a/yarn.lock b/yarn.lock index 69f98fe8bf..9aeaa205b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1708,7 +1708,7 @@ __metadata: languageName: node linkType: hard -"@fullcalendar/daygrid@npm:5.9.0": +"@fullcalendar/daygrid@npm:5.9.0, @fullcalendar/daygrid@npm:~5.9.0": version: 5.9.0 resolution: "@fullcalendar/daygrid@npm:5.9.0" dependencies: @@ -1738,6 +1738,17 @@ __metadata: languageName: node linkType: hard +"@fullcalendar/timegrid@npm:5.9.0": + version: 5.9.0 + resolution: "@fullcalendar/timegrid@npm:5.9.0" + dependencies: + "@fullcalendar/common": ~5.9.0 + "@fullcalendar/daygrid": ~5.9.0 + tslib: ^2.1.0 + checksum: dedef1e1147cd17aa277b159c806e0f927715d67c513d940bec61cb97bfdf97c71b43c03166d8442e9683e2d7d6f03d81619a694de84e04e5995b9e8ef3585b9 + languageName: node + linkType: hard + "@gfx/zopfli@npm:^1.0.9": version: 1.0.11 resolution: "@gfx/zopfli@npm:1.0.11" @@ -9030,6 +9041,7 @@ fsevents@^1.2.7: "@fullcalendar/daygrid": 5.9.0 "@fullcalendar/interaction": 5.9.0 "@fullcalendar/list": 5.9.0 + "@fullcalendar/timegrid": 5.9.0 "@koa/cors": ^3.1.0 "@lit-labs/motion": ^1.0.2 "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch" From 651cafc4641dcea278a5e937b793d4df8f17499a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 13 Aug 2022 20:57:28 +0200 Subject: [PATCH 024/134] Allow Markdown and description placeholder usage in data field descriptions (#13377) Co-authored-by: Zack Barett --- src/dialogs/config-flow/show-dialog-config-flow.ts | 8 ++++++-- src/dialogs/config-flow/show-dialog-data-entry-flow.ts | 2 +- src/dialogs/config-flow/show-dialog-options-flow.ts | 8 ++++++-- src/dialogs/config-flow/step-flow-form.ts | 6 +++--- src/panels/config/repairs/show-dialog-repair-flow.ts | 8 ++++++-- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/dialogs/config-flow/show-dialog-config-flow.ts b/src/dialogs/config-flow/show-dialog-config-flow.ts index e987d6be40..c1d56c6d73 100644 --- a/src/dialogs/config-flow/show-dialog-config-flow.ts +++ b/src/dialogs/config-flow/show-dialog-config-flow.ts @@ -89,9 +89,13 @@ export const showConfigFlowDialog = ( }, renderShowFormStepFieldHelper(hass, step, field) { - return hass.localize( - `component.${step.handler}.config.step.${step.step_id}.data_description.${field.name}` + const description = hass.localize( + `component.${step.handler}.config.step.${step.step_id}.data_description.${field.name}`, + step.description_placeholders ); + return description + ? html`` + : ""; }, renderShowFormStepFieldError(hass, step, error) { diff --git a/src/dialogs/config-flow/show-dialog-data-entry-flow.ts b/src/dialogs/config-flow/show-dialog-data-entry-flow.ts index 380faaf064..1c5f407c69 100644 --- a/src/dialogs/config-flow/show-dialog-data-entry-flow.ts +++ b/src/dialogs/config-flow/show-dialog-data-entry-flow.ts @@ -61,7 +61,7 @@ export interface FlowConfig { hass: HomeAssistant, step: DataEntryFlowStepForm, field: HaFormSchema - ): string; + ): TemplateResult | string; renderShowFormStepFieldError( hass: HomeAssistant, diff --git a/src/dialogs/config-flow/show-dialog-options-flow.ts b/src/dialogs/config-flow/show-dialog-options-flow.ts index 560d9a2946..b043f1390c 100644 --- a/src/dialogs/config-flow/show-dialog-options-flow.ts +++ b/src/dialogs/config-flow/show-dialog-options-flow.ts @@ -93,9 +93,13 @@ export const showOptionsFlowDialog = ( }, renderShowFormStepFieldHelper(hass, step, field) { - return hass.localize( - `component.${configEntry.domain}.options.step.${step.step_id}.data_description.${field.name}` + const description = hass.localize( + `component.${configEntry.domain}.options.step.${step.step_id}.data_description.${field.name}`, + step.description_placeholders ); + return description + ? html`` + : ""; }, renderShowFormStepFieldError(hass, step, error) { diff --git a/src/dialogs/config-flow/step-flow-form.ts b/src/dialogs/config-flow/step-flow-form.ts index 6b1926e31f..55996eb494 100644 --- a/src/dialogs/config-flow/step-flow-form.ts +++ b/src/dialogs/config-flow/step-flow-form.ts @@ -10,12 +10,12 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; +import "../../components/ha-alert"; import "../../components/ha-circular-progress"; import { computeInitialHaFormData } from "../../components/ha-form/compute-initial-ha-form-data"; -import type { HaFormSchema } from "../../components/ha-form/types"; import "../../components/ha-form/ha-form"; +import type { HaFormSchema } from "../../components/ha-form/types"; import "../../components/ha-markdown"; -import "../../components/ha-alert"; import type { DataEntryFlowStepForm } from "../../data/data_entry_flow"; import type { HomeAssistant } from "../../types"; import type { FlowConfig } from "./show-dialog-data-entry-flow"; @@ -167,7 +167,7 @@ class StepFlowForm extends LitElement { private _labelCallback = (field: HaFormSchema): string => this.flowConfig.renderShowFormStepFieldLabel(this.hass, this.step, field); - private _helperCallback = (field: HaFormSchema): string => + private _helperCallback = (field: HaFormSchema): string | TemplateResult => this.flowConfig.renderShowFormStepFieldHelper(this.hass, this.step, field); private _errorCallback = (error: string) => diff --git a/src/panels/config/repairs/show-dialog-repair-flow.ts b/src/panels/config/repairs/show-dialog-repair-flow.ts index a6fc3b73f6..2b1850a1c9 100644 --- a/src/panels/config/repairs/show-dialog-repair-flow.ts +++ b/src/panels/config/repairs/show-dialog-repair-flow.ts @@ -100,11 +100,15 @@ export const showRepairsFlowDialog = ( }, renderShowFormStepFieldHelper(hass, step, field) { - return hass.localize( + const description = hass.localize( `component.${issue.domain}.issues.${ issue.translation_key || issue.issue_id - }.fix_flow.step.${step.step_id}.data_description.${field.name}` + }.fix_flow.step.${step.step_id}.data_description.${field.name}`, + step.description_placeholders ); + return description + ? html`` + : ""; }, renderShowFormStepFieldError(hass, step, error) { From e0448be24d0022568eb9e960563935651a23c5f0 Mon Sep 17 00:00:00 2001 From: alvinchen1 <26409535+alvinchen1@users.noreply.github.com> Date: Sat, 13 Aug 2022 17:29:33 -0400 Subject: [PATCH 025/134] Add initial field to the helper input_number in UI (#13378) --- .../config/helpers/forms/ha-input_number-form.ts | 13 +++++++++++++ src/translations/en.json | 1 + 2 files changed, 14 insertions(+) diff --git a/src/panels/config/helpers/forms/ha-input_number-form.ts b/src/panels/config/helpers/forms/ha-input_number-form.ts index 759d96a507..9fa309e54a 100644 --- a/src/panels/config/helpers/forms/ha-input_number-form.ts +++ b/src/panels/config/helpers/forms/ha-input_number-form.ts @@ -26,6 +26,8 @@ class HaInputNumberForm extends LitElement { @state() private _min?: number; + @state() private _initial?: number; + @state() private _mode?: string; @state() private _step?: number; @@ -42,6 +44,7 @@ class HaInputNumberForm extends LitElement { this._min = item.min ?? 0; this._mode = item.mode || "slider"; this._step = item.step ?? 1; + this._initial = item.initial ?? 0; this._unit_of_measurement = item.unit_of_measurement; } else { this._item = { @@ -54,6 +57,7 @@ class HaInputNumberForm extends LitElement { this._min = 0; this._mode = "slider"; this._step = 1; + this._initial = 0; } } @@ -112,6 +116,15 @@ class HaInputNumberForm extends LitElement { "ui.dialogs.helper_settings.input_number.max" )} > + ${this.hass.userData?.showAdvanced ? html`
diff --git a/src/translations/en.json b/src/translations/en.json index 9e5b4d5a4e..cf62a4996c 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -916,6 +916,7 @@ "pattern": "Regex pattern for client-side validation" }, "input_number": { + "initial": "Initial value", "min": "Minimum value", "max": "Maximum value", "mode": "Display mode", From a989eb1c66731533c0011e5648de2c7b465eaf69 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Sat, 13 Aug 2022 20:56:15 -0500 Subject: [PATCH 026/134] Fix Target Selector (#13380) --- src/components/ha-selector/ha-selector-target.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ha-selector/ha-selector-target.ts b/src/components/ha-selector/ha-selector-target.ts index 70443a097a..606e8d1702 100644 --- a/src/components/ha-selector/ha-selector-target.ts +++ b/src/components/ha-selector/ha-selector-target.ts @@ -64,7 +64,8 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) { super.updated(changedProperties); if ( changedProperties.has("selector") && - this.selector.target.device?.integration && + (this.selector.target.device?.integration || + this.selector.target.entity?.integration) && !this._entitySources ) { fetchEntitySourcesWithCache(this.hass).then((sources) => { From 3aa813e39192b1546590bd78340053871aa894c2 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Tue, 16 Aug 2022 22:10:18 +0800 Subject: [PATCH 027/134] Bump hls.js to v1.2.0 (#13383) --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index c2a91ffd2e..188181f117 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "deep-freeze": "^0.0.1", "fuse.js": "^6.0.0", "google-timezones-json": "^1.0.2", - "hls.js": "^1.1.5", + "hls.js": "^1.2.0", "home-assistant-js-websocket": "^7.1.0", "idb-keyval": "^5.1.3", "intl-messageformat": "^9.9.1", diff --git a/yarn.lock b/yarn.lock index 9aeaa205b9..6640f93a24 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8993,10 +8993,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"hls.js@npm:^1.1.5": - version: 1.1.5 - resolution: "hls.js@npm:1.1.5" - checksum: 7363eb8be6ad35be73fe3497a2fa1d493b42c76382ccc6b05f298394545eb75d6c466fb233748f3226859360d7aeea99bc5ea3e372ec074c6c2e354dcc5f6435 +"hls.js@npm:^1.2.0": + version: 1.2.0 + resolution: "hls.js@npm:1.2.0" + checksum: 8c8b76533c90cefbf503875299ecb1193fa02124c1ec50966061fc5500d727bd85cce78666fc8a909ea31ce0e2dfc37062a802163a01ac83c0f5ac1d9fd5475f languageName: node linkType: hard @@ -9148,7 +9148,7 @@ fsevents@^1.2.7: gulp-merge-json: ^1.3.1 gulp-rename: ^2.0.0 gulp-zopfli-green: ^3.0.1 - hls.js: ^1.1.5 + hls.js: ^1.2.0 home-assistant-js-websocket: ^7.1.0 html-minifier: ^4.0.0 husky: ^1.3.1 From 089f5314922b16c1a353dbe0ea49a6d00c66e47f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 16 Aug 2022 17:17:18 -0400 Subject: [PATCH 028/134] Add redirect_uri to each interaction with login flow (#13389) --- src/auth/ha-auth-flow.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/auth/ha-auth-flow.ts b/src/auth/ha-auth-flow.ts index 6f4e2a1367..a189cf9ab3 100644 --- a/src/auth/ha-auth-flow.ts +++ b/src/auth/ha-auth-flow.ts @@ -359,7 +359,11 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) { } this._submitting = true; - const postData = { ...this._stepData, client_id: this.clientId }; + const postData = { + ...this._stepData, + client_id: this.clientId, + redirect_uri: this.redirectUri, + }; try { const response = await fetch(`/auth/login_flow/${this._step.flow_id}`, { From 33ce27de022bdbdc492bd88c7cfd43892e69427f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 16 Aug 2022 17:18:38 -0400 Subject: [PATCH 029/134] Bumped version to 20220816.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2666a9c73f..ccba07572c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20220802.0" +version = "20220816.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From eb4dbef61085088d17901a3ad8283d348780229c Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Wed, 17 Aug 2022 08:09:07 -0400 Subject: [PATCH 030/134] Bump husky & lint-staged and prevent translation edits (#13392) --- .husky/pre-commit | 4 + lint-staged.config.js | 11 +- package.json | 13 +- yarn.lock | 592 ++++++++++++++++++------------------------ 4 files changed, 268 insertions(+), 352 deletions(-) create mode 100755 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000000..b088d5ae66 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +lint-staged --relative --shell "/bin/bash" diff --git a/lint-staged.config.js b/lint-staged.config.js index 187d2b1d5b..cfec1fbeac 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -1,4 +1,11 @@ module.exports = { - "*.{js,ts}": 'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix', - "!(/translations)*.{js,ts,json,css,md,html}": "prettier --write", + "*.{js,ts}": [ + "prettier --write", + 'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix', + ], + "!(/translations)*.{json,css,md,html}": "prettier --write", + "translations/*/*.json": (files) => + 'printf "%s\n" "These files should not be modified. Instead, make the necessary modifications in src/translations/en.json. Please see translations/README.md for details." ' + + files.join(" ") + + " >&2 && exit 1", }; diff --git a/package.json b/package.json index 188181f117..d393d2b221 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,9 @@ "lint:lit": "lit-analyzer \"**/src/**/*.ts\" --format markdown --outFile result.md", "lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types", "format": "yarn run format:eslint && yarn run format:prettier", + "postinstall": "husky install", + "prepack": "pinst --disable", + "postpack": "pinst --enable", "test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.js \"test/**/*.ts\"" }, "author": "Paulus Schoutsen (http://paulusschoutsen.nl)", @@ -203,9 +206,9 @@ "gulp-rename": "^2.0.0", "gulp-zopfli-green": "^3.0.1", "html-minifier": "^4.0.0", - "husky": "^1.3.1", + "husky": "^8.0.1", "instant-mocha": "^1.3.1", - "lint-staged": "^11.1.2", + "lint-staged": "^13.0.3", "lit-analyzer": "^1.2.1", "lodash.template": "^4.5.0", "magic-string": "^0.25.7", @@ -214,6 +217,7 @@ "mocha": "^8.4.0", "object-hash": "^2.0.3", "open": "^7.0.4", + "pinst": "^3.0.0", "prettier": "^2.4.1", "require-dir": "^1.2.0", "rollup": "^2.8.2", @@ -246,11 +250,6 @@ "@lit/reactive-element": "1.2.1" }, "main": "src/home-assistant.js", - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, "prettier": { "trailingComma": "es5", "arrowParens": "always" diff --git a/yarn.lock b/yarn.lock index 6640f93a24..ad01698c29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27,7 +27,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.14.5, @babel/code-frame@npm:^7.5.5": +"@babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.14.5, @babel/code-frame@npm:^7.5.5": version: 7.14.5 resolution: "@babel/code-frame@npm:7.14.5" dependencies: @@ -3984,13 +3984,6 @@ __metadata: languageName: node linkType: hard -"@types/parse-json@npm:^4.0.0": - version: 4.0.0 - resolution: "@types/parse-json@npm:4.0.0" - checksum: fd6bce2b674b6efc3db4c7c3d336bd70c90838e8439de639b909ce22f3720d21344f52427f1d9e57b265fcb7f6c018699b99e5e0c208a1a4823014269a6bf35b - languageName: node - linkType: hard - "@types/parse5@npm:^5.0.3": version: 5.0.3 resolution: "@types/parse5@npm:5.0.3" @@ -5048,6 +5041,13 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^6.0.0": + version: 6.1.0 + resolution: "ansi-styles@npm:6.1.0" + checksum: 7a7f8528c07a9d20c3a92bccd2b6bc3bb4d26e5cb775c02826921477377bd495d615d61f710d56216344b6238d1d11ef2b0348e146c5b128715578bfb3217229 + languageName: node + linkType: hard + "ansi-wrap@npm:0.1.0, ansi-wrap@npm:^0.1.0": version: 0.1.0 resolution: "ansi-wrap@npm:0.1.0" @@ -5681,7 +5681,7 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.1, braces@npm:~3.0.2": +"braces@npm:^3.0.2, braces@npm:~3.0.2": version: 3.0.2 resolution: "braces@npm:3.0.2" dependencies: @@ -5864,31 +5864,6 @@ __metadata: languageName: node linkType: hard -"caller-callsite@npm:^2.0.0": - version: 2.0.0 - resolution: "caller-callsite@npm:2.0.0" - dependencies: - callsites: ^2.0.0 - checksum: b685e9d126d9247b320cfdfeb3bc8da0c4be28d8fb98c471a96bc51aab3130099898a2fe3bf0308f0fe048d64c37d6d09f563958b9afce1a1e5e63d879c128a2 - languageName: node - linkType: hard - -"caller-path@npm:^2.0.0": - version: 2.0.0 - resolution: "caller-path@npm:2.0.0" - dependencies: - caller-callsite: ^2.0.0 - checksum: 3e12ccd0c71ec10a057aac69e3ec175b721ca858c640df021ef0d25999e22f7c1d864934b596b7d47038e9b56b7ec315add042abbd15caac882998b50102fb12 - languageName: node - linkType: hard - -"callsites@npm:^2.0.0": - version: 2.0.0 - resolution: "callsites@npm:2.0.0" - checksum: be2f67b247df913732b7dec1ec0bbfcdbaea263e5a95968b19ec7965affae9496b970e3024317e6d4baa8e28dc6ba0cec03f46fdddc2fdcc51396600e53c2623 - languageName: node - linkType: hard - "callsites@npm:^3.0.0": version: 3.0.0 resolution: "callsites@npm:3.0.0" @@ -5977,7 +5952,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.1": +"chalk@npm:^4.0.0, chalk@npm:^4.1.0": version: 4.1.1 resolution: "chalk@npm:4.1.1" dependencies: @@ -6085,13 +6060,6 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^2.0.0": - version: 2.0.0 - resolution: "ci-info@npm:2.0.0" - checksum: 3b374666a85ea3ca43fa49aa3a048d21c9b475c96eb13c133505d2324e7ae5efd6a454f41efe46a152269e9b6a00c9edbe63ec7fa1921957165aae16625acd67 - languageName: node - linkType: hard - "ci-info@npm:^3.1.1": version: 3.2.0 resolution: "ci-info@npm:3.2.0" @@ -6153,6 +6121,16 @@ __metadata: languageName: node linkType: hard +"cli-truncate@npm:^3.1.0": + version: 3.1.0 + resolution: "cli-truncate@npm:3.1.0" + dependencies: + slice-ansi: ^5.0.0 + string-width: ^5.0.0 + checksum: c3243e41974445691c63f8b405df1d5a24049dc33d324fe448dc572e561a7b772ae982692900b1a5960901cc4fc7def25a629b9c69a4208ee89d12ab3332617a + languageName: node + linkType: hard + "clipboardy@npm:1.2.3": version: 1.2.3 resolution: "clipboardy@npm:1.2.3" @@ -6333,17 +6311,17 @@ __metadata: languageName: node linkType: hard -"colorette@npm:^1.2.1, colorette@npm:^1.2.2": +"colorette@npm:^1.2.1": version: 1.2.2 resolution: "colorette@npm:1.2.2" checksum: 69fec14ddaedd0f5b00e4bae40dc4bc61f7050ebdc82983a595d6fd64e650b9dc3c033fff378775683138e992e0ddd8717ac7c7cec4d089679dcfbe3cd921b04 languageName: node linkType: hard -"colorette@npm:^2.0.10": - version: 2.0.12 - resolution: "colorette@npm:2.0.12" - checksum: 1df990c6749826694756ecd05a771fb70fd426cf2b264cf496fb93a621fb4554fabfa8adde0923421f3e38b7265d546f6a46caf833f2d7facf45a40042b510b3 +"colorette@npm:^2.0.10, colorette@npm:^2.0.16, colorette@npm:^2.0.17": + version: 2.0.19 + resolution: "colorette@npm:2.0.19" + checksum: 888cf5493f781e5fcf54ce4d49e9d7d698f96ea2b2ef67906834bb319a392c667f9ec69f4a10e268d2946d13a9503d2d19b3abaaaf174e3451bfe91fb9d82427 languageName: node linkType: hard @@ -6385,13 +6363,20 @@ __metadata: languageName: node linkType: hard -"commander@npm:^7.0.0, commander@npm:^7.2.0": +"commander@npm:^7.0.0": version: 7.2.0 resolution: "commander@npm:7.2.0" checksum: 53501cbeee61d5157546c0bef0fedb6cdfc763a882136284bed9a07225f09a14b82d2a84e7637edfd1a679fb35ed9502fd58ef1d091e6287f60d790147f68ddc languageName: node linkType: hard +"commander@npm:^9.3.0": + version: 9.4.0 + resolution: "commander@npm:9.4.0" + checksum: a322de584a6ccd1ea83c24f6a660e52d16ffbe2613fcfbb8d2cc68bc9dec637492456d754fe8bb5b039ad843ed8e04fb0b107e581a75f62cde9e1a0ab1546e09 + languageName: node + linkType: hard + "common-tags@npm:^1.8.0": version: 1.8.0 resolution: "common-tags@npm:1.8.0" @@ -6596,31 +6581,6 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:^5.0.7": - version: 5.2.0 - resolution: "cosmiconfig@npm:5.2.0" - dependencies: - import-fresh: ^2.0.0 - is-directory: ^0.3.1 - js-yaml: ^3.13.0 - parse-json: ^4.0.0 - checksum: 697f235e6134e7e7b545b5b27bde8d7a3b182cad262f5b0a5ffbd1b63070788521c3683da64c40a011eab89156d6850605a41cab5df695b10676cb27d51aabbb - languageName: node - linkType: hard - -"cosmiconfig@npm:^7.0.0": - version: 7.0.0 - resolution: "cosmiconfig@npm:7.0.0" - dependencies: - "@types/parse-json": ^4.0.0 - import-fresh: ^3.2.1 - parse-json: ^5.0.0 - path-type: ^4.0.0 - yaml: ^1.10.0 - checksum: 6801feaa0249e9b9fdde5b3d70dc33b4f9c69095bec94d67e3fe08b66eac24dc7e2099f053597cfbc94b743de269aa5d2cfa7da3fde765433423b06bd122941a - languageName: node - linkType: hard - "crelt@npm:^1.0.5": version: 1.0.5 resolution: "crelt@npm:1.0.5" @@ -6646,19 +6606,6 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^6.0.0": - version: 6.0.5 - resolution: "cross-spawn@npm:6.0.5" - dependencies: - nice-try: ^1.0.4 - path-key: ^2.0.1 - semver: ^5.5.0 - shebang-command: ^1.2.0 - which: ^1.2.9 - checksum: f893bb0d96cd3d5751d04e67145bdddf25f99449531a72e82dcbbd42796bbc8268c1076c6b3ea51d4d455839902804b94bc45dfb37ecbb32ea8e54a6741c3ab9 - languageName: node - linkType: hard - "cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" @@ -6716,15 +6663,15 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1": - version: 4.3.2 - resolution: "debug@npm:4.3.2" +"debug@npm:4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.4": + version: 4.3.4 + resolution: "debug@npm:4.3.4" dependencies: ms: 2.1.2 peerDependenciesMeta: supports-color: optional: true - checksum: 820ea160e267e23c953c9ed87e7ad93494d8cda2f7349af5e7e3bb236d23707ee3022f477d5a7d2ee86ef2bf7d60aa9ab22d1f58080d7deb9dccd073585e1e43 + checksum: 3dbad3f94ea64f34431a9cbf0bafb61853eda57bff2880036153438f50fb5a84f27683ba0d8e5426bf41a8c6ff03879488120cf5b3a761e77953169c0600a708 languageName: node linkType: hard @@ -7104,6 +7051,13 @@ __metadata: languageName: node linkType: hard +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 7d00d7cd8e49b9afa762a813faac332dee781932d6f2c848dc348939c4253f1d4564341b7af1d041853bc3f32c2ef141b58e0a4d9862c17a7f08f68df1e0f1ed + languageName: node + linkType: hard + "ee-first@npm:1.1.1": version: 1.1.1 resolution: "ee-first@npm:1.1.1" @@ -7143,6 +7097,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 8487182da74aabd810ac6d6f1994111dfc0e331b01271ae01ec1eb0ad7b5ecc2bbbbd2f053c05cb55a1ac30449527d819bbfbf0e3de1023db308cbcb47f86601 + languageName: node + linkType: hard + "emojis-list@npm:^3.0.0": version: 3.0.0 resolution: "emojis-list@npm:3.0.0" @@ -7196,7 +7157,7 @@ __metadata: languageName: node linkType: hard -"enquirer@npm:^2.3.5, enquirer@npm:^2.3.6": +"enquirer@npm:^2.3.5": version: 2.3.6 resolution: "enquirer@npm:2.3.6" dependencies: @@ -7771,21 +7732,6 @@ __metadata: languageName: node linkType: hard -"execa@npm:^1.0.0": - version: 1.0.0 - resolution: "execa@npm:1.0.0" - dependencies: - cross-spawn: ^6.0.0 - get-stream: ^4.0.0 - is-stream: ^1.1.0 - npm-run-path: ^2.0.0 - p-finally: ^1.0.0 - signal-exit: ^3.0.0 - strip-eof: ^1.0.0 - checksum: ddf1342c1c7d02dd93b41364cd847640f6163350d9439071abf70bf4ceb1b9b2b2e37f54babb1d8dc1df8e0d8def32d0e81e74a2e62c3e1d70c303eb4c306bc4 - languageName: node - linkType: hard - "execa@npm:^5.0.0": version: 5.0.0 resolution: "execa@npm:5.0.0" @@ -7803,6 +7749,23 @@ __metadata: languageName: node linkType: hard +"execa@npm:^6.1.0": + version: 6.1.0 + resolution: "execa@npm:6.1.0" + dependencies: + cross-spawn: ^7.0.3 + get-stream: ^6.0.1 + human-signals: ^3.0.1 + is-stream: ^3.0.0 + merge-stream: ^2.0.0 + npm-run-path: ^5.1.0 + onetime: ^6.0.0 + signal-exit: ^3.0.7 + strip-final-newline: ^3.0.0 + checksum: 1a4af799839134f5c72eb63d525b87304c1114a63aa71676c91d57ccef2e26f2f53e14c11384ab11c4ec479be1efa83d11c8190e00040355c2c5c3364327fa8e + languageName: node + linkType: hard + "exif-parser@npm:^0.1.12": version: 0.1.12 resolution: "exif-parser@npm:0.1.12" @@ -8515,13 +8478,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"get-stdin@npm:^6.0.0": - version: 6.0.0 - resolution: "get-stdin@npm:6.0.0" - checksum: 593f6fb4fff4c8d49ec93a07c430c1edc6bd4fe7e429d222b5da2f367276a98809af9e90467ad88a2d83722ff95b9b35bbaba02b56801421c5e3668173fe12b4 - languageName: node - linkType: hard - "get-stream@npm:^3.0.0": version: 3.0.0 resolution: "get-stream@npm:3.0.0" @@ -8529,19 +8485,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"get-stream@npm:^4.0.0": - version: 4.1.0 - resolution: "get-stream@npm:4.1.0" - dependencies: - pump: ^3.0.0 - checksum: 443e1914170c15bd52ff8ea6eff6dfc6d712b031303e36302d2778e3de2506af9ee964d6124010f7818736dcfde05c04ba7ca6cc26883106e084357a17ae7d73 - languageName: node - linkType: hard - -"get-stream@npm:^6.0.0": - version: 6.0.0 - resolution: "get-stream@npm:6.0.0" - checksum: 587e6a93127f9991b494a566f4971cf7a2645dfa78034818143480a80587027bdd8826cdcf80d0eff4a4a19de0d231d157280f24789fc9cc31492e1dcc1290cf +"get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: e04ecece32c92eebf5b8c940f51468cd53554dcbb0ea725b2748be583c9523d00128137966afce410b9b051eb2ef16d657cd2b120ca8edafcf5a65e81af63cad languageName: node linkType: hard @@ -9151,14 +9098,14 @@ fsevents@^1.2.7: hls.js: ^1.2.0 home-assistant-js-websocket: ^7.1.0 html-minifier: ^4.0.0 - husky: ^1.3.1 + husky: ^8.0.1 idb-keyval: ^5.1.3 instant-mocha: ^1.3.1 intl-messageformat: ^9.9.1 js-yaml: ^4.1.0 leaflet: ^1.7.1 leaflet-draw: ^1.0.4 - lint-staged: ^11.1.2 + lint-staged: ^13.0.3 lit: ^2.1.2 lit-analyzer: ^1.2.1 lit-vaadin-helpers: ^0.3.0 @@ -9172,6 +9119,7 @@ fsevents@^1.2.7: node-vibrant: 3.2.1-alpha.1 object-hash: ^2.0.3 open: ^7.0.4 + pinst: ^3.0.0 prettier: ^2.4.1 proxy-polyfill: ^0.3.2 punycode: ^2.1.1 @@ -9412,6 +9360,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"human-signals@npm:^3.0.1": + version: 3.0.1 + resolution: "human-signals@npm:3.0.1" + checksum: f252a7769c8094a5c9dc6772816bdb417b188820b04c8b42d0fc468e03a0ba905b1dd07afabe9385cc83504af1ccc2b985cd1e4aeeeb8e0029896c5af2e6f354 + languageName: node + linkType: hard + "humanize-ms@npm:^1.2.1": version: 1.2.1 resolution: "humanize-ms@npm:1.2.1" @@ -9421,23 +9376,12 @@ fsevents@^1.2.7: languageName: node linkType: hard -"husky@npm:^1.3.1": - version: 1.3.1 - resolution: "husky@npm:1.3.1" - dependencies: - cosmiconfig: ^5.0.7 - execa: ^1.0.0 - find-up: ^3.0.0 - get-stdin: ^6.0.0 - is-ci: ^2.0.0 - pkg-dir: ^3.0.0 - please-upgrade-node: ^3.1.1 - read-pkg: ^4.0.1 - run-node: ^1.0.0 - slash: ^2.0.0 +"husky@npm:^8.0.1": + version: 8.0.1 + resolution: "husky@npm:8.0.1" bin: - husky-upgrade: ./lib/upgrader/bin.js - checksum: d9f8402428a7145278aed41f11765f8a0744b20146f0ec5e295cc8fec7c216438f490e86ab0158a305059dd442bb2a6c457fec347237dcb0d0b3986c2f713716 + husky: lib/bin.js + checksum: 943a73a13d0201318fd30e83d299bb81d866bd245b69e6277804c3b462638dc1921694cb94c2b8c920a4a187060f7d6058d3365152865406352e934c5fff70dc languageName: node linkType: hard @@ -9512,16 +9456,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"import-fresh@npm:^2.0.0": - version: 2.0.0 - resolution: "import-fresh@npm:2.0.0" - dependencies: - caller-path: ^2.0.0 - resolve-from: ^3.0.0 - checksum: 610255f9753cc6775df00be08e9f43691aa39f7703e3636c45afe22346b8b545e600ccfe100c554607546fc8e861fa149a0d1da078c8adedeea30fff326eef79 - languageName: node - linkType: hard - "import-fresh@npm:^3.0.0, import-fresh@npm:^3.2.1": version: 3.3.0 resolution: "import-fresh@npm:3.3.0" @@ -9799,17 +9733,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"is-ci@npm:^2.0.0": - version: 2.0.0 - resolution: "is-ci@npm:2.0.0" - dependencies: - ci-info: ^2.0.0 - bin: - is-ci: bin.js - checksum: 77b869057510f3efa439bbb36e9be429d53b3f51abd4776eeea79ab3b221337fe1753d1e50058a9e2c650d38246108beffb15ccfd443929d77748d8c0cc90144 - languageName: node - linkType: hard - "is-core-module@npm:^2.2.0, is-core-module@npm:^2.4.0, is-core-module@npm:^2.6.0": version: 2.7.0 resolution: "is-core-module@npm:2.7.0" @@ -9866,13 +9789,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"is-directory@npm:^0.3.1": - version: 0.3.1 - resolution: "is-directory@npm:0.3.1" - checksum: dce9a9d3981e38f2ded2a80848734824c50ee8680cd09aa477bef617949715cfc987197a2ca0176c58a9fb192a1a0d69b535c397140d241996a609d5906ae524 - languageName: node - linkType: hard - "is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": version: 2.2.1 resolution: "is-docker@npm:2.2.1" @@ -9928,6 +9844,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"is-fullwidth-code-point@npm:^4.0.0": + version: 4.0.0 + resolution: "is-fullwidth-code-point@npm:4.0.0" + checksum: 8ae89bf5057bdf4f57b346fb6c55e9c3dd2549983d54191d722d5c739397a903012cc41a04ee3403fd872e811243ef91a7c5196da7b5841dc6b6aae31a264a8d + languageName: node + linkType: hard + "is-function@npm:^1.0.1": version: 1.0.1 resolution: "is-function@npm:1.0.1" @@ -10152,6 +10075,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"is-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "is-stream@npm:3.0.0" + checksum: 172093fe99119ffd07611ab6d1bcccfe8bc4aa80d864b15f43e63e54b7abc71e779acd69afdb854c4e2a67fdc16ae710e370eda40088d1cfc956a50ed82d8f16 + languageName: node + linkType: hard + "is-string@npm:^1.0.5, is-string@npm:^1.0.7": version: 1.0.7 resolution: "is-string@npm:1.0.7" @@ -10179,13 +10109,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"is-unicode-supported@npm:^0.1.0": - version: 0.1.0 - resolution: "is-unicode-supported@npm:0.1.0" - checksum: a2aab86ee7712f5c2f999180daaba5f361bdad1efadc9610ff5b8ab5495b86e4f627839d085c6530363c6d6d4ecbde340fb8e54bdb83da4ba8e0865ed5513c52 - languageName: node - linkType: hard - "is-utf8@npm:^0.2.0, is-utf8@npm:^0.2.1": version: 0.2.1 resolution: "is-utf8@npm:0.2.1" @@ -10363,7 +10286,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"js-yaml@npm:^3.13.0, js-yaml@npm:^3.13.1": +"js-yaml@npm:^3.13.1": version: 3.13.1 resolution: "js-yaml@npm:3.13.1" dependencies: @@ -10411,13 +10334,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"json-parse-even-better-errors@npm:^2.3.0": - version: 2.3.1 - resolution: "json-parse-even-better-errors@npm:2.3.1" - checksum: 798ed4cf3354a2d9ccd78e86d2169515a0097a5c133337807cdf7f1fc32e1391d207ccfc276518cc1d7d8d4db93288b8a50ba4293d212ad1336e52a8ec0a941f - languageName: node - linkType: hard - "json-schema-traverse@npm:^0.4.1": version: 0.4.1 resolution: "json-schema-traverse@npm:0.4.1" @@ -10734,51 +10650,54 @@ fsevents@^1.2.7: languageName: node linkType: hard -"lines-and-columns@npm:^1.1.6": - version: 1.1.6 - resolution: "lines-and-columns@npm:1.1.6" - checksum: 198a5436b1fa5cf703bae719c01c686b076f0ad7e1aafd95a58d626cabff302dc0414822126f2f80b58a8c3d66cda8a7b6da064f27130f87e1d3506d6dfd0d68 +"lilconfig@npm:2.0.5": + version: 2.0.5 + resolution: "lilconfig@npm:2.0.5" + checksum: f7bb9e42656f06930ad04e583026f087508ae408d3526b8b54895e934eb2a966b7aafae569656f2c79a29fe6d779b3ec44ba577e80814734c8655d6f71cdf2d1 languageName: node linkType: hard -"lint-staged@npm:^11.1.2": - version: 11.1.2 - resolution: "lint-staged@npm:11.1.2" +"lint-staged@npm:^13.0.3": + version: 13.0.3 + resolution: "lint-staged@npm:13.0.3" dependencies: - chalk: ^4.1.1 - cli-truncate: ^2.1.0 - commander: ^7.2.0 - cosmiconfig: ^7.0.0 - debug: ^4.3.1 - enquirer: ^2.3.6 - execa: ^5.0.0 - listr2: ^3.8.2 - log-symbols: ^4.1.0 - micromatch: ^4.0.4 + cli-truncate: ^3.1.0 + colorette: ^2.0.17 + commander: ^9.3.0 + debug: ^4.3.4 + execa: ^6.1.0 + lilconfig: 2.0.5 + listr2: ^4.0.5 + micromatch: ^4.0.5 normalize-path: ^3.0.0 - please-upgrade-node: ^3.2.0 - string-argv: 0.3.1 - stringify-object: ^3.3.0 + object-inspect: ^1.12.2 + pidtree: ^0.6.0 + string-argv: ^0.3.1 + yaml: ^2.1.1 bin: lint-staged: bin/lint-staged.js - checksum: 0050d1836dda879c58561fa4efd100f5cd14fcbf8ee3fdeab7e89ec4219c019543bb5bf2442f760557ebe4bb8b7bfc56a9c98b9384acecfe0f8553f091723e36 + checksum: 53d585007df06e162febab6b0836b55016d902586a267823c8a1158529d8c742dc7297e523f7023dff02250bef3eb0d6934f4ec4f9961adfc2ebbed5f54162d0 languageName: node linkType: hard -"listr2@npm:^3.8.2": - version: 3.10.0 - resolution: "listr2@npm:3.10.0" +"listr2@npm:^4.0.5": + version: 4.0.5 + resolution: "listr2@npm:4.0.5" dependencies: cli-truncate: ^2.1.0 - colorette: ^1.2.2 + colorette: ^2.0.16 log-update: ^4.0.0 p-map: ^4.0.0 - rxjs: ^6.6.7 + rfdc: ^1.3.0 + rxjs: ^7.5.5 through: ^2.3.8 wrap-ansi: ^7.0.0 peerDependencies: enquirer: ">= 2.3.0 < 3" - checksum: 9dc1a896972462642fe55817b946174b4f6d067cfe1d9a323517e235ed745773526797f20ef65b346d244356690f929c763d06243e511c4ba6abdcffeb009efc + peerDependenciesMeta: + enquirer: + optional: true + checksum: 7af31851abe25969ef0581c6db808117e36af15b131401795182427769d9824f451ba9e8aff6ccd25b6a4f6c8796f816292caf08e5f1f9b1775e8e9c313dc6c5 languageName: node linkType: hard @@ -11052,16 +10971,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"log-symbols@npm:^4.1.0": - version: 4.1.0 - resolution: "log-symbols@npm:4.1.0" - dependencies: - chalk: ^4.1.0 - is-unicode-supported: ^0.1.0 - checksum: fce1497b3135a0198803f9f07464165e9eb83ed02ceb2273930a6f8a508951178d8cf4f0378e9d28300a2ed2bc49050995d2bd5f53ab716bb15ac84d58c6ef74 - languageName: node - linkType: hard - "log-update@npm:^4.0.0": version: 4.0.0 resolution: "log-update@npm:4.0.0" @@ -11291,13 +11200,13 @@ fsevents@^1.2.7: languageName: node linkType: hard -"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4": - version: 4.0.4 - resolution: "micromatch@npm:4.0.4" +"micromatch@npm:^4.0.2, micromatch@npm:^4.0.5": + version: 4.0.5 + resolution: "micromatch@npm:4.0.5" dependencies: - braces: ^3.0.1 - picomatch: ^2.2.3 - checksum: ef3d1c88e79e0a68b0e94a03137676f3324ac18a908c245a9e5936f838079fcc108ac7170a5fadc265a9c2596963462e402841406bda1a4bb7b68805601d631c + braces: ^3.0.2 + picomatch: ^2.3.1 + checksum: 02a17b671c06e8fefeeb6ef996119c1e597c942e632a21ef589154f23898c9c6a9858526246abb14f8bca6e77734aa9dcf65476fca47cedfb80d9577d52843fc languageName: node linkType: hard @@ -11349,6 +11258,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"mimic-fn@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-fn@npm:4.0.0" + checksum: 995dcece15ee29aa16e188de6633d43a3db4611bcf93620e7e62109ec41c79c0f34277165b8ce5e361205049766e371851264c21ac64ca35499acb5421c2ba56 + languageName: node + linkType: hard + "min-document@npm:^2.19.0": version: 2.19.0 resolution: "min-document@npm:2.19.0" @@ -11686,13 +11602,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"nice-try@npm:^1.0.4": - version: 1.0.5 - resolution: "nice-try@npm:1.0.5" - checksum: 0b4af3b5bb5d86c289f7a026303d192a7eb4417231fe47245c460baeabae7277bcd8fd9c728fb6bd62c30b3e15cd6620373e2cf33353b095d8b403d3e8a15aff - languageName: node - linkType: hard - "nise@npm:^5.0.4": version: 5.1.0 resolution: "nise@npm:5.1.0" @@ -11902,6 +11811,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"npm-run-path@npm:^5.1.0": + version: 5.1.0 + resolution: "npm-run-path@npm:5.1.0" + dependencies: + path-key: ^4.0.0 + checksum: dc184eb5ec239d6a2b990b43236845332ef12f4e0beaa9701de724aa797fe40b6bbd0157fb7639d24d3ab13f5d5cf22d223a19c6300846b8126f335f788bee66 + languageName: node + linkType: hard + "npmlog@npm:^4.0.2, npmlog@npm:^4.1.2": version: 4.1.2 resolution: "npmlog@npm:4.1.2" @@ -11946,10 +11864,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"object-inspect@npm:^1.11.0, object-inspect@npm:^1.9.0": - version: 1.12.0 - resolution: "object-inspect@npm:1.12.0" - checksum: 2b36d4001a9c921c6b342e2965734519c9c58c355822243c3207fbf0aac271f8d44d30d2d570d450b2cc6f0f00b72bcdba515c37827d2560e5f22b1899a31cf4 +"object-inspect@npm:^1.11.0, object-inspect@npm:^1.12.2, object-inspect@npm:^1.9.0": + version: 1.12.2 + resolution: "object-inspect@npm:1.12.2" + checksum: a534fc1b8534284ed71f25ce3a496013b7ea030f3d1b77118f6b7b1713829262be9e6243acbcb3ef8c626e2b64186112cb7f6db74e37b2789b9c789ca23048b2 languageName: node linkType: hard @@ -12092,6 +12010,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"onetime@npm:^6.0.0": + version: 6.0.0 + resolution: "onetime@npm:6.0.0" + dependencies: + mimic-fn: ^4.0.0 + checksum: 0846ce78e440841335d4e9182ef69d5762e9f38aa7499b19f42ea1c4cd40f0b4446094c455c713f9adac3f4ae86f613bb5e30c99e52652764d06a89f709b3788 + languageName: node + linkType: hard + "only@npm:~0.0.2": version: 0.0.2 resolution: "only@npm:0.0.2" @@ -12397,18 +12324,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"parse-json@npm:^5.0.0": - version: 5.2.0 - resolution: "parse-json@npm:5.2.0" - dependencies: - "@babel/code-frame": ^7.0.0 - error-ex: ^1.3.1 - json-parse-even-better-errors: ^2.3.0 - lines-and-columns: ^1.1.6 - checksum: 62085b17d64da57f40f6afc2ac1f4d95def18c4323577e1eced571db75d9ab59b297d1d10582920f84b15985cbfc6b6d450ccbf317644cfa176f3ed982ad87e2 - languageName: node - linkType: hard - "parse-node-version@npm:^1.0.0": version: 1.0.1 resolution: "parse-node-version@npm:1.0.1" @@ -12504,7 +12419,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"path-key@npm:^2.0.0, path-key@npm:^2.0.1": +"path-key@npm:^2.0.0": version: 2.0.1 resolution: "path-key@npm:2.0.1" checksum: f7ab0ad42fe3fb8c7f11d0c4f849871e28fbd8e1add65c370e422512fc5887097b9cf34d09c1747d45c942a8c1e26468d6356e2df3f740bf177ab8ca7301ebfd @@ -12518,6 +12433,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"path-key@npm:^4.0.0": + version: 4.0.0 + resolution: "path-key@npm:4.0.0" + checksum: 8e6c314ae6d16b83e93032c61020129f6f4484590a777eed709c4a01b50e498822b00f76ceaf94bc64dbd90b327df56ceadce27da3d83393790f1219e07721d7 + languageName: node + linkType: hard + "path-parse@npm:^1.0.6": version: 1.0.6 resolution: "path-parse@npm:1.0.6" @@ -12612,10 +12534,19 @@ fsevents@^1.2.7: languageName: node linkType: hard -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.2, picomatch@npm:^2.2.3": - version: 2.3.0 - resolution: "picomatch@npm:2.3.0" - checksum: 16818720ea7c5872b6af110760dee856c8e4cd79aed1c7a006d076b1cc09eff3ae41ca5019966694c33fbd2e1cc6ea617ab10e4adac6df06556168f13be3fca2 +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.2, picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 050c865ce81119c4822c45d3c84f1ced46f93a0126febae20737bd05ca20589c564d6e9226977df859ed5e03dc73f02584a2b0faad36e896936238238b0446cf + languageName: node + linkType: hard + +"pidtree@npm:^0.6.0": + version: 0.6.0 + resolution: "pidtree@npm:0.6.0" + bin: + pidtree: bin/pidtree.js + checksum: 8fbc073ede9209dd15e80d616e65eb674986c93be49f42d9ddde8dbbd141bb53d628a7ca4e58ab5c370bb00383f67d75df59a9a226dede8fa801267a7030c27a languageName: node linkType: hard @@ -12656,6 +12587,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"pinst@npm:^3.0.0": + version: 3.0.0 + resolution: "pinst@npm:3.0.0" + bin: + pinst: bin.js + checksum: 4ae48a6a60f79c37071233af51b4d91bfc85cfa3c12b66ccda60cdb642b4d14a4ab0cb3587afc55b1f6192cea1772a5e4822026a0d0d3528296edef00cc2d61f + languageName: node + linkType: hard + "pixelmatch@npm:^4.0.2": version: 4.0.2 resolution: "pixelmatch@npm:4.0.2" @@ -12676,15 +12616,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"pkg-dir@npm:^3.0.0": - version: 3.0.0 - resolution: "pkg-dir@npm:3.0.0" - dependencies: - find-up: ^3.0.0 - checksum: 70c9476ffefc77552cc6b1880176b71ad70bfac4f367604b2b04efd19337309a4eec985e94823271c7c0e83946fa5aeb18cd360d15d10a5d7533e19344bfa808 - languageName: node - linkType: hard - "pkg-dir@npm:^4.1.0, pkg-dir@npm:^4.2.0": version: 4.2.0 resolution: "pkg-dir@npm:4.2.0" @@ -12703,15 +12634,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"please-upgrade-node@npm:^3.1.1, please-upgrade-node@npm:^3.2.0": - version: 3.2.0 - resolution: "please-upgrade-node@npm:3.2.0" - dependencies: - semver-compare: ^1.0.0 - checksum: d87c41581a2a022fbe25965a97006238cd9b8cbbf49b39f78d262548149a9d30bd2bdf35fec3d810e0001e630cd46ef13c7e19c389dea8de7e64db271a2381bb - languageName: node - linkType: hard - "plugin-error@npm:0.1.2": version: 0.1.2 resolution: "plugin-error@npm:0.1.2" @@ -12903,16 +12825,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"pump@npm:^3.0.0": - version: 3.0.0 - resolution: "pump@npm:3.0.0" - dependencies: - end-of-stream: ^1.1.0 - once: ^1.3.1 - checksum: e42e9229fba14732593a718b04cb5e1cfef8254544870997e0ecd9732b189a48e1256e4e5478148ecb47c8511dca2b09eae56b4d0aad8009e6fac8072923cfc9 - languageName: node - linkType: hard - "pumpify@npm:^1.3.5": version: 1.5.1 resolution: "pumpify@npm:1.5.1" @@ -13083,17 +12995,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"read-pkg@npm:^4.0.1": - version: 4.0.1 - resolution: "read-pkg@npm:4.0.1" - dependencies: - normalize-package-data: ^2.3.2 - parse-json: ^4.0.0 - pify: ^3.0.0 - checksum: 56193535486c50a0a40039e4a92f68676362f5a7160628ca4d856c62509e125220f69c562a32940dcc51e3dcd38211af69756bbb5b8a1aed1d09be1bedd1e1a5 - languageName: node - linkType: hard - "readable-stream@npm:2 || 3, readable-stream@npm:^3.0.6": version: 3.6.0 resolution: "readable-stream@npm:3.6.0" @@ -13426,13 +13327,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"resolve-from@npm:^3.0.0": - version: 3.0.0 - resolution: "resolve-from@npm:3.0.0" - checksum: fff9819254d2d62b57f74e5c2ca9c0bdd425ca47287c4d801bc15f947533148d858229ded7793b0f59e61e49e782fffd6722048add12996e1bd4333c29669062 - languageName: node - linkType: hard - "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -13531,6 +13425,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"rfdc@npm:^1.3.0": + version: 1.3.0 + resolution: "rfdc@npm:1.3.0" + checksum: fb2ba8512e43519983b4c61bd3fa77c0f410eff6bae68b08614437bc3f35f91362215f7b4a73cbda6f67330b5746ce07db5dd9850ad3edc91271ad6deea0df32 + languageName: node + linkType: hard + "rimraf@npm:^2.6.1, rimraf@npm:^2.6.3": version: 2.7.1 resolution: "rimraf@npm:2.7.1" @@ -13638,15 +13539,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"run-node@npm:^1.0.0": - version: 1.0.0 - resolution: "run-node@npm:1.0.0" - bin: - run-node: run-node - checksum: 99a0846de16d64c81e7cce487b8d7a77907d7ff69e229ad154cc7ee33ded6f82f71309f47de33cc4c206de6c7b7c37f641d7b6488bb85cc0bfe4da6d8b029242 - languageName: node - linkType: hard - "run-parallel@npm:^1.1.9": version: 1.1.9 resolution: "run-parallel@npm:1.1.9" @@ -13654,12 +13546,12 @@ fsevents@^1.2.7: languageName: node linkType: hard -"rxjs@npm:^6.6.7": - version: 6.6.7 - resolution: "rxjs@npm:6.6.7" +"rxjs@npm:^7.5.5": + version: 7.5.6 + resolution: "rxjs@npm:7.5.6" dependencies: - tslib: ^1.9.0 - checksum: bc334edef1bb8bbf56590b0b25734ba0deaf8825b703256a93714308ea36dff8a11d25533671adf8e104e5e8f256aa6fdfe39b2e248cdbd7a5f90c260acbbd1b + tslib: ^2.1.0 + checksum: fc05f01364a74dac57490fb3e07ea63b422af04017fae1db641a009073f902ef69f285c5daac31359620dc8d9aee7d81e42b370ca2a8573d1feae0b04329383b languageName: node linkType: hard @@ -13744,13 +13636,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"semver-compare@npm:^1.0.0": - version: 1.0.0 - resolution: "semver-compare@npm:1.0.0" - checksum: dd1d7e2909744cf2cf71864ac718efc990297f9de2913b68e41a214319e70174b1d1793ac16e31183b128c2b9812541300cb324db8168e6cf6b570703b171c68 - languageName: node - linkType: hard - "semver-greatest-satisfied-range@npm:^1.1.0": version: 1.1.0 resolution: "semver-greatest-satisfied-range@npm:1.1.0" @@ -13760,7 +13645,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.3.0, semver@npm:^5.5.0, semver@npm:^5.7.1": +"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.3.0, semver@npm:^5.7.1": version: 5.7.1 resolution: "semver@npm:5.7.1" bin: @@ -14007,10 +13892,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3": - version: 3.0.3 - resolution: "signal-exit@npm:3.0.3" - checksum: f0169d3f1263d06df32ca072b0bf33b34c6f8f0341a7a1621558a2444dfbe8f5fec76b35537fcc6f0bc4944bdb5336fe0bdcf41a5422c4e45a1dba3f45475e6c +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 languageName: node linkType: hard @@ -14028,13 +13913,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"slash@npm:^2.0.0": - version: 2.0.0 - resolution: "slash@npm:2.0.0" - checksum: 512d4350735375bd11647233cb0e2f93beca6f53441015eea241fe784d8068281c3987fbaa93e7ef1c38df68d9c60013045c92837423c69115297d6169aa85e6 - languageName: node - linkType: hard - "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -14064,6 +13942,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"slice-ansi@npm:^5.0.0": + version: 5.0.0 + resolution: "slice-ansi@npm:5.0.0" + dependencies: + ansi-styles: ^6.0.0 + is-fullwidth-code-point: ^4.0.0 + checksum: 7e600a2a55e333a21ef5214b987c8358fe28bfb03c2867ff2cbf919d62143d1812ac27b4297a077fdaf27a03da3678e49551c93e35f9498a3d90221908a1180e + languageName: node + linkType: hard + "smart-buffer@npm:^4.1.0": version: 4.1.0 resolution: "smart-buffer@npm:4.1.0" @@ -14369,7 +14257,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"string-argv@npm:0.3.1": +"string-argv@npm:^0.3.1": version: 0.3.1 resolution: "string-argv@npm:0.3.1" checksum: efbd0289b599bee808ce80820dfe49c9635610715429c6b7cc50750f0437e3c2f697c81e5c390208c13b5d5d12d904a1546172a88579f6ee5cbaaaa4dc9ec5cf @@ -14419,6 +14307,17 @@ fsevents@^1.2.7: languageName: node linkType: hard +"string-width@npm:^5.0.0": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: ^0.2.0 + emoji-regex: ^9.2.2 + strip-ansi: ^7.0.1 + checksum: 7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 + languageName: node + linkType: hard + "string.prototype.matchall@npm:^4.0.6": version: 4.0.6 resolution: "string.prototype.matchall@npm:4.0.6" @@ -14520,7 +14419,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"strip-ansi@npm:^7.0.0": +"strip-ansi@npm:^7.0.0, strip-ansi@npm:^7.0.1": version: 7.0.1 resolution: "strip-ansi@npm:7.0.1" dependencies: @@ -14566,6 +14465,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"strip-final-newline@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-final-newline@npm:3.0.0" + checksum: 23ee263adfa2070cd0f23d1ac14e2ed2f000c9b44229aec9c799f1367ec001478469560abefd00c5c99ee6f0b31c137d53ec6029c53e9f32a93804e18c201050 + languageName: node + linkType: hard + "strip-json-comments@npm:3.1.1, strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" @@ -16390,10 +16296,10 @@ typescript@^4.4.3: languageName: node linkType: hard -"yaml@npm:^1.10.0": - version: 1.10.2 - resolution: "yaml@npm:1.10.2" - checksum: ce4ada136e8a78a0b08dc10b4b900936912d15de59905b2bf415b4d33c63df1d555d23acb2a41b23cf9fb5da41c256441afca3d6509de7247daa062fd2c5ea5f +"yaml@npm:^2.1.1": + version: 2.1.1 + resolution: "yaml@npm:2.1.1" + checksum: f48bb209918aa57cfaf78ef6448d1a1f8187f45c746f933268b7023dc59e5456004611879126c9bb5ea55b0a2b1c2b392dfde436931ece0c703a3d754562bb96 languageName: node linkType: hard From 9a1fc02755f292451f7df3446cf7b4687206bae1 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Wed, 17 Aug 2022 12:01:05 -0400 Subject: [PATCH 031/134] Fix localize key types related to form schemas (Group 2) (#13342) --- src/data/selector.ts | 4 +- .../config-elements/hui-gauge-card-editor.ts | 133 ++++++++++-------- .../config-elements/hui-glance-card-editor.ts | 37 ++--- .../config-elements/hui-grid-card-editor.ts | 8 +- .../hui-history-graph-card-editor.ts | 8 +- .../hui-humidifier-card-editor.ts | 8 +- .../config-elements/hui-iframe-card-editor.ts | 8 +- .../config-elements/hui-light-card-editor.ts | 61 ++++---- .../hui-markdown-card-editor.ts | 37 ++--- .../hui-picture-entity-card-editor.ts | 40 ++---- .../hui-picture-glance-card-editor.ts | 44 +++--- .../hui-plant-status-card-editor.ts | 8 +- .../hui-thermostat-card-editor.ts | 8 +- 13 files changed, 199 insertions(+), 205 deletions(-) diff --git a/src/data/selector.ts b/src/data/selector.ts index 1b3997b6de..b56f43d901 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -112,7 +112,7 @@ export interface DurationSelector { export interface EntitySelector { entity: { integration?: string; - domain?: string | string[]; + domain?: string | readonly string[]; device_class?: string; multiple?: boolean; include_entities?: string[]; @@ -180,7 +180,7 @@ export interface SelectSelector { multiple?: boolean; custom_value?: boolean; mode?: "list" | "dropdown"; - options: string[] | SelectOption[]; + options: readonly string[] | readonly SelectOption[]; }; } diff --git a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts index 2baa6c32a0..c390ea3b1d 100644 --- a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts @@ -13,7 +13,7 @@ import { } from "superstruct"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { GaugeCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; @@ -54,57 +54,66 @@ export class HuiGaugeCardEditor this._config = config; } - private _schema = memoizeOne((showSeverity: boolean) => { - const schema = [ - { - name: "entity", - selector: { - entity: { - domain: ["counter", "input_number", "number", "sensor"], + private _schema = memoizeOne( + (showSeverity: boolean) => + [ + { + name: "entity", + selector: { + entity: { + domain: ["counter", "input_number", "number", "sensor"], + }, }, }, - }, - { - name: "", - type: "grid", - schema: [ - { name: "name", selector: { text: {} } }, - { name: "unit", selector: { text: {} } }, - ], - }, - { name: "theme", selector: { theme: {} } }, - { - name: "", - type: "grid", - schema: [ - { name: "min", selector: { number: { min: 1, mode: "box" } } }, - { name: "max", selector: { number: { min: 1, mode: "box" } } }, - ], - }, - { - name: "", - type: "grid", - schema: [ - { name: "needle", selector: { boolean: {} } }, - { name: "show_severity", selector: { boolean: {} } }, - ], - }, - ]; - - if (showSeverity) { - schema.push({ - name: "", - type: "grid", - schema: [ - { name: "green", selector: { number: { min: 0, mode: "box" } } }, - { name: "yellow", selector: { number: { min: 0, mode: "box" } } }, - { name: "red", selector: { number: { min: 0, mode: "box" } } }, - ], - }); - } - - return schema; - }); + { + name: "", + type: "grid", + schema: [ + { name: "name", selector: { text: {} } }, + { name: "unit", selector: { text: {} } }, + ], + }, + { name: "theme", selector: { theme: {} } }, + { + name: "", + type: "grid", + schema: [ + { name: "min", selector: { number: { min: 1, mode: "box" } } }, + { name: "max", selector: { number: { min: 1, mode: "box" } } }, + ], + }, + { + name: "", + type: "grid", + schema: [ + { name: "needle", selector: { boolean: {} } }, + { name: "show_severity", selector: { boolean: {} } }, + ], + }, + ...(showSeverity + ? ([ + { + name: "", + type: "grid", + schema: [ + { + name: "green", + selector: { number: { min: 0, mode: "box" } }, + }, + { + name: "yellow", + selector: { number: { min: 0, mode: "box" } }, + }, + { + name: "red", + selector: { number: { min: 0, mode: "box" } }, + }, + ], + }, + ] as const) + : []), + ] as const + ); protected render(): TemplateResult { if (!this.hass || !this._config) { @@ -152,7 +161,9 @@ export class HuiGaugeCardEditor fireEvent(this, "config-changed", { config }); } - private _computeLabelCallback = (schema: HaFormSchema) => { + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { switch (schema.name) { case "name": return this.hass!.localize( @@ -186,18 +197,16 @@ export class HuiGaugeCardEditor )} (${this.hass!.localize( "ui.panel.lovelace.editor.card.config.optional" )})`; + case "unit": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.unit" + ); + default: + // "green" | "yellow" | "red" + return this.hass!.localize( + `ui.panel.lovelace.editor.card.gauge.severity.${schema.name}` + ); } - return ( - this.hass!.localize( - `ui.panel.lovelace.editor.card.gauge.${schema.name}` - ) || - this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ) || - this.hass!.localize( - `ui.panel.lovelace.editor.card.gauge.severity.${schema.name}` - ) - ); }; } diff --git a/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts index 9cdf13c4cd..31f8ef60f1 100644 --- a/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts @@ -20,7 +20,7 @@ import type { LovelaceCardEditor } from "../../types"; import { processEditorEntities } from "../process-editor-entities"; import { entitiesConfigStruct } from "../structs/entities-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -36,7 +36,7 @@ const cardConfigStruct = assign( }) ); -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "title", selector: { text: {} } }, { name: "", @@ -57,7 +57,7 @@ const SCHEMA: HaFormSchema[] = [ ], }, { name: "state_color", selector: { boolean: {} } }, -]; +] as const; @customElement("hui-glance-card-editor") export class HuiGlanceCardEditor @@ -117,22 +117,23 @@ export class HuiGlanceCardEditor fireEvent(this, "config-changed", { config }); } - private _computeLabelCallback = (schema: HaFormSchema) => { - if (schema.name === "theme") { - return `${this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.theme" - )} (${this.hass!.localize( - "ui.panel.lovelace.editor.card.config.optional" - )})`; + private _computeLabelCallback = (schema: SchemaUnion) => { + switch (schema.name) { + case "theme": + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.theme" + )} (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.optional" + )})`; + case "columns": + return this.hass!.localize( + `ui.panel.lovelace.editor.card.glance.${schema.name}` + ); + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); } - return ( - this.hass!.localize( - `ui.panel.lovelace.editor.card.glance.${schema.name}` - ) || - this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ) - ); }; } diff --git a/src/panels/lovelace/editor/config-elements/hui-grid-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-grid-card-editor.ts index 49c30c100f..2a730609ab 100644 --- a/src/panels/lovelace/editor/config-elements/hui-grid-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-grid-card-editor.ts @@ -12,7 +12,7 @@ import { string, } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { GridCardConfig } from "../../cards/types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { HuiStackCardEditor } from "./hui-stack-card-editor"; @@ -27,7 +27,7 @@ const cardConfigStruct = assign( }) ); -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { type: "grid", name: "", @@ -36,7 +36,7 @@ const SCHEMA: HaFormSchema[] = [ { name: "square", selector: { boolean: {} } }, ], }, -]; +] as const; @customElement("hui-grid-card-editor") export class HuiGridCardEditor extends HuiStackCardEditor { @@ -68,7 +68,7 @@ export class HuiGridCardEditor extends HuiStackCardEditor { fireEvent(this, "config-changed", { config: ev.detail.value }); } - private _computeLabelCallback = (schema: HaFormSchema) => + private _computeLabelCallback = (schema: SchemaUnion) => this.hass!.localize(`ui.panel.lovelace.editor.card.grid.${schema.name}`); } diff --git a/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts index a18b13c2c1..fb9e05f42f 100644 --- a/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts @@ -19,7 +19,7 @@ import type { LovelaceCardEditor } from "../../types"; import { processEditorEntities } from "../process-editor-entities"; import { entitiesConfigStruct } from "../structs/entities-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -31,7 +31,7 @@ const cardConfigStruct = assign( }) ); -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "title", selector: { text: {} } }, { name: "", @@ -44,7 +44,7 @@ const SCHEMA: HaFormSchema[] = [ }, ], }, -]; +] as const; @customElement("hui-history-graph-card-editor") export class HuiHistoryGraphCardEditor @@ -97,7 +97,7 @@ export class HuiHistoryGraphCardEditor fireEvent(this, "config-changed", { config }); } - private _computeLabelCallback = (schema: HaFormSchema) => + private _computeLabelCallback = (schema: SchemaUnion) => this.hass!.localize(`ui.panel.lovelace.editor.card.generic.${schema.name}`); static styles: CSSResultGroup = css` diff --git a/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts index b5e83f7c81..055cdf68c9 100644 --- a/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts @@ -3,7 +3,7 @@ import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { HumidifierCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; @@ -18,7 +18,7 @@ const cardConfigStruct = assign( }) ); -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "entity", required: true, @@ -32,7 +32,7 @@ const SCHEMA: HaFormSchema[] = [ { name: "theme", selector: { theme: {} } }, ], }, -]; +] as const; @customElement("hui-humidifier-card-editor") export class HuiHumidifierCardEditor @@ -68,7 +68,7 @@ export class HuiHumidifierCardEditor fireEvent(this, "config-changed", { config: ev.detail.value }); } - private _computeLabelCallback = (schema: HaFormSchema) => { + private _computeLabelCallback = (schema: SchemaUnion) => { if (schema.name === "entity") { return this.hass!.localize( "ui.panel.lovelace.editor.card.generic.entity" diff --git a/src/panels/lovelace/editor/config-elements/hui-iframe-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-iframe-card-editor.ts index bef193337b..8e53668e29 100644 --- a/src/panels/lovelace/editor/config-elements/hui-iframe-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-iframe-card-editor.ts @@ -3,7 +3,7 @@ import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { IframeCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; @@ -18,7 +18,7 @@ const cardConfigStruct = assign( }) ); -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "title", selector: { text: {} } }, { name: "", @@ -28,7 +28,7 @@ const SCHEMA: HaFormSchema[] = [ { name: "aspect_ratio", selector: { text: {} } }, ], }, -]; +] as const; @customElement("hui-iframe-card-editor") export class HuiIframeCardEditor @@ -64,7 +64,7 @@ export class HuiIframeCardEditor fireEvent(this, "config-changed", { config: ev.detail.value }); } - private _computeLabelCallback = (schema: HaFormSchema) => + private _computeLabelCallback = (schema: SchemaUnion) => this.hass!.localize(`ui.panel.lovelace.editor.card.generic.${schema.name}`); } diff --git a/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts index 42e47ef81a..69e622d506 100644 --- a/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts @@ -15,7 +15,7 @@ import { configElementStyle } from "./config-elements-style"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { domainIcon } from "../../../../common/entity/domain_icon"; import { computeDomain } from "../../../../common/entity/compute_domain"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -44,37 +44,34 @@ export class HuiLightCardEditor } private _schema = memoizeOne( - ( - entity: string, - icon: string | undefined, - entityState: HassEntity - ): HaFormSchema[] => [ - { - name: "entity", - required: true, - selector: { entity: { domain: "light" } }, - }, - { - type: "grid", - name: "", - schema: [ - { name: "name", selector: { text: {} } }, - { - name: "icon", - selector: { - icon: { - placeholder: icon || entityState?.attributes.icon, - fallbackPath: - !icon && !entityState?.attributes.icon && entityState - ? domainIcon(computeDomain(entity), entityState) - : undefined, + (entity: string, icon: string | undefined, entityState: HassEntity) => + [ + { + name: "entity", + required: true, + selector: { entity: { domain: "light" } }, + }, + { + type: "grid", + name: "", + schema: [ + { name: "name", selector: { text: {} } }, + { + name: "icon", + selector: { + icon: { + placeholder: icon || entityState?.attributes.icon, + fallbackPath: + !icon && !entityState?.attributes.icon && entityState + ? domainIcon(computeDomain(entity), entityState) + : undefined, + }, }, }, - }, - ], - }, - { name: "theme", selector: { theme: {} } }, - ] + ], + }, + { name: "theme", selector: { theme: {} } }, + ] as const ); get _hold_action(): ActionConfig { @@ -172,7 +169,9 @@ export class HuiLightCardEditor fireEvent(this, "config-changed", { config: ev.detail.value }); } - private _computeLabelCallback = (schema: HaFormSchema) => { + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { if (schema.name === "entity") { return this.hass!.localize( "ui.panel.lovelace.editor.card.generic.entity" diff --git a/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts index e22892d392..28b639d5c1 100644 --- a/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts @@ -3,7 +3,7 @@ import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { MarkdownCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; @@ -18,11 +18,11 @@ const cardConfigStruct = assign( }) ); -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "title", selector: { text: {} } }, { name: "content", required: true, selector: { template: {} } }, { name: "theme", selector: { theme: {} } }, -]; +] as const; @customElement("hui-markdown-card-editor") export class HuiMarkdownCardEditor @@ -58,22 +58,23 @@ export class HuiMarkdownCardEditor fireEvent(this, "config-changed", { config: ev.detail.value }); } - private _computeLabelCallback = (schema: HaFormSchema) => { - if (schema.name === "theme") { - return `${this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.theme" - )} (${this.hass!.localize( - "ui.panel.lovelace.editor.card.config.optional" - )})`; + private _computeLabelCallback = (schema: SchemaUnion) => { + switch (schema.name) { + case "theme": + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.theme" + )} (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.optional" + )})`; + case "content": + return this.hass!.localize( + `ui.panel.lovelace.editor.card.markdown.${schema.name}` + ); + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); } - return ( - this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ) || - this.hass!.localize( - `ui.panel.lovelace.editor.card.markdown.${schema.name}` - ) - ); }; } diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts index 7367ac09ef..29f26838a8 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts @@ -2,7 +2,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, boolean, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { ActionConfig } from "../../../../data/lovelace"; import type { HomeAssistant } from "../../../../types"; import type { PictureEntityCardConfig } from "../../cards/types"; @@ -30,7 +30,7 @@ const cardConfigStruct = assign( }) ); -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "entity", required: true, selector: { entity: {} } }, { name: "name", selector: { text: {} } }, { name: "image", selector: { text: {} } }, @@ -61,7 +61,7 @@ const SCHEMA: HaFormSchema[] = [ ], }, { name: "theme", selector: { theme: {} } }, -]; +] as const; @customElement("hui-picture-entity-card-editor") export class HuiPictureEntityCardEditor @@ -163,29 +163,19 @@ export class HuiPictureEntityCardEditor fireEvent(this, "config-changed", { config: this._config }); } - private _computeLabelCallback = (schema: HaFormSchema) => { - if (schema.name === "entity") { - return this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.entity" - ); + private _computeLabelCallback = (schema: SchemaUnion) => { + switch (schema.name) { + case "theme": + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.theme" + )} (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.optional" + )})`; + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); } - - if (schema.name === "theme") { - return `${this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.theme" - )} (${this.hass!.localize( - "ui.panel.lovelace.editor.card.config.optional" - )})`; - } - - return ( - this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ) || - this.hass!.localize( - `ui.panel.lovelace.editor.card.picture-entity.${schema.name}` - ) - ); }; static styles: CSSResultGroup = configElementStyle; 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 18e46b154e..aa7340e134 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 @@ -3,7 +3,7 @@ import { customElement, property, state } from "lit/decorators"; import { array, assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-form/ha-form"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { ActionConfig } from "../../../../data/lovelace"; import type { HomeAssistant } from "../../../../types"; import type { PictureGlanceCardConfig } from "../../cards/types"; @@ -36,7 +36,7 @@ const cardConfigStruct = assign( const actions = ["more-info", "toggle", "navigate", "call-service", "none"]; -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "title", selector: { text: {} } }, { name: "image", selector: { text: {} } }, { name: "camera_image", selector: { entity: { domain: "camera" } } }, @@ -53,7 +53,7 @@ const SCHEMA: HaFormSchema[] = [ }, { name: "entity", selector: { entity: {} } }, { name: "theme", selector: { theme: {} } }, -]; +] as const; @customElement("hui-picture-glance-card-editor") export class HuiPictureGlanceCardEditor @@ -158,29 +158,23 @@ export class HuiPictureGlanceCardEditor fireEvent(this, "config-changed", { config: this._config }); } - private _computeLabelCallback = (schema: HaFormSchema) => { - if (schema.name === "entity") { - return this.hass!.localize( - "ui.panel.lovelace.editor.card.picture-glance.state_entity" - ); + private _computeLabelCallback = (schema: SchemaUnion) => { + switch (schema.name) { + case "theme": + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.theme" + )} (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.optional" + )})`; + case "entity": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.picture-glance.state_entity" + ); + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); } - - if (schema.name === "theme") { - return `${this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.theme" - )} (${this.hass!.localize( - "ui.panel.lovelace.editor.card.config.optional" - )})`; - } - - return ( - this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ) || - this.hass!.localize( - `ui.panel.lovelace.editor.card.picture-glance.${schema.name}` - ) - ); }; static styles: CSSResultGroup = configElementStyle; diff --git a/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts index f62fe89c21..dbc19b69a3 100644 --- a/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts @@ -3,7 +3,7 @@ import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { PlantStatusCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; @@ -18,11 +18,11 @@ const cardConfigStruct = assign( }) ); -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "entity", required: true, selector: { entity: { domain: "plant" } } }, { name: "name", selector: { text: {} } }, { name: "theme", selector: { theme: {} } }, -]; +] as const; @customElement("hui-plant-status-card-editor") export class HuiPlantStatusCardEditor @@ -58,7 +58,7 @@ export class HuiPlantStatusCardEditor fireEvent(this, "config-changed", { config: ev.detail.value }); } - private _computeLabelCallback = (schema: HaFormSchema) => { + private _computeLabelCallback = (schema: SchemaUnion) => { if (schema.name === "entity") { return this.hass!.localize( "ui.panel.lovelace.editor.card.generic.entity" diff --git a/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts index 78d6dc3a97..cf5af922f7 100644 --- a/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts @@ -3,7 +3,7 @@ import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { ThermostatCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; @@ -18,7 +18,7 @@ const cardConfigStruct = assign( }) ); -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "entity", selector: { entity: { domain: "climate" } } }, { type: "grid", @@ -28,7 +28,7 @@ const SCHEMA: HaFormSchema[] = [ { name: "theme", required: false, selector: { theme: {} } }, ], }, -]; +] as const; @customElement("hui-thermostat-card-editor") export class HuiThermostatCardEditor @@ -64,7 +64,7 @@ export class HuiThermostatCardEditor fireEvent(this, "config-changed", { config: ev.detail.value }); } - private _computeLabelCallback = (schema: HaFormSchema) => { + private _computeLabelCallback = (schema: SchemaUnion) => { if (schema.name === "entity") { return this.hass!.localize( "ui.panel.lovelace.editor.card.generic.entity" From 12e57dfcae9e72c6f2b94f391b7b9cf60a532dbc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 17 Aug 2022 14:20:35 -0400 Subject: [PATCH 032/134] Allow testing describe functionality in the gallery (#13398) --- .../src/pages/automation/describe-action.ts | 34 ++++++++++++++++++- .../pages/automation/describe-condition.ts | 33 +++++++++++++++++- .../src/pages/automation/describe-trigger.ts | 29 +++++++++++++++- 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/gallery/src/pages/automation/describe-action.ts b/gallery/src/pages/automation/describe-action.ts index 49fa3dc7f9..cf8249901f 100644 --- a/gallery/src/pages/automation/describe-action.ts +++ b/gallery/src/pages/automation/describe-action.ts @@ -1,7 +1,9 @@ import { dump } from "js-yaml"; import { html, css, LitElement, TemplateResult } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import "../../../../src/components/ha-card"; +import "../../../../src/components/ha-yaml-editor"; +import { Action } from "../../../../src/data/script"; import { describeAction } from "../../../../src/data/script_i18n"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; @@ -103,16 +105,38 @@ const ACTIONS = [ }, ]; +const initialAction: Action = { + service: "light.turn_on", + target: { + entity_id: "light.kitchen", + }, +}; + @customElement("demo-automation-describe-action") export class DemoAutomationDescribeAction extends LitElement { @property({ attribute: false }) hass!: HomeAssistant; + @state() _action = initialAction; + protected render(): TemplateResult { if (!this.hass) { return html``; } return html` +
+ + ${this._action + ? describeAction(this.hass, this._action) + : ""} + + +
+ ${ACTIONS.map( (conf) => html`
@@ -132,6 +156,11 @@ export class DemoAutomationDescribeAction extends LitElement { hass.addEntities(ENTITIES); } + private _dataChanged(ev: CustomEvent): void { + ev.stopPropagation(); + this._action = ev.detail.isValid ? ev.detail.value : undefined; + } + static get styles() { return css` ha-card { @@ -147,6 +176,9 @@ export class DemoAutomationDescribeAction extends LitElement { span { margin-right: 16px; } + ha-yaml-editor { + width: 50%; + } `; } } diff --git a/gallery/src/pages/automation/describe-condition.ts b/gallery/src/pages/automation/describe-condition.ts index 11de8b9781..e77a7a2af6 100644 --- a/gallery/src/pages/automation/describe-condition.ts +++ b/gallery/src/pages/automation/describe-condition.ts @@ -1,7 +1,9 @@ import { dump } from "js-yaml"; import { html, css, LitElement, TemplateResult } from "lit"; -import { customElement } from "lit/decorators"; +import { customElement, state } from "lit/decorators"; import "../../../../src/components/ha-card"; +import "../../../../src/components/ha-yaml-editor"; +import { Condition } from "../../../../src/data/automation"; import { describeCondition } from "../../../../src/data/automation_i18n"; const conditions = [ @@ -17,11 +19,32 @@ const conditions = [ { condition: "template" }, ]; +const initialCondition: Condition = { + condition: "state", + entity_id: "light.kitchen", + state: "on", +}; + @customElement("demo-automation-describe-condition") export class DemoAutomationDescribeCondition extends LitElement { + @state() _condition = initialCondition; + protected render(): TemplateResult { return html` +
+ + ${this._condition + ? describeCondition(this._condition) + : ""} + + +
+ ${conditions.map( (conf) => html`
@@ -34,6 +57,11 @@ export class DemoAutomationDescribeCondition extends LitElement { `; } + private _dataChanged(ev: CustomEvent): void { + ev.stopPropagation(); + this._condition = ev.detail.isValid ? ev.detail.value : undefined; + } + static get styles() { return css` ha-card { @@ -49,6 +77,9 @@ export class DemoAutomationDescribeCondition extends LitElement { span { margin-right: 16px; } + ha-yaml-editor { + width: 50%; + } `; } } diff --git a/gallery/src/pages/automation/describe-trigger.ts b/gallery/src/pages/automation/describe-trigger.ts index 6bfe3213c3..41b589489b 100644 --- a/gallery/src/pages/automation/describe-trigger.ts +++ b/gallery/src/pages/automation/describe-trigger.ts @@ -1,7 +1,9 @@ import { dump } from "js-yaml"; import { html, css, LitElement, TemplateResult } from "lit"; -import { customElement } from "lit/decorators"; +import { customElement, state } from "lit/decorators"; import "../../../../src/components/ha-card"; +import "../../../../src/components/ha-yaml-editor"; +import { Trigger } from "../../../../src/data/automation"; import { describeTrigger } from "../../../../src/data/automation_i18n"; const triggers = [ @@ -20,11 +22,28 @@ const triggers = [ { platform: "event" }, ]; +const initialTrigger: Trigger = { + platform: "state", + entity_id: "light.kitchen", +}; + @customElement("demo-automation-describe-trigger") export class DemoAutomationDescribeTrigger extends LitElement { + @state() _trigger = initialTrigger; + protected render(): TemplateResult { return html` +
+ + ${this._trigger ? describeTrigger(this._trigger) : ""} + + +
${triggers.map( (conf) => html`
@@ -37,6 +56,11 @@ export class DemoAutomationDescribeTrigger extends LitElement { `; } + private _dataChanged(ev: CustomEvent): void { + ev.stopPropagation(); + this._trigger = ev.detail.isValid ? ev.detail.value : undefined; + } + static get styles() { return css` ha-card { @@ -52,6 +76,9 @@ export class DemoAutomationDescribeTrigger extends LitElement { span { margin-right: 16px; } + ha-yaml-editor { + width: 50%; + } `; } } From d7b888f7615f0d735f872fb08979ac4c72c7fea8 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Wed, 17 Aug 2022 23:57:26 -0400 Subject: [PATCH 033/134] Fix localize key types related to form schemas (Group 3) (#13400) * Fix key type errors for card editors (Round 4) * Fix key type errors for remaining form schemas --- .../registries/dialog-hassio-registries.ts | 10 +- src/components/ha-form/ha-form.ts | 6 +- .../ha-selector/ha-selector-media.ts | 8 +- src/data/automation.ts | 2 +- src/data/script.ts | 9 +- .../automation/manual-automation-editor.ts | 14 +- .../dialog-lovelace-dashboard-detail.ts | 23 +-- .../dialog-lovelace-resource-detail.ts | 105 +++++++------ src/panels/config/script/ha-script-editor.ts | 116 +++++++------- src/panels/config/zone/dialog-zone-detail.ts | 86 +++++----- .../hui-generic-entity-row-editor.ts | 54 ++++--- .../hui-logbook-card-editor.ts | 34 ++-- .../config-elements/hui-map-card-editor.ts | 24 ++- .../config-elements/hui-sensor-card-editor.ts | 147 +++++++++--------- .../hui-statistics-graph-card-editor.ts | 118 +++++++------- .../hui-weather-forecast-card-editor.ts | 120 +++++++------- .../editor/view-editor/hui-view-editor.ts | 80 +++++----- src/translations/en.json | 4 +- 18 files changed, 503 insertions(+), 457 deletions(-) diff --git a/hassio/src/dialogs/registries/dialog-hassio-registries.ts b/hassio/src/dialogs/registries/dialog-hassio-registries.ts index 7ca0b3de1c..e26753e5dc 100644 --- a/hassio/src/dialogs/registries/dialog-hassio-registries.ts +++ b/hassio/src/dialogs/registries/dialog-hassio-registries.ts @@ -4,7 +4,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { createCloseHeading } from "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-form/ha-form"; -import { HaFormSchema } from "../../../../src/components/ha-form/types"; +import type { SchemaUnion } from "../../../../src/components/ha-form/types"; import "../../../../src/components/ha-icon-button"; import "../../../../src/components/ha-settings-row"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; @@ -19,7 +19,7 @@ import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import type { HomeAssistant } from "../../../../src/types"; import { RegistriesDialogParams } from "./show-dialog-registries"; -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "registry", required: true, @@ -35,7 +35,7 @@ const SCHEMA: HaFormSchema[] = [ required: true, selector: { text: { type: "password" } }, }, -]; +] as const; @customElement("dialog-hassio-registries") class HassioRegistriesDialog extends LitElement { @@ -135,8 +135,8 @@ class HassioRegistriesDialog extends LitElement { `; } - private _computeLabel = (schema: HaFormSchema) => - this.supervisor.localize(`dialog.registries.${schema.name}`) || schema.name; + private _computeLabel = (schema: SchemaUnion) => + this.supervisor.localize(`dialog.registries.${schema.name}`); private _valueChanged(ev: CustomEvent) { this._input = ev.detail.value; diff --git a/src/components/ha-form/ha-form.ts b/src/components/ha-form/ha-form.ts index a42f90d773..7f83b3220c 100644 --- a/src/components/ha-form/ha-form.ts +++ b/src/components/ha-form/ha-form.ts @@ -41,14 +41,14 @@ export class HaForm extends LitElement implements HaFormElement { @property({ type: Boolean }) public disabled = false; - @property() public computeError?: (schema: HaFormSchema, error) => string; + @property() public computeError?: (schema: any, error) => string; @property() public computeLabel?: ( schema: any, - data?: HaFormDataContainer + data: HaFormDataContainer ) => string; - @property() public computeHelper?: (schema: HaFormSchema) => string; + @property() public computeHelper?: (schema: any) => string | undefined; public focus() { const root = this.shadowRoot?.querySelector(".root"); diff --git a/src/components/ha-selector/ha-selector-media.ts b/src/components/ha-selector/ha-selector-media.ts index bd6ad958b8..fc998f5d05 100644 --- a/src/components/ha-selector/ha-selector-media.ts +++ b/src/components/ha-selector/ha-selector-media.ts @@ -15,13 +15,13 @@ import type { HomeAssistant } from "../../types"; import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url"; import "../ha-alert"; import "../ha-form/ha-form"; -import type { HaFormSchema } from "../ha-form/types"; +import type { SchemaUnion } from "../ha-form/types"; import { showMediaBrowserDialog } from "../media-player/show-media-browser-dialog"; const MANUAL_SCHEMA = [ { name: "media_content_id", required: false, selector: { text: {} } }, { name: "media_content_type", required: false, selector: { text: {} } }, -]; +] as const; @customElement("ha-selector-media") export class HaMediaSelector extends LitElement { @@ -163,7 +163,9 @@ export class HaMediaSelector extends LitElement { `}`; } - private _computeLabelCallback = (schema: HaFormSchema): string => + private _computeLabelCallback = ( + schema: SchemaUnion + ): string => this.hass.localize(`ui.components.selectors.media.${schema.name}`); private _entityChanged(ev: CustomEvent) { diff --git a/src/data/automation.ts b/src/data/automation.ts index 3259df2f67..b164df7591 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -8,7 +8,7 @@ import { BlueprintInput } from "./blueprint"; import { DeviceCondition, DeviceTrigger } from "./device_automation"; import { Action, MODES } from "./script"; -export const AUTOMATION_DEFAULT_MODE: ManualAutomationConfig["mode"] = "single"; +export const AUTOMATION_DEFAULT_MODE: typeof MODES[number] = "single"; export interface AutomationEntity extends HassEntityBase { attributes: HassEntityAttributeBase & { diff --git a/src/data/script.ts b/src/data/script.ts index 017a72cc88..42c9c849ac 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -28,7 +28,12 @@ import { import { BlueprintInput } from "./blueprint"; export const MODES = ["single", "restart", "queued", "parallel"] as const; -export const MODES_MAX = ["queued", "parallel"]; +export const MODES_MAX = ["queued", "parallel"] as const; + +export const isMaxMode = ( + mode: typeof MODES[number] +): mode is typeof MODES_MAX[number] => + MODES_MAX.includes(mode as typeof MODES_MAX[number]); export const baseActionStruct = object({ alias: optional(string()), @@ -275,7 +280,7 @@ export const canRun = (state: ScriptEntity) => { } if ( state.state === "on" && - MODES_MAX.includes(state.attributes.mode) && + isMaxMode(state.attributes.mode) && state.attributes.current! < state.attributes.max! ) { return true; diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index 087dfb8dc7..4ed456a86e 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -14,7 +14,7 @@ import { Trigger, triggerAutomationActions, } from "../../../data/automation"; -import { Action, MODES, MODES_MAX } from "../../../data/script"; +import { Action, isMaxMode, MODES } from "../../../data/script"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; @@ -27,13 +27,13 @@ import "./trigger/ha-automation-trigger"; export class HaManualAutomationEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide!: boolean; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow!: boolean; - @property() public config!: ManualAutomationConfig; + @property({ attribute: false }) public config!: ManualAutomationConfig; - @property() public stateObj?: HassEntity; + @property({ attribute: false }) public stateObj?: HassEntity; @state() private _showDescription = false; @@ -114,7 +114,7 @@ export class HaManualAutomationEditor extends LitElement { ` )} - ${this.config.mode && MODES_MAX.includes(this.config.mode) + ${this.config.mode && isMaxMode(this.config.mode) ? html`
+ private _computeLabel = ( + entry: SchemaUnion> + ): string => this.hass.localize( `ui.panel.config.lovelace.dashboards.detail.${ entry.name === "show_in_sidebar" diff --git a/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts b/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts index 283d1f2986..ae19bcb0de 100644 --- a/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts +++ b/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts @@ -5,7 +5,7 @@ import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; import { createCloseHeading } from "../../../../components/ha-dialog"; import "../../../../components/ha-form/ha-form"; -import { HaFormSchema } from "../../../../components/ha-form/types"; +import { SchemaUnion } from "../../../../components/ha-form/types"; import { LovelaceResourcesMutableParams } from "../../../../data/lovelace"; import { haStyleDialog } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; @@ -132,53 +132,68 @@ export class DialogLovelaceResourceDetail extends LitElement { `; } - private _schema = memoizeOne((data) => [ - { - name: "url", - required: true, - selector: { - text: {}, - }, - }, - { - name: "res_type", - required: true, - selector: { - select: { - options: [ - { - value: "module", - label: this.hass!.localize( - "ui.panel.config.lovelace.resources.types.module" - ), - }, - { - value: "css", - label: this.hass!.localize( - "ui.panel.config.lovelace.resources.types.css" - ), - }, - data.type === "js" && { - value: "js", - label: this.hass!.localize( - "ui.panel.config.lovelace.resources.types.js" - ), - }, - data.type === "html" && { - value: "html", - label: this.hass!.localize( - "ui.panel.config.lovelace.resources.types.html" - ), - }, - ].filter(Boolean), + private _schema = memoizeOne( + (data) => + [ + { + name: "url", + required: true, + selector: { + text: {}, + }, }, - }, - }, - ]); + { + name: "res_type", + required: true, + selector: { + select: { + options: [ + { + value: "module", + label: this.hass!.localize( + "ui.panel.config.lovelace.resources.types.module" + ), + }, + { + value: "css", + label: this.hass!.localize( + "ui.panel.config.lovelace.resources.types.css" + ), + }, + ...(data.type === "js" + ? ([ + { + value: "js", + label: this.hass!.localize( + "ui.panel.config.lovelace.resources.types.js" + ), + }, + ] as const) + : []), + ...(data.type === "html" + ? ([ + { + value: "html", + label: this.hass!.localize( + "ui.panel.config.lovelace.resources.types.html" + ), + }, + ] as const) + : []), + ], + }, + }, + }, + ] as const + ); - private _computeLabel = (entry: HaFormSchema): string => + private _computeLabel = ( + entry: SchemaUnion> + ): string => this.hass.localize( - `ui.panel.config.lovelace.resources.detail.${entry.name}` + `ui.panel.config.lovelace.resources.detail.${ + entry.name === "res_type" ? "type" : entry.name + }` ); private _valueChanged(ev: CustomEvent) { diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index 3a6f383f3a..f57d5feb2a 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -30,8 +30,7 @@ import "../../../components/ha-card"; import "../../../components/ha-fab"; import type { HaFormDataContainer, - HaFormSchema, - HaFormSelector, + SchemaUnion, } from "../../../components/ha-form/types"; import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; @@ -41,6 +40,7 @@ import { Action, deleteScript, getScriptEditorInitData, + isMaxMode, ManualScriptConfig, MODES, MODES_MAX, @@ -65,11 +65,11 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { @property() public scriptEntityId: string | null = null; - @property() public route!: Route; + @property({ attribute: false }) public route!: Route; - @property() public isWide?: boolean; + @property({ type: Boolean }) public isWide = false; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow!: boolean; @state() private _config?: ScriptConfig; @@ -86,8 +86,12 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { @query("ha-yaml-editor", true) private _editor?: HaYamlEditor; private _schema = memoizeOne( - (hasID: boolean, useBluePrint?: boolean, currentMode?: string) => { - const schema: HaFormSchema[] = [ + ( + hasID: boolean, + useBluePrint?: boolean, + currentMode?: typeof MODES[number] + ) => + [ { name: "alias", selector: { @@ -102,49 +106,45 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { icon: {}, }, }, - ]; - - if (!hasID) { - schema.push({ - name: "id", - selector: { - text: {}, - }, - }); - } - - if (!useBluePrint) { - schema.push({ - name: "mode", - selector: { - select: { - options: MODES.map((mode) => ({ - label: ` - ${ - this.hass.localize( - `ui.panel.config.script.editor.modes.${mode}` - ) || mode - } - `, - value: mode, - })), - }, - }, - }); - } - - if (currentMode && MODES_MAX.includes(currentMode)) { - schema.push({ - name: "max", - required: true, - selector: { - number: { mode: "box", min: 1, max: Infinity }, - }, - }); - } - - return schema; - } + ...(!hasID + ? ([ + { + name: "id", + selector: { + text: {}, + }, + }, + ] as const) + : []), + ...(!useBluePrint + ? ([ + { + name: "mode", + selector: { + select: { + options: MODES.map((mode) => ({ + label: this.hass.localize( + `ui.panel.config.script.editor.modes.${mode}` + ), + value: mode, + })), + }, + }, + }, + ] as const) + : []), + ...(currentMode && isMaxMode(currentMode) + ? ([ + { + name: "max", + required: true, + selector: { + number: { mode: "box", min: 1, max: Infinity }, + }, + }, + ] as const) + : []), + ] as const ); protected render(): TemplateResult { @@ -161,10 +161,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { const data = { mode: MODES[0], icon: undefined, - max: - this._config.mode && MODES_MAX.includes(this._config.mode) - ? 10 - : undefined, + max: this._config.mode && isMaxMode(this._config.mode) ? 10 : undefined, ...this._config, id: this._entityId, }; @@ -506,15 +503,18 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { } private _computeLabelCallback = ( - schema: HaFormSelector, + schema: SchemaUnion>, data: HaFormDataContainer ): string => { switch (schema.name) { case "mode": return this.hass.localize("ui.panel.config.script.editor.modes.label"); case "max": + // Mode must be one of max modes per schema definition above return this.hass.localize( - `ui.panel.config.script.editor.max.${data.mode}` + `ui.panel.config.script.editor.max.${ + data.mode as typeof MODES_MAX[number] + }` ); default: return this.hass.localize( @@ -524,7 +524,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { }; private _computeHelperCallback = ( - schema: HaFormSelector + schema: SchemaUnion> ): string | undefined => { if (schema.name === "mode") { return this.hass.localize( @@ -562,7 +562,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { private _modeChanged(mode) { this._config = { ...this._config!, mode }; - if (!MODES_MAX.includes(mode)) { + if (!isMaxMode(mode)) { delete this._config.max; } this._dirty = true; diff --git a/src/panels/config/zone/dialog-zone-detail.ts b/src/panels/config/zone/dialog-zone-detail.ts index 07b9fcb4fa..9a0cc11313 100644 --- a/src/panels/config/zone/dialog-zone-detail.ts +++ b/src/panels/config/zone/dialog-zone-detail.ts @@ -6,7 +6,7 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord"; import { createCloseHeading } from "../../../components/ha-dialog"; import "../../../components/ha-form/ha-form"; -import { HaFormSchema } from "../../../components/ha-form/types"; +import { SchemaUnion } from "../../../components/ha-form/types"; import { getZoneEditorInitData, ZoneMutableParams } from "../../../data/zone"; import { haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; @@ -123,51 +123,54 @@ class DialogZoneDetail extends LitElement { `; } - private _schema = memoizeOne((icon?: string): HaFormSchema[] => [ - { - name: "name", - required: true, - selector: { - text: {}, - }, - }, - { - name: "icon", - required: false, - selector: { - icon: {}, - }, - }, - { - name: "location", - required: true, - selector: { location: { radius: true, icon } }, - }, - { - name: "", - type: "grid", - schema: [ + private _schema = memoizeOne( + (icon?: string) => + [ { - name: "latitude", + name: "name", required: true, - selector: { text: {} }, + selector: { + text: {}, + }, }, { - name: "longitude", + name: "icon", + required: false, + selector: { + icon: {}, + }, + }, + { + name: "location", required: true, + selector: { location: { radius: true, icon } }, + }, + { + name: "", + type: "grid", + schema: [ + { + name: "latitude", + required: true, + selector: { text: {} }, + }, + { + name: "longitude", + required: true, - selector: { text: {} }, + selector: { text: {} }, + }, + ], }, - ], - }, - { name: "passive_note", type: "constant" }, - { name: "passive", selector: { boolean: {} } }, - { - name: "radius", - required: false, - selector: { number: { min: 0, max: 999999, mode: "box" } }, - }, - ]); + { name: "passive_note", type: "constant" }, + { name: "passive", selector: { boolean: {} } }, + { + name: "radius", + required: false, + selector: { number: { min: 0, max: 999999, mode: "box" } }, + }, + ] as const + ); private _formData = memoizeOne((data: ZoneMutableParams) => ({ ...data, @@ -197,8 +200,9 @@ class DialogZoneDetail extends LitElement { this._data = value; } - private _computeLabel = (entry: HaFormSchema): string => - this.hass.localize(`ui.panel.config.zone.detail.${entry.name}`); + private _computeLabel = ( + entry: SchemaUnion> + ): string => this.hass.localize(`ui.panel.config.zone.detail.${entry.name}`); private async _updateEntry() { this._submitting = true; diff --git a/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts b/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts index ed49622a37..f02e3a8908 100644 --- a/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts @@ -8,13 +8,13 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; import type { LocalizeFunc } from "../../../../common/translations/localize"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { EntitiesCardEntityConfig } from "../../cards/types"; import type { LovelaceRowEditor } from "../../types"; import { entitiesConfigStruct } from "../structs/entities-struct"; -const SecondaryInfoValues: { [key: string]: { domains?: string[] } } = { +const SecondaryInfoValues = { none: {}, "entity-id": {}, "last-changed": {}, @@ -23,7 +23,7 @@ const SecondaryInfoValues: { [key: string]: { domains?: string[] } } = { position: { domains: ["cover"] }, "tilt-position": { domains: ["cover"] }, brightness: { domains: ["light"] }, -}; +} as const; @customElement("hui-generic-entity-row-editor") export class HuiGenericEntityRowEditor @@ -45,7 +45,7 @@ export class HuiGenericEntityRowEditor icon: string | undefined, entityState: HassEntity, localize: LocalizeFunc - ): HaFormSchema[] => { + ) => { const domain = computeDomain(entity); return [ @@ -73,23 +73,23 @@ export class HuiGenericEntityRowEditor name: "secondary_info", selector: { select: { - options: Object.keys(SecondaryInfoValues) - .filter( + options: ( + Object.keys(SecondaryInfoValues).filter( (info) => !("domains" in SecondaryInfoValues[info]) || ("domains" in SecondaryInfoValues[info] && SecondaryInfoValues[info].domains!.includes(domain)) - ) - .map((info) => ({ - value: info, - label: localize( - `ui.panel.lovelace.editor.card.entities.secondary_info_values.${info}` - ), - })), + ) as Array + ).map((info) => ({ + value: info, + label: localize( + `ui.panel.lovelace.editor.card.entities.secondary_info_values.${info}` + ), + })), }, }, }, - ]; + ] as const; } ); @@ -122,21 +122,19 @@ export class HuiGenericEntityRowEditor fireEvent(this, "config-changed", { config: ev.detail.value }); } - private _computeLabelCallback = (schema: HaFormSchema) => { - if (schema.name === "entity") { - return this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.entity" - ); + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "secondary_info": + return this.hass!.localize( + `ui.panel.lovelace.editor.card.entity-row.${schema.name}` + ); + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); } - - return ( - this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ) || - this.hass!.localize( - `ui.panel.lovelace.editor.card.entity-row.${schema.name}` - ) - ); }; } diff --git a/src/panels/lovelace/editor/config-elements/hui-logbook-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-logbook-card-editor.ts index dcbc6f325b..603e2b4682 100644 --- a/src/panels/lovelace/editor/config-elements/hui-logbook-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-logbook-card-editor.ts @@ -12,7 +12,7 @@ import { } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/entity/ha-entities-picker"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { LogbookCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; @@ -29,7 +29,7 @@ const cardConfigStruct = assign( }) ); -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "title", selector: { text: {} } }, { name: "", @@ -39,7 +39,7 @@ const SCHEMA: HaFormSchema[] = [ { name: "hours_to_show", selector: { number: { mode: "box", min: 1 } } }, ], }, -]; +] as const; @customElement("hui-logbook-card-editor") export class HuiLogbookCardEditor @@ -98,23 +98,19 @@ export class HuiLogbookCardEditor fireEvent(this, "config-changed", { config: ev.detail.value }); } - private _computeLabelCallback = (schema: HaFormSchema) => { - if (schema.name === "theme") { - return `${this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.theme" - )} (${this.hass!.localize( - "ui.panel.lovelace.editor.card.config.optional" - )})`; + private _computeLabelCallback = (schema: SchemaUnion) => { + switch (schema.name) { + case "theme": + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.theme" + )} (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.optional" + )})`; + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); } - - return ( - this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ) || - this.hass!.localize( - `ui.panel.lovelace.editor.card.logbook.${schema.name}` - ) - ); }; } diff --git a/src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts index 3d4e59d18c..4c23122554 100644 --- a/src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts @@ -26,7 +26,7 @@ import { entitiesConfigStruct } from "../structs/entities-struct"; import { EntitiesEditorEvent } from "../types"; import { configElementStyle } from "./config-elements-style"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import { HaFormSchema } from "../../../../components/ha-form/types"; +import { SchemaUnion } from "../../../../components/ha-form/types"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -41,7 +41,7 @@ const cardConfigStruct = assign( }) ); -const SCHEMA: HaFormSchema[] = [ +const SCHEMA = [ { name: "title", selector: { text: {} } }, { name: "", @@ -53,7 +53,7 @@ const SCHEMA: HaFormSchema[] = [ { name: "hours_to_show", selector: { number: { mode: "box", min: 1 } } }, ], }, -]; +] as const; @customElement("hui-map-card-editor") export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor { @@ -149,11 +149,19 @@ export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor { fireEvent(this, "config-changed", { config: ev.detail.value }); } - private _computeLabelCallback = (schema: HaFormSchema) => - this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ) || - this.hass!.localize(`ui.panel.lovelace.editor.card.map.${schema.name}`); + private _computeLabelCallback = (schema: SchemaUnion) => { + switch (schema.name) { + case "dark_mode": + case "default_zoom": + return this.hass!.localize( + `ui.panel.lovelace.editor.card.map.${schema.name}` + ); + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); + } + }; static get styles(): CSSResultGroup { return [ diff --git a/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts index 236f4b2efd..d3b06a385e 100644 --- a/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts @@ -16,7 +16,7 @@ import { import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { SensorCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; @@ -52,61 +52,58 @@ export class HuiSensorCardEditor } private _schema = memoizeOne( - ( - entity: string, - icon: string | undefined, - entityState: HassEntity - ): HaFormSchema[] => [ - { - name: "entity", - selector: { - entity: { domain: ["counter", "input_number", "number", "sensor"] }, + (entity: string, icon: string | undefined, entityState: HassEntity) => + [ + { + name: "entity", + selector: { + entity: { domain: ["counter", "input_number", "number", "sensor"] }, + }, }, - }, - { name: "name", selector: { text: {} } }, - { - type: "grid", - name: "", - schema: [ - { - name: "icon", - selector: { - icon: { - placeholder: icon || entityState?.attributes.icon, - fallbackPath: - !icon && !entityState?.attributes.icon && entityState - ? domainIcon(computeDomain(entity), entityState) - : undefined, + { name: "name", selector: { text: {} } }, + { + type: "grid", + name: "", + schema: [ + { + name: "icon", + selector: { + icon: { + placeholder: icon || entityState?.attributes.icon, + fallbackPath: + !icon && !entityState?.attributes.icon && entityState + ? domainIcon(computeDomain(entity), entityState) + : undefined, + }, }, }, - }, - { - name: "graph", - selector: { - select: { - options: [ - { - value: "none", - label: "None", - }, - { - value: "line", - label: "Line", - }, - ], + { + name: "graph", + selector: { + select: { + options: [ + { + value: "none", + label: "None", + }, + { + value: "line", + label: "Line", + }, + ], + }, }, }, - }, - { name: "unit", selector: { text: {} } }, - { name: "detail", selector: { boolean: {} } }, - { name: "theme", selector: { theme: {} } }, - { - name: "hours_to_show", - selector: { number: { min: 1, mode: "box" } }, - }, - ], - }, - ] + { name: "unit", selector: { text: {} } }, + { name: "detail", selector: { boolean: {} } }, + { name: "theme", selector: { theme: {} } }, + { + name: "hours_to_show", + selector: { number: { min: 1, mode: "box" } }, + }, + ], + }, + ] as const ); protected render(): TemplateResult { @@ -146,33 +143,29 @@ export class HuiSensorCardEditor fireEvent(this, "config-changed", { config }); } - private _computeLabelCallback = (schema: HaFormSchema) => { - if (schema.name === "entity") { - return this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.entity" - ); + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "theme": + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.theme" + )} (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.optional" + )})`; + case "detail": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.sensor.show_more_detail" + ); + case "graph": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.sensor.graph_type" + ); + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); } - - if (schema.name === "theme") { - return `${this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.theme" - )} (${this.hass!.localize( - "ui.panel.lovelace.editor.card.config.optional" - )})`; - } - - if (schema.name === "detail") { - return this.hass!.localize( - "ui.panel.lovelace.editor.card.sensor.show_more_detail" - ); - } - - return ( - this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ) || - this.hass!.localize(`ui.panel.lovelace.editor.card.sensor.${schema.name}`) - ); }; static get styles(): CSSResultGroup { diff --git a/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts index c695e56b54..879a6c8a41 100644 --- a/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts @@ -16,7 +16,7 @@ import { import { fireEvent } from "../../../../common/dom/fire_event"; import type { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/entity/ha-statistics-picker"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { StatisticsGraphCardConfig } from "../../cards/types"; import { processConfigEntities } from "../../common/process-config-entities"; @@ -50,7 +50,7 @@ const cardConfigStruct = assign( }) ); -const periods = ["5minute", "hour", "day", "month"]; +const periods = ["5minute", "hour", "day", "month"] as const; @customElement("hui-statistics-graph-card-editor") export class HuiStatisticsGraphCardEditor @@ -71,54 +71,57 @@ export class HuiStatisticsGraphCardEditor : []; } - private _schema = memoizeOne((localize: LocalizeFunc) => [ - { name: "title", selector: { text: {} } }, - { - name: "", - type: "grid", - schema: [ + private _schema = memoizeOne( + (localize: LocalizeFunc) => + [ + { name: "title", selector: { text: {} } }, { - name: "period", - required: true, - selector: { - select: { - options: periods.map((period) => ({ - value: period, - label: localize( - `ui.panel.lovelace.editor.card.statistics-graph.periods.${period}` - ), - })), + name: "", + type: "grid", + schema: [ + { + name: "period", + required: true, + selector: { + select: { + options: periods.map((period) => ({ + value: period, + label: localize( + `ui.panel.lovelace.editor.card.statistics-graph.periods.${period}` + ), + })), + }, + }, + }, + { + name: "days_to_show", + required: true, + selector: { number: { min: 1, mode: "box" } }, + }, + { + name: "stat_types", + required: true, + type: "multi_select", + options: [ + ["mean", "Mean"], + ["min", "Min"], + ["max", "Max"], + ["sum", "Sum"], + ], + }, + { + name: "chart_type", + required: true, + type: "select", + options: [ + ["line", "Line"], + ["bar", "Bar"], + ], }, - }, - }, - { - name: "days_to_show", - required: true, - selector: { number: { min: 1, mode: "box" } }, - }, - { - name: "stat_types", - required: true, - type: "multi_select", - options: [ - ["mean", "Mean"], - ["min", "Min"], - ["max", "Max"], - ["sum", "Sum"], ], }, - { - name: "chart_type", - required: true, - type: "select", - options: [ - ["line", "Line"], - ["bar", "Bar"], - ], - }, - ], - }, - ]); + ] as const + ); protected render(): TemplateResult { if (!this.hass || !this._config) { @@ -169,13 +172,22 @@ export class HuiStatisticsGraphCardEditor }); } - private _computeLabelCallback = (schema: HaFormSchema) => - this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ) || - this.hass!.localize( - `ui.panel.lovelace.editor.card.statistics-graph.${schema.name}` - ); + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "chart_type": + case "stat_types": + case "period": + return this.hass!.localize( + `ui.panel.lovelace.editor.card.statistics-graph.${schema.name}` + ); + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); + } + }; static styles: CSSResultGroup = css` ha-statistics-picker { diff --git a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts index 70a310b5da..410371bcdd 100644 --- a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts @@ -12,7 +12,7 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { UNAVAILABLE } from "../../../../data/entity"; import type { WeatherEntity } from "../../../../data/weather"; import type { LocalizeFunc } from "../../../../common/translations/localize"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -66,12 +66,8 @@ export class HuiWeatherForecastCardEditor } private _schema = memoize( - ( - entity: string, - localize: LocalizeFunc, - hasForecast?: boolean - ): HaFormSchema[] => { - const schema: HaFormSchema[] = [ + (entity: string, localize: LocalizeFunc, hasForecast?: boolean) => + [ { name: "entity", required: true, @@ -89,38 +85,38 @@ export class HuiWeatherForecastCardEditor { name: "theme", selector: { theme: {} } }, ], }, - ]; - if (hasForecast) { - schema.push({ - name: "forecast", - selector: { - select: { - options: [ - { - value: "show_both", - label: localize( - "ui.panel.lovelace.editor.card.weather-forecast.show_both" - ), + ...(hasForecast + ? ([ + { + name: "forecast", + selector: { + select: { + options: [ + { + value: "show_both", + label: localize( + "ui.panel.lovelace.editor.card.weather-forecast.show_both" + ), + }, + { + value: "show_current", + label: localize( + "ui.panel.lovelace.editor.card.weather-forecast.show_only_current" + ), + }, + { + value: "show_forecast", + label: localize( + "ui.panel.lovelace.editor.card.weather-forecast.show_only_forecast" + ), + }, + ], + }, }, - { - value: "show_current", - label: localize( - "ui.panel.lovelace.editor.card.weather-forecast.show_only_current" - ), - }, - { - value: "show_forecast", - label: localize( - "ui.panel.lovelace.editor.card.weather-forecast.show_only_forecast" - ), - }, - ], - }, - }, - }); - } - return schema; - } + }, + ] as const) + : []), + ] as const ); protected render(): TemplateResult { @@ -175,31 +171,31 @@ export class HuiWeatherForecastCardEditor fireEvent(this, "config-changed", { config }); } - private _computeLabelCallback = (schema: HaFormSchema) => { - if (schema.name === "entity") { - return `${this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.entity" - )} (${this.hass!.localize( - "ui.panel.lovelace.editor.card.config.required" - )})`; + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "entity": + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.entity" + )} (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.required" + )})`; + case "theme": + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.theme" + )} (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.optional" + )})`; + case "forecast": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.weather-forecast.weather_to_show" + ); + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); } - - if (schema.name === "theme") { - return `${this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.theme" - )} (${this.hass!.localize( - "ui.panel.lovelace.editor.card.config.optional" - )})`; - } - - return ( - this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ) || - this.hass!.localize( - `ui.panel.lovelace.editor.card.weather_forecast.${schema.name}` - ) - ); }; } diff --git a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts index 91c284ec73..8f34499782 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts @@ -4,7 +4,8 @@ import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; import { slugify } from "../../../../common/string/slugify"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { LocalizeFunc } from "../../../../common/translations/localize"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { LovelaceViewConfig } from "../../../../data/lovelace"; import type { HomeAssistant } from "../../../../types"; import { @@ -25,38 +26,43 @@ declare global { export class HuiViewEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public isNew!: boolean; + @property({ type: Boolean }) public isNew!: boolean; @state() private _config!: LovelaceViewConfig; private _suggestedPath = false; - private _schema = memoizeOne((localize): HaFormSchema[] => [ - { name: "title", selector: { text: {} } }, - { - name: "icon", - selector: { - icon: {}, - }, - }, - { name: "path", selector: { text: {} } }, - { name: "theme", selector: { theme: {} } }, - { - name: "type", - selector: { - select: { - options: [ - DEFAULT_VIEW_LAYOUT, - SIDEBAR_VIEW_LAYOUT, - PANEL_VIEW_LAYOUT, - ].map((type) => ({ - value: type, - label: localize(`ui.panel.lovelace.editor.edit_view.types.${type}`), - })), + private _schema = memoizeOne( + (localize: LocalizeFunc) => + [ + { name: "title", selector: { text: {} } }, + { + name: "icon", + selector: { + icon: {}, + }, }, - }, - }, - ]); + { name: "path", selector: { text: {} } }, + { name: "theme", selector: { theme: {} } }, + { + name: "type", + selector: { + select: { + options: [ + DEFAULT_VIEW_LAYOUT, + SIDEBAR_VIEW_LAYOUT, + PANEL_VIEW_LAYOUT, + ].map((type) => ({ + value: type, + label: localize( + `ui.panel.lovelace.editor.edit_view.types.${type}` + ), + })), + }, + }, + }, + ] as const + ); set config(config: LovelaceViewConfig) { this._config = config; @@ -114,15 +120,19 @@ export class HuiViewEditor extends LitElement { fireEvent(this, "view-config-changed", { config }); } - private _computeLabelCallback = (schema: HaFormSchema) => { - if (schema.name === "path") { - return this.hass!.localize(`ui.panel.lovelace.editor.card.generic.url`); + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "path": + return this.hass!.localize("ui.panel.lovelace.editor.card.generic.url"); + case "type": + return this.hass.localize("ui.panel.lovelace.editor.edit_view.type"); + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); } - return ( - this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ) || this.hass.localize("ui.panel.lovelace.editor.edit_view.type") - ); }; } diff --git a/src/translations/en.json b/src/translations/en.json index cf62a4996c..107ab50288 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2274,6 +2274,7 @@ "parallel": "Max number of parallel runs" }, "load_error_not_editable": "Only scripts inside scripts.yaml are editable.", + "load_error_unknown": "Error loading script ({err_no}).", "delete_confirm": "Are you sure you want to delete this script?", "delete_script": "Delete script", "save_script": "Save script", @@ -2756,6 +2757,7 @@ "name": "Name", "icon": "Icon", "icon_error_msg": "Icon should be in the format ''prefix:iconname'', for example: ''mdi:home''", + "location": "Map Location", "radius": "Radius", "latitude": "Latitude", "longitude": "Longitude", @@ -3936,7 +3938,6 @@ "geo_location_sources": "Geolocation Sources", "dark_mode": "Dark Mode?", "default_zoom": "Default Zoom", - "hours_to_show": "Hours to Show", "source": "Source", "description": "The Map card that allows you to display entities on a map." }, @@ -3992,6 +3993,7 @@ "weather-forecast": { "name": "Weather Forecast", "description": "The Weather Forecast card displays the weather. Very useful to include on interfaces that people display on the wall.", + "weather_to_show": "Weather to Show", "show_both": "Show current Weather and Forecast", "show_only_current": "Show only current Weather", "show_only_forecast": "Show only Forecast" From 47b820d28fb0832c56e71d982b5d5978ccd0d3cf Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 18 Aug 2022 10:04:35 -0400 Subject: [PATCH 034/134] Collapse automation/script editor sections by default (#13390) --- .../components/ha-expansion-panel.markdown | 5 + .../pages/components/ha-expansion-panel.ts | 157 +++++++ .../device/ha-device-automation-picker.ts | 3 +- src/components/ha-expansion-panel.ts | 101 +++-- .../ha-selector/ha-selector-template.ts | 10 +- src/components/ha-service-control.ts | 4 +- src/data/action.ts | 16 + src/data/automation_i18n.ts | 2 +- src/data/condition.ts | 15 + src/data/script_i18n.ts | 2 +- src/data/trigger.ts | 19 + .../action/ha-automation-action-row.ts | 241 +++++------ .../automation/action/ha-automation-action.ts | 114 ++++-- .../ha-automation-action-activate_scene.ts | 3 + .../types/ha-automation-action-choose.ts | 25 +- .../types/ha-automation-action-condition.ts | 71 +++- .../types/ha-automation-action-repeat.ts | 72 ++-- .../ha-automation-action-wait_for_trigger.ts | 4 + .../ha-automation-condition-editor.ts | 88 +--- .../condition/ha-automation-condition-row.ts | 114 ++++-- .../condition/ha-automation-condition.ts | 112 +++-- .../types/ha-automation-condition-logical.ts | 13 +- .../types/ha-automation-condition-template.ts | 10 +- .../types/ha-automation-condition-zone.ts | 2 +- .../config/automation/ha-automation-editor.ts | 11 +- .../automation/manual-automation-editor.ts | 387 +++++++++--------- .../trigger/ha-automation-trigger-row.ts | 272 +++++------- .../trigger/ha-automation-trigger.ts | 119 ++++-- .../types/ha-automation-trigger-tag.ts | 10 +- .../types/ha-automation-trigger-template.ts | 10 +- src/panels/config/script/ha-script-editor.ts | 183 ++++----- src/translations/en.json | 30 +- 32 files changed, 1305 insertions(+), 920 deletions(-) create mode 100644 gallery/src/pages/components/ha-expansion-panel.markdown create mode 100644 gallery/src/pages/components/ha-expansion-panel.ts create mode 100644 src/data/action.ts create mode 100644 src/data/condition.ts create mode 100644 src/data/trigger.ts diff --git a/gallery/src/pages/components/ha-expansion-panel.markdown b/gallery/src/pages/components/ha-expansion-panel.markdown new file mode 100644 index 0000000000..275627e6a2 --- /dev/null +++ b/gallery/src/pages/components/ha-expansion-panel.markdown @@ -0,0 +1,5 @@ +--- +title: Expansion Panel +--- + +Expansion panel following all the ARIA guidelines. diff --git a/gallery/src/pages/components/ha-expansion-panel.ts b/gallery/src/pages/components/ha-expansion-panel.ts new file mode 100644 index 0000000000..781aa063bb --- /dev/null +++ b/gallery/src/pages/components/ha-expansion-panel.ts @@ -0,0 +1,157 @@ +import { mdiPacMan } from "@mdi/js"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement } from "lit/decorators"; +import "../../../../src/components/ha-card"; +import "../../../../src/components/ha-expansion-panel"; +import "../../../../src/components/ha-markdown"; +import "../../components/demo-black-white-row"; +import { LONG_TEXT } from "../../data/text"; + +const SHORT_TEXT = LONG_TEXT.substring(0, 113); + +const SAMPLES: { + template: (slot: string, leftChevron: boolean) => TemplateResult; +}[] = [ + { + template(slot, leftChevron) { + return html` + + ${SHORT_TEXT} + + `; + }, + }, + { + template(slot, leftChevron) { + return html` + + ${SHORT_TEXT} + + `; + }, + }, + { + template(slot, leftChevron) { + return html` + + ${SHORT_TEXT} + + `; + }, + }, + { + template(slot, leftChevron) { + return html` + + ${SHORT_TEXT} + + `; + }, + }, + { + template(slot, leftChevron) { + return html` + + Slot Secondary + ${SHORT_TEXT} + + `; + }, + }, + { + template(slot, leftChevron) { + return html` + + Slot header + ${SHORT_TEXT} + + `; + }, + }, + { + template(slot, leftChevron) { + return html` + + Slot header with actions + + ${SHORT_TEXT} + + `; + }, + }, + { + template(slot, leftChevron) { + return html` + + + ${SHORT_TEXT} + + `; + }, + }, +]; + +@customElement("demo-components-ha-expansion-panel") +export class DemoHaExpansionPanel extends LitElement { + protected render(): TemplateResult { + return html` + ${SAMPLES.map( + (sample) => html` + + ${["light", "dark"].map((slot) => + sample.template(slot, slot === "dark") + )} + + ` + )} + `; + } + + static get styles() { + return css` + ha-expansion-panel { + margin: -16px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "demo-components-ha-expansion-panel": DemoHaExpansionPanel; + } +} diff --git a/src/components/device/ha-device-automation-picker.ts b/src/components/device/ha-device-automation-picker.ts index 9e12fe6e8e..d14833ca9e 100644 --- a/src/components/device/ha-device-automation-picker.ts +++ b/src/components/device/ha-device-automation-picker.ts @@ -172,8 +172,7 @@ export abstract class HaDeviceAutomationPicker< static get styles(): CSSResultGroup { return css` ha-select { - width: 100%; - margin-top: 4px; + display: block; } `; } diff --git a/src/components/ha-expansion-panel.ts b/src/components/ha-expansion-panel.ts index a4f3817508..33c93d352a 100644 --- a/src/components/ha-expansion-panel.ts +++ b/src/components/ha-expansion-panel.ts @@ -19,6 +19,8 @@ class HaExpansionPanel extends LitElement { @property({ type: Boolean, reflect: true }) outlined = false; + @property({ type: Boolean, reflect: true }) leftChevron = false; + @property() header?: string; @property() secondary?: string; @@ -29,23 +31,42 @@ class HaExpansionPanel extends LitElement { protected render(): TemplateResult { return html` -
- - ${this.header} - ${this.secondary} - - +
+
+ ${this.leftChevron + ? html` + + ` + : ""} + +
+ ${this.header} + ${this.secondary} +
+
+ ${!this.leftChevron + ? html` + + ` + : ""} +
+
{ + if (ev.defaultPrevented) { + return; + } if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") { return; } @@ -98,12 +123,28 @@ class HaExpansionPanel extends LitElement { fireEvent(this, "expanded-changed", { expanded: this.expanded }); } + private _focusChanged(ev) { + this.shadowRoot!.querySelector(".top")!.classList.toggle( + "focused", + ev.type === "focus" + ); + } + static get styles(): CSSResultGroup { return css` :host { display: block; } + .top { + display: flex; + align-items: center; + } + + .top.focused { + background: var(--input-fill-color); + } + :host([outlined]) { box-shadow: none; border-width: 1px; @@ -115,7 +156,17 @@ class HaExpansionPanel extends LitElement { border-radius: var(--ha-card-border-radius, 4px); } + .summary-icon { + margin-left: 8px; + } + + :host([leftchevron]) .summary-icon { + margin-left: 0; + margin-right: 8px; + } + #summary { + flex: 1; display: flex; padding: var(--expansion-panel-summary-padding, 0 8px); min-height: 48px; @@ -126,15 +177,8 @@ class HaExpansionPanel extends LitElement { outline: none; } - #summary:focus { - background: var(--input-fill-color); - } - .summary-icon { transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1); - margin-left: auto; - margin-inline-start: auto; - margin-inline-end: initial; direction: var(--direction); } @@ -142,6 +186,11 @@ class HaExpansionPanel extends LitElement { transform: rotate(180deg); } + .header, + ::slotted([slot="header"]) { + flex: 1; + } + .container { padding: var(--expansion-panel-content-padding, 0 8px); overflow: hidden; @@ -153,10 +202,6 @@ class HaExpansionPanel extends LitElement { height: auto; } - .header { - display: block; - } - .secondary { display: block; color: var(--secondary-text-color); diff --git a/src/components/ha-selector/ha-selector-template.ts b/src/components/ha-selector/ha-selector-template.ts index 0761473dd8..e571f4c2ba 100644 --- a/src/components/ha-selector/ha-selector-template.ts +++ b/src/components/ha-selector/ha-selector-template.ts @@ -1,4 +1,4 @@ -import { html, LitElement } from "lit"; +import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { HomeAssistant } from "../../types"; @@ -48,6 +48,14 @@ export class HaTemplateSelector extends LitElement { } fireEvent(this, "value-changed", { value }); } + + static get styles() { + return css` + p { + margin-top: 0; + } + `; + } } declare global { diff --git a/src/components/ha-service-control.ts b/src/components/ha-service-control.ts index 54cca6a765..a282f9b5f5 100644 --- a/src/components/ha-service-control.ts +++ b/src/components/ha-service-control.ts @@ -230,7 +230,9 @@ export class HaServiceControl extends LitElement { @value-changed=${this._serviceChanged} >
-

${serviceData?.description}

+ ${serviceData?.description + ? html`

${serviceData?.description}

` + : ""} ${this._manifest ? html` - `${trigger.platform} trigger`; + `${trigger.platform || "Unknown"} trigger`; export const describeCondition = (condition: Condition) => { if (condition.alias) { diff --git a/src/data/condition.ts b/src/data/condition.ts new file mode 100644 index 0000000000..2d064a61ff --- /dev/null +++ b/src/data/condition.ts @@ -0,0 +1,15 @@ +import type { Condition } from "./automation"; + +export const CONDITION_TYPES: Condition["condition"][] = [ + "device", + "and", + "or", + "not", + "state", + "numeric_state", + "sun", + "template", + "time", + "trigger", + "zone", +]; diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index a66c9529e0..6e3fcdc36b 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -123,7 +123,7 @@ export const describeAction = ( ? computeStateName(sceneStateObj) : "scene" in config ? config.scene - : config.target?.entity_id || config.entity_id + : config.target?.entity_id || config.entity_id || "" }`; } diff --git a/src/data/trigger.ts b/src/data/trigger.ts new file mode 100644 index 0000000000..6f2d5e1271 --- /dev/null +++ b/src/data/trigger.ts @@ -0,0 +1,19 @@ +import type { Trigger } from "./automation"; + +export const TRIGGER_TYPES: Trigger["platform"][] = [ + "calendar", + "device", + "event", + "state", + "geo_location", + "homeassistant", + "mqtt", + "numeric_state", + "sun", + "tag", + "template", + "time", + "time_pattern", + "webhook", + "zone", +]; 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 7d7d9bbd86..2e2f80c29d 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -3,21 +3,19 @@ import "@material/mwc-list/mwc-list-item"; import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators"; -import memoizeOne from "memoize-one"; +import { classMap } from "lit/directives/class-map"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { stringCompare } from "../../../../common/string/compare"; import { handleStructError } from "../../../../common/structs/handle-errors"; -import { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/ha-alert"; import "../../../../components/ha-button-menu"; import "../../../../components/ha-card"; import "../../../../components/ha-icon-button"; -import "../../../../components/ha-select"; -import type { HaSelect } from "../../../../components/ha-select"; +import "../../../../components/ha-expansion-panel"; import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import { validateConfig } from "../../../../data/config"; import { Action, getActionType } from "../../../../data/script"; +import { describeAction } from "../../../../data/script_i18n"; import { callExecuteScript } from "../../../../data/service"; import { showAlertDialog, @@ -40,23 +38,7 @@ import "./types/ha-automation-action-service"; import "./types/ha-automation-action-stop"; import "./types/ha-automation-action-wait_for_trigger"; import "./types/ha-automation-action-wait_template"; - -const OPTIONS = [ - "condition", - "delay", - "event", - "play_media", - "activate_scene", - "service", - "wait_template", - "wait_for_trigger", - "repeat", - "choose", - "if", - "device_id", - "stop", - "parallel", -]; +import { ACTION_TYPES } from "../../../../data/action"; const getType = (action: Action | undefined) => { if (!action) { @@ -68,7 +50,7 @@ const getType = (action: Action | undefined) => { if (["and", "or", "not"].some((key) => key in action)) { return "condition"; } - return OPTIONS.find((option) => option in action); + return ACTION_TYPES.find((option) => option in action); }; declare global { @@ -104,6 +86,8 @@ export const handleChangeEvent = (element: ActionElement, ev: CustomEvent) => { fireEvent(element, "value-changed", { value: newAction }); }; +const preventDefault = (ev) => ev.preventDefault(); + @customElement("ha-automation-action-row") export default class HaAutomationActionRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -124,19 +108,6 @@ export default class HaAutomationActionRow extends LitElement { @query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor; - private _processedTypes = memoizeOne( - (localize: LocalizeFunc): [string, string][] => - OPTIONS.map( - (action) => - [ - action, - localize( - `ui.panel.config.automation.editor.actions.type.${action}.label` - ), - ] as [string, string] - ).sort((a, b) => stringCompare(a[1], b[1])) - ); - protected willUpdate(changedProperties: PropertyValues) { if (!changedProperties.has("action")) { return; @@ -172,10 +143,14 @@ export default class HaAutomationActionRow extends LitElement { )}
` : ""} -
+ ${this.index !== 0 ? html` ` : ""} - + -
-
- ${this._warnings - ? html` - ${this._warnings!.length > 0 && this._warnings![0] !== undefined - ? html`
    - ${this._warnings!.map( - (warning) => html`
  • ${warning}
  • ` - )} -
` - : ""} - ${this.hass.localize("ui.errors.config.edit_in_yaml_supported")} -
` - : ""} - ${yamlMode - ? html` - ${type === undefined - ? html` - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.unsupported_action", - "action", - type - )} - ` - : ""} -

- ${this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" +
+ ${this._warnings + ? html` - - ` - : html` - - ${this._processedTypes(this.hass.localize).map( - ([opt, label]) => html` - ${label} - ` + ${this._warnings!.length > 0 && + this._warnings![0] !== undefined + ? html`
    + ${this._warnings!.map( + (warning) => html`
  • ${warning}
  • ` + )} +
` + : ""} + ${this.hass.localize( + "ui.errors.config.edit_in_yaml_supported" )} -
- -
- ${dynamicElement(`ha-automation-action-${type}`, { - hass: this.hass, - action: this.action, - narrow: this.narrow, - })} -
- `} -
+ ` + : ""} + ${yamlMode + ? html` + ${type === undefined + ? html` + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.unsupported_action", + "action", + type + )} + ` + : ""} +

+ ${this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} +

+ + ` + : html` +
+ ${dynamicElement(`ha-automation-action-${type}`, { + hass: this.hass, + action: this.action, + narrow: this.narrow, + })} +
+ `} +
+ `; } @@ -319,11 +290,13 @@ export default class HaAutomationActionRow extends LitElement { } } - private _moveUp() { + private _moveUp(ev) { + ev.preventDefault(); fireEvent(this, "move-action", { direction: "up" }); } - private _moveDown() { + private _moveDown(ev) { + ev.preventDefault(); fireEvent(this, "move-action", { direction: "down" }); } @@ -403,31 +376,6 @@ export default class HaAutomationActionRow extends LitElement { }); } - private _typeChanged(ev: CustomEvent) { - const type = (ev.target as HaSelect).value; - - if (!type) { - return; - } - - this._uiModeAvailable = OPTIONS.includes(type); - if (!this._uiModeAvailable && !this._yamlMode) { - this._yamlMode = false; - } - - if (type !== getType(this.action)) { - const elClass = customElements.get( - `ha-automation-action-${type}` - ) as CustomElementConstructor & { defaultConfig: Action }; - - fireEvent(this, "value-changed", { - value: { - ...elClass.defaultConfig, - }, - }); - } - } - private _onYamlChange(ev: CustomEvent) { ev.stopPropagation(); if (!ev.detail.isValid) { @@ -441,17 +389,30 @@ export default class HaAutomationActionRow extends LitElement { this._yamlMode = !this._yamlMode; } + public expand() { + this.updateComplete.then(() => { + this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true; + }); + } + static get styles(): CSSResultGroup { return [ haStyle, css` + ha-button-menu, + ha-icon-button { + --mdc-theme-text-primary-on-background: var(--primary-text-color); + } .disabled { opacity: 0.5; pointer-events: none; } + ha-expansion-panel { + --expansion-panel-summary-padding: 0 0 0 8px; + --expansion-panel-content-padding: 0; + } .card-content { - padding-top: 16px; - margin-top: 0; + padding: 16px; } .disabled-bar { background: var(--divider-color, #e0e0e0); @@ -459,14 +420,7 @@ export default class HaAutomationActionRow extends LitElement { border-top-right-radius: var(--ha-card-border-radius); border-top-left-radius: var(--ha-card-border-radius); } - .card-menu { - float: var(--float-end, right); - z-index: 3; - margin: 4px; - --mdc-theme-text-primary-on-background: var(--primary-text-color); - display: flex; - align-items: center; - } + mwc-list-item[disabled] { --mdc-theme-text-primary-on-background: var(--disabled-text-color); } @@ -476,9 +430,6 @@ export default class HaAutomationActionRow extends LitElement { .warning ul { margin: 4px 0; } - ha-select { - margin-bottom: 24px; - } `, ]; } diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index 3abbbd7d6e..7e27cdac76 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -1,13 +1,36 @@ +import { repeat } from "lit/directives/repeat"; +import { mdiPlus } from "@mdi/js"; import deepClone from "deep-clone-simple"; import "@material/mwc-button"; -import { css, CSSResultGroup, html, LitElement } from "lit"; +import type { ActionDetail } from "@material/mwc-list"; +import memoizeOne from "memoize-one"; +import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/ha-card"; +import "../../../../components/ha-svg-icon"; +import "../../../../components/ha-button-menu"; import { Action } from "../../../../data/script"; import { HomeAssistant } from "../../../../types"; import "./ha-automation-action-row"; -import { HaDeviceAction } from "./types/ha-automation-action-device_id"; +import type HaAutomationActionRow from "./ha-automation-action-row"; +import "./types/ha-automation-action-activate_scene"; +import "./types/ha-automation-action-choose"; +import "./types/ha-automation-action-condition"; +import "./types/ha-automation-action-delay"; +import "./types/ha-automation-action-device_id"; +import "./types/ha-automation-action-event"; +import "./types/ha-automation-action-if"; +import "./types/ha-automation-action-parallel"; +import "./types/ha-automation-action-play_media"; +import "./types/ha-automation-action-repeat"; +import "./types/ha-automation-action-service"; +import "./types/ha-automation-action-stop"; +import "./types/ha-automation-action-wait_for_trigger"; +import "./types/ha-automation-action-wait_template"; +import { ACTION_TYPES } from "../../../../data/action"; +import { stringCompare } from "../../../../common/string/compare"; +import { LocalizeFunc } from "../../../../common/translations/localize"; +import type { HaSelect } from "../../../../components/ha-select"; @customElement("ha-automation-action") export default class HaAutomationAction extends LitElement { @@ -17,9 +40,15 @@ export default class HaAutomationAction extends LitElement { @property() public actions!: Action[]; + private _focusLastActionOnChange = false; + protected render() { return html` - ${this.actions.map( + ${repeat( + this.actions, + // Use the action as key, so moving around keeps the same DOM, + // including expand state + (action) => action, (action, idx) => html` ` )} - -
- - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.add" - )} - -
-
+ + + + + ${this._processedTypes(this.hass.localize).map( + ([opt, label]) => html` + ${label} + ` + )} + `; } - private _addAction() { - const actions = this.actions.concat({ - ...HaDeviceAction.defaultConfig, - }); + protected updated(changedProps: PropertyValues) { + super.updated(changedProps); + if (changedProps.has("actions") && this._focusLastActionOnChange) { + this._focusLastActionOnChange = false; + + const row = this.shadowRoot!.querySelector( + "ha-automation-action-row:last-of-type" + )!; + row.expand(); + row.focus(); + } + } + + private _addAction(ev: CustomEvent) { + const action = (ev.currentTarget as HaSelect).items[ev.detail.index] + .value as typeof ACTION_TYPES[number]; + + const elClass = customElements.get( + `ha-automation-action-${action}` + ) as CustomElementConstructor & { defaultConfig: Action }; + + const actions = this.actions.concat({ + ...elClass.defaultConfig, + }); + this._focusLastActionOnChange = true; fireEvent(this, "value-changed", { value: actions }); } @@ -88,16 +145,27 @@ export default class HaAutomationAction extends LitElement { }); } + private _processedTypes = memoizeOne( + (localize: LocalizeFunc): [string, string][] => + ACTION_TYPES.map( + (action) => + [ + action, + localize( + `ui.panel.config.automation.editor.actions.type.${action}.label` + ), + ] as [string, string] + ).sort((a, b) => stringCompare(a[1], b[1])) + ); + static get styles(): CSSResultGroup { return css` - ha-automation-action-row, - ha-card { + ha-automation-action-row { display: block; - margin-top: 16px; + margin-bottom: 16px; } - .add-card mwc-button { - display: block; - text-align: center; + ha-svg-icon { + height: 20px; } `; } diff --git a/src/panels/config/automation/action/types/ha-automation-action-activate_scene.ts b/src/panels/config/automation/action/types/ha-automation-action-activate_scene.ts index 0ce146589a..aa3199c4ef 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-activate_scene.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-activate_scene.ts @@ -37,6 +37,9 @@ export class HaSceneAction extends LitElement implements ActionElement { return html` ` )} - -
- - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.type.choose.add_option" - )} - -
-
+ + +

${this.hass.localize( "ui.panel.config.automation.editor.actions.type.choose.default" @@ -154,7 +154,7 @@ export class HaChooseAction extends LitElement implements ActionElement { haStyle, css` ha-card { - margin-top: 16px; + margin: 16px 0; } .add-card mwc-button { display: block; @@ -168,6 +168,9 @@ export class HaChooseAction extends LitElement implements ActionElement { ha-form::part(root) { overflow: visible; } + ha-svg-icon { + height: 20px; + } `, ]; } diff --git a/src/panels/config/automation/action/types/ha-automation-action-condition.ts b/src/panels/config/automation/action/types/ha-automation-action-condition.ts index 6509d71c6c..23048b0983 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-condition.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-condition.ts @@ -1,10 +1,16 @@ -import { html, LitElement } from "lit"; +import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import { Condition } from "../../../../../data/automation"; +import { stringCompare } from "../../../../../common/string/compare"; +import type { LocalizeFunc } from "../../../../../common/translations/localize"; +import "../../../../../components/ha-select"; +import type { HaSelect } from "../../../../../components/ha-select"; +import type { Condition } from "../../../../../data/automation"; +import { CONDITION_TYPES } from "../../../../../data/condition"; import { HomeAssistant } from "../../../../../types"; import "../../condition/ha-automation-condition-editor"; -import { ActionElement } from "../ha-automation-action-row"; +import type { ActionElement } from "../ha-automation-action-row"; @customElement("ha-automation-action-condition") export class HaConditionAction extends LitElement implements ActionElement { @@ -18,6 +24,21 @@ export class HaConditionAction extends LitElement implements ActionElement { protected render() { return html` + + ${this._processedTypes(this.hass.localize).map( + ([opt, label]) => html` + ${label} + ` + )} + + CONDITION_TYPES.map( + (condition) => + [ + condition, + localize( + `ui.panel.config.automation.editor.conditions.type.${condition}.label` + ), + ] as [string, string] + ).sort((a, b) => stringCompare(a[1], b[1])) + ); + private _conditionChanged(ev: CustomEvent) { ev.stopPropagation(); @@ -33,6 +67,37 @@ export class HaConditionAction extends LitElement implements ActionElement { value: ev.detail.value, }); } + + private _typeChanged(ev: CustomEvent) { + const type = (ev.target as HaSelect).value; + + if (!type) { + return; + } + + const elClass = customElements.get( + `ha-automation-condition-${type}` + ) as CustomElementConstructor & { + defaultConfig: Omit; + }; + + if (type !== this.action.condition) { + fireEvent(this, "value-changed", { + value: { + condition: type, + ...elClass.defaultConfig, + }, + }); + } + } + + static get styles() { + return css` + ha-select { + margin-bottom: 24px; + } + `; + } } declare global { diff --git a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts index 6aaf19f8f1..6b78e6c0cf 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts @@ -52,42 +52,42 @@ export class HaRepeatAction extends LitElement implements ActionElement { ` )} - ${type === "count" - ? html` - - ` - : ""} - ${type === "while" - ? html`

- ${this.hass.localize( - `ui.panel.config.automation.editor.actions.type.repeat.type.while.conditions` - )}: -

- ` - : ""} - ${type === "until" - ? html`

- ${this.hass.localize( - `ui.panel.config.automation.editor.actions.type.repeat.type.until.conditions` - )}: -

- ` - : ""} +
+ ${type === "count" + ? html` + + ` + : type === "while" + ? html`

+ ${this.hass.localize( + `ui.panel.config.automation.editor.actions.type.repeat.type.while.conditions` + )}: +

+ ` + : type === "until" + ? html`

+ ${this.hass.localize( + `ui.panel.config.automation.editor.actions.type.repeat.type.until.conditions` + )}: +

+ ` + : ""} +

${this.hass.localize( "ui.panel.config.automation.editor.actions.type.repeat.sequence" diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts index e0f4554fa4..bee68da94c 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts @@ -68,6 +68,10 @@ export class HaWaitForTriggerAction display: block; margin-bottom: 24px; } + ha-automation-trigger { + display: block; + margin-top: 24px; + } `; } } diff --git a/src/panels/config/automation/condition/ha-automation-condition-editor.ts b/src/panels/config/automation/condition/ha-automation-condition-editor.ts index b694b3536c..45b026695f 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -1,12 +1,8 @@ -import { css, html, LitElement } from "lit"; +import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { stringCompare } from "../../../../common/string/compare"; -import type { LocalizeFunc } from "../../../../common/translations/localize"; -import "../../../../components/ha-select"; -import type { HaSelect } from "../../../../components/ha-select"; import "../../../../components/ha-yaml-editor"; import type { Condition } from "../../../../data/automation"; import { expandConditionWithShorthand } from "../../../../data/automation"; @@ -24,20 +20,6 @@ import "./types/ha-automation-condition-time"; import "./types/ha-automation-condition-trigger"; import "./types/ha-automation-condition-zone"; -const OPTIONS = [ - "device", - "and", - "or", - "not", - "state", - "numeric_state", - "sun", - "template", - "time", - "trigger", - "zone", -] as const; - @customElement("ha-automation-condition-editor") export default class HaAutomationConditionEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -50,27 +32,16 @@ export default class HaAutomationConditionEditor extends LitElement { expandConditionWithShorthand(condition) ); - private _processedTypes = memoizeOne( - (localize: LocalizeFunc): [string, string][] => - OPTIONS.map( - (condition) => - [ - condition, - localize( - `ui.panel.config.automation.editor.conditions.type.${condition}.label` - ), - ] as [string, string] - ).sort((a, b) => stringCompare(a[1], b[1])) - ); - protected render() { const condition = this._processedCondition(this.condition); - const selected = OPTIONS.indexOf(condition.condition); - const yamlMode = this.yamlMode || selected === -1; + const supported = + customElements.get(`ha-automation-condition-${condition.condition}`) !== + undefined; + const yamlMode = this.yamlMode || !supported; return html` ${yamlMode ? html` - ${selected === -1 + ${!supported ? html` ${this.hass.localize( "ui.panel.config.automation.editor.conditions.unsupported_condition", @@ -91,21 +62,6 @@ export default class HaAutomationConditionEditor extends LitElement { > ` : html` - - ${this._processedTypes(this.hass.localize).map( - ([opt, label]) => html` - ${label} - ` - )} - -
${dynamicElement( `ha-automation-condition-${condition.condition}`, @@ -116,29 +72,6 @@ export default class HaAutomationConditionEditor extends LitElement { `; } - private _typeChanged(ev: CustomEvent) { - const type = (ev.target as HaSelect).value; - - if (!type) { - return; - } - - const elClass = customElements.get( - `ha-automation-condition-${type}` - ) as CustomElementConstructor & { - defaultConfig: Omit; - }; - - if (type !== this._processedCondition(this.condition).condition) { - fireEvent(this, "value-changed", { - value: { - condition: type, - ...elClass.defaultConfig, - }, - }); - } - } - private _onYamlChange(ev: CustomEvent) { ev.stopPropagation(); if (!ev.detail.isValid) { @@ -148,14 +81,7 @@ export default class HaAutomationConditionEditor extends LitElement { fireEvent(this, "value-changed", { value: ev.detail.value, yaml: true }); } - static styles = [ - haStyle, - css` - ha-select { - margin-bottom: 24px; - } - `, - ]; + static styles = haStyle; } declare global { diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index f5450196e0..4ccf7fedd8 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -3,6 +3,7 @@ import "@material/mwc-list/mwc-list-item"; import { mdiDotsVertical } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property, query, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../../../common/dom/fire_event"; import { handleStructError } from "../../../../common/structs/handle-errors"; import "../../../../components/ha-button-menu"; @@ -10,6 +11,7 @@ import "../../../../components/ha-card"; import "../../../../components/buttons/ha-progress-button"; import type { HaProgressButton } from "../../../../components/buttons/ha-progress-button"; import "../../../../components/ha-icon-button"; +import "../../../../components/ha-expansion-panel"; import { Condition, testCondition } from "../../../../data/automation"; import { showAlertDialog, @@ -20,11 +22,14 @@ import { HomeAssistant } from "../../../../types"; import "./ha-automation-condition-editor"; import { validateConfig } from "../../../../data/config"; import { HaYamlEditor } from "../../../../components/ha-yaml-editor"; +import { describeCondition } from "../../../../data/automation_i18n"; export interface ConditionElement extends LitElement { condition: Condition; } +const preventDefault = (ev) => ev.preventDefault(); + export const handleChangeEvent = ( element: ConditionElement, ev: CustomEvent @@ -75,13 +80,23 @@ export default class HaAutomationConditionRow extends LitElement { )}
` : ""} -
- + + + ${this.hass.localize( "ui.panel.config.automation.editor.conditions.test" )} - + -
-
- ${this._warnings - ? html` - ${this._warnings!.length > 0 && this._warnings![0] !== undefined - ? html`
    - ${this._warnings!.map( - (warning) => html`
  • ${warning}
  • ` - )} -
` - : ""} - ${this.hass.localize("ui.errors.config.edit_in_yaml_supported")} -
` - : ""} - -
+ +
+ ${this._warnings + ? html` + ${this._warnings!.length > 0 && + this._warnings![0] !== undefined + ? html`
    + ${this._warnings!.map( + (warning) => html`
  • ${warning}
  • ` + )} +
` + : ""} + ${this.hass.localize( + "ui.errors.config.edit_in_yaml_supported" + )} +
` + : ""} + +
+ `; } @@ -212,6 +232,7 @@ export default class HaAutomationConditionRow extends LitElement { } private async _testCondition(ev) { + ev.preventDefault(); const condition = this.condition; const button = ev.target as HaProgressButton; if (button.progress) { @@ -269,17 +290,30 @@ export default class HaAutomationConditionRow extends LitElement { } } + public expand() { + this.updateComplete.then(() => { + this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true; + }); + } + static get styles(): CSSResultGroup { return [ haStyle, css` + ha-button-menu, + ha-progress-button { + --mdc-theme-text-primary-on-background: var(--primary-text-color); + } .disabled { opacity: 0.5; pointer-events: none; } + ha-expansion-panel { + --expansion-panel-summary-padding: 0 0 0 8px; + --expansion-panel-content-padding: 0; + } .card-content { - padding-top: 16px; - margin-top: 0; + padding: 16px; } .disabled-bar { background: var(--divider-color, #e0e0e0); @@ -287,14 +321,6 @@ export default class HaAutomationConditionRow extends LitElement { border-top-right-radius: var(--ha-card-border-radius); border-top-left-radius: var(--ha-card-border-radius); } - .card-menu { - float: var(--float-end, right); - z-index: 3; - margin: 4px; - --mdc-theme-text-primary-on-background: var(--primary-text-color); - display: flex; - align-items: center; - } mwc-list-item[disabled] { --mdc-theme-text-primary-on-background: var(--disabled-text-color); } diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index de5972e164..9229fa5657 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -1,13 +1,34 @@ +import { mdiPlus } from "@mdi/js"; +import { repeat } from "lit/directives/repeat"; import deepClone from "deep-clone-simple"; import "@material/mwc-button"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import type { ActionDetail } from "@material/mwc-list"; import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/ha-card"; -import { Condition } from "../../../../data/automation"; -import { HomeAssistant } from "../../../../types"; +import "../../../../components/ha-svg-icon"; +import "../../../../components/ha-button-menu"; +import type { Condition } from "../../../../data/automation"; +import type { HomeAssistant } from "../../../../types"; import "./ha-automation-condition-row"; -import { HaDeviceCondition } from "./types/ha-automation-condition-device"; +import type HaAutomationConditionRow from "./ha-automation-condition-row"; +// Uncommenting these and this element doesn't load +// import "./types/ha-automation-condition-not"; +// import "./types/ha-automation-condition-or"; +import "./types/ha-automation-condition-and"; +import "./types/ha-automation-condition-device"; +import "./types/ha-automation-condition-numeric_state"; +import "./types/ha-automation-condition-state"; +import "./types/ha-automation-condition-sun"; +import "./types/ha-automation-condition-template"; +import "./types/ha-automation-condition-time"; +import "./types/ha-automation-condition-trigger"; +import "./types/ha-automation-condition-zone"; +import { CONDITION_TYPES } from "../../../../data/condition"; +import { stringCompare } from "../../../../common/string/compare"; +import type { LocalizeFunc } from "../../../../common/translations/localize"; +import type { HaSelect } from "../../../../components/ha-select"; @customElement("ha-automation-condition") export default class HaAutomationCondition extends LitElement { @@ -15,10 +36,13 @@ export default class HaAutomationCondition extends LitElement { @property() public conditions!: Condition[]; + private _focusLastConditionOnChange = false; + protected updated(changedProperties: PropertyValues) { if (!changedProperties.has("conditions")) { return; } + let updatedConditions: Condition[] | undefined; if (!Array.isArray(this.conditions)) { updatedConditions = [this.conditions]; @@ -38,6 +62,13 @@ export default class HaAutomationCondition extends LitElement { fireEvent(this, "value-changed", { value: updatedConditions, }); + } else if (this._focusLastConditionOnChange) { + this._focusLastConditionOnChange = false; + const row = this.shadowRoot!.querySelector( + "ha-automation-condition-row:last-of-type" + )!; + row.expand(); + row.focus(); } } @@ -46,7 +77,11 @@ export default class HaAutomationCondition extends LitElement { return html``; } return html` - ${this.conditions.map( + ${repeat( + this.conditions, + // Use the condition as key, so moving around keeps the same DOM, + // including expand state + (condition) => condition, (cond, idx) => html` ` )} - -
- - ${this.hass.localize( - "ui.panel.config.automation.editor.conditions.add" - )} - -
-
+ + + + + ${this._processedTypes(this.hass.localize).map( + ([opt, label]) => html` + ${label} + ` + )} + `; } - private _addCondition() { - const conditions = this.conditions.concat({ - condition: "device", - ...HaDeviceCondition.defaultConfig, - }); + private _addCondition(ev: CustomEvent) { + const condition = (ev.currentTarget as HaSelect).items[ev.detail.index] + .value as Condition["condition"]; + const elClass = customElements.get( + `ha-automation-condition-${condition}` + ) as CustomElementConstructor & { + defaultConfig: Omit; + }; + + const conditions = this.conditions.concat({ + condition: condition as any, + ...elClass.defaultConfig, + }); + this._focusLastConditionOnChange = true; fireEvent(this, "value-changed", { value: conditions }); } @@ -101,16 +152,27 @@ export default class HaAutomationCondition extends LitElement { }); } + private _processedTypes = memoizeOne( + (localize: LocalizeFunc): [string, string][] => + CONDITION_TYPES.map( + (condition) => + [ + condition, + localize( + `ui.panel.config.automation.editor.conditions.type.${condition}.label` + ), + ] as [string, string] + ).sort((a, b) => stringCompare(a[1], b[1])) + ); + static get styles(): CSSResultGroup { return css` - ha-automation-condition-row, - ha-card { + ha-automation-condition-row { display: block; - margin-top: 16px; + margin-bottom: 16px; } - .add-card mwc-button { - display: block; - text-align: center; + ha-svg-icon { + height: 20px; } `; } diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts b/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts index 18933eb6df..628c050663 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts @@ -1,14 +1,10 @@ import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import type { - Condition, - LogicalCondition, -} from "../../../../../data/automation"; +import type { LogicalCondition } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; import "../ha-automation-condition"; import type { ConditionElement } from "../ha-automation-condition-row"; -import { HaStateCondition } from "./ha-automation-condition-state"; @customElement("ha-automation-condition-logical") export class HaLogicalCondition extends LitElement implements ConditionElement { @@ -18,12 +14,7 @@ export class HaLogicalCondition extends LitElement implements ConditionElement { public static get defaultConfig() { return { - conditions: [ - { - condition: "state", - ...HaStateCondition.defaultConfig, - }, - ] as Condition[], + conditions: [], }; } diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-template.ts b/src/panels/config/automation/condition/types/ha-automation-condition-template.ts index ed9bc32e82..2d21860834 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-template.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-template.ts @@ -1,4 +1,4 @@ -import { html, LitElement } from "lit"; +import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import "../../../../../components/ha-textarea"; import type { TemplateCondition } from "../../../../../data/automation"; @@ -39,6 +39,14 @@ export class HaTemplateCondition extends LitElement { private _valueChanged(ev: CustomEvent): void { handleChangeEvent(this, ev); } + + static get styles() { + return css` + p { + margin-top: 0; + } + `; + } } declare global { diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-zone.ts b/src/panels/config/automation/condition/types/ha-automation-condition-zone.ts index f1825750c1..def76b97d8 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-zone.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-zone.ts @@ -73,7 +73,7 @@ export class HaZoneCondition extends LitElement { } static styles = css` - ha-entity-picker { + ha-entity-picker:first-child { display: block; margin-bottom: 24px; } diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index d2ceff6b61..6c0c087d4a 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -50,10 +50,8 @@ import { HomeAssistant, Route } from "../../../types"; import { showToast } from "../../../util/toast"; import "../ha-config-section"; import { configSections } from "../ha-panel-config"; -import { HaDeviceAction } from "./action/types/ha-automation-action-device_id"; import "./blueprint-automation-editor"; import "./manual-automation-editor"; -import { HaDeviceTrigger } from "./trigger/types/ha-automation-trigger-device"; declare global { interface HTMLElementTagNameMap { @@ -329,9 +327,9 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { baseConfig = { ...baseConfig, mode: "single", - trigger: [{ platform: "device", ...HaDeviceTrigger.defaultConfig }], + trigger: [], condition: [], - action: [{ ...HaDeviceAction.defaultConfig }], + action: [], }; } this._config = { @@ -570,6 +568,11 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { flex-direction: column; padding-bottom: 0; } + manual-automation-editor { + margin: 0 auto; + max-width: 1040px; + padding: 28px 20px 0; + } ha-yaml-editor { flex-grow: 1; --code-mirror-height: 100%; diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index 4ed456a86e..eb8b7ef3b1 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -1,4 +1,5 @@ import "@material/mwc-button/mwc-button"; +import { mdiHelpCircle } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -7,6 +8,7 @@ import "../../../components/entity/ha-entity-toggle"; import "../../../components/ha-card"; import "../../../components/ha-textarea"; import "../../../components/ha-textfield"; +import "../../../components/ha-icon-button"; import { AUTOMATION_DEFAULT_MODE, Condition, @@ -18,7 +20,6 @@ import { Action, isMaxMode, MODES } from "../../../data/script"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; -import "../ha-config-section"; import "./action/ha-automation-action"; import "./condition/ha-automation-condition"; import "./trigger/ha-automation-trigger"; @@ -38,218 +39,198 @@ export class HaManualAutomationEditor extends LitElement { @state() private _showDescription = false; protected render() { - return html` - ${!this.narrow - ? html`${this.config.alias}` - : ""} - - ${this.hass.localize( - "ui.panel.config.automation.editor.introduction" - )} - - -
- ${this.stateObj + return html` + +
+ + + ${this._showDescription ? html` -
-
- - ${this.hass.localize( - "ui.panel.config.automation.editor.enable_disable" - )} -
-
- - - ${this.hass.localize( - "ui.panel.config.automation.editor.show_trace" - )} - - - - ${this.hass.localize("ui.card.automation.trigger")} - -
-
+ ` - : ""} - - + : html` + + `} + ${this.hass.localize( + "ui.panel.config.automation.editor.modes.learn_more" + )} + `} + > + ${MODES.map( + (mode) => html` + + ${this.hass.localize( + `ui.panel.config.automation.editor.modes.${mode}` + ) || mode} + + ` + )} + + ${this.config.mode && isMaxMode(this.config.mode) + ? html` +
+ + ` + : html``} +
+ ${this.stateObj + ? html` +
+
+ + ${this.hass.localize( + "ui.panel.config.automation.editor.enable_disable" + )} +
+
+ + + ${this.hass.localize( + "ui.panel.config.automation.editor.show_trace" + )} + + + + ${this.hass.localize("ui.card.automation.trigger")} + +
+
+ ` + : ""} +
- - +
+
${this.hass.localize( "ui.panel.config.automation.editor.triggers.header" )} - - -

- ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.introduction" - )} -

- - ${this.hass.localize( +
+ + - - - + > + +
- - + + +
+
${this.hass.localize( "ui.panel.config.automation.editor.conditions.header" )} - - -

- ${this.hass.localize( - "ui.panel.config.automation.editor.conditions.introduction" - )} -

- - ${this.hass.localize( +
+ + - - - + > + +
- - + + +
+
${this.hass.localize( "ui.panel.config.automation.editor.actions.header" )} - - -

- ${this.hass.localize( - "ui.panel.config.automation.editor.actions.introduction" - )} -

- - ${this.hass.localize( +
+ + - - - `; + > + +
+ + + `; } protected willUpdate(changedProps: PropertyValues): void { @@ -341,6 +322,9 @@ export class HaManualAutomationEditor extends LitElement { return [ haStyle, css` + :host { + display: block; + } ha-card { overflow: hidden; } @@ -351,9 +335,7 @@ export class HaManualAutomationEditor extends LitElement { ha-textfield { display: block; } - span[slot="introduction"] a { - color: var(--primary-color); - } + p { margin-bottom: 0; } @@ -365,6 +347,19 @@ export class HaManualAutomationEditor extends LitElement { margin-top: 16px; width: 200px; } + .header { + display: flex; + margin: 16px 0; + align-items: center; + } + .header .name { + font-size: 20px; + font-weight: 400; + flex: 1; + } + .header a { + color: var(--secondary-text-color); + } `, ]; } diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 0894164036..a565bcae4f 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -5,20 +5,16 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import memoizeOne from "memoize-one"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { stringCompare } from "../../../../common/string/compare"; import { handleStructError } from "../../../../common/structs/handle-errors"; -import { LocalizeFunc } from "../../../../common/translations/localize"; import { debounce } from "../../../../common/util/debounce"; import "../../../../components/ha-alert"; import "../../../../components/ha-button-menu"; import "../../../../components/ha-card"; +import "../../../../components/ha-expansion-panel"; import "../../../../components/ha-icon-button"; import { HaYamlEditor } from "../../../../components/ha-yaml-editor"; -import "../../../../components/ha-select"; -import type { HaSelect } from "../../../../components/ha-select"; import "../../../../components/ha-textfield"; import { subscribeTrigger, Trigger } from "../../../../data/automation"; import { validateConfig } from "../../../../data/config"; @@ -43,24 +39,7 @@ import "./types/ha-automation-trigger-time"; import "./types/ha-automation-trigger-time_pattern"; import "./types/ha-automation-trigger-webhook"; import "./types/ha-automation-trigger-zone"; - -const OPTIONS = [ - "calendar", - "device", - "event", - "state", - "geo_location", - "homeassistant", - "mqtt", - "numeric_state", - "sun", - "tag", - "template", - "time", - "time_pattern", - "webhook", - "zone", -] as const; +import { describeTrigger } from "../../../../data/automation_i18n"; export interface TriggerElement extends LitElement { trigger: Trigger; @@ -88,6 +67,8 @@ export const handleChangeEvent = (element: TriggerElement, ev: CustomEvent) => { fireEvent(element, "value-changed", { value: newTrigger }); }; +const preventDefault = (ev) => ev.preventDefault(); + @customElement("ha-automation-trigger-row") export default class HaAutomationTriggerRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -108,35 +89,36 @@ export default class HaAutomationTriggerRow extends LitElement { private _triggerUnsub?: Promise; - private _processedTypes = memoizeOne( - (localize: LocalizeFunc): [string, string][] => - OPTIONS.map( - (action) => - [ - action, - localize( - `ui.panel.config.automation.editor.triggers.type.${action}.label` - ), - ] as [string, string] - ).sort((a, b) => stringCompare(a[1], b[1])) - ); - protected render() { - const selected = OPTIONS.indexOf(this.trigger.platform); - const yamlMode = this._yamlMode || selected === -1; + const supported = + customElements.get(`ha-automation-trigger-${this.trigger.platform}`) !== + undefined; + const yamlMode = this._yamlMode || !supported; const showId = "id" in this.trigger || this._requestShowId; return html` ${this.trigger.enabled === false - ? html`
- ${this.hass.localize( - "ui.panel.config.automation.editor.actions.disabled" - )} -
` + ? html` +
+ ${this.hass.localize( + "ui.panel.config.automation.editor.actions.disabled" + )} +
+ ` : ""} -
- + + + - + ${yamlMode ? this.hass.localize( "ui.panel.config.automation.editor.edit_ui" @@ -176,86 +158,77 @@ export default class HaAutomationTriggerRow extends LitElement { )} -
-
- ${this._warnings - ? html` - ${this._warnings.length && this._warnings[0] !== undefined - ? html`
    - ${this._warnings.map( - (warning) => html`
  • ${warning}
  • ` - )} -
` - : ""} - ${this.hass.localize("ui.errors.config.edit_in_yaml_supported")} -
` - : ""} - ${yamlMode - ? html` - ${selected === -1 - ? html` - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.unsupported_platform", - "platform", - this.trigger.platform - )} - ` - : ""} -

- ${this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" + +
+ ${this._warnings + ? html` - - ` - : html` - - ${this._processedTypes(this.hass.localize).map( - ([opt, label]) => html` - ${label} - ` - )} - - ${showId - ? html` - + ${this._warnings.map( + (warning) => html`
  • ${warning}
  • ` )} - .value=${this.trigger.id || ""} - @change=${this._idChanged} - > -
    - ` - : ""} -
    - ${dynamicElement( - `ha-automation-trigger-${this.trigger.platform}`, - { hass: this.hass, trigger: this.trigger } + ` + : ""} + ${this.hass.localize( + "ui.errors.config.edit_in_yaml_supported" )} -
    - `} -
    + ` + : ""} + ${yamlMode + ? html` + ${!supported + ? html` + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.unsupported_platform", + "platform", + this.trigger.platform + )} + ` + : ""} +

    + ${this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} +

    + + ` + : html` + ${showId + ? html` + + + ` + : ""} +
    + ${dynamicElement( + `ha-automation-trigger-${this.trigger.platform}`, + { hass: this.hass, trigger: this.trigger } + )} +
    + `} +
    + +
    ; - }; - - if (type !== this.trigger.platform) { - const value = { - platform: type, - ...elClass.defaultConfig, - }; - if (this.trigger.id) { - value.id = this.trigger.id; - } - fireEvent(this, "value-changed", { - value, - }); - } - } - private _idChanged(ev: CustomEvent) { const newId = (ev.target as any).value; if (newId === (this.trigger.id ?? "")) { @@ -468,17 +415,29 @@ export default class HaAutomationTriggerRow extends LitElement { }); } + public expand() { + this.updateComplete.then(() => { + this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true; + }); + } + static get styles(): CSSResultGroup { return [ haStyle, css` + ha-button-menu { + --mdc-theme-text-primary-on-background: var(--primary-text-color); + } .disabled { opacity: 0.5; pointer-events: none; } + ha-expansion-panel { + --expansion-panel-summary-padding: 0 0 0 8px; + --expansion-panel-content-padding: 0; + } .card-content { - padding-top: 16px; - margin-top: 0; + padding: 16px; } .disabled-bar { background: var(--divider-color, #e0e0e0); @@ -486,14 +445,6 @@ export default class HaAutomationTriggerRow extends LitElement { border-top-right-radius: var(--ha-card-border-radius); border-top-left-radius: var(--ha-card-border-radius); } - .card-menu { - float: var(--float-end, right); - z-index: 3; - margin: 4px; - --mdc-theme-text-primary-on-background: var(--primary-text-color); - display: flex; - align-items: center; - } .triggered { cursor: pointer; position: absolute; @@ -525,9 +476,6 @@ export default class HaAutomationTriggerRow extends LitElement { mwc-list-item[disabled] { --mdc-theme-text-primary-on-background: var(--disabled-text-color); } - ha-select { - margin-bottom: 24px; - } ha-textfield { display: block; margin-bottom: 24px; diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index 1b359dd83e..9480f49220 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -1,13 +1,37 @@ +import { repeat } from "lit/directives/repeat"; +import { mdiPlus } from "@mdi/js"; import deepClone from "deep-clone-simple"; +import memoizeOne from "memoize-one"; import "@material/mwc-button"; -import { css, CSSResultGroup, html, LitElement } from "lit"; +import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; +import type { ActionDetail } from "@material/mwc-list"; import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/ha-card"; +import "../../../../components/ha-svg-icon"; +import "../../../../components/ha-button-menu"; import { Trigger } from "../../../../data/automation"; +import { TRIGGER_TYPES } from "../../../../data/trigger"; import { HomeAssistant } from "../../../../types"; import "./ha-automation-trigger-row"; -import { HaDeviceTrigger } from "./types/ha-automation-trigger-device"; +import type HaAutomationTriggerRow from "./ha-automation-trigger-row"; +import type { LocalizeFunc } from "../../../../common/translations/localize"; +import { stringCompare } from "../../../../common/string/compare"; +import type { HaSelect } from "../../../../components/ha-select"; +import "./types/ha-automation-trigger-calendar"; +import "./types/ha-automation-trigger-device"; +import "./types/ha-automation-trigger-event"; +import "./types/ha-automation-trigger-geo_location"; +import "./types/ha-automation-trigger-homeassistant"; +import "./types/ha-automation-trigger-mqtt"; +import "./types/ha-automation-trigger-numeric_state"; +import "./types/ha-automation-trigger-state"; +import "./types/ha-automation-trigger-sun"; +import "./types/ha-automation-trigger-tag"; +import "./types/ha-automation-trigger-template"; +import "./types/ha-automation-trigger-time"; +import "./types/ha-automation-trigger-time_pattern"; +import "./types/ha-automation-trigger-webhook"; +import "./types/ha-automation-trigger-zone"; @customElement("ha-automation-trigger") export default class HaAutomationTrigger extends LitElement { @@ -15,9 +39,15 @@ export default class HaAutomationTrigger extends LitElement { @property() public triggers!: Trigger[]; + private _focusLastTriggerOnChange = false; + protected render() { return html` - ${this.triggers.map( + ${repeat( + this.triggers, + // Use the trigger as key, so moving around keeps the same DOM, + // including expand state + (trigger) => trigger, (trg, idx) => html` ` )} - -
    - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.add" - )} - -
    -
    + + + + + ${this._processedTypes(this.hass.localize).map( + ([opt, label]) => html` + ${label} + ` + )} + `; } - private _addTrigger() { - const triggers = this.triggers.concat({ - platform: "device", - ...HaDeviceTrigger.defaultConfig, - }); + protected updated(changedProps: PropertyValues) { + super.updated(changedProps); + if (changedProps.has("triggers") && this._focusLastTriggerOnChange) { + this._focusLastTriggerOnChange = false; + + const row = this.shadowRoot!.querySelector( + "ha-automation-trigger-row:last-of-type" + )!; + row.expand(); + row.focus(); + } + } + + private _addTrigger(ev: CustomEvent) { + const platform = (ev.currentTarget as HaSelect).items[ev.detail.index] + .value as Trigger["platform"]; + + const elClass = customElements.get( + `ha-automation-trigger-${platform}` + ) as CustomElementConstructor & { + defaultConfig: Omit; + }; + + const triggers = this.triggers.concat({ + platform: platform as any, + ...elClass.defaultConfig, + }); + this._focusLastTriggerOnChange = true; fireEvent(this, "value-changed", { value: triggers }); } @@ -72,16 +132,27 @@ export default class HaAutomationTrigger extends LitElement { }); } + private _processedTypes = memoizeOne( + (localize: LocalizeFunc): [string, string][] => + TRIGGER_TYPES.map( + (action) => + [ + action, + localize( + `ui.panel.config.automation.editor.triggers.type.${action}.label` + ), + ] as [string, string] + ).sort((a, b) => stringCompare(a[1], b[1])) + ); + static get styles(): CSSResultGroup { return css` - ha-automation-trigger-row, - ha-card { + ha-automation-trigger-row { display: block; - margin-top: 16px; + margin-bottom: 16px; } - .add-card mwc-button { - display: block; - text-align: center; + ha-svg-icon { + height: 20px; } `; } diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts index 9565c87347..ec80254f35 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts @@ -1,5 +1,5 @@ import "@material/mwc-list/mwc-list-item"; -import { html, LitElement, PropertyValues } from "lit"; +import { css, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { caseInsensitiveStringCompare } from "../../../../../common/string/compare"; @@ -63,6 +63,14 @@ export class HaTagTrigger extends LitElement implements TriggerElement { }, }); } + + static get styles() { + return css` + ha-select { + display: block; + } + `; + } } declare global { diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-template.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-template.ts index eeb78b986a..063aece21c 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-template.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-template.ts @@ -1,5 +1,5 @@ import "../../../../../components/ha-textarea"; -import { html, LitElement } from "lit"; +import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import type { TemplateTrigger } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; @@ -39,6 +39,14 @@ export class HaTemplateTrigger extends LitElement { private _valueChanged(ev: CustomEvent): void { handleChangeEvent(this, ev); } + + static get styles() { + return css` + p { + margin-top: 0; + } + `; + } } declare global { diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index f57d5feb2a..d0309111f9 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -6,6 +6,7 @@ import { mdiContentSave, mdiDelete, mdiDotsVertical, + mdiHelpCircle, } from "@mdi/js"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; @@ -56,7 +57,6 @@ import type { HomeAssistant, Route } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import { showToast } from "../../../util/toast"; import { HaDeviceAction } from "../automation/action/types/ha-automation-action-device_id"; -import "../ha-config-section"; import { configSections } from "../ha-panel-config"; import "./blueprint-script-editor"; @@ -276,59 +276,47 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { > ${this._config ? html` - - ${!this.narrow + +
    + +
    + ${this.scriptEntityId ? html` - ${this._config.alias} - ` - : ""} - - ${this.hass.localize( - "ui.panel.config.script.editor.introduction" - )} - - -
    - -
    - ${this.scriptEntityId - ? html` -
    + - - - ${this.hass.localize( - "ui.panel.config.script.editor.show_trace" - )} - - - + ${this.hass.localize( - "ui.panel.config.script.picker.run_script" + "ui.panel.config.script.editor.show_trace" )} -
    - ` - : ``} -
    -
    + + + ${this.hass.localize( + "ui.panel.config.script.picker.run_script" + )} + +
    + ` + : ``} +
    ${"use_blueprint" in this._config ? html` @@ -341,40 +329,34 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { > ` : html` - - +
    +
    ${this.hass.localize( "ui.panel.config.script.editor.sequence" )} - - -

    - ${this.hass.localize( - "ui.panel.config.script.editor.sequence_sentence" - )} -

    - - ${this.hass.localize( +
    + + - - - + > + +
    + + `} ` : ""} @@ -525,25 +507,22 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { private _computeHelperCallback = ( schema: SchemaUnion> - ): string | undefined => { + ): string | undefined | TemplateResult => { if (schema.name === "mode") { - return this.hass.localize( - "ui.panel.config.script.editor.modes.description", - "documentation_link", - html` - ${this.hass.localize( - "ui.panel.config.script.editor.modes.documentation" - )} - ` - ); + return html` + ${this.hass.localize( + "ui.panel.config.script.editor.modes.learn_more" + )} + `; } return undefined; }; @@ -806,7 +785,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { color: var(--error-color); } .content { - padding-bottom: 20px; + padding: 16px 16px 20px; } .yaml-mode { height: 100%; @@ -841,6 +820,16 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { li[role="separator"] { border-bottom-color: var(--divider-color); } + .header { + display: flex; + margin: 16px 0; + align-items: center; + } + .header .name { + font-size: 20px; + font-weight: 400; + flex: 1; + } `, ]; } diff --git a/src/translations/en.json b/src/translations/en.json index 107ab50288..2a44fbd3fc 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1833,9 +1833,8 @@ }, "modes": { "label": "Mode", - "description": "The mode controls what happens when the automation is triggered while the actions are still running from a previous trigger. Check the {documentation_link} for more info.", - "documentation": "automation documentation", - "single": "Single (default)", + "learn_more": "Learn about modes", + "single": "Single", "restart": "Restart", "queued": "Queued", "parallel": "Parallel" @@ -1848,10 +1847,7 @@ "edit_ui": "Edit in visual editor", "copy_to_clipboard": "Copy to Clipboard", "triggers": { - "name": "Trigger", "header": "Triggers", - "introduction": "Triggers are what starts the processing of an automation rule. It is possible to specify multiple triggers for the same rule. Once a trigger starts, Home Assistant will validate the conditions, if any, and call the action.", - "learn_more": "Learn more about triggers", "triggered": "Triggered", "add": "Add trigger", "id": "Trigger ID", @@ -1965,10 +1961,7 @@ } }, "conditions": { - "name": "Condition", "header": "Conditions", - "introduction": "Conditions are optional and will prevent the automation from running unless all conditions are satisfied.", - "learn_more": "Learn more about conditions", "add": "Add condition", "test": "Test", "invalid_condition": "Invalid condition configuration", @@ -2054,10 +2047,7 @@ } }, "actions": { - "name": "Action", "header": "Actions", - "introduction": "The actions are what Home Assistant will do when the automation is triggered.", - "learn_more": "Learn more about actions", "add": "Add action", "invalid_action": "Invalid action", "run_action": "Run action", @@ -2117,7 +2107,8 @@ } }, "activate_scene": { - "label": "Activate scene" + "label": "Activate scene", + "scene": "Scene" }, "repeat": { "label": "Repeat", @@ -2261,13 +2252,12 @@ "header": "Script: {name}", "default_name": "New Script", "modes": { - "label": "Mode", - "description": "The mode controls what happens when script is invoked while it is still running from one or more previous invocations. Check the {documentation_link} for more info.", - "documentation": "script documentation", - "single": "Single (default)", - "restart": "Restart", - "queued": "Queued", - "parallel": "Parallel" + "label": "[%key:ui::panel::config::automation::editor::modes::label%]", + "learn_more": "[%key:ui::panel::config::automation::editor::modes::learn_more%]", + "single": "[%key:ui::panel::config::automation::editor::modes::single%]", + "restart": "[%key:ui::panel::config::automation::editor::modes::restart%]", + "queued": "[%key:ui::panel::config::automation::editor::modes::queued%]", + "parallel": "[%key:ui::panel::config::automation::editor::modes::parallel%]" }, "max": { "queued": "Queue length", From 8c71885b4c10469d730d144906ae61b943224939 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 18 Aug 2022 11:43:43 -0400 Subject: [PATCH 035/134] Add support for the file selector (#13382) --- .../ha-form/compute-initial-ha-form-data.ts | 1 + .../ha-selector/ha-selector-file.ts | 98 +++++++++++++++++++ src/components/ha-selector/ha-selector.ts | 1 + src/data/file_upload.ts | 22 +++++ src/data/selector.ts | 7 ++ 5 files changed, 129 insertions(+) create mode 100644 src/components/ha-selector/ha-selector-file.ts create mode 100644 src/data/file_upload.ts diff --git a/src/components/ha-form/compute-initial-ha-form-data.ts b/src/components/ha-form/compute-initial-ha-form-data.ts index 6d24bbe531..96c1ae58fb 100644 --- a/src/components/ha-form/compute-initial-ha-form-data.ts +++ b/src/components/ha-form/compute-initial-ha-form-data.ts @@ -50,6 +50,7 @@ export const computeInitialHaFormData = ( "text" in selector || "addon" in selector || "attribute" in selector || + "file" in selector || "icon" in selector || "theme" in selector ) { diff --git a/src/components/ha-selector/ha-selector-file.ts b/src/components/ha-selector/ha-selector-file.ts new file mode 100644 index 0000000000..589e43cf9d --- /dev/null +++ b/src/components/ha-selector/ha-selector-file.ts @@ -0,0 +1,98 @@ +import { mdiFile } from "@mdi/js"; +import { html, LitElement, PropertyValues } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../common/dom/fire_event"; +import { removeFile, uploadFile } from "../../data/file_upload"; +import { FileSelector } from "../../data/selector"; +import { showAlertDialog } from "../../dialogs/generic/show-dialog-box"; +import { HomeAssistant } from "../../types"; +import "../ha-file-upload"; + +@customElement("ha-selector-file") +export class HaFileSelector extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public selector!: FileSelector; + + @property() public value?: string; + + @property() public label?: string; + + @property() public helper?: string; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean }) public required = true; + + @state() private _filename?: { fileId: string; name: string }; + + @state() private _busy = false; + + protected render() { + return html` + + `; + } + + protected willUpdate(changedProps: PropertyValues) { + super.willUpdate(changedProps); + if ( + changedProps.has("value") && + this._filename && + this.value !== this._filename.fileId + ) { + this._filename = undefined; + } + } + + private async _uploadFile(ev) { + this._busy = true; + + const file = ev.detail.files![0]; + + try { + const fileId = await uploadFile(this.hass, file); + this._filename = { fileId, name: file.name }; + fireEvent(this, "value-changed", { value: fileId }); + } catch (err: any) { + showAlertDialog(this, { + text: this.hass.localize("ui.components.selectors.file.upload_failed", { + reason: err.message || err, + }), + }); + } finally { + this._busy = false; + } + } + + private _removeFile = async () => { + this._busy = true; + try { + await removeFile(this.hass, this.value!); + } catch (err) { + // Not ideal if removal fails, but will be cleaned up later + } finally { + this._busy = false; + } + this._filename = undefined; + fireEvent(this, "value-changed", { value: "" }); + }; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-file": HaFileSelector; + } +} diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index e2b3fdbf97..7d6400b289 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -14,6 +14,7 @@ import "./ha-selector-datetime"; import "./ha-selector-device"; import "./ha-selector-duration"; import "./ha-selector-entity"; +import "./ha-selector-file"; import "./ha-selector-number"; import "./ha-selector-object"; import "./ha-selector-select"; diff --git a/src/data/file_upload.ts b/src/data/file_upload.ts new file mode 100644 index 0000000000..8ee9a4393b --- /dev/null +++ b/src/data/file_upload.ts @@ -0,0 +1,22 @@ +import { HomeAssistant } from "../types"; + +export const uploadFile = async (hass: HomeAssistant, file: File) => { + const fd = new FormData(); + fd.append("file", file); + const resp = await hass.fetchWithAuth("/api/file_upload", { + method: "POST", + body: fd, + }); + if (resp.status === 413) { + throw new Error(`Uploaded file is too large (${file.name})`); + } else if (resp.status !== 200) { + throw new Error("Unknown error"); + } + const data = await resp.json(); + return data.file_id; +}; + +export const removeFile = async (hass: HomeAssistant, file_id: string) => + hass.callApi("DELETE", "file_upload", { + file_id, + }); diff --git a/src/data/selector.ts b/src/data/selector.ts index b56f43d901..86d6d0eda9 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -16,6 +16,7 @@ export type Selector = | DeviceSelector | DurationSelector | EntitySelector + | FileSelector | IconSelector | LocationSelector | MediaSelector @@ -120,6 +121,12 @@ export interface EntitySelector { }; } +export interface FileSelector { + file: { + accept: string; + }; +} + export interface IconSelector { icon: { placeholder?: string; From ede9d8a07357ab3cf4edf9b76f7c20bafe12f2b2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 18 Aug 2022 12:17:19 -0400 Subject: [PATCH 036/134] Add back learn more labels to automation editor (#13401) --- src/translations/en.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/translations/en.json b/src/translations/en.json index 2a44fbd3fc..99d5bf27b0 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1848,6 +1848,7 @@ "copy_to_clipboard": "Copy to Clipboard", "triggers": { "header": "Triggers", + "learn_more": "Learn more about triggers", "triggered": "Triggered", "add": "Add trigger", "id": "Trigger ID", @@ -1962,6 +1963,7 @@ }, "conditions": { "header": "Conditions", + "learn_more": "Learn more about conditions", "add": "Add condition", "test": "Test", "invalid_condition": "Invalid condition configuration", @@ -2048,6 +2050,7 @@ }, "actions": { "header": "Actions", + "learn_more": "Learn more about actions", "add": "Add action", "invalid_action": "Invalid action", "run_action": "Run action", From 088b3587e0212ea7304d9694c4e39ffe13d74dc3 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Thu, 18 Aug 2022 12:43:15 -0400 Subject: [PATCH 037/134] Remove string exception for localize keys (#13354) --- src/auth/ha-auth-flow.ts | 3 ++- src/common/translations/localize.ts | 1 - src/data/panel.ts | 8 ++++---- src/dialogs/quick-bar/ha-quick-bar.ts | 3 ++- .../devices/device-detail/ha-device-actions-card.ts | 4 ++-- .../devices/device-detail/ha-device-automation-card.ts | 8 ++++---- .../devices/device-detail/ha-device-conditions-card.ts | 4 ++-- .../devices/device-detail/ha-device-triggers-card.ts | 4 ++-- src/panels/config/entities/dialog-entity-editor.ts | 2 +- .../zwave_js/zwave_js-config-dashboard.ts | 2 +- .../integration-panels/zwave_js/zwave_js-node-config.ts | 3 +-- src/panels/config/logs/dialog-system-log-detail.ts | 2 +- src/panels/config/logs/system-log-card.ts | 3 +-- src/panels/lovelace/common/handle-action.ts | 3 +-- 14 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/auth/ha-auth-flow.ts b/src/auth/ha-auth-flow.ts index a189cf9ab3..eef3da0d33 100644 --- a/src/auth/ha-auth-flow.ts +++ b/src/auth/ha-auth-flow.ts @@ -314,7 +314,8 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) { } private _computeStepDescription(step: DataEntryFlowStepForm) { - const resourceKey = `ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description`; + const resourceKey = + `ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description` as const; const args: string[] = []; const placeholders = step.description_placeholders || {}; Object.keys(placeholders).forEach((key) => { diff --git a/src/common/translations/localize.ts b/src/common/translations/localize.ts index 13c5a2e49b..20be6e44db 100644 --- a/src/common/translations/localize.ts +++ b/src/common/translations/localize.ts @@ -11,7 +11,6 @@ import { getLocalLanguage } from "../../util/common-translation"; // Fixing component category will require tighter definition of types from backend and/or web socket export type LocalizeKeys = | FlattenObjectKeys> - | `${string}` | `panel.${string}` | `state.${string}` | `state_attributes.${string}` diff --git a/src/data/panel.ts b/src/data/panel.ts index b71721c71c..fb6013e0fd 100644 --- a/src/data/panel.ts +++ b/src/data/panel.ts @@ -21,16 +21,16 @@ export const getDefaultPanel = (hass: HomeAssistant): PanelInfo => ? hass.panels[hass.defaultPanel] : hass.panels[DEFAULT_PANEL]; -export const getPanelNameTranslationKey = (panel: PanelInfo): string => { +export const getPanelNameTranslationKey = (panel: PanelInfo) => { if (panel.url_path === "lovelace") { - return "panel.states"; + return "panel.states" as const; } if (panel.url_path === "profile") { - return "panel.profile"; + return "panel.profile" as const; } - return `panel.${panel.title}`; + return `panel.${panel.title}` as const; }; export const getPanelTitle = (hass: HomeAssistant): string | undefined => { diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index ee987044ab..7a49575171 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -550,7 +550,7 @@ export class QuickBar extends LitElement { } private _generateServerControlCommands(): CommandItem[] { - const serverActions = ["restart", "stop"]; + const serverActions = ["restart", "stop"] as const; return serverActions.map((action) => { const categoryKey: CommandItem["categoryKey"] = "server_control"; @@ -673,6 +673,7 @@ export class QuickBar extends LitElement { this.hass.localize( `ui.dialogs.quick-bar.commands.navigation.${name}` )) || + // @ts-expect-error (page.translationKey && this.hass.localize(page.translationKey)); if (caption) { diff --git a/src/panels/config/devices/device-detail/ha-device-actions-card.ts b/src/panels/config/devices/device-detail/ha-device-actions-card.ts index 4a85f1951a..6498b7df9a 100644 --- a/src/panels/config/devices/device-detail/ha-device-actions-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-actions-card.ts @@ -7,9 +7,9 @@ import { HaDeviceAutomationCard } from "./ha-device-automation-card"; @customElement("ha-device-actions-card") export class HaDeviceActionsCard extends HaDeviceAutomationCard { - protected type = "action"; + readonly type = "action"; - protected headerKey = "ui.panel.config.devices.automation.actions.caption"; + readonly headerKey = "ui.panel.config.devices.automation.actions.caption"; constructor() { super(localizeDeviceAutomationAction); diff --git a/src/panels/config/devices/device-detail/ha-device-automation-card.ts b/src/panels/config/devices/device-detail/ha-device-automation-card.ts index 369a31d405..dc22f8a1dc 100644 --- a/src/panels/config/devices/device-detail/ha-device-automation-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-automation-card.ts @@ -25,15 +25,15 @@ export abstract class HaDeviceAutomationCard< @property() public deviceId?: string; - @property() public script = false; + @property({ type: Boolean }) public script = false; - @property() public automations: T[] = []; + @property({ attribute: false }) public automations: T[] = []; @state() public _showSecondary = false; - protected headerKey = ""; + abstract headerKey: Parameters[0]; - protected type = ""; + abstract type: "action" | "condition" | "trigger"; private _localizeDeviceAutomation: ( hass: HomeAssistant, diff --git a/src/panels/config/devices/device-detail/ha-device-conditions-card.ts b/src/panels/config/devices/device-detail/ha-device-conditions-card.ts index 3ed9548e84..3fb7e150d3 100644 --- a/src/panels/config/devices/device-detail/ha-device-conditions-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-conditions-card.ts @@ -7,9 +7,9 @@ import { HaDeviceAutomationCard } from "./ha-device-automation-card"; @customElement("ha-device-conditions-card") export class HaDeviceConditionsCard extends HaDeviceAutomationCard { - protected type = "condition"; + readonly type = "condition"; - protected headerKey = "ui.panel.config.devices.automation.conditions.caption"; + readonly headerKey = "ui.panel.config.devices.automation.conditions.caption"; constructor() { super(localizeDeviceAutomationCondition); diff --git a/src/panels/config/devices/device-detail/ha-device-triggers-card.ts b/src/panels/config/devices/device-detail/ha-device-triggers-card.ts index bdf15ee03b..c65acc738a 100644 --- a/src/panels/config/devices/device-detail/ha-device-triggers-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-triggers-card.ts @@ -7,9 +7,9 @@ import { HaDeviceAutomationCard } from "./ha-device-automation-card"; @customElement("ha-device-triggers-card") export class HaDeviceTriggersCard extends HaDeviceAutomationCard { - protected type = "trigger"; + readonly type = "trigger"; - protected headerKey = "ui.panel.config.devices.automation.triggers.caption"; + readonly headerKey = "ui.panel.config.devices.automation.triggers.caption"; constructor() { super(localizeDeviceAutomationTrigger); diff --git a/src/panels/config/entities/dialog-entity-editor.ts b/src/panels/config/entities/dialog-entity-editor.ts index 8b1d412ec1..ec234961bb 100644 --- a/src/panels/config/entities/dialog-entity-editor.ts +++ b/src/panels/config/entities/dialog-entity-editor.ts @@ -31,7 +31,7 @@ interface Tabs { interface Tab { component: string; - translationKey: string; + translationKey: Parameters[0]; } @customElement("dialog-entity-editor") diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts index a588af70d8..8681103d5c 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts @@ -494,7 +494,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) { private _renderErrorScreen() { const item = this._configEntry!; - let stateText: [string, ...unknown[]] | undefined; + let stateText: Parameters | undefined; let stateTextExtra: TemplateResult | string | undefined; if (item.disabled_by) { diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts index 5128f7fecd..771b260274 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts @@ -217,8 +217,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) { slot="item-icon" > ${this.hass.localize( - "ui.panel.config.zwave_js.node_config.set_param_" + - result.status + `ui.panel.config.zwave_js.node_config.set_param_${result.status}` )} ${result.status === "error" && result.error ? html`
    ${result.error} ` diff --git a/src/panels/config/logs/dialog-system-log-detail.ts b/src/panels/config/logs/dialog-system-log-detail.ts index f73f0b8f43..42113c9772 100644 --- a/src/panels/config/logs/dialog-system-log-detail.ts +++ b/src/panels/config/logs/dialog-system-log-detail.ts @@ -74,7 +74,7 @@ class DialogSystemLogDetail extends LitElement { "level", html`${this.hass.localize( - "ui.panel.config.logs.level." + item.level.toLowerCase() + `ui.panel.config.logs.level.${item.level.toLowerCase()}` )}` ); diff --git a/src/panels/config/logs/system-log-card.ts b/src/panels/config/logs/system-log-card.ts index 617d1bf8a1..e2b2b8a10c 100644 --- a/src/panels/config/logs/system-log-card.ts +++ b/src/panels/config/logs/system-log-card.ts @@ -106,8 +106,7 @@ export class SystemLogCard extends LitElement { ${this._timestamp(item)} – ${html`(${this.hass.localize( - "ui.panel.config.logs.level." + - item.level.toLowerCase() + `ui.panel.config.logs.level.${item.level.toLowerCase()}` )}) `} ${integrations[idx] diff --git a/src/panels/lovelace/common/handle-action.ts b/src/panels/lovelace/common/handle-action.ts index ef489d49ca..6f8865f7d2 100644 --- a/src/panels/lovelace/common/handle-action.ts +++ b/src/panels/lovelace/common/handle-action.ts @@ -72,8 +72,7 @@ export const handleAction = async ( "action", serviceName || hass.localize( - "ui.panel.lovelace.editor.action-editor.actions." + - actionConfig.action + `ui.panel.lovelace.editor.action-editor.actions.${actionConfig.action}` ) || actionConfig.action ), From f3d92ba0e085c2448d9ea65ec3ac7783f7faee92 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 18 Aug 2022 23:32:56 -0400 Subject: [PATCH 038/134] Fix Gallery menu expansion (#13408) --- gallery/src/ha-gallery.ts | 7 ++++--- src/components/ha-expansion-panel.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/gallery/src/ha-gallery.ts b/gallery/src/ha-gallery.ts index 4f22bac519..31f2337a12 100644 --- a/gallery/src/ha-gallery.ts +++ b/gallery/src/ha-gallery.ts @@ -5,7 +5,7 @@ import { html, css, LitElement, PropertyValues } from "lit"; import { customElement, property, query } from "lit/decorators"; import "../../src/components/ha-icon-button"; import "../../src/managers/notification-manager"; -import "../../src/components/ha-expansion-panel"; +import { HaExpansionPanel } from "../../src/components/ha-expansion-panel"; import { haStyle } from "../../src/resources/styles"; import { PAGES, SIDEBAR } from "../build/import-pages"; import { dynamicElement } from "../../src/common/dom/dynamic-element-directive"; @@ -174,9 +174,10 @@ class HaGallery extends LitElement { const menuItem = this.shadowRoot!.querySelector( `a[href="#${this._page}"]` )!; + // Make sure section is expanded - if (menuItem.parentElement instanceof HTMLDetailsElement) { - menuItem.parentElement.open = true; + if (menuItem.parentElement instanceof HaExpansionPanel) { + menuItem.parentElement.expanded = true; } } diff --git a/src/components/ha-expansion-panel.ts b/src/components/ha-expansion-panel.ts index 33c93d352a..8510584584 100644 --- a/src/components/ha-expansion-panel.ts +++ b/src/components/ha-expansion-panel.ts @@ -14,7 +14,7 @@ import { nextRender } from "../common/util/render-status"; import "./ha-svg-icon"; @customElement("ha-expansion-panel") -class HaExpansionPanel extends LitElement { +export class HaExpansionPanel extends LitElement { @property({ type: Boolean, reflect: true }) expanded = false; @property({ type: Boolean, reflect: true }) outlined = false; From 5c16447eedf41c1db5e4b48fc1dc01dbfc184792 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 19 Aug 2022 08:05:15 -0400 Subject: [PATCH 039/134] Fix DOM reuse for trigger/condition/action (#13407) --- .../automation/action/ha-automation-action.ts | 18 +++++++++++++++--- .../condition/ha-automation-condition.ts | 18 +++++++++++++++--- .../trigger/ha-automation-trigger.ts | 18 +++++++++++++++--- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index 7e27cdac76..c6c7fe3eec 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -42,13 +42,13 @@ export default class HaAutomationAction extends LitElement { private _focusLastActionOnChange = false; + private _actionKeys = new WeakMap(); + protected render() { return html` ${repeat( this.actions, - // Use the action as key, so moving around keeps the same DOM, - // including expand state - (action) => action, + (action) => this._getKey(action), (action, idx) => html` ) { const action = (ev.currentTarget as HaSelect).items[ev.detail.index] .value as typeof ACTION_TYPES[number]; @@ -131,6 +139,10 @@ export default class HaAutomationAction extends LitElement { if (newValue === null) { actions.splice(index, 1); } else { + // Store key on new value. + const key = this._getKey(actions[index]); + this._actionKeys.set(newValue, key); + actions[index] = newValue; } diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 9229fa5657..b8b62a7d14 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -38,6 +38,8 @@ export default class HaAutomationCondition extends LitElement { private _focusLastConditionOnChange = false; + private _conditionKeys = new WeakMap(); + protected updated(changedProperties: PropertyValues) { if (!changedProperties.has("conditions")) { return; @@ -79,9 +81,7 @@ export default class HaAutomationCondition extends LitElement { return html` ${repeat( this.conditions, - // Use the condition as key, so moving around keeps the same DOM, - // including expand state - (condition) => condition, + (condition) => this._getKey(condition), (cond, idx) => html` ) { const condition = (ev.currentTarget as HaSelect).items[ev.detail.index] .value as Condition["condition"]; @@ -138,6 +146,10 @@ export default class HaAutomationCondition extends LitElement { if (newValue === null) { conditions.splice(index, 1); } else { + // Store key on new value. + const key = this._getKey(conditions[index]); + this._conditionKeys.set(newValue, key); + conditions[index] = newValue; } diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index 9480f49220..d961c96b10 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -41,13 +41,13 @@ export default class HaAutomationTrigger extends LitElement { private _focusLastTriggerOnChange = false; + private _triggerKeys = new WeakMap(); + protected render() { return html` ${repeat( this.triggers, - // Use the trigger as key, so moving around keeps the same DOM, - // including expand state - (trigger) => trigger, + (trigger) => this._getKey(trigger), (trg, idx) => html` ) { const platform = (ev.currentTarget as HaSelect).items[ev.detail.index] .value as Trigger["platform"]; @@ -118,6 +126,10 @@ export default class HaAutomationTrigger extends LitElement { if (newValue === null) { triggers.splice(index, 1); } else { + // Store key on new value. + const key = this._getKey(triggers[index]); + this._triggerKeys.set(newValue, key); + triggers[index] = newValue; } From 196456d0c429d63c35fd2d6ca9d29ee5efc0edf5 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Fri, 19 Aug 2022 08:57:58 -0400 Subject: [PATCH 040/134] Add translations to nightly artifacts (#13409) --- .github/workflows/nightly.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 516e8c6704..b59debf690 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -55,9 +55,19 @@ jobs: rm -rf dist home_assistant_frontend.egg-info python3 -m build + - name: Archive translations + run: tar -czvf translations.tar.gz translations + - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: wheels path: dist/home_assistant_frontend*.whl if-no-files-found: error + + - name: Upload translations + uses: actions/upload-artifact@v3 + with: + name: translations + path: translations.tar.gz + if-no-files-found: error From d2a19e04ef83507842c2ffce753dca3c893cc34c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 19 Aug 2022 15:20:13 +0200 Subject: [PATCH 041/134] Add state selector (#13411) --- gallery/src/pages/components/ha-form.ts | 5 + gallery/src/pages/components/ha-selector.ts | 4 + src/common/entity/get_states.ts | 89 +++++++++++++++ .../entity/ha-entity-state-picker.ts | 107 ++++++++++++++++++ .../ha-selector/ha-selector-state.ts | 49 ++++++++ src/components/ha-selector/ha-selector.ts | 1 + src/data/selector.ts | 7 ++ .../types/ha-automation-condition-state.ts | 2 +- .../types/ha-automation-trigger-state.ts | 14 ++- src/translations/en.json | 3 + 10 files changed, 278 insertions(+), 3 deletions(-) create mode 100644 src/common/entity/get_states.ts create mode 100644 src/components/entity/ha-entity-state-picker.ts create mode 100644 src/components/ha-selector/ha-selector-state.ts diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts index 4d8ad42b73..54d8f709da 100644 --- a/gallery/src/pages/components/ha-form.ts +++ b/gallery/src/pages/components/ha-form.ts @@ -136,6 +136,11 @@ const SCHEMAS: { schema: [ { name: "addon", selector: { addon: {} } }, { name: "entity", selector: { entity: {} } }, + { + name: "State", + selector: { state: { entity_id: "" } }, + context: { filter_entity: "entity" }, + }, { name: "Attribute", selector: { attribute: { entity_id: "" } }, diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index 6ce04e0995..6549c8e30e 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -115,6 +115,10 @@ const SCHEMAS: { name: "One of each", input: { entity: { name: "Entity", selector: { entity: {} } }, + state: { + name: "State", + selector: { state: { entity_id: "alarm_control_panel.alarm" } }, + }, attribute: { name: "Attribute", selector: { attribute: { entity_id: "" } }, diff --git a/src/common/entity/get_states.ts b/src/common/entity/get_states.ts new file mode 100644 index 0000000000..c40ad804d0 --- /dev/null +++ b/src/common/entity/get_states.ts @@ -0,0 +1,89 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import { computeStateDomain } from "./compute_state_domain"; +import { UNAVAILABLE_STATES } from "../../data/entity"; + +const FIXED_DOMAIN_STATES = { + alarm_control_panel: [ + "armed_away", + "armed_custom_bypass", + "armed_home", + "armed_night", + "armed_vacation", + "arming", + "disarmed", + "disarming", + "pending", + "triggered", + ], + automation: ["on", "off"], + binary_sensor: ["on", "off"], + button: [], + calendar: ["on", "off"], + camera: ["idle", "recording", "streaming"], + cover: ["closed", "closing", "open", "opening"], + device_tracker: ["home", "not_home"], + fan: ["on", "off"], + humidifier: ["on", "off"], + input_boolean: ["on", "off"], + input_button: [], + light: ["on", "off"], + lock: ["jammed", "locked", "locking", "unlocked", "unlocking"], + media_player: ["idle", "off", "paused", "playing", "standby"], + person: ["home", "not_home"], + remote: ["on", "off"], + scene: [], + schedule: ["on", "off"], + script: ["on", "off"], + siren: ["on", "off"], + sun: ["above_horizon", "below_horizon"], + switch: ["on", "off"], + update: ["on", "off"], + vacuum: ["cleaning", "docked", "error", "idle", "paused", "returning"], + weather: [ + "clear-night", + "cloudy", + "exceptional", + "fog", + "hail", + "lightning-rainy", + "lightning", + "partlycloudy", + "pouring", + "rainy", + "snowy-rainy", + "snowy", + "sunny", + "windy-variant", + "windy", + ], +}; + +export const getStates = (state: HassEntity): string[] => { + const domain = computeStateDomain(state); + const result: string[] = []; + + if (domain in FIXED_DOMAIN_STATES) { + result.push(...FIXED_DOMAIN_STATES[domain]); + } else { + // If not fixed, we at least know the current state + result.push(state.state); + } + + // Dynamic values based on the entities + switch (domain) { + case "climate": + result.push(...state.attributes.hvac_modes); + break; + case "input_select": + case "select": + result.push(...state.attributes.options); + break; + case "water_heater": + result.push(...state.attributes.operation_list); + break; + } + + // All entities can have unavailable states + result.push(...UNAVAILABLE_STATES); + return [...new Set(result)]; +}; diff --git a/src/components/entity/ha-entity-state-picker.ts b/src/components/entity/ha-entity-state-picker.ts new file mode 100644 index 0000000000..968bdedf46 --- /dev/null +++ b/src/components/entity/ha-entity-state-picker.ts @@ -0,0 +1,107 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { customElement, property, query } from "lit/decorators"; +import { computeStateDisplay } from "../../common/entity/compute_state_display"; +import { PolymerChangedEvent } from "../../polymer-types"; +import { getStates } from "../../common/entity/get_states"; +import { HomeAssistant } from "../../types"; +import "../ha-combo-box"; +import type { HaComboBox } from "../ha-combo-box"; + +export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean; + +@customElement("ha-entity-state-picker") +class HaEntityStatePicker extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public entityId?: string; + + @property({ type: Boolean }) public autofocus = false; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean }) public required = false; + + @property({ type: Boolean, attribute: "allow-custom-value" }) + public allowCustomValue; + + @property() public label?: string; + + @property() public value?: string; + + @property() public helper?: string; + + @property({ type: Boolean }) private _opened = false; + + @query("ha-combo-box", true) private _comboBox!: HaComboBox; + + protected shouldUpdate(changedProps: PropertyValues) { + return !(!changedProps.has("_opened") && this._opened); + } + + protected updated(changedProps: PropertyValues) { + if (changedProps.has("_opened") && this._opened) { + const state = this.entityId ? this.hass.states[this.entityId] : undefined; + (this._comboBox as any).items = + this.entityId && state + ? getStates(state).map((key) => ({ + value: key, + label: computeStateDisplay( + this.hass.localize, + state, + this.hass.locale, + key + ), + })) + : []; + } + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` + + + `; + } + + private _openedChanged(ev: PolymerChangedEvent) { + this._opened = ev.detail.value; + } + + private _valueChanged(ev: PolymerChangedEvent) { + this.value = ev.detail.value; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-entity-state-picker": HaEntityStatePicker; + } +} diff --git a/src/components/ha-selector/ha-selector-state.ts b/src/components/ha-selector/ha-selector-state.ts new file mode 100644 index 0000000000..4f2a39c540 --- /dev/null +++ b/src/components/ha-selector/ha-selector-state.ts @@ -0,0 +1,49 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { StateSelector } from "../../data/selector"; +import { SubscribeMixin } from "../../mixins/subscribe-mixin"; +import { HomeAssistant } from "../../types"; +import "../entity/ha-entity-state-picker"; + +@customElement("ha-selector-state") +export class HaSelectorState extends SubscribeMixin(LitElement) { + @property() public hass!: HomeAssistant; + + @property() public selector!: StateSelector; + + @property() public value?: any; + + @property() public label?: string; + + @property() public helper?: string; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean }) public required = true; + + @property() public context?: { + filter_entity?: string; + }; + + protected render() { + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-state": HaSelectorState; + } +} diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index 7d6400b289..cc13845e01 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -18,6 +18,7 @@ import "./ha-selector-file"; import "./ha-selector-number"; import "./ha-selector-object"; import "./ha-selector-select"; +import "./ha-selector-state"; import "./ha-selector-target"; import "./ha-selector-template"; import "./ha-selector-text"; diff --git a/src/data/selector.ts b/src/data/selector.ts index 86d6d0eda9..17474651b1 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -23,6 +23,7 @@ export type Selector = | NumberSelector | ObjectSelector | SelectSelector + | StateSelector | StringSelector | TargetSelector | TemplateSelector @@ -191,6 +192,12 @@ export interface SelectSelector { }; } +export interface StateSelector { + state: { + entity_id?: string; + }; +} + export interface StringSelector { text: { multiline?: boolean; diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts index ad0f86c146..310dc88d01 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts @@ -37,7 +37,7 @@ export class HaStateCondition extends LitElement implements ConditionElement { name: "attribute", selector: { attribute: { entity_id: entityId } }, }, - { name: "state", selector: { text: {} } }, + { name: "state", selector: { state: { entity_id: entityId } } }, { name: "for", selector: { duration: {} } }, ] as const ); diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts index 00ded53fac..6a24bf98d5 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts @@ -56,8 +56,18 @@ export class HaStateTrigger extends LitElement implements TriggerElement { name: "attribute", selector: { attribute: { entity_id: entityId } }, }, - { name: "from", selector: { text: {} } }, - { name: "to", selector: { text: {} } }, + { + name: "from", + selector: { + state: { entity_id: entityId ? entityId[0] : undefined }, + }, + }, + { + name: "to", + selector: { + state: { entity_id: entityId ? entityId[0] : undefined }, + }, + }, { name: "for", selector: { duration: {} } }, ] as const ); diff --git a/src/translations/en.json b/src/translations/en.json index 99d5bf27b0..0414bfa152 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -400,6 +400,9 @@ "entity-attribute-picker": { "attribute": "Attribute", "show_attributes": "Show attributes" + }, + "entity-state-picker": { + "state": "State" } }, "target-picker": { From 57fdea19fd910f8b9163760684e468c748b0e07b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 19 Aug 2022 15:44:02 +0200 Subject: [PATCH 042/134] Fix trigger state attribute selector (#13410) --- .../automation/trigger/types/ha-automation-trigger-state.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts index 6a24bf98d5..8e689c15fe 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts @@ -54,7 +54,9 @@ export class HaStateTrigger extends LitElement implements TriggerElement { }, { name: "attribute", - selector: { attribute: { entity_id: entityId } }, + selector: { + attribute: { entity_id: entityId ? entityId[0] : undefined }, + }, }, { name: "from", From 38fd6108b4fecd7e83990c6a9ae7b39c02383d8b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 19 Aug 2022 15:44:42 +0200 Subject: [PATCH 043/134] Keep formatted attribute name in attribute selector (#13413) --- src/components/entity/ha-entity-attribute-picker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/entity/ha-entity-attribute-picker.ts b/src/components/entity/ha-entity-attribute-picker.ts index 3a73ad3fcb..d47fda3ffd 100644 --- a/src/components/entity/ha-entity-attribute-picker.ts +++ b/src/components/entity/ha-entity-attribute-picker.ts @@ -58,7 +58,7 @@ class HaEntityAttributePicker extends LitElement { return html` Date: Fri, 19 Aug 2022 22:27:07 -0400 Subject: [PATCH 044/134] Combine more info and entity registry editor (#13416) --- src/common/const.ts | 43 --- src/components/ha-related-items.ts | 3 + src/dialogs/more-info/const.ts | 89 +++++ src/dialogs/more-info/ha-more-info-dialog.ts | 314 +++++++----------- .../ha-more-info-history-and-logbook.ts | 43 +++ src/dialogs/more-info/ha-more-info-info.ts | 80 +++++ .../more-info/ha-more-info-settings.ts | 117 +++++++ .../more-info/show-ha-more-info-dialog.ts | 10 + .../more-info/state_more_info_control.ts | 4 +- .../config/areas/ha-config-area-page.ts | 7 +- .../device-detail/ha-device-entities-card.ts | 19 +- .../config/entities/dialog-entity-editor.ts | 287 ---------------- .../settings/entity-settings-helper-tab.ts | 8 +- .../entities/entity-registry-settings.ts | 15 +- .../config/entities/ha-config-entities.ts | 34 +- .../entities/show-dialog-entity-editor.ts | 30 -- .../config/helpers/ha-config-helpers.ts | 7 +- src/state/more-info-mixin.ts | 1 + src/translations/en.json | 4 +- 19 files changed, 495 insertions(+), 620 deletions(-) create mode 100644 src/dialogs/more-info/const.ts create mode 100644 src/dialogs/more-info/ha-more-info-history-and-logbook.ts create mode 100644 src/dialogs/more-info/ha-more-info-info.ts create mode 100644 src/dialogs/more-info/ha-more-info-settings.ts create mode 100644 src/dialogs/more-info/show-ha-more-info-dialog.ts delete mode 100644 src/panels/config/entities/dialog-entity-editor.ts delete mode 100644 src/panels/config/entities/show-dialog-entity-editor.ts diff --git a/src/common/const.ts b/src/common/const.ts index 68489f95d2..432e85b6b9 100644 --- a/src/common/const.ts +++ b/src/common/const.ts @@ -166,46 +166,6 @@ export const DOMAINS_WITH_CARD = [ "water_heater", ]; -/** Domains with separate more info dialog. */ -export const DOMAINS_WITH_MORE_INFO = [ - "alarm_control_panel", - "automation", - "camera", - "climate", - "configurator", - "counter", - "cover", - "fan", - "group", - "humidifier", - "input_datetime", - "light", - "lock", - "media_player", - "person", - "remote", - "script", - "scene", - "sun", - "timer", - "update", - "vacuum", - "water_heater", - "weather", -]; - -/** Domains that do not show the default more info dialog content (e.g. the attribute section) - * and do not have a separate more info (so not in DOMAINS_WITH_MORE_INFO). */ -export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [ - "input_number", - "input_select", - "input_text", - "number", - "scene", - "update", - "select", -]; - /** Domains that render an input element instead of a text value when displayed in a row. * Those rows should then not show a cursor pointer when hovered (which would normally * be the default) unless the element itself enforces it (e.g. a button). Also those elements @@ -237,9 +197,6 @@ export const DOMAINS_INPUT_ROW = [ "vacuum", ]; -/** Domains that should have the history hidden in the more info dialog. */ -export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator"]; - /** States that we consider "off". */ export const STATES_OFF = ["closed", "locked", "off"]; diff --git a/src/components/ha-related-items.ts b/src/components/ha-related-items.ts index 54baf0f25f..89974e6be8 100644 --- a/src/components/ha-related-items.ts +++ b/src/components/ha-related-items.ts @@ -326,6 +326,9 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) { line-height: var(--paper-font-title_-_line-height); opacity: var(--dark-primary-opacity); } + h3:first-child { + margin-top: 0; + } `; } } diff --git a/src/dialogs/more-info/const.ts b/src/dialogs/more-info/const.ts new file mode 100644 index 0000000000..2d9a8d4295 --- /dev/null +++ b/src/dialogs/more-info/const.ts @@ -0,0 +1,89 @@ +import { isComponentLoaded } from "../../common/config/is_component_loaded"; +import { computeDomain } from "../../common/entity/compute_domain"; +import { CONTINUOUS_DOMAINS } from "../../data/logbook"; +import { HomeAssistant } from "../../types"; + +export const DOMAINS_NO_INFO = ["camera", "configurator"]; +/** + * Entity domains that should be editable *if* they have an id present; + * {@see shouldShowEditIcon}. + * */ +export const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"]; +/** + * Entity Domains that should always be editable; {@see shouldShowEditIcon}. + * */ +export const EDITABLE_DOMAINS = ["script"]; + +/** Domains with separate more info dialog. */ +export const DOMAINS_WITH_MORE_INFO = [ + "alarm_control_panel", + "automation", + "camera", + "climate", + "configurator", + "counter", + "cover", + "fan", + "group", + "humidifier", + "input_datetime", + "light", + "lock", + "media_player", + "person", + "remote", + "script", + "scene", + "sun", + "timer", + "update", + "vacuum", + "water_heater", + "weather", +]; + +/** Domains that do not show the default more info dialog content (e.g. the attribute section) + * and do not have a separate more info (so not in DOMAINS_WITH_MORE_INFO). */ +export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [ + "input_number", + "input_select", + "input_text", + "number", + "scene", + "update", + "select", +]; + +/** Domains that should have the history hidden in the more info dialog. */ +export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator"]; + +export const computeShowHistoryComponent = ( + hass: HomeAssistant, + entityId: string +) => + isComponentLoaded(hass, "history") && + !DOMAINS_MORE_INFO_NO_HISTORY.includes(computeDomain(entityId)); + +export const computeShowLogBookComponent = ( + hass: HomeAssistant, + entityId: string +): boolean => { + if (!isComponentLoaded(hass, "logbook")) { + return false; + } + + const stateObj = hass.states[entityId]; + if (!stateObj || stateObj.attributes.unit_of_measurement) { + return false; + } + + const domain = computeDomain(entityId); + if ( + CONTINUOUS_DOMAINS.includes(domain) || + DOMAINS_MORE_INFO_NO_HISTORY.includes(domain) + ) { + return false; + } + + return true; +}; diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index fb7179a782..9df9d29514 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -1,15 +1,11 @@ +import type { HassEntity } from "home-assistant-js-websocket"; import "@material/mwc-button"; import "@material/mwc-tab"; import "@material/mwc-tab-bar"; -import { mdiClose, mdiCog, mdiPencil } from "@mdi/js"; -import { css, html, LitElement } from "lit"; +import { mdiClose, mdiPencil } from "@mdi/js"; +import { css, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { cache } from "lit/directives/cache"; -import { isComponentLoaded } from "../../common/config/is_component_loaded"; -import { - DOMAINS_MORE_INFO_NO_HISTORY, - DOMAINS_WITH_MORE_INFO, -} from "../../common/const"; import { fireEvent } from "../../common/dom/fire_event"; import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; @@ -17,34 +13,28 @@ import { navigate } from "../../common/navigate"; import "../../components/ha-dialog"; import "../../components/ha-header-bar"; import "../../components/ha-icon-button"; -import { removeEntityRegistryEntry } from "../../data/entity_registry"; -import { CONTINUOUS_DOMAINS } from "../../data/logbook"; -import { showEntityEditorDialog } from "../../panels/config/entities/show-dialog-entity-editor"; +import "../../components/ha-related-items"; import { haStyleDialog } from "../../resources/styles"; import "../../state-summary/state-card-content"; import { HomeAssistant } from "../../types"; -import { showConfirmationDialog } from "../generic/show-dialog-box"; -import { replaceDialog } from "../make-dialog-manager"; +import { + EDITABLE_DOMAINS_WITH_ID, + EDITABLE_DOMAINS, + DOMAINS_MORE_INFO_NO_HISTORY, +} from "./const"; import "./controls/more-info-default"; -import "./ha-more-info-history"; -import "./ha-more-info-logbook"; +import "./ha-more-info-info"; +import "./ha-more-info-settings"; +import "./ha-more-info-history-and-logbook"; import "./more-info-content"; -const DOMAINS_NO_INFO = ["camera", "configurator"]; -/** - * Entity domains that should be editable *if* they have an id present; - * {@see shouldShowEditIcon}. - * */ -const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"]; -/** - * Entity Domains that should always be editable; {@see shouldShowEditIcon}. - * */ -const EDITABLE_DOMAINS = ["script"]; - export interface MoreInfoDialogParams { entityId: string | null; + tab?: Tab; } +type Tab = "info" | "history" | "settings" | "related"; + @customElement("ha-more-info-dialog") export class MoreInfoDialog extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -53,10 +43,13 @@ export class MoreInfoDialog extends LitElement { @state() private _entityId?: string | null; - @state() private _currTabIndex = 0; + @state() private _currTab: Tab = "info"; public showDialog(params: MoreInfoDialogParams) { this._entityId = params.entityId; + if (params.tab) { + this._currTab = params.tab; + } if (!this._entityId) { this.closeDialog(); return; @@ -66,12 +59,14 @@ export class MoreInfoDialog extends LitElement { public closeDialog() { this._entityId = undefined; - this._currTabIndex = 0; fireEvent(this, "dialog-closed", { dialog: this.localName }); } - protected shouldShowEditIcon(domain, stateObj): boolean { - if (__DEMO__) { + protected shouldShowEditIcon( + domain: string, + stateObj: HassEntity | undefined + ): boolean { + if (__DEMO__ || !stateObj) { return false; } if (EDITABLE_DOMAINS_WITH_ID.includes(domain) && stateObj.attributes.id) { @@ -94,12 +89,9 @@ export class MoreInfoDialog extends LitElement { const entityId = this._entityId; const stateObj = this.hass.states[entityId]; - if (!stateObj) { - return html``; - } - const domain = computeDomain(entityId); - const name = computeStateName(stateObj); + const name = stateObj ? computeStateName(stateObj) : entityId; + const tabs = this._getTabs(entityId, this.hass.user!.is_admin); return html` ${name}

    - ${this.hass.user!.is_admin - ? html` - - ` - : ""} ${this.shouldShowEditIcon(domain, stateObj) ? html` - ${DOMAINS_WITH_MORE_INFO.includes(domain) && - (this._computeShowHistoryComponent(entityId) || - this._computeShowLogBookComponent(entityId)) + + ${tabs.length > 1 ? html` - - + ${tabs.map( + (tab) => html` + + ` + )} ` : ""}
    ${cache( - this._currTabIndex === 0 + this._currTab === "info" ? html` - ${DOMAINS_NO_INFO.includes(domain) - ? "" - : html` - - `} - ${DOMAINS_WITH_MORE_INFO.includes(domain) || - !this._computeShowHistoryComponent(entityId) - ? "" - : html``} - ${DOMAINS_WITH_MORE_INFO.includes(domain) || - !this._computeShowLogBookComponent(entityId) - ? "" - : html``} - - ${stateObj.attributes.restored - ? html` -

    - ${this.hass.localize( - "ui.dialogs.more_info_control.restored.not_provided" - )} -

    -

    - ${this.hass.localize( - "ui.dialogs.more_info_control.restored.remove_intro" - )} -

    - - ${this.hass.localize( - "ui.dialogs.more_info_control.restored.remove_action" - )} - - ` - : ""} + .entityId=${this._entityId} + > + ` + : this._currTab === "history" + ? html` + + ` + : this._currTab === "settings" + ? html` + ` : html` - - + .itemId=${entityId} + itemType="entity" + > ` )}
    @@ -245,63 +189,49 @@ export class MoreInfoDialog extends LitElement { `; } + protected firstUpdated(changedProps: PropertyValues) { + super.firstUpdated(changedProps); + this.addEventListener("close-dialog", () => this.closeDialog()); + } + + protected willUpdate(changedProps: PropertyValues) { + super.willUpdate(changedProps); + if (!this._entityId) { + return; + } + const tabs = this._getTabs(this._entityId, this.hass.user!.is_admin); + if (!tabs.includes(this._currTab)) { + this._currTab = tabs[0]; + } + } + + protected updated(changedProps: PropertyValues) { + super.updated(changedProps); + if (changedProps.has("_currTab")) { + this.setAttribute("tab", this._currTab); + } + } + + private _getTabs(entityId: string, isAdmin: boolean): Tab[] { + const domain = computeDomain(entityId); + const tabs: Tab[] = ["info"]; + + if (!DOMAINS_MORE_INFO_NO_HISTORY.includes(domain)) { + tabs.push("history"); + } + + if (isAdmin) { + tabs.push("settings"); + tabs.push("related"); + } + + return tabs; + } + private _enlarge() { this.large = !this.large; } - private _computeShowHistoryComponent(entityId) { - return ( - isComponentLoaded(this.hass, "history") && - !DOMAINS_MORE_INFO_NO_HISTORY.includes(computeDomain(entityId)) - ); - } - - private _computeShowLogBookComponent(entityId): boolean { - if (!isComponentLoaded(this.hass, "logbook")) { - return false; - } - - const stateObj = this.hass.states[entityId]; - if (!stateObj || stateObj.attributes.unit_of_measurement) { - return false; - } - - const domain = computeDomain(entityId); - if ( - CONTINUOUS_DOMAINS.includes(domain) || - DOMAINS_MORE_INFO_NO_HISTORY.includes(domain) - ) { - return false; - } - - return true; - } - - private _removeEntity() { - const entityId = this._entityId!; - showConfirmationDialog(this, { - title: this.hass.localize( - "ui.dialogs.more_info_control.restored.confirm_remove_title" - ), - text: this.hass.localize( - "ui.dialogs.more_info_control.restored.confirm_remove_text" - ), - confirmText: this.hass.localize("ui.common.remove"), - dismissText: this.hass.localize("ui.common.cancel"), - confirm: () => { - removeEntityRegistryEntry(this.hass, entityId); - }, - }); - } - - private _gotoSettings() { - replaceDialog(this); - showEntityEditorDialog(this, { - entity_id: this._entityId!, - }); - this.closeDialog(); - } - private _gotoEdit() { const stateObj = this.hass.states[this._entityId!]; const domain = computeDomain(this._entityId!); @@ -315,12 +245,14 @@ export class MoreInfoDialog extends LitElement { } private _handleTabChanged(ev: CustomEvent): void { - const newTab = ev.detail.index; - if (newTab === this._currTabIndex) { + const newTab = this._getTabs(this._entityId!, this.hass.user!.is_admin)[ + ev.detail.index + ]; + if (newTab === this._currTab) { return; } - this._currTabIndex = ev.detail.index; + this._currTab = newTab; } static get styles() { @@ -354,17 +286,21 @@ export class MoreInfoDialog extends LitElement { var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12)); } - @media all and (min-width: 451px) and (min-height: 501px) { + :host([tab="settings"]) ha-dialog { + --dialog-content-padding: 0px; + } + + @media all and (min-width: 600px) and (min-height: 501px) { ha-dialog { - --mdc-dialog-max-width: 90vw; + --mdc-dialog-min-width: 560px; + --mdc-dialog-max-width: 560px; + --dialog-surface-position: fixed; + --dialog-surface-top: 40px; + --mdc-dialog-max-height: calc(100% - 72px); } - .content { - width: 352px; - } - - ha-header-bar { - width: 400px; + ha-icon-button[slot="navigationIcon"] { + display: none; } .main-title { @@ -373,18 +309,10 @@ export class MoreInfoDialog extends LitElement { cursor: default; } - ha-dialog[data-domain="camera"] .content, - ha-dialog[data-domain="camera"] ha-header-bar { - width: auto; - } - - :host([large]) .content { - width: calc(90vw - 48px); - } - - :host([large]) ha-dialog[data-domain="camera"] .content, - :host([large]) ha-header-bar { - width: 90vw; + :host([large]) ha-dialog, + ha-dialog[data-domain="camera"] { + --mdc-dialog-min-width: 90vw; + --mdc-dialog-max-width: 90vw; } } diff --git a/src/dialogs/more-info/ha-more-info-history-and-logbook.ts b/src/dialogs/more-info/ha-more-info-history-and-logbook.ts new file mode 100644 index 0000000000..c0ae54602f --- /dev/null +++ b/src/dialogs/more-info/ha-more-info-history-and-logbook.ts @@ -0,0 +1,43 @@ +import { LitElement, html } from "lit"; +import { customElement, property } from "lit/decorators"; +import { HomeAssistant } from "../../types"; +import { + computeShowHistoryComponent, + computeShowLogBookComponent, +} from "./const"; +import "./ha-more-info-history"; +import "./ha-more-info-logbook"; + +@customElement("ha-more-info-history-and-logbook") +export class MoreInfoHistoryAndLogbook extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public entityId!: string; + + protected render() { + return html` + ${computeShowHistoryComponent(this.hass, this.entityId) + ? html` + + ` + : ""} + ${computeShowLogBookComponent(this.hass, this.entityId) + ? html` + + ` + : ""} + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-more-info-history-and-logbook": MoreInfoHistoryAndLogbook; + } +} diff --git a/src/dialogs/more-info/ha-more-info-info.ts b/src/dialogs/more-info/ha-more-info-info.ts new file mode 100644 index 0000000000..0167943d34 --- /dev/null +++ b/src/dialogs/more-info/ha-more-info-info.ts @@ -0,0 +1,80 @@ +import { LitElement, html } from "lit"; +import { customElement, property } from "lit/decorators"; +import { computeDomain } from "../../common/entity/compute_domain"; +import { removeEntityRegistryEntry } from "../../data/entity_registry"; +import type { HomeAssistant } from "../../types"; +import { showConfirmationDialog } from "../generic/show-dialog-box"; +import { DOMAINS_NO_INFO } from "./const"; +import "./ha-more-info-history"; +import "./ha-more-info-logbook"; + +@customElement("ha-more-info-info") +export class MoreInfoInfo extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public entityId!: string; + + protected render() { + const entityId = this.entityId; + const stateObj = this.hass.states[entityId]; + const domain = computeDomain(entityId); + + return html` + ${DOMAINS_NO_INFO.includes(domain) + ? "" + : html` + + `} + + ${stateObj.attributes.restored + ? html` +

    + ${this.hass.localize( + "ui.dialogs.more_info_control.restored.not_provided" + )} +

    +

    + ${this.hass.localize( + "ui.dialogs.more_info_control.restored.remove_intro" + )} +

    + + ${this.hass.localize( + "ui.dialogs.more_info_control.restored.remove_action" + )} + + ` + : ""} + `; + } + + private _removeEntity() { + const entityId = this.entityId!; + showConfirmationDialog(this, { + title: this.hass.localize( + "ui.dialogs.more_info_control.restored.confirm_remove_title" + ), + text: this.hass.localize( + "ui.dialogs.more_info_control.restored.confirm_remove_text" + ), + confirmText: this.hass.localize("ui.common.remove"), + dismissText: this.hass.localize("ui.common.cancel"), + confirm: () => { + removeEntityRegistryEntry(this.hass, entityId); + }, + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-more-info-info": MoreInfoInfo; + } +} diff --git a/src/dialogs/more-info/ha-more-info-settings.ts b/src/dialogs/more-info/ha-more-info-settings.ts new file mode 100644 index 0000000000..7a436aa86f --- /dev/null +++ b/src/dialogs/more-info/ha-more-info-settings.ts @@ -0,0 +1,117 @@ +import "@material/mwc-tab"; +import "@material/mwc-tab-bar"; +import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { dynamicElement } from "../../common/dom/dynamic-element-directive"; +import { + EntityRegistryEntry, + ExtEntityRegistryEntry, + getExtendedEntityRegistryEntry, +} from "../../data/entity_registry"; +import { PLATFORMS_WITH_SETTINGS_TAB } from "../../panels/config/entities/const"; +import type { HomeAssistant } from "../../types"; +import { documentationUrl } from "../../util/documentation-url"; +import "../../panels/config/entities/entity-registry-settings"; + +@customElement("ha-more-info-settings") +export class HaMoreInfoSettings extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public entityId!: string; + + @state() private _entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null; + + @state() private _settingsElementTag?: string; + + protected render() { + // loading. + if (this._entry === undefined) { + return html``; + } + + // No unique ID + if (this._entry === null) { + return html` +
    + ${this.hass.localize( + "ui.dialogs.entity_registry.no_unique_id", + "entity_id", + this.entityId, + "faq_link", + html`${this.hass.localize("ui.dialogs.entity_registry.faq")}` + )} +
    + `; + } + + if (!this._settingsElementTag) { + return html``; + } + + return html` + ${dynamicElement(this._settingsElementTag, { + hass: this.hass, + entry: this._entry, + entityId: this.entityId, + })} + `; + } + + protected willUpdate(changedProps: PropertyValues) { + super.willUpdate(changedProps); + if (changedProps.has("entityId")) { + this._entry = undefined; + if (this.entityId) { + this._getEntityReg(); + } + } + } + + private async _getEntityReg() { + try { + this._entry = await getExtendedEntityRegistryEntry( + this.hass, + this.entityId + ); + this._loadPlatformSettingTabs(); + } catch { + this._entry = null; + } + } + + private async _loadPlatformSettingTabs(): Promise { + if (!this._entry) { + return; + } + if ( + !Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this._entry.platform) + ) { + this._settingsElementTag = "entity-registry-settings"; + return; + } + const tag = PLATFORMS_WITH_SETTINGS_TAB[this._entry.platform]; + await import(`../../panels/config/entities/editor-tabs/settings/${tag}`); + this._settingsElementTag = tag; + } + + static get styles(): CSSResultGroup { + return [ + css` + .content { + padding: 24px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-more-info-settings": HaMoreInfoSettings; + } +} diff --git a/src/dialogs/more-info/show-ha-more-info-dialog.ts b/src/dialogs/more-info/show-ha-more-info-dialog.ts new file mode 100644 index 0000000000..b5a4197979 --- /dev/null +++ b/src/dialogs/more-info/show-ha-more-info-dialog.ts @@ -0,0 +1,10 @@ +import { fireEvent } from "../../common/dom/fire_event"; +import type { MoreInfoDialogParams } from "./ha-more-info-dialog"; + +export const showMoreInfoDialog = ( + element: HTMLElement, + params: MoreInfoDialogParams +) => fireEvent(element, "hass-more-info", params); + +export const hideMoreInfoDialog = (element: HTMLElement) => + fireEvent(element, "hass-more-info", { entityId: null }); diff --git a/src/dialogs/more-info/state_more_info_control.ts b/src/dialogs/more-info/state_more_info_control.ts index 3df5e85b4b..d4ebaed326 100644 --- a/src/dialogs/more-info/state_more_info_control.ts +++ b/src/dialogs/more-info/state_more_info_control.ts @@ -1,8 +1,8 @@ import type { HassEntity } from "home-assistant-js-websocket"; import { - DOMAINS_HIDE_DEFAULT_MORE_INFO, DOMAINS_WITH_MORE_INFO, -} from "../../common/const"; + DOMAINS_HIDE_DEFAULT_MORE_INFO, +} from "./const"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; const LAZY_LOADED_MORE_INFO_CONTROL = { diff --git a/src/panels/config/areas/ha-config-area-page.ts b/src/panels/config/areas/ha-config-area-page.ts index ed4573578c..74aec4c709 100644 --- a/src/panels/config/areas/ha-config-area-page.ts +++ b/src/panels/config/areas/ha-config-area-page.ts @@ -42,11 +42,11 @@ import { SceneEntity } from "../../../data/scene"; import { ScriptEntity } from "../../../data/script"; import { findRelated, RelatedResult } from "../../../data/search"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; +import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; import "../../logbook/ha-logbook"; -import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor"; import { configSections } from "../ha-panel-config"; import { loadAreaRegistryDetailDialog, @@ -620,9 +620,8 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { private _openEntity(ev) { const entry: EntityRegistryEntry = (ev.currentTarget as any).entity; - showEntityEditorDialog(this, { - entity_id: entry.entity_id, - entry, + showMoreInfoDialog(this, { + entityId: entry.entity_id, }); } diff --git a/src/panels/config/devices/device-detail/ha-device-entities-card.ts b/src/panels/config/devices/device-detail/ha-device-entities-card.ts index 96599b1e5f..e0aeffd445 100644 --- a/src/panels/config/devices/device-detail/ha-device-entities-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-entities-card.ts @@ -21,13 +21,13 @@ import { ExtEntityRegistryEntry, getExtendedEntityRegistryEntry, } from "../../../../data/entity_registry"; +import { showMoreInfoDialog } from "../../../../dialogs/more-info/show-ha-more-info-dialog"; import type { HomeAssistant } from "../../../../types"; import type { HuiErrorCard } from "../../../lovelace/cards/hui-error-card"; import { createRowElement } from "../../../lovelace/create-element/create-row-element"; import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities-to-view"; import type { LovelaceRowConfig } from "../../../lovelace/entity-rows/types"; import { LovelaceRow } from "../../../lovelace/entity-rows/types"; -import { showEntityEditorDialog } from "../../entities/show-dialog-entity-editor"; import { EntityRegistryStateEntry } from "../ha-config-device-page"; @customElement("ha-device-entities-card") @@ -90,7 +90,7 @@ export class HaDeviceEntitiesCard extends LitElement { return html` -
    +
    ${shownEntities.map((entry) => this.hass.states[entry.entity_id] ? this._renderEntity(entry) @@ -221,20 +221,11 @@ export class HaDeviceEntitiesCard extends LitElement { `; } - private _overrideMoreInfo(ev: Event): void { - ev.stopPropagation(); - const entry = (ev.target! as any).entry; - showEntityEditorDialog(this, { - entry, - entity_id: entry.entity_id, - }); - } - private _openEditEntry(ev: Event): void { const entry = (ev.currentTarget! as any).entry; - showEntityEditorDialog(this, { - entry, - entity_id: entry.entity_id, + showMoreInfoDialog(this, { + entityId: entry.entity_id, + tab: "settings", }); } diff --git a/src/panels/config/entities/dialog-entity-editor.ts b/src/panels/config/entities/dialog-entity-editor.ts deleted file mode 100644 index ec234961bb..0000000000 --- a/src/panels/config/entities/dialog-entity-editor.ts +++ /dev/null @@ -1,287 +0,0 @@ -import "@material/mwc-tab"; -import "@material/mwc-tab-bar"; -import { mdiClose, mdiTune } from "@mdi/js"; -import { HassEntity } from "home-assistant-js-websocket"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { cache } from "lit/directives/cache"; -import { dynamicElement } from "../../../common/dom/dynamic-element-directive"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { computeStateName } from "../../../common/entity/compute_state_name"; -import "../../../components/ha-dialog"; -import "../../../components/ha-header-bar"; -import "../../../components/ha-icon-button"; -import "../../../components/ha-related-items"; -import { - EntityRegistryEntry, - ExtEntityRegistryEntry, - getExtendedEntityRegistryEntry, -} from "../../../data/entity_registry"; -import { replaceDialog } from "../../../dialogs/make-dialog-manager"; -import { haStyleDialog } from "../../../resources/styles"; -import type { HomeAssistant } from "../../../types"; -import { documentationUrl } from "../../../util/documentation-url"; -import { PLATFORMS_WITH_SETTINGS_TAB } from "./const"; -import "./entity-registry-settings"; -import type { EntityRegistryDetailDialogParams } from "./show-dialog-entity-editor"; - -interface Tabs { - [key: string]: Tab; -} - -interface Tab { - component: string; - translationKey: Parameters[0]; -} - -@customElement("dialog-entity-editor") -export class DialogEntityEditor extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @state() private _params?: EntityRegistryDetailDialogParams; - - @state() private _entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null; - - @state() private _curTab = "tab-settings"; - - @state() private _extraTabs: Tabs = {}; - - @state() private _settingsElementTag?: string; - - private _curTabIndex = 0; - - public showDialog(params: EntityRegistryDetailDialogParams): void { - this._params = params; - this._entry = undefined; - this._settingsElementTag = undefined; - this._extraTabs = {}; - this._getEntityReg(); - } - - public closeDialog(): void { - this._params = undefined; - fireEvent(this, "dialog-closed", { dialog: this.localName }); - } - - protected render(): TemplateResult { - if (!this._params || this._entry === undefined) { - return html``; - } - const entityId = this._params.entity_id; - const entry = this._entry; - const stateObj: HassEntity | undefined = this.hass.states[entityId]; - - return html` - -
    - - - - ${stateObj ? computeStateName(stateObj) : entry?.name || entityId} - - ${stateObj - ? html` - - ` - : ""} - - - - - ${Object.entries(this._extraTabs).map( - ([key, tab]) => html` - - - ` - )} - - - -
    -
    ${cache(this._renderTab())}
    -
    - `; - } - - private _renderTab() { - switch (this._curTab) { - case "tab-settings": - if (this._entry) { - if (this._settingsElementTag) { - return html` - ${dynamicElement(this._settingsElementTag, { - hass: this.hass, - entry: this._entry, - entityId: this._params!.entity_id, - })} - `; - } - return html``; - } - return html` -
    - ${this.hass.localize( - "ui.dialogs.entity_registry.no_unique_id", - "entity_id", - this._params!.entity_id, - "faq_link", - html`${this.hass.localize("ui.dialogs.entity_registry.faq")}` - )} -
    - `; - case "tab-related": - return html` - - `; - default: - return html``; - } - } - - private async _getEntityReg() { - try { - this._entry = await getExtendedEntityRegistryEntry( - this.hass, - this._params!.entity_id - ); - this._loadPlatformSettingTabs(); - } catch { - this._entry = null; - } - } - - private _handleTabActivated(ev: CustomEvent): void { - this._curTabIndex = ev.detail.index; - } - - private _handleTabInteracted(ev: CustomEvent): void { - this._curTab = ev.detail.tabId; - } - - private async _loadPlatformSettingTabs(): Promise { - if (!this._entry) { - return; - } - if ( - !Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this._entry.platform) - ) { - this._settingsElementTag = "entity-registry-settings"; - return; - } - const tag = PLATFORMS_WITH_SETTINGS_TAB[this._entry.platform]; - await import(`./editor-tabs/settings/${tag}`); - this._settingsElementTag = tag; - } - - private _openMoreInfo(): void { - replaceDialog(this); - fireEvent(this, "hass-more-info", { - entityId: this._params!.entity_id, - }); - this.closeDialog(); - } - - static get styles(): CSSResultGroup { - return [ - haStyleDialog, - css` - ha-header-bar { - --mdc-theme-on-primary: var(--primary-text-color); - --mdc-theme-primary: var(--mdc-theme-surface); - flex-shrink: 0; - } - - mwc-tab-bar { - border-bottom: 1px solid - var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12)); - } - - ha-dialog { - --dialog-content-position: static; - --dialog-content-padding: 0; - --dialog-z-index: 6; - } - - @media all and (min-width: 451px) and (min-height: 501px) { - .wrapper { - min-width: 400px; - } - } - - .content { - display: block; - padding: 20px 24px; - } - - /* overrule the ha-style-dialog max-height on small screens */ - @media all and (max-width: 450px), all and (max-height: 500px) { - ha-header-bar { - --mdc-theme-primary: var(--app-header-background-color); - --mdc-theme-on-primary: var(--app-header-text-color, white); - } - } - - mwc-button.warning { - --mdc-theme-primary: var(--error-color); - } - - :host([rtl]) app-toolbar { - direction: rtl; - text-align: right; - } - `, - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "dialog-entity-editor": DialogEntityEditor; - } -} diff --git a/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts b/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts index da462f9ca9..0ad83b15df 100644 --- a/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts +++ b/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts @@ -182,18 +182,12 @@ export class EntityRegistrySettingsHelper extends LitElement { } .form { padding: 20px 24px; - margin-bottom: 53px; } .buttons { - position: absolute; - bottom: 0; - width: 100%; box-sizing: border-box; - border-top: 1px solid - var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12)); display: flex; justify-content: space-between; - padding: 8px; + padding: 0 24px 24px 24px; background-color: var(--mdc-theme-surface, #fff); } .error { diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index c3ae97d0e3..a2b1d57f96 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -66,11 +66,11 @@ import { showAlertDialog, showConfirmationDialog, } from "../../../dialogs/generic/show-dialog-box"; +import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail"; -import { showEntityEditorDialog } from "./show-dialog-entity-editor"; const OVERRIDE_DEVICE_CLASSES = { cover: [ @@ -977,8 +977,9 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { if (!entity) { return; } - showEntityEditorDialog(parent, { - entity_id: entity.entity_id, + showMoreInfoDialog(parent, { + entityId: entity.entity_id, + tab: "settings", }); }); }, "entity_registry_updated"); @@ -1046,17 +1047,11 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { .container { padding: 20px 24px; } - .form { - margin-bottom: 53px; - } .buttons { - position: absolute; - bottom: 0; - width: 100%; box-sizing: border-box; display: flex; padding: 0 24px 24px 24px; - justify-content: flex-end; + justify-content: space-between; padding-bottom: max(env(safe-area-inset-bottom), 24px); background-color: var(--mdc-theme-surface, #fff); } diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 154f2cb424..5a610d8169 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -55,6 +55,10 @@ import { showAlertDialog, showConfirmationDialog, } from "../../../dialogs/generic/show-dialog-box"; +import { + hideMoreInfoDialog, + showMoreInfoDialog, +} from "../../../dialogs/more-info/show-ha-more-info-dialog"; import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-tabs-subpage-data-table"; import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table"; @@ -63,11 +67,6 @@ import { haStyle } from "../../../resources/styles"; import type { HomeAssistant, Route } from "../../../types"; import { configSections } from "../ha-panel-config"; import "../integrations/ha-integration-overflow-menu"; -import { DialogEntityEditor } from "./dialog-entity-editor"; -import { - loadEntityEditorDialog, - showEntityEditorDialog, -} from "./show-dialog-entity-editor"; export interface StateEntity extends EntityRegistryEntry { readonly?: boolean; @@ -121,8 +120,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { @query("hass-tabs-subpage-data-table", true) private _dataTable!: HaTabsSubpageDataTable; - private getDialog?: () => DialogEntityEditor | undefined; - private _activeFilters = memoize( ( filters: URLSearchParams, @@ -454,14 +451,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { public disconnectedCallback() { super.disconnectedCallback(); - if (!this.getDialog) { - return; - } - const dialog = this.getDialog(); - if (!dialog) { - return; - } - dialog.closeDialog(); + hideMoreInfoDialog(this); } protected render(): TemplateResult { @@ -695,11 +685,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { `; } - protected firstUpdated(changedProps): void { - super.firstUpdated(changedProps); - loadEntityEditorDialog(); - } - public willUpdate(changedProps): void { super.willUpdate(changedProps); const oldHass = changedProps.get("hass"); @@ -923,12 +908,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { private _openEditEntry(ev: CustomEvent): void { const entityId = (ev.detail as RowClickedEvent).id; - const entry = this._entities!.find( - (entity) => entity.entity_id === entityId - ); - this.getDialog = showEntityEditorDialog(this, { - entry, - entity_id: entityId, + showMoreInfoDialog(this, { + entityId, + tab: "settings", }); } diff --git a/src/panels/config/entities/show-dialog-entity-editor.ts b/src/panels/config/entities/show-dialog-entity-editor.ts deleted file mode 100644 index 46df6fbe7d..0000000000 --- a/src/panels/config/entities/show-dialog-entity-editor.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { fireEvent } from "../../../common/dom/fire_event"; -import { EntityRegistryEntry } from "../../../data/entity_registry"; -import type { DialogEntityEditor } from "./dialog-entity-editor"; - -export interface EntityRegistryDetailDialogParams { - entry?: EntityRegistryEntry; - entity_id: string; - tab?: string; -} - -export const loadEntityEditorDialog = () => import("./dialog-entity-editor"); - -const getDialog = () => - document - .querySelector("home-assistant")! - .shadowRoot!.querySelector("dialog-entity-editor") as - | DialogEntityEditor - | undefined; - -export const showEntityEditorDialog = ( - element: HTMLElement, - entityDetailParams: EntityRegistryDetailDialogParams -): (() => DialogEntityEditor | undefined) => { - fireEvent(element, "show-dialog", { - dialogTag: "dialog-entity-editor", - dialogImport: loadEntityEditorDialog, - dialogParams: entityDetailParams, - }); - return getDialog; -}; diff --git a/src/panels/config/helpers/ha-config-helpers.ts b/src/panels/config/helpers/ha-config-helpers.ts index 2cd3d23499..31c2ae5175 100644 --- a/src/panels/config/helpers/ha-config-helpers.ts +++ b/src/panels/config/helpers/ha-config-helpers.ts @@ -29,11 +29,11 @@ import { showAlertDialog, showConfirmationDialog, } from "../../../dialogs/generic/show-dialog-box"; +import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog"; import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-tabs-subpage-data-table"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { HomeAssistant, Route } from "../../../types"; -import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor"; import { configSections } from "../ha-panel-config"; import "../integrations/ha-integration-overflow-menu"; import { HELPER_DOMAINS } from "./const"; @@ -357,8 +357,9 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { private async _openEditDialog(ev: CustomEvent): Promise { const entityId = (ev.detail as RowClickedEvent).id; - showEntityEditorDialog(this, { - entity_id: entityId, + showMoreInfoDialog(this, { + entityId, + tab: "settings", }); } diff --git a/src/state/more-info-mixin.ts b/src/state/more-info-mixin.ts index e05c76ed72..509c1a4b7a 100644 --- a/src/state/more-info-mixin.ts +++ b/src/state/more-info-mixin.ts @@ -29,6 +29,7 @@ export default >(superClass: T) => "ha-more-info-dialog", { entityId: ev.detail.entityId, + tab: ev.detail.tab, }, () => import("../dialogs/more-info/ha-more-info-dialog") ); diff --git a/src/translations/en.json b/src/translations/en.json index 0414bfa152..a5a31809f8 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -738,9 +738,11 @@ }, "more_info_control": { "dismiss": "Dismiss dialog", - "settings": "Entity settings", + "settings": "Settings", "edit": "Edit entity", "details": "Details", + "info": "Info", + "related": "Related", "history": "History", "logbook": "Logbook", "last_changed": "Last changed", From 8b13a9ff2ef4a0cdcc6db123fe1d058126e78537 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 20 Aug 2022 16:24:11 +0200 Subject: [PATCH 045/134] Add attribute support to state selector (#13420) --- gallery/src/pages/components/ha-form.ts | 16 +- src/common/entity/get_states.ts | 208 +++++++++++++++++- .../entity/ha-entity-state-picker.ts | 18 +- .../ha-selector/ha-selector-state.ts | 3 + src/data/selector.ts | 1 + .../types/ha-automation-condition-state.ts | 12 +- .../types/ha-automation-trigger-state.ts | 14 +- 7 files changed, 243 insertions(+), 29 deletions(-) diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts index 54d8f709da..8117ac826b 100644 --- a/gallery/src/pages/components/ha-form.ts +++ b/gallery/src/pages/components/ha-form.ts @@ -20,16 +20,22 @@ const ENTITIES = [ }), getEntity("media_player", "livingroom", "playing", { friendly_name: "Livingroom", + media_content_type: "music", + device_class: "tv", }), getEntity("media_player", "lounge", "idle", { friendly_name: "Lounge", supported_features: 444983, + device_class: "speaker", }), getEntity("light", "bedroom", "on", { friendly_name: "Bedroom", + effect: "colorloop", + effect_list: ["colorloop", "random"], }), getEntity("switch", "coffee", "off", { friendly_name: "Coffee", + device_class: "switch", }), ]; @@ -136,16 +142,16 @@ const SCHEMAS: { schema: [ { name: "addon", selector: { addon: {} } }, { name: "entity", selector: { entity: {} } }, - { - name: "State", - selector: { state: { entity_id: "" } }, - context: { filter_entity: "entity" }, - }, { name: "Attribute", selector: { attribute: { entity_id: "" } }, context: { filter_entity: "entity" }, }, + { + name: "State", + selector: { state: { entity_id: "" } }, + context: { filter_entity: "entity", filter_attribute: "Attribute" }, + }, { name: "Device", selector: { device: {} } }, { name: "Duration", selector: { duration: {} } }, { name: "area", selector: { area: {} } }, diff --git a/src/common/entity/get_states.ts b/src/common/entity/get_states.ts index c40ad804d0..7adddf87e1 100644 --- a/src/common/entity/get_states.ts +++ b/src/common/entity/get_states.ts @@ -58,32 +58,220 @@ const FIXED_DOMAIN_STATES = { ], }; -export const getStates = (state: HassEntity): string[] => { +const FIXED_DOMAIN_ATTRIBUTE_STATES = { + alarm_control_panel: { + code_format: ["number", "text"], + }, + binary_sensor: { + device_class: [ + "battery", + "battery_charging", + "co", + "cold", + "connectivity", + "door", + "garage_door", + "gas", + "heat", + "light", + "lock", + "moisture", + "motion", + "moving", + "occupancy", + "opening", + "plug", + "power", + "presence", + "problem", + "running", + "safety", + "smoke", + "sound", + "tamper", + "update", + "vibration", + "window", + ], + }, + button: { + device_class: ["restart", "update"], + }, + camera: { + frontend_stream_type: ["hls", "web_rtc"], + }, + climate: { + hvac_action: ["off", "idle", "heating", "cooling", "drying", "fan"], + }, + cover: { + device_class: [ + "awning", + "blind", + "curtain", + "damper", + "door", + "garage", + "gate", + "shade", + "shutter", + "window", + ], + }, + humidifier: { + device_class: ["humidifier", "dehumidifier"], + }, + media_player: { + device_class: ["tv", "speaker", "receiver"], + media_content_type: [ + "app", + "channel", + "episode", + "game", + "image", + "movie", + "music", + "playlist", + "tvshow", + "url", + "video", + ], + }, + number: { + device_class: ["temperature"], + }, + sensor: { + device_class: [ + "apparent_power", + "aqi", + "battery", + "carbon_dioxide", + "carbon_monoxide", + "current", + "date", + "duration", + "energy", + "frequency", + "gas", + "humidity", + "illuminance", + "monetary", + "nitrogen_dioxide", + "nitrogen_monoxide", + "nitrous_oxide", + "ozone", + "pm1", + "pm10", + "pm25", + "power_factor", + "power", + "pressure", + "reactive_power", + "signal_strength", + "sulphur_dioxide", + "temperature", + "timestamp", + "volatile_organic_compounds", + "voltage", + ], + state_class: ["measurement", "total", "total_increasing"], + }, + switch: { + device_class: ["outlet", "switch"], + }, + update: { + device_class: ["firmware"], + }, + water_heater: { + away_mode: ["on", "off"], + }, +}; + +export const getStates = ( + state: HassEntity, + attribute: string | undefined = undefined +): string[] => { const domain = computeStateDomain(state); const result: string[] = []; - if (domain in FIXED_DOMAIN_STATES) { + if (!attribute && domain in FIXED_DOMAIN_STATES) { result.push(...FIXED_DOMAIN_STATES[domain]); - } else { - // If not fixed, we at least know the current state - result.push(state.state); + } else if ( + attribute && + domain in FIXED_DOMAIN_ATTRIBUTE_STATES && + attribute in FIXED_DOMAIN_ATTRIBUTE_STATES[domain] + ) { + result.push(...FIXED_DOMAIN_ATTRIBUTE_STATES[domain][attribute]); } // Dynamic values based on the entities switch (domain) { case "climate": - result.push(...state.attributes.hvac_modes); + if (!attribute) { + result.push(...state.attributes.hvac_modes); + } else if (attribute === "fan_mode") { + result.push(...state.attributes.fan_modes); + } else if (attribute === "preset_mode") { + result.push(...state.attributes.preset_modes); + } else if (attribute === "swing_mode") { + result.push(...state.attributes.swing_modes); + } + break; + case "device_tracker": + case "person": + if (!attribute) { + result.push("home", "not_home"); + } + break; + case "fan": + if (attribute === "preset_mode") { + result.push(...state.attributes.preset_modes); + } + break; + case "humidifier": + if (attribute === "mode") { + result.push(...state.attributes.available_modes); + } break; case "input_select": case "select": - result.push(...state.attributes.options); + if (!attribute) { + result.push(...state.attributes.options); + } + break; + case "light": + if (attribute === "effect") { + result.push(...state.attributes.effect_list); + } else if (attribute === "color_mode") { + result.push(...state.attributes.color_modes); + } + break; + case "media_player": + if (attribute === "sound_mode") { + result.push(...state.attributes.sound_mode_list); + } else if (attribute === "source") { + result.push(...state.attributes.source_list); + } + break; + case "remote": + if (attribute === "current_activity") { + result.push(...state.attributes.activity_list); + } + break; + case "vacuum": + if (attribute === "fan_speed") { + result.push(...state.attributes.fan_speed_list); + } break; case "water_heater": - result.push(...state.attributes.operation_list); + if (!attribute || attribute === "operation_mode") { + result.push(...state.attributes.operation_list); + } break; } - // All entities can have unavailable states - result.push(...UNAVAILABLE_STATES); + if (!attribute) { + // All entities can have unavailable states + result.push(...UNAVAILABLE_STATES); + } return [...new Set(result)]; }; diff --git a/src/components/entity/ha-entity-state-picker.ts b/src/components/entity/ha-entity-state-picker.ts index 968bdedf46..c9c8965e94 100644 --- a/src/components/entity/ha-entity-state-picker.ts +++ b/src/components/entity/ha-entity-state-picker.ts @@ -16,6 +16,8 @@ class HaEntityStatePicker extends LitElement { @property() public entityId?: string; + @property() public attribute?: string; + @property({ type: Boolean }) public autofocus = false; @property({ type: Boolean }) public disabled = false; @@ -44,14 +46,16 @@ class HaEntityStatePicker extends LitElement { const state = this.entityId ? this.hass.states[this.entityId] : undefined; (this._comboBox as any).items = this.entityId && state - ? getStates(state).map((key) => ({ + ? getStates(state, this.attribute).map((key) => ({ value: key, - label: computeStateDisplay( - this.hass.localize, - state, - this.hass.locale, - key - ), + label: !this.attribute + ? computeStateDisplay( + this.hass.localize, + state, + this.hass.locale, + key + ) + : key, })) : []; } diff --git a/src/components/ha-selector/ha-selector-state.ts b/src/components/ha-selector/ha-selector-state.ts index 4f2a39c540..b72b0349bd 100644 --- a/src/components/ha-selector/ha-selector-state.ts +++ b/src/components/ha-selector/ha-selector-state.ts @@ -22,6 +22,7 @@ export class HaSelectorState extends SubscribeMixin(LitElement) { @property({ type: Boolean }) public required = true; @property() public context?: { + filter_attribute?: string; filter_entity?: string; }; @@ -31,6 +32,8 @@ export class HaSelectorState extends SubscribeMixin(LitElement) { .hass=${this.hass} .entityId=${this.selector.state.entity_id || this.context?.filter_entity} + .attribute=${this.selector.state.attribute || + this.context?.filter_attribute} .value=${this.value} .label=${this.label} .helper=${this.helper} diff --git a/src/data/selector.ts b/src/data/selector.ts index 17474651b1..7514c0fd1b 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -195,6 +195,7 @@ export interface SelectSelector { export interface StateSelector { state: { entity_id?: string; + attribute?: string; }; } diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts index 310dc88d01..0a72d74a86 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts @@ -30,14 +30,17 @@ export class HaStateCondition extends LitElement implements ConditionElement { } private _schema = memoizeOne( - (entityId) => + (entityId, attribute) => [ { name: "entity_id", required: true, selector: { entity: {} } }, { name: "attribute", selector: { attribute: { entity_id: entityId } }, }, - { name: "state", selector: { state: { entity_id: entityId } } }, + { + name: "state", + selector: { state: { entity_id: entityId, attribute: attribute } }, + }, { name: "for", selector: { duration: {} } }, ] as const ); @@ -57,7 +60,10 @@ export class HaStateCondition extends LitElement implements ConditionElement { protected render() { const trgFor = createDurationData(this.condition.for); const data = { ...this.condition, for: trgFor }; - const schema = this._schema(this.condition.entity_id); + const schema = this._schema( + this.condition.entity_id, + this.condition.attribute + ); return html` + (entityId, attribute) => [ { name: "entity_id", @@ -61,13 +61,19 @@ export class HaStateTrigger extends LitElement implements TriggerElement { { name: "from", selector: { - state: { entity_id: entityId ? entityId[0] : undefined }, + state: { + entity_id: entityId ? entityId[0] : undefined, + attribute: attribute, + }, }, }, { name: "to", selector: { - state: { entity_id: entityId ? entityId[0] : undefined }, + state: { + entity_id: entityId ? entityId[0] : undefined, + attribute: attribute, + }, }, }, { name: "for", selector: { duration: {} } }, @@ -111,7 +117,7 @@ export class HaStateTrigger extends LitElement implements TriggerElement { entity_id: ensureArray(this.trigger.entity_id), for: trgFor, }; - const schema = this._schema(this.trigger.entity_id); + const schema = this._schema(this.trigger.entity_id, this.trigger.attribute); return html` Date: Sat, 20 Aug 2022 17:29:29 +0300 Subject: [PATCH 046/134] Logbook card: place a gap between an icon/image & a text (#13348) --- src/panels/logbook/ha-logbook-renderer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/panels/logbook/ha-logbook-renderer.ts b/src/panels/logbook/ha-logbook-renderer.ts index e5384b8778..81d4424974 100644 --- a/src/panels/logbook/ha-logbook-renderer.ts +++ b/src/panels/logbook/ha-logbook-renderer.ts @@ -614,7 +614,8 @@ class HaLogbookRenderer extends LitElement { .narrow .icon-message state-badge { margin-left: 0; margin-inline-start: 0; - margin-inline-end: initial; + margin-inline-end: 8px; + margin-right: 8px; direction: var(--direction); } `, From 24509425cab18929aa9bafe39c37e3950bbe54ff Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Sat, 20 Aug 2022 13:36:58 -0400 Subject: [PATCH 047/134] Fix some localize key type errors in lovelace editors (#13403) --- .../get-headerfooter-stub-config.ts | 2 +- src/panels/lovelace/editor/types.ts | 4 ++-- .../lovelace/editor/view-editor/hui-view-editor.ts | 12 +++++++----- src/panels/lovelace/header-footer/types.ts | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/panels/lovelace/editor/header-footer-editor/get-headerfooter-stub-config.ts b/src/panels/lovelace/editor/header-footer-editor/get-headerfooter-stub-config.ts index dd6132ee0b..d1705e6643 100644 --- a/src/panels/lovelace/editor/header-footer-editor/get-headerfooter-stub-config.ts +++ b/src/panels/lovelace/editor/header-footer-editor/get-headerfooter-stub-config.ts @@ -4,7 +4,7 @@ import { LovelaceHeaderFooterConfig } from "../../header-footer/types"; export const getHeaderFooterStubConfig = async ( hass: HomeAssistant, - type: string, + type: LovelaceHeaderFooterConfig["type"], entities: string[], entitiesFallback: string[] ): Promise => { diff --git a/src/panels/lovelace/editor/types.ts b/src/panels/lovelace/editor/types.ts index 821c2dabcb..998c33b6a0 100644 --- a/src/panels/lovelace/editor/types.ts +++ b/src/panels/lovelace/editor/types.ts @@ -64,7 +64,7 @@ export interface Card { } export interface HeaderFooter { - type: string; + type: LovelaceHeaderFooterConfig["type"]; icon?: string; } @@ -75,7 +75,7 @@ export interface CardPickTarget extends EventTarget { export interface SubElementEditorConfig { index?: number; elementConfig?: LovelaceRowConfig | LovelaceHeaderFooterConfig; - type: string; + type: "header" | "footer" | "row"; } export interface EditSubElementEvent { diff --git a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts index 8f34499782..323e6dab96 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts @@ -48,11 +48,13 @@ export class HuiViewEditor extends LitElement { name: "type", selector: { select: { - options: [ - DEFAULT_VIEW_LAYOUT, - SIDEBAR_VIEW_LAYOUT, - PANEL_VIEW_LAYOUT, - ].map((type) => ({ + options: ( + [ + DEFAULT_VIEW_LAYOUT, + SIDEBAR_VIEW_LAYOUT, + PANEL_VIEW_LAYOUT, + ] as const + ).map((type) => ({ value: type, label: localize( `ui.panel.lovelace.editor.edit_view.types.${type}` diff --git a/src/panels/lovelace/header-footer/types.ts b/src/panels/lovelace/header-footer/types.ts index a13cfc2401..45ff294723 100644 --- a/src/panels/lovelace/header-footer/types.ts +++ b/src/panels/lovelace/header-footer/types.ts @@ -2,7 +2,7 @@ import { ActionConfig } from "../../../data/lovelace"; import { EntitiesCardEntityConfig } from "../cards/types"; export interface LovelaceHeaderFooterConfig { - type: string; + type: "buttons" | "graph" | "picture"; } export interface ButtonsHeaderFooterConfig extends LovelaceHeaderFooterConfig { From 52a1594969a64ef4bdf0d992d7c8be16c79e9ee8 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Sat, 20 Aug 2022 13:37:57 -0400 Subject: [PATCH 048/134] Remove all exceptions from supervisor localize keys (#13402) --- hassio/src/addon-view/info/hassio-addon-info.ts | 3 ++- hassio/src/components/supervisor-backup-content.ts | 11 +++++++---- src/data/hassio/addon.ts | 6 +++++- src/data/hassio/resolution.ts | 6 +++--- src/data/supervisor/store.ts | 4 ++-- src/data/supervisor/supervisor.ts | 4 +--- src/translations/en.json | 1 + 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/hassio/src/addon-view/info/hassio-addon-info.ts b/hassio/src/addon-view/info/hassio-addon-info.ts index 6675d7fd55..3753b8a42d 100644 --- a/hassio/src/addon-view/info/hassio-addon-info.ts +++ b/hassio/src/addon-view/info/hassio-addon-info.ts @@ -40,6 +40,7 @@ import "../../../../src/components/ha-settings-row"; import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-switch"; import { + AddonCapability, fetchHassioAddonChangelog, fetchHassioAddonInfo, HassioAddonDetails, @@ -701,7 +702,7 @@ class HassioAddonInfo extends LitElement { } private _showMoreInfo(ev): void { - const id = ev.currentTarget.id; + const id = ev.currentTarget.id as AddonCapability; showHassioMarkdownDialog(this, { title: this.supervisor.localize(`addon.dashboard.capability.${id}.title`), content: diff --git a/hassio/src/components/supervisor-backup-content.ts b/hassio/src/components/supervisor-backup-content.ts index cea5951729..03df51bbe2 100644 --- a/hassio/src/components/supervisor-backup-content.ts +++ b/hassio/src/components/supervisor-backup-content.ts @@ -17,9 +17,12 @@ import { } from "../../../src/data/hassio/backup"; import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { PolymerChangedEvent } from "../../../src/polymer-types"; -import { HomeAssistant } from "../../../src/types"; +import { HomeAssistant, TranslationDict } from "../../../src/types"; import "./supervisor-formfield-label"; +type BackupOrRestoreKey = keyof TranslationDict["supervisor"]["backup"] & + keyof TranslationDict["ui"]["panel"]["page-onboarding"]["restore"]; + interface CheckboxItem { slug: string; checked: boolean; @@ -108,9 +111,9 @@ export class SupervisorBackupContent extends LitElement { this._focusTarget?.focus(); } - private _localize = (string: string) => - this.supervisor?.localize(`backup.${string}`) || - this.localize!(`ui.panel.page-onboarding.restore.${string}`); + private _localize = (key: BackupOrRestoreKey) => + this.supervisor?.localize(`backup.${key}`) || + this.localize!(`ui.panel.page-onboarding.restore.${key}`); protected render(): TemplateResult { if (!this.onboarding && !this.supervisor) { diff --git a/src/data/hassio/addon.ts b/src/data/hassio/addon.ts index 5c1e7f49c2..a1a0828dd9 100644 --- a/src/data/hassio/addon.ts +++ b/src/data/hassio/addon.ts @@ -1,6 +1,6 @@ import { atLeastVersion } from "../../common/config/version"; import type { HaFormSchema } from "../../components/ha-form/types"; -import { HomeAssistant } from "../../types"; +import { HomeAssistant, TranslationDict } from "../../types"; import { supervisorApiCall } from "../supervisor/common"; import { StoreAddonDetails } from "../supervisor/store"; import { Supervisor, SupervisorArch } from "../supervisor/supervisor"; @@ -10,6 +10,10 @@ import { HassioResponse, } from "./common"; +export type AddonCapability = Exclude< + keyof TranslationDict["supervisor"]["addon"]["dashboard"]["capability"], + "label" | "role" | "stages" +>; export type AddonStage = "stable" | "experimental" | "deprecated"; export type AddonAppArmour = "disable" | "default" | "profile"; export type AddonRole = "default" | "homeassistant" | "manager" | "admin"; diff --git a/src/data/hassio/resolution.ts b/src/data/hassio/resolution.ts index f5b002a289..e6ff08f274 100644 --- a/src/data/hassio/resolution.ts +++ b/src/data/hassio/resolution.ts @@ -1,10 +1,10 @@ import { atLeastVersion } from "../../common/config/version"; -import { HomeAssistant } from "../../types"; +import { HomeAssistant, TranslationDict } from "../../types"; import { hassioApiResultExtractor, HassioResponse } from "./common"; export interface HassioResolution { - unsupported: string[]; - unhealthy: string[]; + unsupported: (keyof TranslationDict["supervisor"]["system"]["supervisor"]["unsupported_reason"])[]; + unhealthy: (keyof TranslationDict["supervisor"]["system"]["supervisor"]["unhealthy_reason"])[]; issues: string[]; suggestions: string[]; } diff --git a/src/data/supervisor/store.ts b/src/data/supervisor/store.ts index d71f96401c..b777009caa 100644 --- a/src/data/supervisor/store.ts +++ b/src/data/supervisor/store.ts @@ -1,5 +1,5 @@ import { HomeAssistant } from "../../types"; -import { AddonStage } from "../hassio/addon"; +import { AddonRole, AddonStage } from "../hassio/addon"; import { supervisorApiCall } from "./common"; import { SupervisorArch } from "./supervisor"; @@ -31,7 +31,7 @@ export interface StoreAddonDetails extends StoreAddon { documentation: boolean; full_access: boolean; hassio_api: boolean; - hassio_role: string; + hassio_role: AddonRole; homeassistant_api: boolean; host_network: boolean; host_pid: boolean; diff --git a/src/data/supervisor/supervisor.ts b/src/data/supervisor/supervisor.ts index 2610c76fda..6b15e87f96 100644 --- a/src/data/supervisor/supervisor.ts +++ b/src/data/supervisor/supervisor.ts @@ -60,9 +60,7 @@ export interface SupervisorEvent { [key: string]: any; } -export type SupervisorKeys = - | FlattenObjectKeys - | `${keyof TranslationDict["supervisor"]}.${string}`; +export type SupervisorKeys = FlattenObjectKeys; export interface Supervisor { host: HassioHostInfo; diff --git a/src/translations/en.json b/src/translations/en.json index a5a31809f8..2fbd93aee1 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4564,6 +4564,7 @@ "hide_log": "Hide full log", "full_backup": "[%key:supervisor::backup::full_backup%]", "partial_backup": "[%key:supervisor::backup::partial_backup%]", + "name": "[%key:supervisor::backup::name%]", "type": "[%key:supervisor::backup::type%]", "select_type": "[%key:supervisor::backup::select_type%]", "folders": "[%key:supervisor::backup::folders%]", From 209ba79823cffec2c2638be00ee0a5b02558aa89 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 20 Aug 2022 14:29:43 -0400 Subject: [PATCH 049/134] Expand trigger/condition/row when menu item changes editor (#13424) --- .../config/automation/action/ha-automation-action-row.ts | 6 +----- .../condition/ha-automation-condition-editor.ts | 5 ----- .../automation/condition/ha-automation-condition-row.ts | 9 ++------- .../automation/trigger/ha-automation-trigger-row.ts | 6 +----- 4 files changed, 4 insertions(+), 22 deletions(-) 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 2e2f80c29d..1ff7f143d2 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -254,11 +254,6 @@ export default class HaAutomationActionRow extends LitElement { )} ` : ""} -

    - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} -

    - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - Date: Sat, 20 Aug 2022 14:38:03 -0400 Subject: [PATCH 050/134] Do not hide overflow in expansion panel when expanded (#13423) --- src/components/ha-expansion-panel.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/ha-expansion-panel.ts b/src/components/ha-expansion-panel.ts index 8510584584..8c0fd25539 100644 --- a/src/components/ha-expansion-panel.ts +++ b/src/components/ha-expansion-panel.ts @@ -85,11 +85,18 @@ export class HaExpansionPanel extends LitElement { super.willUpdate(changedProps); if (changedProps.has("expanded") && this.expanded) { this._showContent = this.expanded; + setTimeout(() => { + // Verify we're still expanded + if (this.expanded) { + this._container.style.overflow = "initial"; + } + }, 300); } } private _handleTransitionEnd() { this._container.style.removeProperty("height"); + this._container.style.overflow = this.expanded ? "initial" : "hidden"; this._showContent = this.expanded; } @@ -103,6 +110,7 @@ export class HaExpansionPanel extends LitElement { ev.preventDefault(); const newExpanded = !this.expanded; fireEvent(this, "expanded-will-change", { expanded: newExpanded }); + this._container.style.overflow = "hidden"; if (newExpanded) { this._showContent = true; From aa2641d5c9796a50bb515758cb0d56e8028bf15d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 20 Aug 2022 20:54:42 +0200 Subject: [PATCH 051/134] Add exclude attributes support to attribute selector (#13421) * Add exclude attribute support to attribute selector * Fix typing * Fix rebase f-up * Revert const removal * Make exclude_attributes readonly and fix some propert mismatches Co-authored-by: Zack Co-authored-by: Steve Repsher --- .../entity/ha-entity-attribute-picker.ts | 18 ++++- .../ha-selector/ha-selector-attribute.ts | 7 +- src/data/selector.ts | 1 + .../ha-automation-condition-numeric_state.ts | 74 ++++++++++++++++++- .../types/ha-automation-condition-state.ts | 32 +++++++- .../ha-automation-trigger-numeric_state.ts | 74 ++++++++++++++++++- .../types/ha-automation-trigger-state.ts | 30 +++++++- 7 files changed, 219 insertions(+), 17 deletions(-) diff --git a/src/components/entity/ha-entity-attribute-picker.ts b/src/components/entity/ha-entity-attribute-picker.ts index d47fda3ffd..c79386d355 100644 --- a/src/components/entity/ha-entity-attribute-picker.ts +++ b/src/components/entity/ha-entity-attribute-picker.ts @@ -15,6 +15,14 @@ class HaEntityAttributePicker extends LitElement { @property() public entityId?: string; + /** + * List of attributes to be excluded. + * @type {Array} + * @attr exclude-attributes + */ + @property({ type: Array, attribute: "exclude-attributes" }) + public excludeAttributes?: string[]; + @property({ type: Boolean }) public autofocus = false; @property({ type: Boolean }) public disabled = false; @@ -42,10 +50,12 @@ class HaEntityAttributePicker extends LitElement { if (changedProps.has("_opened") && this._opened) { const state = this.entityId ? this.hass.states[this.entityId] : undefined; (this._comboBox as any).items = state - ? Object.keys(state.attributes).map((key) => ({ - value: key, - label: formatAttributeName(key), - })) + ? Object.keys(state.attributes) + .filter((key) => !this.excludeAttributes?.includes(key)) + .map((key) => ({ + value: key, + label: formatAttributeName(key), + })) : []; } } diff --git a/src/components/ha-selector/ha-selector-attribute.ts b/src/components/ha-selector/ha-selector-attribute.ts index 4d00fa8f9c..af66b97515 100644 --- a/src/components/ha-selector/ha-selector-attribute.ts +++ b/src/components/ha-selector/ha-selector-attribute.ts @@ -8,9 +8,9 @@ import "../entity/ha-entity-attribute-picker"; @customElement("ha-selector-attribute") export class HaSelectorAttribute extends SubscribeMixin(LitElement) { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: AttributeSelector; + @property({ attribute: false }) public selector!: AttributeSelector; @property() public value?: any; @@ -22,7 +22,7 @@ export class HaSelectorAttribute extends SubscribeMixin(LitElement) { @property({ type: Boolean }) public required = true; - @property() public context?: { + @property({ attribute: false }) public context?: { filter_entity?: string; }; @@ -32,6 +32,7 @@ export class HaSelectorAttribute extends SubscribeMixin(LitElement) { .hass=${this.hass} .entityId=${this.selector.attribute.entity_id || this.context?.filter_entity} + .excludeAttributes=${this.selector.attribute.exclude_attributes} .value=${this.value} .label=${this.label} .helper=${this.helper} diff --git a/src/data/selector.ts b/src/data/selector.ts index 7514c0fd1b..affeeb4bd9 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -65,6 +65,7 @@ export interface AreaSelector { export interface AttributeSelector { attribute: { entity_id?: string; + exclude_attributes?: readonly string[]; }; } diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts index 47b1c9919b..73974c5a15 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts @@ -1,11 +1,11 @@ -import "../../../../../components/ha-form/ha-form"; import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; import { NumericStateCondition } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; -import type { SchemaUnion } from "../../../../../components/ha-form/types"; @customElement("ha-automation-condition-numeric_state") export default class HaNumericStateCondition extends LitElement { @@ -25,7 +25,75 @@ export default class HaNumericStateCondition extends LitElement { { name: "entity_id", required: true, selector: { entity: {} } }, { name: "attribute", - selector: { attribute: { entity_id: entityId } }, + selector: { + attribute: { + entity_id: entityId, + exclude_attributes: [ + "access_token", + "auto_update", + "available_modes", + "away_mode", + "changed_by", + "code_format", + "color_modes", + "current_activity", + "device_class", + "editable", + "effect_list", + "effect", + "entity_picture", + "fan_mode", + "fan_modes", + "fan_speed_list", + "forecast", + "friendly_name", + "frontend_stream_type", + "has_date", + "has_time", + "hs_color", + "hvac_mode", + "hvac_modes", + "icon", + "media_album_name", + "media_artist", + "media_content_type", + "media_position_updated_at", + "media_title", + "next_dawn", + "next_dusk", + "next_midnight", + "next_noon", + "next_rising", + "next_setting", + "operation_list", + "operation_mode", + "options", + "preset_mode", + "preset_modes", + "release_notes", + "release_summary", + "release_url", + "restored", + "rgb_color", + "rgbw_color", + "shuffle", + "sound_mode_list", + "sound_mode", + "source_list", + "source_type", + "source", + "state_class", + "supported_features", + "swing_mode", + "swing_mode", + "swing_modes", + "title", + "token", + "unit_of_measurement", + "xy_color", + ], + }, + }, }, { name: "above", selector: { text: {} } }, { name: "below", selector: { text: {} } }, diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts index 0a72d74a86..fd4998893a 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts @@ -4,12 +4,12 @@ import memoizeOne from "memoize-one"; import { assert, literal, object, optional, string, union } from "superstruct"; import { createDurationData } from "../../../../../common/datetime/create_duration_data"; import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; import type { StateCondition } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; import { forDictStruct } from "../../structs"; import type { ConditionElement } from "../ha-automation-condition-row"; -import "../../../../../components/ha-form/ha-form"; -import type { SchemaUnion } from "../../../../../components/ha-form/types"; const stateConditionStruct = object({ condition: literal("state"), @@ -35,7 +35,33 @@ export class HaStateCondition extends LitElement implements ConditionElement { { name: "entity_id", required: true, selector: { entity: {} } }, { name: "attribute", - selector: { attribute: { entity_id: entityId } }, + selector: { + attribute: { + entity_id: entityId, + exclude_attributes: [ + "access_token", + "available_modes", + "color_modes", + "editable", + "effect_list", + "entity_picture", + "fan_modes", + "fan_speed_list", + "forecast", + "friendly_name", + "hvac_modes", + "icon", + "operation_list", + "options", + "preset_modes", + "sound_mode_list", + "source_list", + "state_class", + "swing_modes", + "token", + ], + }, + }, }, { name: "state", diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts index 88be9564a5..763fca4bc3 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts @@ -1,13 +1,13 @@ -import "../../../../../components/ha-form/ha-form"; import { html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { createDurationData } from "../../../../../common/datetime/create_duration_data"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { hasTemplate } from "../../../../../common/string/has-template"; +import "../../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; import type { NumericStateTrigger } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; -import type { SchemaUnion } from "../../../../../components/ha-form/types"; @customElement("ha-automation-trigger-numeric_state") export class HaNumericStateTrigger extends LitElement { @@ -21,7 +21,75 @@ export class HaNumericStateTrigger extends LitElement { { name: "entity_id", required: true, selector: { entity: {} } }, { name: "attribute", - selector: { attribute: { entity_id: entityId } }, + selector: { + attribute: { + entity_id: entityId, + exclude_attributes: [ + "access_token", + "auto_update", + "available_modes", + "away_mode", + "changed_by", + "code_format", + "color_modes", + "current_activity", + "device_class", + "editable", + "effect_list", + "effect", + "entity_picture", + "fan_mode", + "fan_modes", + "fan_speed_list", + "forecast", + "friendly_name", + "frontend_stream_type", + "has_date", + "has_time", + "hs_color", + "hvac_mode", + "hvac_modes", + "icon", + "media_album_name", + "media_artist", + "media_content_type", + "media_position_updated_at", + "media_title", + "next_dawn", + "next_dusk", + "next_midnight", + "next_noon", + "next_rising", + "next_setting", + "operation_list", + "operation_mode", + "options", + "preset_mode", + "preset_modes", + "release_notes", + "release_summary", + "release_url", + "restored", + "rgb_color", + "rgbw_color", + "shuffle", + "sound_mode_list", + "sound_mode", + "source_list", + "source_type", + "source", + "state_class", + "supported_features", + "swing_mode", + "swing_mode", + "swing_modes", + "title", + "token", + "unit_of_measurement", + "xy_color", + ], + }, + }, }, { name: "above", selector: { text: {} } }, { name: "below", selector: { text: {} } }, diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts index 9405a76f75..dcd0daafc3 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts @@ -55,7 +55,35 @@ export class HaStateTrigger extends LitElement implements TriggerElement { { name: "attribute", selector: { - attribute: { entity_id: entityId ? entityId[0] : undefined }, + attribute: { + entity_id: entityId ? entityId[0] : undefined, + exclude_attributes: [ + "access_token", + "available_modes", + "color_modes", + "device_class", + "editable", + "effect_list", + "entity_picture", + "fan_modes", + "fan_speed_list", + "friendly_name", + "has_date", + "has_time", + "hvac_modes", + "icon", + "operation_list", + "options", + "preset_modes", + "sound_mode_list", + "source_list", + "state_class", + "supported_features", + "swing_modes", + "token", + "unit_of_measurement", + ], + }, }, }, { From ec7dea93a0acb06b456263ba34b954876c7dfb3b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 21 Aug 2022 00:38:44 +0200 Subject: [PATCH 052/134] Allow days in calendar trigger offset (#13427) --- .../automation/trigger/types/ha-automation-trigger-calendar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts index 6ce9382efb..a9a5ae7030 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts @@ -44,7 +44,7 @@ export class HaCalendarTrigger extends LitElement implements TriggerElement { ], ], }, - { name: "offset", selector: { duration: {} } }, + { name: "offset", selector: { duration: { enable_day: true } } }, { name: "offset_type", type: "select", From d4262ecb0979e988df3bef6090cb95fff6a7e586 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 21 Aug 2022 04:00:20 +0200 Subject: [PATCH 053/134] Use dropdown mode for script mode selector (#13429) --- src/panels/config/script/ha-script-editor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index d0309111f9..1e47d76195 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -122,6 +122,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { name: "mode", selector: { select: { + mode: "dropdown", options: MODES.map((mode) => ({ label: this.hass.localize( `ui.panel.config.script.editor.modes.${mode}` From 7d3d800d4cf7e060c040360fae239ae3474d5c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 22 Aug 2022 10:46:07 +0200 Subject: [PATCH 054/134] Fix progressbar margins (#13435) --- src/dialogs/more-info/controls/more-info-update.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dialogs/more-info/controls/more-info-update.ts b/src/dialogs/more-info/controls/more-info-update.ts index ee855ddb13..6e77f7f31e 100644 --- a/src/dialogs/more-info/controls/more-info-update.ts +++ b/src/dialogs/more-info/controls/more-info-update.ts @@ -257,8 +257,8 @@ class MoreInfoUpdate extends LitElement { justify-content: center; } mwc-linear-progress { - margin-bottom: -10px; - margin-top: -10px; + margin-bottom: -8px; + margin-top: 4px; } `; } From 1b5c30712e393e31dfe70f1337b7ea01c42d1b45 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Aug 2022 11:25:17 +0200 Subject: [PATCH 055/134] Rename exclude_attributes to hide_attributes for clarity (#13436) --- src/components/entity/ha-entity-attribute-picker.ts | 10 +++++----- src/components/ha-selector/ha-selector-attribute.ts | 2 +- src/data/selector.ts | 2 +- .../types/ha-automation-condition-numeric_state.ts | 2 +- .../condition/types/ha-automation-condition-state.ts | 2 +- .../types/ha-automation-trigger-numeric_state.ts | 2 +- .../trigger/types/ha-automation-trigger-state.ts | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/entity/ha-entity-attribute-picker.ts b/src/components/entity/ha-entity-attribute-picker.ts index c79386d355..6fb98f55b9 100644 --- a/src/components/entity/ha-entity-attribute-picker.ts +++ b/src/components/entity/ha-entity-attribute-picker.ts @@ -16,12 +16,12 @@ class HaEntityAttributePicker extends LitElement { @property() public entityId?: string; /** - * List of attributes to be excluded. + * List of attributes to be hidden. * @type {Array} - * @attr exclude-attributes + * @attr hide-attributes */ - @property({ type: Array, attribute: "exclude-attributes" }) - public excludeAttributes?: string[]; + @property({ type: Array, attribute: "hide-attributes" }) + public hideAttributes?: string[]; @property({ type: Boolean }) public autofocus = false; @@ -51,7 +51,7 @@ class HaEntityAttributePicker extends LitElement { const state = this.entityId ? this.hass.states[this.entityId] : undefined; (this._comboBox as any).items = state ? Object.keys(state.attributes) - .filter((key) => !this.excludeAttributes?.includes(key)) + .filter((key) => !this.hideAttributes?.includes(key)) .map((key) => ({ value: key, label: formatAttributeName(key), diff --git a/src/components/ha-selector/ha-selector-attribute.ts b/src/components/ha-selector/ha-selector-attribute.ts index af66b97515..da06508e2f 100644 --- a/src/components/ha-selector/ha-selector-attribute.ts +++ b/src/components/ha-selector/ha-selector-attribute.ts @@ -32,7 +32,7 @@ export class HaSelectorAttribute extends SubscribeMixin(LitElement) { .hass=${this.hass} .entityId=${this.selector.attribute.entity_id || this.context?.filter_entity} - .excludeAttributes=${this.selector.attribute.exclude_attributes} + .hideAttributes=${this.selector.attribute.hide_attributes} .value=${this.value} .label=${this.label} .helper=${this.helper} diff --git a/src/data/selector.ts b/src/data/selector.ts index affeeb4bd9..10d0fb8bf1 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -65,7 +65,7 @@ export interface AreaSelector { export interface AttributeSelector { attribute: { entity_id?: string; - exclude_attributes?: readonly string[]; + hide_attributes?: readonly string[]; }; } diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts index 73974c5a15..b45dd1da83 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts @@ -28,7 +28,7 @@ export default class HaNumericStateCondition extends LitElement { selector: { attribute: { entity_id: entityId, - exclude_attributes: [ + hide_attributes: [ "access_token", "auto_update", "available_modes", diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts index fd4998893a..d34264466c 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts @@ -38,7 +38,7 @@ export class HaStateCondition extends LitElement implements ConditionElement { selector: { attribute: { entity_id: entityId, - exclude_attributes: [ + hide_attributes: [ "access_token", "available_modes", "color_modes", diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts index 763fca4bc3..5a3c95fad3 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts @@ -24,7 +24,7 @@ export class HaNumericStateTrigger extends LitElement { selector: { attribute: { entity_id: entityId, - exclude_attributes: [ + hide_attributes: [ "access_token", "auto_update", "available_modes", diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts index dcd0daafc3..bd46fca7cf 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts @@ -57,7 +57,7 @@ export class HaStateTrigger extends LitElement implements TriggerElement { selector: { attribute: { entity_id: entityId ? entityId[0] : undefined, - exclude_attributes: [ + hide_attributes: [ "access_token", "available_modes", "color_modes", From 4b54cb4a35e14d9d81c6d5bc0f1c619767a5c3f9 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Aug 2022 14:17:52 +0200 Subject: [PATCH 056/134] Make updates more distinct recognizable by device name (#13433) --- .../config/dashboard/ha-config-updates.ts | 68 +++++++++++++++---- src/translations/en.json | 1 - 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/panels/config/dashboard/ha-config-updates.ts b/src/panels/config/dashboard/ha-config-updates.ts index a24bce31e2..1fac70165a 100644 --- a/src/panels/config/dashboard/ha-config-updates.ts +++ b/src/panels/config/dashboard/ha-config-updates.ts @@ -2,6 +2,8 @@ import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/entity/state-badge"; import "../../../components/ha-alert"; @@ -10,9 +12,19 @@ import type { UpdateEntity } from "../../../data/update"; import type { HomeAssistant } from "../../../types"; import "../../../components/ha-circular-progress"; import "../../../components/ha-list-item"; +import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; +import { + computeDeviceName, + DeviceRegistryEntry, + subscribeDeviceRegistry, +} from "../../../data/device_registry"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../../../data/entity_registry"; @customElement("ha-config-updates") -class HaConfigUpdates extends LitElement { +class HaConfigUpdates extends SubscribeMixin(LitElement) { @property({ attribute: false }) public hass!: HomeAssistant; @property({ type: Boolean }) public narrow!: boolean; @@ -20,9 +32,36 @@ class HaConfigUpdates extends LitElement { @property({ attribute: false }) public updateEntities?: UpdateEntity[]; + @property({ attribute: false, type: Array }) + private devices?: DeviceRegistryEntry[]; + + @property({ attribute: false, type: Array }) + private entities?: EntityRegistryEntry[]; + @property({ type: Number }) public total?: number; + public hassSubscribe(): UnsubscribeFunc[] { + return [ + subscribeDeviceRegistry(this.hass.connection, (entries) => { + this.devices = entries; + }), + subscribeEntityRegistry(this.hass.connection!, (entities) => { + this.entities = entities.filter((entity) => entity.device_id !== null); + }), + ]; + } + + private getDeviceEntry = memoizeOne( + (deviceId: string): DeviceRegistryEntry | undefined => + this.devices?.find((device) => device.id === deviceId) + ); + + private getEntityEntry = memoizeOne( + (entityId: string): EntityRegistryEntry | undefined => + this.entities?.find((entity) => entity.entity_id === entityId) + ); + protected render(): TemplateResult { if (!this.updateEntities?.length) { return html``; @@ -37,8 +76,14 @@ class HaConfigUpdates extends LitElement { })}
    - ${updates.map( - (entity) => html` + ${updates.map((entity) => { + const entityEntry = this.getEntityEntry(entity.entity_id); + const deviceEntry = + entityEntry && entityEntry.device_id + ? this.getDeviceEntry(entityEntry.device_id) + : undefined; + + return html` ` : ""} ${entity.attributes.title || - entity.attributes.friendly_name}${deviceEntry + ? computeDeviceName(deviceEntry, this.hass) + : entity.attributes.friendly_name} - ${this.hass.localize( - "ui.panel.config.updates.version_available", - { - version_available: entity.attributes.latest_version, - } - )}${entity.attributes.skipped_version + ${entity.attributes.title} ${entity.attributes.latest_version} + ${entity.attributes.skipped_version ? `(${this.hass.localize("ui.panel.config.updates.skipped")})` : ""} @@ -88,8 +130,8 @@ class HaConfigUpdates extends LitElement { : html`` : ""} - ` - )} + `; + })} `; } diff --git a/src/translations/en.json b/src/translations/en.json index 2fbd93aee1..76ce515eb3 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1225,7 +1225,6 @@ "updates_refreshed": "{count} {count, plural,\n one {update}\n other {updates}\n} refreshed", "title": "{count} {count, plural,\n one {update}\n other {updates}\n}", "unable_to_fetch": "Unable to load updates", - "version_available": "Version {version_available} is available", "more_updates": "Show all updates", "show": "show", "show_skipped": "Show skipped updates", From 9ed0cb30112d442767c3ef2fa58d1bfe5b9972bc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Aug 2022 14:55:26 +0200 Subject: [PATCH 057/134] Use duration input for timeout in wait_for_trigger action (#13426) * Use duration input for timeout in wait_for_trigger action * Specify event type --- src/data/script.ts | 10 ++++++- .../ha-automation-action-wait_for_trigger.ts | 29 ++++++++++++++----- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/data/script.ts b/src/data/script.ts index 42c9c849ac..9685af17dd 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -155,9 +155,17 @@ export interface WaitAction extends BaseAction { continue_on_timeout?: boolean; } +export interface WaitForTriggerActionParts extends BaseAction { + milliseconds?: number; + seconds?: number; + minutes?: number; + hours?: number; + days?: number; +} + export interface WaitForTriggerAction extends BaseAction { wait_for_trigger: Trigger | Trigger[]; - timeout?: number; + timeout?: number | Partial | string; continue_on_timeout?: boolean; } diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts index bee68da94c..8107017d54 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts @@ -7,6 +7,9 @@ import { WaitForTriggerAction } from "../../../../../data/script"; import { HomeAssistant } from "../../../../../types"; import "../../trigger/ha-automation-trigger"; import { ActionElement, handleChangeEvent } from "../ha-automation-action-row"; +import "../../../../../components/ha-duration-input"; +import { createDurationData } from "../../../../../common/datetime/create_duration_data"; +import { TimeChangedEvent } from "../../../../../components/ha-base-time-input"; @customElement("ha-automation-action-wait_for_trigger") export class HaWaitForTriggerAction @@ -22,17 +25,18 @@ export class HaWaitForTriggerAction } protected render() { - const { wait_for_trigger, continue_on_timeout, timeout } = this.action; + const { wait_for_trigger, continue_on_timeout } = this.action; + const timeData = createDurationData(this.action.timeout); return html` - + .data=${timeData} + enableMillisecond + @value-changed=${this._timeoutChanged} + > ): void { + ev.stopPropagation(); + const value = ev.detail.value; + if (!value) { + return; + } + fireEvent(this, "value-changed", { + value: { ...this.action, timeout: value }, + }); + } + private _continueChanged(ev) { fireEvent(this, "value-changed", { value: { ...this.action, continue_on_timeout: ev.target.checked }, @@ -64,7 +79,7 @@ export class HaWaitForTriggerAction static get styles(): CSSResultGroup { return css` - ha-textfield { + ha-duration-input { display: block; margin-bottom: 24px; } From dfface69040a023e7ee2f7828e3e4ee79c65410f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 22 Aug 2022 09:56:48 -0400 Subject: [PATCH 058/134] Merge more info "info" and "history" like before (#13425) --- src/dialogs/more-info/ha-more-info-dialog.ts | 27 +++++++--------- src/dialogs/more-info/ha-more-info-info.ts | 34 ++++++++++++++++++-- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 9df9d29514..eb5f3d857d 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -20,7 +20,9 @@ import { HomeAssistant } from "../../types"; import { EDITABLE_DOMAINS_WITH_ID, EDITABLE_DOMAINS, - DOMAINS_MORE_INFO_NO_HISTORY, + DOMAINS_WITH_MORE_INFO, + computeShowHistoryComponent, + computeShowLogBookComponent, } from "./const"; import "./controls/more-info-default"; import "./ha-more-info-info"; @@ -47,13 +49,11 @@ export class MoreInfoDialog extends LitElement { public showDialog(params: MoreInfoDialogParams) { this._entityId = params.entityId; - if (params.tab) { - this._currTab = params.tab; - } if (!this._entityId) { this.closeDialog(); return; } + this._currTab = params.tab || "info"; this.large = false; } @@ -216,7 +216,13 @@ export class MoreInfoDialog extends LitElement { const domain = computeDomain(entityId); const tabs: Tab[] = ["info"]; - if (!DOMAINS_MORE_INFO_NO_HISTORY.includes(domain)) { + // Info and history are combined in info when there are no + // dedicated more-info controls. If not combined, add a history tab. + if ( + DOMAINS_WITH_MORE_INFO.includes(domain) && + (computeShowHistoryComponent(this.hass, entityId) || + computeShowLogBookComponent(this.hass, entityId)) + ) { tabs.push("history"); } @@ -299,10 +305,6 @@ export class MoreInfoDialog extends LitElement { --mdc-dialog-max-height: calc(100% - 72px); } - ha-icon-button[slot="navigationIcon"] { - display: none; - } - .main-title { overflow: hidden; text-overflow: ellipsis; @@ -319,13 +321,6 @@ export class MoreInfoDialog extends LitElement { ha-dialog[data-domain="camera"] { --dialog-content-padding: 0; } - - state-card-content, - ha-more-info-history, - ha-more-info-logbook:not(:last-child) { - display: block; - margin-bottom: 16px; - } `, ]; } diff --git a/src/dialogs/more-info/ha-more-info-info.ts b/src/dialogs/more-info/ha-more-info-info.ts index 0167943d34..d66adaed41 100644 --- a/src/dialogs/more-info/ha-more-info-info.ts +++ b/src/dialogs/more-info/ha-more-info-info.ts @@ -1,10 +1,15 @@ -import { LitElement, html } from "lit"; +import { LitElement, html, css } from "lit"; import { customElement, property } from "lit/decorators"; import { computeDomain } from "../../common/entity/compute_domain"; import { removeEntityRegistryEntry } from "../../data/entity_registry"; import type { HomeAssistant } from "../../types"; import { showConfirmationDialog } from "../generic/show-dialog-box"; -import { DOMAINS_NO_INFO } from "./const"; +import { + computeShowHistoryComponent, + computeShowLogBookComponent, + DOMAINS_NO_INFO, + DOMAINS_WITH_MORE_INFO, +} from "./const"; import "./ha-more-info-history"; import "./ha-more-info-logbook"; @@ -29,6 +34,20 @@ export class MoreInfoInfo extends LitElement { .hass=${this.hass} > `} + ${DOMAINS_WITH_MORE_INFO.includes(domain) || + !computeShowHistoryComponent(this.hass, entityId) + ? "" + : html``} + ${DOMAINS_WITH_MORE_INFO.includes(domain) || + !computeShowLogBookComponent(this.hass, entityId) + ? "" + : html``} Date: Mon, 22 Aug 2022 12:04:02 -0400 Subject: [PATCH 059/134] Tighten UI localize key exceptions (#13430) --- src/common/translations/localize.ts | 33 ++++++++++++++++++- .../zwave_js/zwave_js-config-dashboard.ts | 2 +- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/common/translations/localize.ts b/src/common/translations/localize.ts index 20be6e44db..9a6cbf67b3 100644 --- a/src/common/translations/localize.ts +++ b/src/common/translations/localize.ts @@ -15,7 +15,38 @@ export type LocalizeKeys = | `state.${string}` | `state_attributes.${string}` | `state_badge.${string}` - | `ui.${string}` + | `ui.card.alarm_control_panel.${string}` + | `ui.card.weather.attributes.${string}` + | `ui.card.weather.cardinal_direction.${string}` + | `ui.components.combo-box.${string}` + | `ui.components.logbook.${string}` + | `ui.components.selectors.file.${string}` + | `ui.dialogs.entity_registry.editor.${string}` + | `ui.dialogs.more_info_control.vacuum.${string}` + | `ui.dialogs.options_flow.loading.${string}` + | `ui.dialogs.quick-bar.commands.${string}` + | `ui.dialogs.repair_flow.loading.${string}` + | `ui.dialogs.unhealthy.reason.${string}` + | `ui.dialogs.unsupported.reason.${string}` + | `ui.panel.config.${string}.${"caption" | "description"}` + | `ui.panel.config.automation.${string}` + | `ui.panel.config.dashboard.${string}` + | `ui.panel.config.devices.${string}` + | `ui.panel.config.energy.${string}` + | `ui.panel.config.helpers.${string}` + | `ui.panel.config.info.${string}` + | `ui.panel.config.integrations.${string}` + | `ui.panel.config.logs.${string}` + | `ui.panel.config.lovelace.${string}` + | `ui.panel.config.network.${string}` + | `ui.panel.config.scene.${string}` + | `ui.panel.config.url.${string}` + | `ui.panel.config.zha.${string}` + | `ui.panel.config.zwave_js.${string}` + | `ui.panel.developer-tools.tabs.${string}` + | `ui.panel.lovelace.card.${string}` + | `ui.panel.lovelace.editor.${string}` + | `ui.panel.page-authorize.form.${string}` | `component.${string}`; // Tweaked from https://www.raygesualdo.com/posts/flattening-object-keys-with-typescript-types diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts index 8681103d5c..2668262a98 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts @@ -545,7 +545,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {

    ${stateTextExtra}

    - ${this.hass?.localize("ui.panel.error.go_back") || "go back"} + ${this.hass?.localize("ui.common.back")}
    ` From 82f48d106f61e6c4b96b071670a071677a05ef32 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Aug 2022 18:04:36 +0200 Subject: [PATCH 060/134] Use template selector in numeric state templates (#13428) --- .../condition/types/ha-automation-condition-numeric_state.ts | 2 +- .../trigger/types/ha-automation-trigger-numeric_state.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts index b45dd1da83..5b76d3008a 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts @@ -99,7 +99,7 @@ export default class HaNumericStateCondition extends LitElement { { name: "below", selector: { text: {} } }, { name: "value_template", - selector: { text: { multiline: true } }, + selector: { template: {} }, }, ] as const ); diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts index 5a3c95fad3..e49cfbcd39 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts @@ -95,7 +95,7 @@ export class HaNumericStateTrigger extends LitElement { { name: "below", selector: { text: {} } }, { name: "value_template", - selector: { text: { multiline: true } }, + selector: { template: {} }, }, { name: "for", selector: { duration: {} } }, ] as const @@ -106,7 +106,7 @@ export class HaNumericStateTrigger extends LitElement { return; } // Check for templates in trigger. If found, revert to YAML mode. - if (this.trigger && hasTemplate(this.trigger)) { + if (this.trigger && hasTemplate(this.trigger.for)) { fireEvent( this, "ui-mode-not-available", From 5fb2e3316a6c9d99bc99bf3d90b9ecef07da8de4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Aug 2022 18:16:11 +0200 Subject: [PATCH 061/134] Use number selector for above & below in numeric trigger/condition (#13422) --- .../ha-automation-condition-numeric_state.ts | 24 +++++++++++++++++-- .../ha-automation-trigger-numeric_state.ts | 24 +++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts index 5b76d3008a..9a8d3f0a32 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts @@ -95,8 +95,28 @@ export default class HaNumericStateCondition extends LitElement { }, }, }, - { name: "above", selector: { text: {} } }, - { name: "below", selector: { text: {} } }, + { + name: "above", + selector: { + number: { + mode: "box", + min: Number.MIN_SAFE_INTEGER, + max: Number.MAX_SAFE_INTEGER, + step: 0.1, + }, + }, + }, + { + name: "below", + selector: { + number: { + mode: "box", + min: Number.MIN_SAFE_INTEGER, + max: Number.MAX_SAFE_INTEGER, + step: 0.1, + }, + }, + }, { name: "value_template", selector: { template: {} }, diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts index e49cfbcd39..f7a8540a8b 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts @@ -91,8 +91,28 @@ export class HaNumericStateTrigger extends LitElement { }, }, }, - { name: "above", selector: { text: {} } }, - { name: "below", selector: { text: {} } }, + { + name: "above", + selector: { + number: { + mode: "box", + min: Number.MIN_SAFE_INTEGER, + max: Number.MAX_SAFE_INTEGER, + step: 0.1, + }, + }, + }, + { + name: "below", + selector: { + number: { + mode: "box", + min: Number.MIN_SAFE_INTEGER, + max: Number.MAX_SAFE_INTEGER, + step: 0.1, + }, + }, + }, { name: "value_template", selector: { template: {} }, From 738367a7c7dbfd5e9cdf646ed741cd9c08b8ea1b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Aug 2022 20:27:13 +0200 Subject: [PATCH 062/134] Use alias instead of description in case set in actions/conditions (#13441) --- src/panels/config/automation/action/ha-automation-action-row.ts | 2 +- .../automation/action/types/ha-automation-action-service.ts | 1 + .../config/automation/condition/ha-automation-condition-row.ts | 2 +- .../automation/condition/types/ha-automation-condition-state.ts | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) 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 1ff7f143d2..8c58ca65ef 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -145,7 +145,7 @@ export default class HaAutomationActionRow extends LitElement { : ""} ${this.index !== 0 ? html` diff --git a/src/panels/config/automation/action/types/ha-automation-action-service.ts b/src/panels/config/automation/action/types/ha-automation-action-service.ts index fdac1e1824..69f87c3583 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-service.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-service.ts @@ -10,6 +10,7 @@ import type { HomeAssistant } from "../../../../../types"; import { ActionElement } from "../ha-automation-action-row"; const actionStruct = object({ + alias: optional(string()), service: optional(string()), entity_id: optional(entityIdOrAll()), target: optional(any()), diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index fd77ce8783..aa2eb92746 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -80,7 +80,7 @@ export default class HaAutomationConditionRow extends LitElement { ${this.hass.localize( diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts index d34264466c..ca5c923f85 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts @@ -12,6 +12,7 @@ import { forDictStruct } from "../../structs"; import type { ConditionElement } from "../ha-automation-condition-row"; const stateConditionStruct = object({ + alias: optional(string()), condition: literal("state"), entity_id: optional(string()), attribute: optional(string()), From 44422086d76943ff7aa9427e4a16d46d24a6b332 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Aug 2022 23:08:32 +0200 Subject: [PATCH 063/134] Add alias support to all triggers (#13442) --- src/data/automation.ts | 1 + .../config/automation/trigger/ha-automation-trigger-row.ts | 2 +- .../automation/trigger/types/ha-automation-trigger-state.ts | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/data/automation.ts b/src/data/automation.ts index b164df7591..91591a595b 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -62,6 +62,7 @@ export interface ContextConstraint { } export interface BaseTrigger { + alias?: string; platform: string; id?: string; variables?: Record; diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index d6f32bf995..5525925bd6 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -110,7 +110,7 @@ export default class HaAutomationTriggerRow extends LitElement { Date: Tue, 23 Aug 2022 03:25:27 +0200 Subject: [PATCH 064/134] Tweak displayed action/condition/trigger names (#13445) --- src/data/automation_i18n.ts | 8 ++++++-- .../config/automation/action/ha-automation-action-row.ts | 5 ++++- .../automation/condition/ha-automation-condition-row.ts | 3 ++- .../automation/trigger/ha-automation-trigger-row.ts | 3 ++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index d2e1a7b215..a22e803c49 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -1,7 +1,11 @@ import { Condition, Trigger } from "./automation"; -export const describeTrigger = (trigger: Trigger) => - `${trigger.platform || "Unknown"} trigger`; +export const describeTrigger = (trigger: Trigger) => { + if (trigger.alias) { + return trigger.alias; + } + return `${trigger.platform || "Unknown"} trigger`; +}; export const describeCondition = (condition: Condition) => { if (condition.alias) { 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 8c58ca65ef..8c7daedb86 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -39,6 +39,7 @@ import "./types/ha-automation-action-stop"; import "./types/ha-automation-action-wait_for_trigger"; import "./types/ha-automation-action-wait_template"; import { ACTION_TYPES } from "../../../../data/action"; +import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; const getType = (action: Action | undefined) => { if (!action) { @@ -145,7 +146,9 @@ export default class HaAutomationActionRow extends LitElement { : ""} ${this.index !== 0 ? html` diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index aa2eb92746..df38ecc5a5 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -22,6 +22,7 @@ import { HomeAssistant } from "../../../../types"; import "./ha-automation-condition-editor"; import { validateConfig } from "../../../../data/config"; import { describeCondition } from "../../../../data/automation_i18n"; +import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; export interface ConditionElement extends LitElement { condition: Condition; @@ -80,7 +81,7 @@ export default class HaAutomationConditionRow extends LitElement { ${this.hass.localize( diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 5525925bd6..24a3d30762 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -40,6 +40,7 @@ import "./types/ha-automation-trigger-time_pattern"; import "./types/ha-automation-trigger-webhook"; import "./types/ha-automation-trigger-zone"; import { describeTrigger } from "../../../../data/automation_i18n"; +import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; export interface TriggerElement extends LitElement { trigger: Trigger; @@ -110,7 +111,7 @@ export default class HaAutomationTriggerRow extends LitElement { Date: Tue, 23 Aug 2022 14:58:05 +0200 Subject: [PATCH 065/134] Use trigger alias in trace timelines (#13447) --- src/components/trace/hat-trace-timeline.ts | 6 +++++- src/data/trace.ts | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/trace/hat-trace-timeline.ts b/src/components/trace/hat-trace-timeline.ts index 3a47a9f819..0174a12bf0 100644 --- a/src/components/trace/hat-trace-timeline.ts +++ b/src/components/trace/hat-trace-timeline.ts @@ -317,7 +317,11 @@ class ActionRenderer { private _handleTrigger(index: number, triggerStep: TriggerTraceStep): number { this._renderEntry( triggerStep.path, - `Triggered ${ + `${ + triggerStep.changed_variables.trigger.alias + ? `${triggerStep.changed_variables.trigger.alias} triggered` + : "Triggered" + } ${ triggerStep.path === "trigger" ? "manually" : `by the ${this.trace.trigger}` diff --git a/src/data/trace.ts b/src/data/trace.ts index 600d6c9466..1c64152904 100644 --- a/src/data/trace.ts +++ b/src/data/trace.ts @@ -16,6 +16,7 @@ interface BaseTraceStep { export interface TriggerTraceStep extends BaseTraceStep { changed_variables: { trigger: { + alias?: string; description: string; [key: string]: unknown; }; From c82782fa1b3dbda3af3a14cadb749ea327833498 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 23 Aug 2022 08:47:15 -0500 Subject: [PATCH 066/134] Update Automation Picker Table (#13405) --- src/common/datetime/format_date_time.ts | 24 ++- .../config/automation/ha-automation-picker.ts | 200 ++++++------------ 2 files changed, 85 insertions(+), 139 deletions(-) diff --git a/src/common/datetime/format_date_time.ts b/src/common/datetime/format_date_time.ts index 3310de0291..9aef50e2c8 100644 --- a/src/common/datetime/format_date_time.ts +++ b/src/common/datetime/format_date_time.ts @@ -1,7 +1,7 @@ import memoizeOne from "memoize-one"; import { FrontendLocaleData } from "../../data/translation"; -import { useAmPm } from "./use_am_pm"; import { polyfillsLoaded } from "../translations/localize"; +import { useAmPm } from "./use_am_pm"; if (__BUILD__ === "latest" && polyfillsLoaded) { await polyfillsLoaded; @@ -28,6 +28,28 @@ const formatDateTimeMem = memoizeOne( ) ); +// Aug 9, 8:23 AM +export const formatShortDateTime = ( + dateObj: Date, + locale: FrontendLocaleData +) => formatShortDateTimeMem(locale).format(dateObj); + +const formatShortDateTimeMem = memoizeOne( + (locale: FrontendLocaleData) => + new Intl.DateTimeFormat( + locale.language === "en" && !useAmPm(locale) + ? "en-u-hc-h23" + : locale.language, + { + month: "short", + day: "numeric", + hour: useAmPm(locale) ? "numeric" : "2-digit", + minute: "2-digit", + hour12: useAmPm(locale), + } + ) +); + // August 9, 2021, 8:23:15 AM export const formatDateTimeWithSeconds = ( dateObj: Date, diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index 7c637b4dc7..aba85d255b 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -1,32 +1,22 @@ -import { - mdiHelpCircle, - mdiHistory, - mdiInformationOutline, - mdiPencil, - mdiPencilOff, - mdiPlayCircleOutline, - mdiPlus, -} from "@mdi/js"; +import { mdiHelpCircle, mdiInformationOutline, mdiPlus } from "@mdi/js"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; -import { formatDateTime } from "../../../common/datetime/format_date_time"; -import { fireEvent } from "../../../common/dom/fire_event"; +import { formatShortDateTime } from "../../../common/datetime/format_date_time"; +import { relativeTime } from "../../../common/datetime/relative_time"; +import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { navigate } from "../../../common/navigate"; -import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table"; -import "../../../components/entity/ha-entity-toggle"; +import type { + DataTableColumnContainer, + RowClickedEvent, +} from "../../../components/data-table/ha-data-table"; import "../../../components/ha-button-related-filter-menu"; import "../../../components/ha-fab"; import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; -import "../../../components/ha-icon-overflow-menu"; -import { - AutomationEntity, - triggerAutomationActions, -} from "../../../data/automation"; -import { UNAVAILABLE_STATES } from "../../../data/entity"; +import type { AutomationEntity } from "../../../data/automation"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-tabs-subpage-data-table"; import { haStyle } from "../../../resources/styles"; @@ -35,6 +25,8 @@ import { documentationUrl } from "../../../util/documentation-url"; import { configSections } from "../ha-panel-config"; import { showNewAutomationDialog } from "./show-dialog-new-automation"; +const DAY_IN_MILLISECONDS = 86400000; + @customElement("ha-automation-picker") class HaAutomationPicker extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -78,20 +70,6 @@ class HaAutomationPicker extends LitElement { private _columns = memoizeOne( (narrow: boolean, _locale): DataTableColumnContainer => { const columns: DataTableColumnContainer = { - toggle: { - title: "", - label: this.hass.localize( - "ui.panel.config.automation.picker.headers.toggle" - ), - type: "icon", - template: (_toggle, automation: any) => - html` - - `, - }, name: { title: this.hass.localize( "ui.panel.config.automation.picker.headers.name" @@ -101,19 +79,25 @@ class HaAutomationPicker extends LitElement { direction: "asc", grows: true, template: narrow - ? (name, automation: any) => - html` + ? (name, automation: any) => { + const date = new Date(automation.attributes.last_triggered); + const now = new Date(); + + const diff = now.getTime() - date.getTime(); + const dayDiff = diff / DAY_IN_MILLISECONDS; + + return html` ${name}
    ${this.hass.localize("ui.card.automation.last_triggered")}: ${automation.attributes.last_triggered - ? formatDateTime( - new Date(automation.attributes.last_triggered), - this.hass.locale - ) + ? dayDiff > 3 + ? formatShortDateTime(date, this.hass.locale) + : relativeTime(date, this.hass.locale) : this.hass.localize("ui.components.relative_time.never")}
    - ` + `; + } : undefined, }, }; @@ -122,31 +106,21 @@ class HaAutomationPicker extends LitElement { sortable: true, width: "20%", title: this.hass.localize("ui.card.automation.last_triggered"), - template: (last_triggered) => html` - ${last_triggered - ? formatDateTime(new Date(last_triggered), this.hass.locale) - : this.hass.localize("ui.components.relative_time.never")} - `, - }; - columns.trigger = { - label: this.hass.localize( - "ui.panel.config.automation.picker.headers.trigger" - ), - title: html` - - ${this.hass.localize("ui.card.automation.trigger")} - - `, - width: "20%", - template: (_info, automation: any) => html` - - ${this.hass.localize("ui.card.automation.trigger")} - - `, + template: (last_triggered) => { + const date = new Date(last_triggered); + const now = new Date(); + + const diff = now.getTime() - date.getTime(); + const dayDiff = diff / DAY_IN_MILLISECONDS; + + return html` + ${last_triggered + ? dayDiff > 3 + ? formatShortDateTime(date, this.hass.locale) + : relativeTime(date, this.hass.locale) + : this.hass.localize("ui.components.relative_time.never")} + `; + }, }; } columns.actions = { @@ -154,71 +128,16 @@ class HaAutomationPicker extends LitElement { label: this.hass.localize( "ui.panel.config.automation.picker.headers.actions" ), - type: "overflow-menu", + type: "icon-button", template: (_info, automation: any) => html` - this._showInfo(automation), - }, - // Trigger Button - { - path: mdiPlayCircleOutline, - label: this.hass.localize("ui.card.automation.trigger"), - narrowOnly: true, - action: () => this._runActions(automation), - }, - // Trace Button - { - path: mdiHistory, - disabled: !automation.attributes.id, - label: this.hass.localize( - "ui.panel.config.automation.picker.dev_automation" - ), - tooltip: !automation.attributes.id - ? this.hass.localize( - "ui.panel.config.automation.picker.dev_only_editable" - ) - : "", - action: () => { - if (automation.attributes.id) { - navigate( - `/config/automation/trace/${automation.attributes.id}` - ); - } - }, - }, - // Edit Button - { - path: automation.attributes.id ? mdiPencil : mdiPencilOff, - disabled: !automation.attributes.id, - label: this.hass.localize( - "ui.panel.config.automation.picker.edit_automation" - ), - tooltip: !automation.attributes.id - ? this.hass.localize( - "ui.panel.config.automation.picker.dev_only_editable" - ) - : "", - action: () => { - if (automation.attributes.id) { - navigate( - `/config/automation/edit/${automation.attributes.id}` - ); - } - }, - }, - ]} - style="color: var(--secondary-text-color)" - > - + `, }; return columns; @@ -237,11 +156,13 @@ class HaAutomationPicker extends LitElement { .activeFilters=${this._activeFilters} .columns=${this._columns(this.narrow, this.hass.locale)} .data=${this._automations(this.automations, this._filteredAutomations)} + @row-click=${this._handleRowClicked} .noDataText=${this.hass.localize( "ui.panel.config.automation.picker.no_automations" )} @clear-filter=${this._clearFilter} hasFab + clickable > { - this._runActions(ev.currentTarget.automation); - }; + private _handleRowClicked(ev: HASSDomEvent) { + const automation = this.automations.find( + (a) => a.entity_id === ev.detail.id + ); - private _runActions = (automation: AutomationEntity) => { - triggerAutomationActions(this.hass, automation.entity_id); - }; + if (automation?.attributes.id) { + navigate(`/config/automation/edit/${automation?.attributes.id}`); + } + } private _createNew() { if (isComponentLoaded(this.hass, "blueprint")) { From f5b44656cf94bba165c58771fc9d8dc9306e55a1 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 23 Aug 2022 09:12:55 -0500 Subject: [PATCH 067/134] Update a few Condition Descriptions (#13404) * Update a few Describe-conditions * Update to multiple states --- .../pages/automation/describe-condition.ts | 50 +++++- src/data/automation_i18n.ts | 169 +++++++++++++++++- src/data/script_i18n.ts | 12 +- .../condition/ha-automation-condition-row.ts | 16 +- 4 files changed, 225 insertions(+), 22 deletions(-) diff --git a/gallery/src/pages/automation/describe-condition.ts b/gallery/src/pages/automation/describe-condition.ts index e77a7a2af6..2f6cef5f47 100644 --- a/gallery/src/pages/automation/describe-condition.ts +++ b/gallery/src/pages/automation/describe-condition.ts @@ -1,20 +1,41 @@ import { dump } from "js-yaml"; -import { html, css, LitElement, TemplateResult } from "lit"; -import { customElement, state } from "lit/decorators"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; import "../../../../src/components/ha-card"; import "../../../../src/components/ha-yaml-editor"; import { Condition } from "../../../../src/data/automation"; import { describeCondition } from "../../../../src/data/automation_i18n"; +import { getEntity } from "../../../../src/fake_data/entity"; +import { provideHass } from "../../../../src/fake_data/provide_hass"; +import { HomeAssistant } from "../../../../src/types"; + +const ENTITIES = [ + getEntity("light", "kitchen", "on", { + friendly_name: "Kitchen Light", + }), + getEntity("device_tracker", "person", "home", { + friendly_name: "Person", + }), + getEntity("zone", "home", "", { + friendly_name: "Home", + }), +]; const conditions = [ { condition: "and" }, { condition: "not" }, { condition: "or" }, - { condition: "state" }, - { condition: "numeric_state" }, + { condition: "state", entity_id: "light.kitchen", state: "on" }, + { + condition: "numeric_state", + entity_id: "light.kitchen", + attribute: "brightness", + below: 80, + above: 20, + }, { condition: "sun", after: "sunset" }, - { condition: "sun", after: "sunrise" }, - { condition: "zone" }, + { condition: "sun", after: "sunrise", offset: "-01:00" }, + { condition: "zone", entity_id: "device_tracker.person", zone: "zone.home" }, { condition: "time" }, { condition: "template" }, ]; @@ -27,15 +48,21 @@ const initialCondition: Condition = { @customElement("demo-automation-describe-condition") export class DemoAutomationDescribeCondition extends LitElement { + @property({ attribute: false }) hass!: HomeAssistant; + @state() _condition = initialCondition; protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + return html`
    ${this._condition - ? describeCondition(this._condition) + ? describeCondition(this._condition, this.hass) : ""} html`
    - ${describeCondition(conf as any)} + ${describeCondition(conf as any, this.hass)}
    ${dump(conf)}
    ` @@ -57,6 +84,13 @@ export class DemoAutomationDescribeCondition extends LitElement { `; } + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + const hass = provideHass(this); + hass.updateTranslations(null, "en"); + hass.addEntities(ENTITIES); + } + private _dataChanged(ev: CustomEvent): void { ev.stopPropagation(); this._condition = ev.detail.isValid ? ev.detail.value : undefined; diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index a22e803c49..5b72330818 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -1,3 +1,6 @@ +import secondsToDuration from "../common/datetime/seconds_to_duration"; +import { computeStateName } from "../common/entity/compute_state_name"; +import type { HomeAssistant } from "../types"; import { Condition, Trigger } from "./automation"; export const describeTrigger = (trigger: Trigger) => { @@ -7,12 +10,176 @@ export const describeTrigger = (trigger: Trigger) => { return `${trigger.platform || "Unknown"} trigger`; }; -export const describeCondition = (condition: Condition) => { +export const describeCondition = ( + condition: Condition, + hass: HomeAssistant +) => { if (condition.alias) { return condition.alias; } + if (["or", "and", "not"].includes(condition.condition)) { return `multiple conditions using "${condition.condition}"`; } + + // State Condition + if (condition.condition === "state" && condition.entity_id) { + let base = "Confirm"; + const stateObj = hass.states[condition.entity_id]; + const entity = stateObj ? computeStateName(stateObj) : condition.entity_id; + + if ("attribute" in condition) { + base += ` ${condition.attribute} from`; + } + + let states = ""; + + if (Array.isArray(condition.state)) { + for (const [index, state] of condition.state.entries()) { + states += `${index > 0 ? "," : ""} ${ + condition.state.length > 1 && index === condition.state.length - 1 + ? "or" + : "" + } ${state}`; + } + } else { + states = condition.state.toString(); + } + + base += ` ${entity} is ${states}`; + + if ("for" in condition) { + let duration: string; + if (typeof condition.for === "number") { + duration = `for ${secondsToDuration(condition.for)!}`; + } else if (typeof condition.for === "string") { + duration = `for ${condition.for}`; + } else { + duration = `for ${JSON.stringify(condition.for)}`; + } + base += ` for ${duration}`; + } + + return base; + } + + // Numeric State Condition + if (condition.condition === "numeric_state" && condition.entity_id) { + let base = "Confirm"; + const stateObj = hass.states[condition.entity_id]; + const entity = stateObj ? computeStateName(stateObj) : condition.entity_id; + + if ("attribute" in condition) { + base += ` ${condition.attribute} from`; + } + + base += ` ${entity} is`; + + if ("above" in condition) { + base += ` above ${condition.above}`; + } + + if ("below" in condition && "above" in condition) { + base += " and"; + } + + if ("below" in condition) { + base += ` below ${condition.below}`; + } + + return base; + } + + // Sun condition + if ( + condition.condition === "sun" && + ("before" in condition || "after" in condition) + ) { + let base = "Confirm"; + + if (!condition.after && !condition.before) { + base += " sun"; + return base; + } + + base += " sun"; + + if (condition.after) { + let duration = ""; + + if (condition.after_offset) { + if (typeof condition.after_offset === "number") { + duration = ` offset by ${secondsToDuration(condition.after_offset)!}`; + } else if (typeof condition.after_offset === "string") { + duration = ` offset by ${condition.after_offset}`; + } else { + duration = ` offset by ${JSON.stringify(condition.after_offset)}`; + } + } + + base += ` after ${condition.after}${duration}`; + } + + if (condition.before) { + base += ` before ${condition.before}`; + } + + return base; + } + + // Zone condition + if (condition.condition === "zone" && condition.entity_id && condition.zone) { + let entities = ""; + let entitiesPlural = false; + let zones = ""; + let zonesPlural = false; + + const states = hass.states; + + if (Array.isArray(condition.entity_id)) { + if (condition.entity_id.length > 1) { + entitiesPlural = true; + } + for (const [index, entity] of condition.entity_id.entries()) { + if (states[entity]) { + entities += `${index > 0 ? "," : ""} ${ + condition.entity_id.length > 1 && + index === condition.entity_id.length - 1 + ? "or" + : "" + } ${computeStateName(states[entity]) || entity}`; + } + } + } else { + entities = states[condition.entity_id] + ? computeStateName(states[condition.entity_id]) + : condition.entity_id; + } + + if (Array.isArray(condition.zone)) { + if (condition.zone.length > 1) { + zonesPlural = true; + } + + for (const [index, zone] of condition.zone.entries()) { + if (states[zone]) { + zones += `${index > 0 ? "," : ""} ${ + condition.zone.length > 1 && index === condition.zone.length - 1 + ? "or" + : "" + } ${computeStateName(states[zone]) || zone}`; + } + } + } else { + zones = states[condition.zone] + ? computeStateName(states[condition.zone]) + : condition.zone; + } + + return `Confirm ${entities} ${entitiesPlural ? "are" : "is"} in ${zones} ${ + zonesPlural ? "zones" : "zone" + }`; + } + return `${condition.condition} condition`; }; diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index 6e3fcdc36b..899a5242f9 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -163,7 +163,7 @@ export const describeAction = ( } if (actionType === "check_condition") { - return `Test ${describeCondition(action as Condition)}`; + return `Test ${describeCondition(action as Condition, hass)}`; } if (actionType === "stop") { @@ -177,7 +177,7 @@ export const describeAction = ( typeof config.if === "string" ? config.if : ensureArray(config.if) - .map((condition) => describeCondition(condition)) + .map((condition) => describeCondition(condition, hass)) .join(", ") } then ${ensureArray(config.then).map((thenAction) => describeAction(hass, thenAction) @@ -200,7 +200,7 @@ export const describeAction = ( typeof chooseAction.conditions === "string" ? chooseAction.conditions : ensureArray(chooseAction.conditions) - .map((condition) => describeCondition(condition)) + .map((condition) => describeCondition(condition, hass)) .join(", ") } then ${ensureArray(chooseAction.sequence) .map((chooseSeq) => describeAction(hass, chooseSeq)) @@ -223,11 +223,11 @@ export const describeAction = ( )} ${"count" in config.repeat ? `${config.repeat.count} times` : ""}${ "while" in config.repeat ? `while ${ensureArray(config.repeat.while) - .map((condition) => describeCondition(condition)) + .map((condition) => describeCondition(condition, hass)) .join(", ")} is true` : "until" in config.repeat ? `until ${ensureArray(config.repeat.until) - .map((condition) => describeCondition(condition)) + .map((condition) => describeCondition(condition, hass)) .join(", ")} is true` : "for_each" in config.repeat ? `for every item: ${ensureArray(config.repeat.for_each) @@ -238,7 +238,7 @@ export const describeAction = ( } if (actionType === "check_condition") { - return `Test ${describeCondition(action as Condition)}`; + return `Test ${describeCondition(action as Condition, hass)}`; } if (actionType === "device_action") { diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index df38ecc5a5..cd9b7e6d4b 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -5,14 +5,17 @@ import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; import { handleStructError } from "../../../../common/structs/handle-errors"; -import "../../../../components/ha-button-menu"; -import "../../../../components/ha-card"; import "../../../../components/buttons/ha-progress-button"; import type { HaProgressButton } from "../../../../components/buttons/ha-progress-button"; -import "../../../../components/ha-icon-button"; +import "../../../../components/ha-button-menu"; +import "../../../../components/ha-card"; import "../../../../components/ha-expansion-panel"; +import "../../../../components/ha-icon-button"; import { Condition, testCondition } from "../../../../data/automation"; +import { describeCondition } from "../../../../data/automation_i18n"; +import { validateConfig } from "../../../../data/config"; import { showAlertDialog, showConfirmationDialog, @@ -20,9 +23,6 @@ import { import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import "./ha-automation-condition-editor"; -import { validateConfig } from "../../../../data/config"; -import { describeCondition } from "../../../../data/automation_i18n"; -import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; export interface ConditionElement extends LitElement { condition: Condition; @@ -81,7 +81,9 @@ export default class HaAutomationConditionRow extends LitElement { ${this.hass.localize( From 8d18fb79fb4514386a5cdfb885c41429fa6df5d2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 23 Aug 2022 16:13:39 +0200 Subject: [PATCH 068/134] Use icons in add trigger/condition/action menus (#13452) --- src/data/action.ts | 48 +++++++++++------ src/data/condition.ts | 40 +++++++++----- src/data/trigger.ts | 52 ++++++++++++------- .../action/ha-automation-action-row.ts | 2 +- .../automation/action/ha-automation-action.ts | 33 ++++++------ .../types/ha-automation-action-condition.ts | 29 ++++++----- .../condition/ha-automation-condition.ts | 29 ++++++----- .../trigger/ha-automation-trigger.ts | 29 ++++++----- 8 files changed, 162 insertions(+), 100 deletions(-) diff --git a/src/data/action.ts b/src/data/action.ts index 5398ca51c2..82c72f021a 100644 --- a/src/data/action.ts +++ b/src/data/action.ts @@ -1,16 +1,32 @@ -export const ACTION_TYPES = [ - "condition", - "delay", - "event", - "play_media", - "activate_scene", - "service", - "wait_template", - "wait_for_trigger", - "repeat", - "choose", - "if", - "device_id", - "stop", - "parallel", -]; +import { + mdiAbTesting, + mdiArrowDecision, + mdiCallSplit, + mdiChevronRight, + mdiCloseOctagon, + mdiDevices, + mdiExclamation, + mdiPalette, + mdiPlay, + mdiRefresh, + mdiShuffleDisabled, + mdiTimerOutline, + mdiTrafficLight, +} from "@mdi/js"; + +export const ACTION_TYPES = { + condition: mdiAbTesting, + delay: mdiTimerOutline, + event: mdiExclamation, + play_media: mdiPlay, + activate_scene: mdiPalette, + service: mdiChevronRight, + wait_template: mdiTrafficLight, + wait_for_trigger: mdiTrafficLight, + repeat: mdiRefresh, + choose: mdiArrowDecision, + if: mdiCallSplit, + device_id: mdiDevices, + stop: mdiCloseOctagon, + parallel: mdiShuffleDisabled, +}; diff --git a/src/data/condition.ts b/src/data/condition.ts index 2d064a61ff..9d0be5b040 100644 --- a/src/data/condition.ts +++ b/src/data/condition.ts @@ -1,15 +1,27 @@ -import type { Condition } from "./automation"; +import { + mdiAmpersand, + mdiCancel, + mdiClockOutline, + mdiCodeBraces, + mdiDevices, + mdiExclamation, + mdiGateOr, + mdiMapMarkerRadius, + mdiNumeric, + mdiStateMachine, + mdiWeatherSunny, +} from "@mdi/js"; -export const CONDITION_TYPES: Condition["condition"][] = [ - "device", - "and", - "or", - "not", - "state", - "numeric_state", - "sun", - "template", - "time", - "trigger", - "zone", -]; +export const CONDITION_TYPES = { + device: mdiDevices, + and: mdiAmpersand, + or: mdiGateOr, + not: mdiCancel, + state: mdiStateMachine, + numeric_state: mdiNumeric, + sun: mdiWeatherSunny, + template: mdiCodeBraces, + time: mdiClockOutline, + trigger: mdiExclamation, + zone: mdiMapMarkerRadius, +}; diff --git a/src/data/trigger.ts b/src/data/trigger.ts index 6f2d5e1271..c6b3e0a77c 100644 --- a/src/data/trigger.ts +++ b/src/data/trigger.ts @@ -1,19 +1,35 @@ -import type { Trigger } from "./automation"; +import { + mdiAvTimer, + mdiCalendar, + mdiClockOutline, + mdiCodeBraces, + mdiDevices, + mdiExclamation, + mdiHomeAssistant, + mdiMapMarker, + mdiMapMarkerRadius, + mdiNfcVariant, + mdiNumeric, + mdiStateMachine, + mdiSwapHorizontal, + mdiWeatherSunny, + mdiWebhook, +} from "@mdi/js"; -export const TRIGGER_TYPES: Trigger["platform"][] = [ - "calendar", - "device", - "event", - "state", - "geo_location", - "homeassistant", - "mqtt", - "numeric_state", - "sun", - "tag", - "template", - "time", - "time_pattern", - "webhook", - "zone", -]; +export const TRIGGER_TYPES = { + calendar: mdiCalendar, + device: mdiDevices, + event: mdiExclamation, + state: mdiStateMachine, + geo_location: mdiMapMarker, + homeassistant: mdiHomeAssistant, + mqtt: mdiSwapHorizontal, + numeric_state: mdiNumeric, + sun: mdiWeatherSunny, + tag: mdiNfcVariant, + template: mdiCodeBraces, + time: mdiClockOutline, + time_pattern: mdiAvTimer, + webhook: mdiWebhook, + zone: mdiMapMarkerRadius, +}; 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 8c7daedb86..9f72db5417 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -51,7 +51,7 @@ const getType = (action: Action | undefined) => { if (["and", "or", "not"].some((key) => key in action)) { return "condition"; } - return ACTION_TYPES.find((option) => option in action); + return Object.keys(ACTION_TYPES).find((option) => option in action); }; declare global { diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index c6c7fe3eec..b34d814cdf 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -73,8 +73,10 @@ export default class HaAutomationAction extends LitElement { ${this._processedTypes(this.hass.localize).map( - ([opt, label]) => html` - ${label} + ([opt, label, icon]) => html` + + ${label} ` )} @@ -104,9 +106,7 @@ export default class HaAutomationAction extends LitElement { } private _addAction(ev: CustomEvent) { - const action = (ev.currentTarget as HaSelect).items[ev.detail.index] - .value as typeof ACTION_TYPES[number]; - + const action = (ev.currentTarget as HaSelect).items[ev.detail.index].value; const elClass = customElements.get( `ha-automation-action-${action}` ) as CustomElementConstructor & { defaultConfig: Action }; @@ -158,16 +158,19 @@ export default class HaAutomationAction extends LitElement { } private _processedTypes = memoizeOne( - (localize: LocalizeFunc): [string, string][] => - ACTION_TYPES.map( - (action) => - [ - action, - localize( - `ui.panel.config.automation.editor.actions.type.${action}.label` - ), - ] as [string, string] - ).sort((a, b) => stringCompare(a[1], b[1])) + (localize: LocalizeFunc): [string, string, string][] => + Object.entries(ACTION_TYPES) + .map( + ([action, icon]) => + [ + action, + localize( + `ui.panel.config.automation.editor.actions.type.${action}.label` + ), + icon, + ] as [string, string, string] + ) + .sort((a, b) => stringCompare(a[1], b[1])) ); static get styles(): CSSResultGroup { diff --git a/src/panels/config/automation/action/types/ha-automation-action-condition.ts b/src/panels/config/automation/action/types/ha-automation-action-condition.ts index 23048b0983..da577f91f1 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-condition.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-condition.ts @@ -34,8 +34,10 @@ export class HaConditionAction extends LitElement implements ActionElement { @selected=${this._typeChanged} > ${this._processedTypes(this.hass.localize).map( - ([opt, label]) => html` - ${label} + ([opt, label, icon]) => html` + + ${label} ` )} @@ -48,16 +50,19 @@ export class HaConditionAction extends LitElement implements ActionElement { } private _processedTypes = memoizeOne( - (localize: LocalizeFunc): [string, string][] => - CONDITION_TYPES.map( - (condition) => - [ - condition, - localize( - `ui.panel.config.automation.editor.conditions.type.${condition}.label` - ), - ] as [string, string] - ).sort((a, b) => stringCompare(a[1], b[1])) + (localize: LocalizeFunc): [string, string, string][] => + Object.entries(CONDITION_TYPES) + .map( + ([condition, icon]) => + [ + condition, + localize( + `ui.panel.config.automation.editor.conditions.type.${condition}.label` + ), + icon, + ] as [string, string, string] + ) + .sort((a, b) => stringCompare(a[1], b[1])) ); private _conditionChanged(ev: CustomEvent) { diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index b8b62a7d14..52ce971bbc 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -103,8 +103,10 @@ export default class HaAutomationCondition extends LitElement { ${this._processedTypes(this.hass.localize).map( - ([opt, label]) => html` - ${label} + ([opt, label, icon]) => html` + + ${label} ` )} @@ -165,16 +167,19 @@ export default class HaAutomationCondition extends LitElement { } private _processedTypes = memoizeOne( - (localize: LocalizeFunc): [string, string][] => - CONDITION_TYPES.map( - (condition) => - [ - condition, - localize( - `ui.panel.config.automation.editor.conditions.type.${condition}.label` - ), - ] as [string, string] - ).sort((a, b) => stringCompare(a[1], b[1])) + (localize: LocalizeFunc): [string, string, string][] => + Object.entries(CONDITION_TYPES) + .map( + ([condition, icon]) => + [ + condition, + localize( + `ui.panel.config.automation.editor.conditions.type.${condition}.label` + ), + icon, + ] as [string, string, string] + ) + .sort((a, b) => stringCompare(a[1], b[1])) ); static get styles(): CSSResultGroup { diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index d961c96b10..7ba92d87c2 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -69,8 +69,10 @@ export default class HaAutomationTrigger extends LitElement { ${this._processedTypes(this.hass.localize).map( - ([opt, label]) => html` - ${label} + ([opt, label, icon]) => html` + + ${label} ` )} @@ -145,16 +147,19 @@ export default class HaAutomationTrigger extends LitElement { } private _processedTypes = memoizeOne( - (localize: LocalizeFunc): [string, string][] => - TRIGGER_TYPES.map( - (action) => - [ - action, - localize( - `ui.panel.config.automation.editor.triggers.type.${action}.label` - ), - ] as [string, string] - ).sort((a, b) => stringCompare(a[1], b[1])) + (localize: LocalizeFunc): [string, string, string][] => + Object.entries(TRIGGER_TYPES) + .map( + ([action, icon]) => + [ + action, + localize( + `ui.panel.config.automation.editor.triggers.type.${action}.label` + ), + icon, + ] as [string, string, string] + ) + .sort((a, b) => stringCompare(a[1], b[1])) ); static get styles(): CSSResultGroup { From 1616911ba96f41d0e3ba346071d38679f502cebb Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Tue, 23 Aug 2022 10:24:38 -0400 Subject: [PATCH 069/134] Display ZHA network settings and allow downloading backups (#13415) --- src/data/zha.ts | 80 +++++++++++++ .../zha/zha-config-dashboard.ts | 108 +++++++++++++++++- src/translations/en.json | 4 +- 3 files changed, 190 insertions(+), 2 deletions(-) diff --git a/src/data/zha.ts b/src/data/zha.ts index 247f1f1827..fb0e2b0f31 100644 --- a/src/data/zha.ts +++ b/src/data/zha.ts @@ -128,6 +128,54 @@ export interface ZHAConfiguration { schemas: Record; } +export interface ZHANetworkBackupNodeInfo { + nwk: string; + ieee: string; + logical_type: "coordinator" | "router" | "end_device"; +} + +export interface ZHANetworkBackupKey { + key: string; + tx_counter: number; + rx_counter: number; + seq: number; + partner_ieee: string; +} + +export interface ZHANetworkBackupNetworkInfo { + extended_pan_id: string; + pan_id: string; + nwk_update_id: number; + nwk_manager_id: string; + channel: number; + channel_mask: number[]; + security_level: number; + network_key: ZHANetworkBackupKey; + tc_link_key: ZHANetworkBackupKey; + key_table: ZHANetworkBackupKey[]; + children: string[]; + nwk_addresses: Record; + stack_specific?: Record; + metadata: Record; + source: string; +} + +export interface ZHANetworkBackup { + backup_time: string; + network_info: ZHANetworkBackupNetworkInfo; + node_info: ZHANetworkBackupNodeInfo; +} + +export interface ZHANetworkSettings { + settings: ZHANetworkBackup; + radio_type: "ezsp" | "znp" | "deconz" | "zigate" | "xbee"; +} + +export interface ZHANetworkBackupAndMetadata { + backup: ZHANetworkBackup; + is_complete: boolean; +} + export interface ZHAGroupMember { ieee: string; endpoint_id: string; @@ -349,6 +397,38 @@ export const updateZHAConfiguration = ( data: data, }); +export const fetchZHANetworkSettings = ( + hass: HomeAssistant +): Promise => + hass.callWS({ + type: "zha/network/settings", + }); + +export const createZHANetworkBackup = ( + hass: HomeAssistant +): Promise => + hass.callWS({ + type: "zha/network/backups/create", + }); + +export const restoreZHANetworkBackup = ( + hass: HomeAssistant, + backup: ZHANetworkBackup, + ezspForceWriteEUI64 = false +): Promise => + hass.callWS({ + type: "zha/network/backups/restore", + backup: backup, + ezsp_force_write_eui64: ezspForceWriteEUI64, + }); + +export const listZHANetworkBackups = ( + hass: HomeAssistant +): Promise => + hass.callWS({ + type: "zha/network/backups/list", + }); + export const INITIALIZED = "INITIALIZED"; export const INTERVIEW_COMPLETE = "INTERVIEW_COMPLETE"; export const CONFIGURED = "CONFIGURED"; diff --git a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts index 77213cbae1..e3f3125224 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts @@ -8,10 +8,11 @@ import { PropertyValues, TemplateResult, } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { computeRTL } from "../../../../../common/util/compute_rtl"; import "../../../../../components/ha-card"; import "../../../../../components/ha-fab"; +import { fileDownload } from "../../../../../util/file_download"; import "../../../../../components/ha-icon-next"; import "../../../../../layouts/hass-tabs-subpage"; import type { PageNavigation } from "../../../../../layouts/hass-tabs-subpage"; @@ -19,11 +20,17 @@ import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant, Route } from "../../../../../types"; import "../../../ha-config-section"; import "../../../../../components/ha-form/ha-form"; +import "../../../../../components/buttons/ha-progress-button"; import { fetchZHAConfiguration, updateZHAConfiguration, ZHAConfiguration, + fetchZHANetworkSettings, + createZHANetworkBackup, + ZHANetworkSettings, + ZHANetworkBackupAndMetadata, } from "../../../../../data/zha"; +import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box"; export const zhaTabs: PageNavigation[] = [ { @@ -57,11 +64,16 @@ class ZHAConfigDashboard extends LitElement { @property() private _configuration?: ZHAConfiguration; + @property() private _networkSettings?: ZHANetworkSettings; + + @state() private _generatingBackup = false; + protected firstUpdated(changedProperties: PropertyValues): void { super.firstUpdated(changedProperties); if (this.hass) { this.hass.loadBackendTranslation("config_panel", "zha", false); this._fetchConfiguration(); + this._fetchSettings(); } } @@ -102,6 +114,51 @@ class ZHAConfigDashboard extends LitElement {
    ` : ""}
    + ${this._networkSettings + ? html` +
    +
    + PAN ID: + ${this._networkSettings.settings.network_info.pan_id} +
    +
    + Extended PAN ID: + ${this._networkSettings.settings.network_info.extended_pan_id} +
    +
    + Channel: + ${this._networkSettings.settings.network_info.channel} +
    +
    + Coordinator IEEE: + ${this._networkSettings.settings.node_info.ieee} +
    +
    + Network key: + ${this._networkSettings.settings.network_info.network_key.key} +
    +
    + Radio type: + ${this._networkSettings.radio_type} +
    +
    +
    + + ${this.hass.localize( + "ui.panel.config.zha.configuration_page.download_backup" + )} + +
    +
    ` + : ""} ${this._configuration ? Object.entries(this._configuration.schemas).map( ([section, schema]) => html` { + this._networkSettings = await fetchZHANetworkSettings(this.hass!); + } + + private async _createAndDownloadBackup(): Promise { + let backup_and_metadata: ZHANetworkBackupAndMetadata; + + this._generatingBackup = true; + + try { + backup_and_metadata = await createZHANetworkBackup(this.hass!); + } catch (err: any) { + showAlertDialog(this, { + title: "Failed to create backup", + text: err.message, + warning: true, + }); + return; + } finally { + this._generatingBackup = false; + } + + if (!backup_and_metadata.is_complete) { + await showAlertDialog(this, { + title: "Backup is incomplete", + text: "A backup has been created but it is incomplete and cannot be restored. This is a coordinator firmware limitation.", + }); + } + + const backupJSON: string = + "data:text/plain;charset=utf-8," + + encodeURIComponent(JSON.stringify(backup_and_metadata.backup, null, 4)); + const backupTime: Date = new Date( + Date.parse(backup_and_metadata.backup.backup_time) + ); + let basename = `ZHA backup ${backupTime.toISOString().replace(/:/g, "-")}`; + + if (!backup_and_metadata.is_complete) { + basename = `Incomplete ${basename}`; + } + + fileDownload(backupJSON, `${basename}.json`); + } + private _dataChanged(ev) { this._configuration!.data[ev.currentTarget!.section] = ev.detail.value; } @@ -176,6 +277,11 @@ class ZHAConfigDashboard extends LitElement { margin-top: 16px; max-width: 500px; } + + .network-settings > div { + word-break: break-all; + margin-top: 2px; + } `, ]; } diff --git a/src/translations/en.json b/src/translations/en.json index 76ce515eb3..cc604a5cf0 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2998,7 +2998,9 @@ }, "configuration_page": { "shortcuts_title": "Shortcuts", - "update_button": "Update Configuration" + "update_button": "Update Configuration", + "download_backup": "Download Network Backup", + "network_settings_title": "Network Settings" }, "add_device_page": { "spinner": "Searching for Zigbee devices…", From dff3ffe9355482b1acea637959f3c76f727b17d4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 23 Aug 2022 17:21:16 +0200 Subject: [PATCH 070/134] Add support for renaming actions, conditions and triggers (#13444) --- src/data/automation_i18n.ts | 9 ++-- src/data/script_i18n.ts | 5 ++- src/dialogs/generic/dialog-box.ts | 9 ++++ src/dialogs/generic/show-dialog-box.ts | 1 + .../action/ha-automation-action-row.ts | 44 +++++++++++++++++-- .../condition/ha-automation-condition-row.ts | 42 ++++++++++++++++-- .../trigger/ha-automation-trigger-row.ts | 43 ++++++++++++++++-- src/translations/en.json | 9 ++++ 8 files changed, 147 insertions(+), 15 deletions(-) diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index 5b72330818..909061103f 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -3,8 +3,8 @@ import { computeStateName } from "../common/entity/compute_state_name"; import type { HomeAssistant } from "../types"; import { Condition, Trigger } from "./automation"; -export const describeTrigger = (trigger: Trigger) => { - if (trigger.alias) { +export const describeTrigger = (trigger: Trigger, ignoreAlias = false) => { + if (trigger.alias && !ignoreAlias) { return trigger.alias; } return `${trigger.platform || "Unknown"} trigger`; @@ -12,9 +12,10 @@ export const describeTrigger = (trigger: Trigger) => { export const describeCondition = ( condition: Condition, - hass: HomeAssistant + hass: HomeAssistant, + ignoreAlias = false ) => { - if (condition.alias) { + if (condition.alias && !ignoreAlias) { return condition.alias; } diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index 899a5242f9..bef0eaf2dc 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -26,9 +26,10 @@ import { export const describeAction = ( hass: HomeAssistant, action: ActionTypes[T], - actionType?: T + actionType?: T, + ignoreAlias = false ): string => { - if (action.alias) { + if (action.alias && !ignoreAlias) { return action.alias; } if (!actionType) { diff --git a/src/dialogs/generic/dialog-box.ts b/src/dialogs/generic/dialog-box.ts index 7aa5ebb5e6..27da086a0b 100644 --- a/src/dialogs/generic/dialog-box.ts +++ b/src/dialogs/generic/dialog-box.ts @@ -73,6 +73,7 @@ class DialogBox extends LitElement { void; } 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 9f72db5417..d37f820928 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -20,6 +20,7 @@ import { callExecuteScript } from "../../../../data/service"; import { showAlertDialog, showConfirmationDialog, + showPromptDialog, } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; @@ -200,6 +201,11 @@ export default class HaAutomationActionRow extends LitElement { "ui.panel.config.automation.editor.edit_yaml" )} + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.rename" + )} + ${this.hass.localize( "ui.panel.config.automation.editor.actions.duplicate" @@ -298,7 +304,7 @@ export default class HaAutomationActionRow extends LitElement { fireEvent(this, "move-action", { direction: "down" }); } - private _handleAction(ev: CustomEvent) { + private async _handleAction(ev: CustomEvent) { switch (ev.detail.index) { case 0: this._runAction(); @@ -308,12 +314,15 @@ export default class HaAutomationActionRow extends LitElement { this.expand(); break; case 2: - fireEvent(this, "duplicate"); + await this._renameAction(); break; case 3: - this._onDisable(); + fireEvent(this, "duplicate"); break; case 4: + this._onDisable(); + break; + case 5: this._onDelete(); break; } @@ -388,6 +397,35 @@ export default class HaAutomationActionRow extends LitElement { this._yamlMode = !this._yamlMode; } + private async _renameAction(): Promise { + const alias = await showPromptDialog(this, { + title: this.hass.localize( + "ui.panel.config.automation.editor.actions.change_alias" + ), + inputLabel: this.hass.localize( + "ui.panel.config.automation.editor.actions.alias" + ), + inputType: "string", + placeholder: capitalizeFirstLetter( + describeAction(this.hass, this.action, undefined, true) + ), + defaultValue: this.action.alias, + confirmText: this.hass.localize("ui.common.submit"), + }); + const value = { ...this.action }; + if (!alias) { + delete value.alias; + } else { + value.alias = alias; + } + fireEvent(this, "value-changed", { + value, + }); + if (this._yamlMode) { + this._yamlEditor?.setValue(value); + } + } + public expand() { this.updateComplete.then(() => { this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true; diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index cd9b7e6d4b..56570711df 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -19,6 +19,7 @@ import { validateConfig } from "../../../../data/config"; import { showAlertDialog, showConfirmationDialog, + showPromptDialog, } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; @@ -112,6 +113,11 @@ export default class HaAutomationConditionRow extends LitElement { "ui.panel.config.automation.editor.edit_yaml" )} + + ${this.hass.localize( + "ui.panel.config.automation.editor.conditions.rename" + )} + ${this.hass.localize( "ui.panel.config.automation.editor.actions.duplicate" @@ -187,19 +193,22 @@ export default class HaAutomationConditionRow extends LitElement { } } - private _handleAction(ev: CustomEvent) { + private async _handleAction(ev: CustomEvent) { switch (ev.detail.index) { case 0: this._switchYamlMode(); this.expand(); break; case 1: - fireEvent(this, "duplicate"); + await this._renameCondition(); break; case 2: - this._onDisable(); + fireEvent(this, "duplicate"); break; case 3: + this._onDisable(); + break; + case 4: this._onDelete(); break; } @@ -288,6 +297,33 @@ export default class HaAutomationConditionRow extends LitElement { } } + private async _renameCondition(): Promise { + const alias = await showPromptDialog(this, { + title: this.hass.localize( + "ui.panel.config.automation.editor.conditions.change_alias" + ), + inputLabel: this.hass.localize( + "ui.panel.config.automation.editor.conditions.alias" + ), + inputType: "string", + placeholder: capitalizeFirstLetter( + describeCondition(this.condition, this.hass, true) + ), + defaultValue: this.condition.alias, + confirmText: this.hass.localize("ui.common.submit"), + }); + + const value = { ...this.condition }; + if (!alias) { + delete value.alias; + } else { + value.alias = alias; + } + fireEvent(this, "value-changed", { + value, + }); + } + public expand() { this.updateComplete.then(() => { this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true; diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 24a3d30762..c4a619653f 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -21,6 +21,7 @@ import { validateConfig } from "../../../../data/config"; import { showAlertDialog, showConfirmationDialog, + showPromptDialog, } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; @@ -139,6 +140,11 @@ export default class HaAutomationTriggerRow extends LitElement { "ui.panel.config.automation.editor.edit_yaml" )} + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.rename" + )} + ${this.hass.localize( "ui.panel.config.automation.editor.actions.duplicate" @@ -325,7 +331,7 @@ export default class HaAutomationTriggerRow extends LitElement { } } - private _handleAction(ev: CustomEvent) { + private async _handleAction(ev: CustomEvent) { switch (ev.detail.index) { case 0: this._requestShowId = true; @@ -336,12 +342,15 @@ export default class HaAutomationTriggerRow extends LitElement { this.expand(); break; case 2: - fireEvent(this, "duplicate"); + await this._renameTrigger(); break; case 3: - this._onDisable(); + fireEvent(this, "duplicate"); break; case 4: + this._onDisable(); + break; + case 5: this._onDelete(); break; } @@ -412,6 +421,34 @@ export default class HaAutomationTriggerRow extends LitElement { }); } + private async _renameTrigger(): Promise { + const alias = await showPromptDialog(this, { + title: this.hass.localize( + "ui.panel.config.automation.editor.triggers.change_alias" + ), + inputLabel: this.hass.localize( + "ui.panel.config.automation.editor.triggers.alias" + ), + inputType: "string", + placeholder: capitalizeFirstLetter(describeTrigger(this.trigger, true)), + defaultValue: this.trigger.alias, + confirmText: this.hass.localize("ui.common.submit"), + }); + + const value = { ...this.trigger }; + if (!alias) { + delete value.alias; + } else { + value.alias = alias; + } + fireEvent(this, "value-changed", { + value, + }); + if (this._yamlMode) { + this._yamlEditor?.setValue(value); + } + } + public expand() { this.updateComplete.then(() => { this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true; diff --git a/src/translations/en.json b/src/translations/en.json index cc604a5cf0..222b1033f3 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1858,6 +1858,9 @@ "id": "Trigger ID", "edit_id": "Edit trigger ID", "duplicate": "Duplicate", + "rename": "Rename", + "change_alias": "Rename trigger", + "alias": "Trigger name", "delete": "[%key:ui::panel::mailbox::delete_button%]", "delete_confirm": "Are you sure you want to delete this?", "unsupported_platform": "No visual editor support for platform: {platform}", @@ -1973,6 +1976,9 @@ "invalid_condition": "Invalid condition configuration", "test_failed": "Error occurred while testing condition", "duplicate": "[%key:ui::panel::config::automation::editor::triggers::duplicate%]", + "rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]", + "change_alias": "Rename condition", + "alias": "Condition name", "delete": "[%key:ui::panel::mailbox::delete_button%]", "delete_confirm": "[%key:ui::panel::config::automation::editor::triggers::delete_confirm%]", "unsupported_condition": "No visual editor support for condition: {condition}", @@ -2061,6 +2067,9 @@ "run_action_error": "Error running action", "run_action_success": "Action run successfully", "duplicate": "[%key:ui::panel::config::automation::editor::triggers::duplicate%]", + "rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]", + "change_alias": "Rename action", + "alias": "Action name", "enable": "Enable", "disable": "Disable", "disabled": "Disabled", From 2475f6bd414c5ec3932d8efc23f1822976fc8a49 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 23 Aug 2022 11:45:08 -0400 Subject: [PATCH 071/134] Glue percent sign (#13458) --- gallery/src/pages/lovelace/entities-card.ts | 5 +++++ src/common/entity/compute_state_display.ts | 9 ++++++--- src/components/ha-gauge.ts | 4 +++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/gallery/src/pages/lovelace/entities-card.ts b/gallery/src/pages/lovelace/entities-card.ts index a4fc76a510..6cec33cba5 100644 --- a/gallery/src/pages/lovelace/entities-card.ts +++ b/gallery/src/pages/lovelace/entities-card.ts @@ -75,6 +75,10 @@ const ENTITIES = [ timestamp: 1641801600, friendly_name: "Date and Time", }), + getEntity("sensor", "humidity", "23.2", { + friendly_name: "Humidity", + unit_of_measurement: "%", + }), getEntity("input_select", "dropdown", "Soda", { friendly_name: "Dropdown", options: ["Soda", "Beer", "Wine"], @@ -142,6 +146,7 @@ const CONFIGS = [ - light.non_existing - climate.ecobee - input_number.number + - sensor.humidity `, }, { diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index bc7e1de564..ba759b197f 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -64,9 +64,12 @@ export const computeStateDisplayFromEntityAttributes = ( // fallback to default } } - return `${formatNumber(state, locale)}${ - attributes.unit_of_measurement ? " " + attributes.unit_of_measurement : "" - }`; + const unit = !attributes.unit_of_measurement + ? "" + : attributes.unit_of_measurement === "%" + ? "%" + : ` ${attributes.unit_of_measurement}`; + return `${formatNumber(state, locale)}${unit}`; } const domain = computeDomain(entityId); diff --git a/src/components/ha-gauge.ts b/src/components/ha-gauge.ts index 1d60da1b3c..e37d80a0b1 100644 --- a/src/components/ha-gauge.ts +++ b/src/components/ha-gauge.ts @@ -132,7 +132,9 @@ export class Gauge extends LitElement { this._segment_label ? this._segment_label : this.valueText || formatNumber(this.value, this.locale) - } ${this._segment_label ? "" : this.label} + }${ + this._segment_label ? "" : this.label === "%" ? "%" : ` ${this.label}` + } `; } From fc1481d365f194de7a8d0c098c83f54fa84e8da3 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 23 Aug 2022 21:13:22 +0200 Subject: [PATCH 072/134] Add icons to device action overflow menu (#13455) * Add icons to device action overflow menu * Update size of meta icon * Tweak naming as suggest by Paulus * Missed on suggestion, also adjusted * Delete device -> Delete * View network * Rename key * Prettier Co-authored-by: Zack --- .../mqtt/device-actions.ts | 2 + .../zha/device-actions.ts | 18 ++++++-- .../zwave_js/device-actions.ts | 14 ++++++ .../zwave_js/ha-device-info-zwave_js.ts | 2 +- .../config/devices/ha-config-device-page.ts | 46 +++++++++++++++++-- src/translations/en.json | 38 +++++++-------- 6 files changed, 94 insertions(+), 26 deletions(-) diff --git a/src/panels/config/devices/device-detail/integration-elements/mqtt/device-actions.ts b/src/panels/config/devices/device-detail/integration-elements/mqtt/device-actions.ts index ff3be5d1a9..faa99cf83d 100644 --- a/src/panels/config/devices/device-detail/integration-elements/mqtt/device-actions.ts +++ b/src/panels/config/devices/device-detail/integration-elements/mqtt/device-actions.ts @@ -1,3 +1,4 @@ +import { mdiInformation } from "@mdi/js"; import type { DeviceRegistryEntry } from "../../../../../../data/device_registry"; import type { DeviceAction } from "../../../ha-config-device-page"; import { showMQTTDeviceDebugInfoDialog } from "./show-dialog-mqtt-device-debug-info"; @@ -8,6 +9,7 @@ export const getMQTTDeviceActions = ( ): DeviceAction[] => [ { label: "MQTT Info", + icon: mdiInformation, action: async () => showMQTTDeviceDebugInfoDialog(el, { device }), }, ]; diff --git a/src/panels/config/devices/device-detail/integration-elements/zha/device-actions.ts b/src/panels/config/devices/device-detail/integration-elements/zha/device-actions.ts index 5685b053d4..e914e6aead 100644 --- a/src/panels/config/devices/device-detail/integration-elements/zha/device-actions.ts +++ b/src/panels/config/devices/device-detail/integration-elements/zha/device-actions.ts @@ -1,3 +1,11 @@ +import { + mdiCogRefresh, + mdiDrawPen, + mdiFamilyTree, + mdiFileTree, + mdiGroup, + mdiPlus, +} from "@mdi/js"; import { navigate } from "../../../../../../common/navigate"; import type { DeviceRegistryEntry } from "../../../../../../data/device_registry"; import { fetchZHADevice } from "../../../../../../data/zha"; @@ -33,6 +41,7 @@ export const getZHADeviceActions = async ( if (!zhaDevice.active_coordinator) { actions.push({ label: hass.localize("ui.dialogs.zha_device_info.buttons.reconfigure"), + icon: mdiCogRefresh, action: () => showZHAReconfigureDeviceDialog(el, { device: zhaDevice }), }); } @@ -46,12 +55,14 @@ export const getZHADeviceActions = async ( ...[ { label: hass.localize("ui.dialogs.zha_device_info.buttons.add"), + icon: mdiPlus, action: () => navigate(`/config/zha/add/${zhaDevice!.ieee}`), }, { label: hass.localize( "ui.dialogs.zha_device_info.buttons.device_children" ), + icon: mdiFileTree, action: () => showZHADeviceChildrenDialog(el, { device: zhaDevice! }), }, ] @@ -64,16 +75,17 @@ export const getZHADeviceActions = async ( label: hass.localize( "ui.dialogs.zha_device_info.buttons.zigbee_information" ), + icon: mdiDrawPen, action: () => showZHADeviceZigbeeInfoDialog(el, { device: zhaDevice }), }, { label: hass.localize("ui.dialogs.zha_device_info.buttons.clusters"), + icon: mdiGroup, action: () => showZHAClusterDialog(el, { device: zhaDevice }), }, { - label: hass.localize( - "ui.dialogs.zha_device_info.buttons.view_in_visualization" - ), + label: hass.localize("ui.dialogs.zha_device_info.buttons.view_network"), + icon: mdiFamilyTree, action: () => navigate(`/config/zha/visualization/${zhaDevice!.device_reg_id}`), }, diff --git a/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts b/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts index 5201a4ae89..5687081ecc 100644 --- a/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts +++ b/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts @@ -1,3 +1,11 @@ +import { + mdiChatQuestion, + mdiCog, + mdiDeleteForever, + mdiHospitalBox, + mdiInformation, + mdiUpload, +} from "@mdi/js"; import { getConfigEntries } from "../../../../../../data/config_entries"; import { DeviceRegistryEntry } from "../../../../../../data/device_registry"; import { @@ -44,12 +52,14 @@ export const getZwaveDeviceActions = async ( label: hass.localize( "ui.panel.config.zwave_js.device_info.device_config" ), + icon: mdiCog, href: `/config/zwave_js/node_config/${device.id}?config_entry=${entryId}`, }, { label: hass.localize( "ui.panel.config.zwave_js.device_info.reinterview_device" ), + icon: mdiChatQuestion, action: () => showZWaveJSReinterviewNodeDialog(el, { device_id: device.id, @@ -57,6 +67,7 @@ export const getZwaveDeviceActions = async ( }, { label: hass.localize("ui.panel.config.zwave_js.device_info.heal_node"), + icon: mdiHospitalBox, action: () => showZWaveJSHealNodeDialog(el, { device, @@ -66,6 +77,7 @@ export const getZwaveDeviceActions = async ( label: hass.localize( "ui.panel.config.zwave_js.device_info.remove_failed" ), + icon: mdiDeleteForever, action: () => showZWaveJSRemoveFailedNodeDialog(el, { device_id: device.id, @@ -75,6 +87,7 @@ export const getZwaveDeviceActions = async ( label: hass.localize( "ui.panel.config.zwave_js.device_info.node_statistics" ), + icon: mdiInformation, action: () => showZWaveJSNodeStatisticsDialog(el, { device, @@ -97,6 +110,7 @@ export const getZwaveDeviceActions = async ( label: hass.localize( "ui.panel.config.zwave_js.device_info.update_firmware" ), + icon: mdiUpload, action: async () => { if ( isNodeFirmwareUpdateInProgress || diff --git a/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-info-zwave_js.ts b/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-info-zwave_js.ts index 637b895d62..69cd2b88ec 100644 --- a/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-info-zwave_js.ts +++ b/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-info-zwave_js.ts @@ -102,7 +102,7 @@ export class HaDeviceInfoZWaveJS extends SubscribeMixin(LitElement) { ` : ""}
    - ${this.hass.localize("ui.panel.config.zwave_js.common.node_id")}: + ${this.hass.localize("ui.panel.config.zwave_js.device_info.node_id")}: ${this._node.node_id}
    ${!this._node.is_controller_node diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 3ccf58df9b..1838e6a411 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -1,5 +1,8 @@ import { + mdiCog, + mdiDelete, mdiDotsVertical, + mdiDownload, mdiOpenInNew, mdiPencil, mdiPlusCircle, @@ -79,6 +82,7 @@ export interface DeviceAction { href?: string; action?: (ev: any) => void; label: string; + icon?: string; trailingIcon?: string; classes?: string; } @@ -714,8 +718,20 @@ export class HaConfigDevicePage extends LitElement { class=${ifDefined(firstDeviceAction!.classes)} .action=${firstDeviceAction!.action} @click=${this._deviceActionClicked} + graphic="icon" > ${firstDeviceAction!.label} + ${firstDeviceAction!.icon + ? html` + + ` + : ""} ${firstDeviceAction!.trailingIcon ? html` ${deviceAction.label} + ${deviceAction.icon + ? html` + + ` + : ""} ${deviceAction.trailingIcon ? html` ` @@ -869,6 +901,7 @@ export class HaConfigDevicePage extends LitElement { links as { link: string; domain: string }[] ).map((link) => ({ href: link.link, + icon: mdiDownload, action: (ev) => this._signUrl(ev), label: links.length > 1 @@ -914,6 +947,7 @@ export class HaConfigDevicePage extends LitElement { ); }, classes: "warning", + icon: mdiDelete, label: buttons.length > 1 ? this.hass.localize( @@ -950,10 +984,9 @@ export class HaConfigDevicePage extends LitElement { if (configurationUrl) { deviceActions.push({ href: configurationUrl, + icon: mdiCog, label: this.hass.localize( - `ui.panel.config.devices.open_configuration_url_${ - device.entry_type || "device" - }` + "ui.panel.config.devices.open_configuration_url" ), trailingIcon: mdiOpenInNew, }); @@ -1376,6 +1409,13 @@ export class HaConfigDevicePage extends LitElement { ha-svg-icon[slot="trailingIcon"] { display: block; + width: 18px; + height: 18px; + } + + ha-svg-icon[slot="meta"] { + width: 18px; + height: 18px; } .items { diff --git a/src/translations/en.json b/src/translations/en.json index 222b1033f3..5c4342b975 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1006,13 +1006,13 @@ "device_signature": "Zigbee device signature", "device_children": "Zigbee device children", "buttons": { - "add": "Add Devices via this device", - "remove": "Remove Device", - "clusters": "Manage Clusters", - "reconfigure": "Reconfigure Device", - "zigbee_information": "Zigbee device signature", - "device_children": "View Children", - "view_in_visualization": "View in Visualization" + "add": "Add devices via this device", + "remove": "Remove", + "clusters": "Manage clusters", + "reconfigure": "Reconfigure", + "zigbee_information": "Zigbee signature", + "device_children": "View children", + "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.", @@ -2558,11 +2558,10 @@ "config_entry": "Config entry" }, "enabled_description": "Disabled devices will not be shown and entities belonging to the device will be disabled and not added to Home Assistant.", - "open_configuration_url_device": "Visit device", - "open_configuration_url_service": "Visit service", + "open_configuration_url": "Visit", "download_diagnostics": "Download diagnostics", "download_diagnostics_integration": "Download {integration} diagnostics", - "delete_device": "Delete device", + "delete_device": "Delete", "delete_device_integration": "Remove {integration} from device", "type": { "device_heading": "Device", @@ -3164,18 +3163,19 @@ }, "device_info": { "zwave_info": "Z-Wave Info", - "node_status": "Device Status", - "node_ready": "Device Ready", - "device_config": "Configure Device", - "reinterview_device": "Re-interview Device", - "heal_node": "Heal Device", - "remove_failed": "Remove Failed Device", - "update_firmware": "Update Device Firmware", - "highest_security": "Highest Security", + "node_id": "ID", + "node_status": "Status", + "node_ready": "Ready", + "device_config": "Configure", + "reinterview_device": "Re-interview", + "heal_node": "Heal", + "remove_failed": "Remove failed", + "update_firmware": "Update", + "highest_security": "Highest security", "unknown": "Unknown", "zwave_plus": "Z-Wave Plus", "zwave_plus_version": "Version {version}", - "node_statistics": "Show Device Statistics" + "node_statistics": "Statistics" }, "node_statistics": { "title": "Device Statistics", From 8bcbeb299b43b1402a6ff6fac94ede9899cb643a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 23 Aug 2022 21:37:37 +0200 Subject: [PATCH 073/134] Add icons to integration action overflow menu (#13457) --- .../integrations/ha-config-flow-card.ts | 34 +++-- .../integrations/ha-integration-card.ts | 129 ++++++++++++------ 2 files changed, 114 insertions(+), 49 deletions(-) diff --git a/src/panels/config/integrations/ha-config-flow-card.ts b/src/panels/config/integrations/ha-config-flow-card.ts index e3d9fb0265..fdd7c37d88 100644 --- a/src/panels/config/integrations/ha-config-flow-card.ts +++ b/src/panels/config/integrations/ha-config-flow-card.ts @@ -1,7 +1,13 @@ import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import { mdiDotsVertical, mdiOpenInNew } from "@mdi/js"; +import { + mdiBookshelf, + mdiCog, + mdiDotsVertical, + mdiEyeOff, + mdiOpenInNew, +} from "@mdi/js"; import { fireEvent } from "../../../common/dom/fire_event"; import { ATTENTION_SOURCES, @@ -71,13 +77,12 @@ export class HaConfigFlowCard extends LitElement { ? "_self" : "_blank"} > - + ${this.hass.localize( "ui.panel.config.integrations.config_entry.open_configuration_url" - )} + )} + + ` : ""} @@ -92,23 +97,26 @@ export class HaConfigFlowCard extends LitElement { rel="noreferrer" target="_blank" > - + ${this.hass.localize( "ui.panel.config.integrations.config_entry.documentation" - )} + ` : ""} ${DISCOVERY_SOURCES.includes(this.flow.context.source) && this.flow.context.unique_id ? html` - + ${this.hass.localize( "ui.panel.config.integrations.ignore.ignore" )} + ` : ""} @@ -170,6 +178,10 @@ export class HaConfigFlowCard extends LitElement { text-decoration: none; color: var(--primary-color); } + ha-svg-icon[slot="meta"] { + width: 18px; + height: 18px; + } `; } diff --git a/src/panels/config/integrations/ha-integration-card.ts b/src/panels/config/integrations/ha-integration-card.ts index 348cdc3e6f..7da9ce51d7 100644 --- a/src/panels/config/integrations/ha-integration-card.ts +++ b/src/panels/config/integrations/ha-integration-card.ts @@ -3,9 +3,18 @@ import "@material/mwc-list/mwc-list-item"; import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item"; import { mdiAlertCircle, + mdiBookshelf, + mdiBug, mdiChevronLeft, + mdiCog, + mdiDelete, mdiDotsVertical, + mdiDownload, mdiOpenInNew, + mdiReload, + mdiRenameBox, + mdiToggleSwitch, + mdiToggleSwitchOff, } from "@mdi/js"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox"; @@ -320,16 +329,47 @@ export class HaIntegrationCard extends LitElement { .label=${this.hass.localize("ui.common.menu")} .path=${mdiDotsVertical} >
    - + ${!item.disabled_by && + RECOVERABLE_STATES.includes(item.state) && + item.supports_unload && + item.source !== "system" + ? html` + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.reload" + )} + + ` + : ""} + + ${this.hass.localize( "ui.panel.config.integrations.config_entry.rename" )} + - - ${this.hass.localize( - "ui.panel.config.integrations.config_entry.system_options" - )} - + ${this.supportsDiagnostics && item.state === "loaded" + ? html` + + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.download_diagnostics" + )} + + + ` + : ""} + +
  • + ${this.manifest ? html` - + ${this.hass.localize( "ui.panel.config.integrations.config_entry.documentation" - )} + ` : ""} @@ -358,59 +400,66 @@ export class HaIntegrationCard extends LitElement { rel="noreferrer" target="_blank" > - + ${this.hass.localize( "ui.panel.config.integrations.config_entry.known_issues" - )} - - ` - : ""} - ${!item.disabled_by && - RECOVERABLE_STATES.includes(item.state) && - item.supports_unload && - item.source !== "system" - ? html` - ${this.hass.localize( - "ui.panel.config.integrations.config_entry.reload" - )} - ` - : ""} - ${this.supportsDiagnostics && item.state === "loaded" - ? html` - - ${this.hass.localize( - "ui.panel.config.integrations.config_entry.download_diagnostics" )} + + ` : ""} + +
  • + + + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.system_options" + )} + + ${item.disabled_by === "user" - ? html` + ? html` ${this.hass.localize("ui.common.enable")} + ` : item.source !== "system" ? html` ${this.hass.localize("ui.common.disable")} + ` : ""} ${item.source !== "system" ? html` ${this.hass.localize( "ui.panel.config.integrations.config_entry.delete" )} + ` : ""}
    @@ -807,6 +856,10 @@ export class HaIntegrationCard extends LitElement { mwc-list-item ha-svg-icon { color: var(--secondary-text-color); } + ha-svg-icon[slot="meta"] { + width: 18px; + height: 18px; + } `, ]; } From ed82ae9f68e6639bde03d2ba6d9ff788cac23eee Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Aug 2022 00:26:15 +0200 Subject: [PATCH 074/134] Add config entry selector (#13432) * Add config entry selector * Let backend filter by integration * Add to gallery * Adjust translation * Fix imports * Rename in gallery as well * Fix typo in localize key * Prettier --- gallery/src/pages/components/ha-form.ts | 3 + gallery/src/pages/components/ha-selector.ts | 6 + src/components/ha-config-entry-picker.ts | 156 ++++++++++++++++++ .../ha-selector/ha-selector-config-entry.ts | 47 ++++++ src/components/ha-selector/ha-selector.ts | 1 + src/data/selector.ts | 7 + src/translations/en.json | 3 + 7 files changed, 223 insertions(+) create mode 100644 src/components/ha-config-entry-picker.ts create mode 100644 src/components/ha-selector/ha-selector-config-entry.ts diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts index 8117ac826b..5ddf38b765 100644 --- a/gallery/src/pages/components/ha-form.ts +++ b/gallery/src/pages/components/ha-form.ts @@ -3,6 +3,7 @@ import "@material/mwc-button"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, state } from "lit/decorators"; import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry"; +import { mockConfigEntries } from "../../../../demo/src/stubs/config_entries"; import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry"; import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry"; import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor"; @@ -153,6 +154,7 @@ const SCHEMAS: { context: { filter_entity: "entity", filter_attribute: "Attribute" }, }, { name: "Device", selector: { device: {} } }, + { name: "Config entry", selector: { config_entry: {} } }, { name: "Duration", selector: { duration: {} } }, { name: "area", selector: { area: {} } }, { name: "target", selector: { target: {} } }, @@ -434,6 +436,7 @@ class DemoHaForm extends LitElement { hass.addEntities(ENTITIES); mockEntityRegistry(hass); mockDeviceRegistry(hass, DEVICES); + mockConfigEntries(hass); mockAreaRegistry(hass, AREAS); mockHassioSupervisor(hass); } diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index 6549c8e30e..dcc099b636 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -3,6 +3,7 @@ import "@material/mwc-button"; import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, state } from "lit/decorators"; import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry"; +import { mockConfigEntries } from "../../../../demo/src/stubs/config_entries"; import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry"; import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry"; import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor"; @@ -124,6 +125,10 @@ const SCHEMAS: { selector: { attribute: { entity_id: "" } }, }, device: { name: "Device", selector: { device: {} } }, + config_entry: { + name: "Integration", + selector: { config_entry: {} }, + }, duration: { name: "Duration", selector: { duration: {} } }, addon: { name: "Addon", selector: { addon: {} } }, area: { name: "Area", selector: { area: {} } }, @@ -280,6 +285,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement { hass.addEntities(ENTITIES); mockEntityRegistry(hass); mockDeviceRegistry(hass, DEVICES); + mockConfigEntries(hass); mockAreaRegistry(hass, AREAS); mockHassioSupervisor(hass); hass.mockWS("auth/sign_path", (params) => params); diff --git a/src/components/ha-config-entry-picker.ts b/src/components/ha-config-entry-picker.ts new file mode 100644 index 0000000000..739491cd17 --- /dev/null +++ b/src/components/ha-config-entry-picker.ts @@ -0,0 +1,156 @@ +import "@material/mwc-list/mwc-list-item"; +import { html, LitElement, TemplateResult } from "lit"; +import { ComboBoxLitRenderer } from "lit-vaadin-helpers"; +import { customElement, property, query, state } from "lit/decorators"; +import { fireEvent } from "../common/dom/fire_event"; +import { PolymerChangedEvent } from "../polymer-types"; +import { HomeAssistant } from "../types"; +import type { HaComboBox } from "./ha-combo-box"; +import { ConfigEntry, getConfigEntries } from "../data/config_entries"; +import { domainToName } from "../data/integration"; +import { caseInsensitiveStringCompare } from "../common/string/compare"; +import { brandsUrl } from "../util/brands-url"; +import "./ha-combo-box"; + +export interface ConfigEntryExtended extends ConfigEntry { + localized_domain_name?: string; +} + +@customElement("ha-config-entry-picker") +class HaConfigEntryPicker extends LitElement { + public hass!: HomeAssistant; + + @property() public integration?: string; + + @property() public label?: string; + + @property() public value = ""; + + @property() public helper?: string; + + @state() private _configEntries?: ConfigEntryExtended[]; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean }) public required = false; + + @query("ha-combo-box") private _comboBox!: HaComboBox; + + public open() { + this._comboBox?.open(); + } + + public focus() { + this._comboBox?.focus(); + } + + protected firstUpdated() { + this._getConfigEntries(); + } + + private _rowRenderer: ComboBoxLitRenderer = ( + item + ) => html` + ${item.title || + this.hass.localize( + "ui.panel.config.integrations.config_entry.unnamed_entry" + )} + ${item.localized_domain_name} + + `; + + protected render(): TemplateResult { + if (!this._configEntries) { + return html``; + } + return html` + + `; + } + + private _onImageLoad(ev) { + ev.target.style.visibility = "initial"; + } + + private _onImageError(ev) { + ev.target.style.visibility = "hidden"; + } + + private async _getConfigEntries() { + getConfigEntries(this.hass, { + type: "integration", + domain: this.integration, + }).then((configEntries) => { + this._configEntries = configEntries + .map( + (entry: ConfigEntry): ConfigEntryExtended => ({ + ...entry, + localized_domain_name: domainToName( + this.hass.localize, + entry.domain + ), + }) + ) + .sort((conf1, conf2) => + caseInsensitiveStringCompare( + conf1.localized_domain_name + conf1.title, + conf2.localized_domain_name + conf2.title + ) + ); + }); + } + + private get _value() { + return this.value || ""; + } + + private _valueChanged(ev: PolymerChangedEvent) { + ev.stopPropagation(); + const newValue = ev.detail.value; + + if (newValue !== this._value) { + this._setValue(newValue); + } + } + + private _setValue(value: string) { + this.value = value; + setTimeout(() => { + fireEvent(this, "value-changed", { value }); + fireEvent(this, "change"); + }, 0); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-entry-picker": HaConfigEntryPicker; + } +} diff --git a/src/components/ha-selector/ha-selector-config-entry.ts b/src/components/ha-selector/ha-selector-config-entry.ts new file mode 100644 index 0000000000..85f6823276 --- /dev/null +++ b/src/components/ha-selector/ha-selector-config-entry.ts @@ -0,0 +1,47 @@ +import { css, html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { ConfigEntrySelector } from "../../data/selector"; +import { HomeAssistant } from "../../types"; +import "../ha-config-entry-picker"; + +@customElement("ha-selector-config_entry") +export class HaConfigEntrySelector extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public selector!: ConfigEntrySelector; + + @property() public value?: any; + + @property() public label?: string; + + @property() public helper?: string; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean }) public required = true; + + protected render() { + return html``; + } + + static styles = css` + ha-config-entry-picker { + width: 100%; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-config_entry": HaConfigEntrySelector; + } +} diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index cc13845e01..40f22e1a13 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -9,6 +9,7 @@ import "./ha-selector-area"; import "./ha-selector-attribute"; import "./ha-selector-boolean"; import "./ha-selector-color-rgb"; +import "./ha-selector-config-entry"; import "./ha-selector-date"; import "./ha-selector-datetime"; import "./ha-selector-device"; diff --git a/src/data/selector.ts b/src/data/selector.ts index 10d0fb8bf1..1620f1c306 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -11,6 +11,7 @@ export type Selector = | BooleanSelector | ColorRGBSelector | ColorTempSelector + | ConfigEntrySelector | DateSelector | DateTimeSelector | DeviceSelector @@ -86,6 +87,12 @@ export interface ColorTempSelector { }; } +export interface ConfigEntrySelector { + config_entry: { + integration?: string; + }; +} + export interface DateSelector { // eslint-disable-next-line @typescript-eslint/ban-types date: {}; diff --git a/src/translations/en.json b/src/translations/en.json index 5c4342b975..631adac4fc 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -417,6 +417,9 @@ "add_device_id": "Choose device", "add_entity_id": "Choose entity" }, + "config-entry-picker": { + "config_entry": "Integration" + }, "theme-picker": { "theme": "Theme", "no_theme": "No theme" From be169f9c83222962b37f6e3d808d2e0feab80d39 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Aug 2022 00:26:38 +0200 Subject: [PATCH 075/134] Redesign trigger/condition/action overflow menus (#13453) * Redesign trigger/condition/action overflow menus * Reorder items, changed enable/disable icons, cleanup aria * Simplify menu item names --- .../action/ha-automation-action-row.ts | 109 ++++++++++++----- .../condition/ha-automation-condition-row.ts | 98 +++++++++++---- .../trigger/ha-automation-trigger-row.ts | 113 +++++++++++++----- src/translations/en.json | 4 +- 4 files changed, 242 insertions(+), 82 deletions(-) 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 d37f820928..06da9e19a7 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -1,6 +1,17 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; -import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js"; +import { + mdiArrowDown, + mdiArrowUp, + mdiCheck, + mdiContentDuplicate, + mdiDelete, + mdiDotsVertical, + mdiPlay, + mdiPlayCircleOutline, + mdiRenameBox, + mdiStopCircleOutline, +} from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -187,31 +198,56 @@ export default class HaAutomationActionRow extends LitElement { .label=${this.hass.localize("ui.common.menu")} .path=${mdiDotsVertical} > - + ${this.hass.localize( - "ui.panel.config.automation.editor.actions.run_action" + "ui.panel.config.automation.editor.actions.run" )} + - - ${yamlMode - ? this.hass.localize( - "ui.panel.config.automation.editor.edit_ui" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - - + + ${this.hass.localize( "ui.panel.config.automation.editor.actions.rename" )} + - + ${this.hass.localize( "ui.panel.config.automation.editor.actions.duplicate" )} + - + +
  • + + + ${this.hass.localize("ui.panel.config.automation.editor.edit_ui")} + ${!yamlMode + ? html`` + : ``} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} + ${yamlMode + ? html`` + : ``} + + +
  • + + ${this.action.enabled === false ? this.hass.localize( "ui.panel.config.automation.editor.actions.enable" @@ -219,11 +255,22 @@ export default class HaAutomationActionRow extends LitElement { : this.hass.localize( "ui.panel.config.automation.editor.actions.disable" )} + - + ${this.hass.localize( "ui.panel.config.automation.editor.actions.delete" )} +
    { @@ -461,9 +517,6 @@ export default class HaAutomationActionRow extends LitElement { mwc-list-item[disabled] { --mdc-theme-text-primary-on-background: var(--disabled-text-color); } - .warning { - margin-bottom: 8px; - } .warning ul { margin: 4px 0; } diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index 56570711df..c0a07724f7 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -1,6 +1,14 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; -import { mdiDotsVertical } from "@mdi/js"; +import { + mdiCheck, + mdiContentDuplicate, + mdiDelete, + mdiDotsVertical, + mdiPlayCircleOutline, + mdiRenameBox, + mdiStopCircleOutline, +} from "@mdi/js"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -104,26 +112,50 @@ export default class HaAutomationConditionRow extends LitElement { .path=${mdiDotsVertical} > - - ${this._yamlMode - ? this.hass.localize( - "ui.panel.config.automation.editor.edit_ui" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - - + + ${this.hass.localize( "ui.panel.config.automation.editor.conditions.rename" )} + - + ${this.hass.localize( "ui.panel.config.automation.editor.actions.duplicate" )} + - + +
  • + + + ${this.hass.localize("ui.panel.config.automation.editor.edit_ui")} + ${!this._yamlMode + ? html`` + : ``} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} + ${this._yamlMode + ? html`` + : ``} + + +
  • + + ${this.condition.enabled === false ? this.hass.localize( "ui.panel.config.automation.editor.actions.enable" @@ -131,11 +163,22 @@ export default class HaAutomationConditionRow extends LitElement { : this.hass.localize( "ui.panel.config.automation.editor.actions.disable" )} + - + ${this.hass.localize( "ui.panel.config.automation.editor.actions.delete" )} + @@ -196,19 +239,23 @@ export default class HaAutomationConditionRow extends LitElement { private async _handleAction(ev: CustomEvent) { switch (ev.detail.index) { case 0: + await this._renameCondition(); + break; + case 1: + fireEvent(this, "duplicate"); + break; + case 2: + this._switchUiMode(); + this.expand(); + break; + case 3: this._switchYamlMode(); this.expand(); break; - case 1: - await this._renameCondition(); - break; - case 2: - fireEvent(this, "duplicate"); - break; - case 3: + case 4: this._onDisable(); break; - case 4: + case 5: this._onDelete(); break; } @@ -233,9 +280,14 @@ export default class HaAutomationConditionRow extends LitElement { }); } + private _switchUiMode() { + this._warnings = undefined; + this._yamlMode = false; + } + private _switchYamlMode() { this._warnings = undefined; - this._yamlMode = !this._yamlMode; + this._yamlMode = true; } private async _testCondition(ev) { diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index c4a619653f..5dc2d0c1e1 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -1,6 +1,15 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; -import { mdiDotsVertical } from "@mdi/js"; +import { + mdiCheck, + mdiContentDuplicate, + mdiDelete, + mdiDotsVertical, + mdiIdentifier, + mdiPlayCircleOutline, + mdiRenameBox, + mdiStopCircleOutline, +} from "@mdi/js"; import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators"; @@ -126,31 +135,57 @@ export default class HaAutomationTriggerRow extends LitElement { .label=${this.hass.localize("ui.common.menu")} .path=${mdiDotsVertical} > - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.edit_id" - )} - - - ${yamlMode - ? this.hass.localize( - "ui.panel.config.automation.editor.edit_ui" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - - + + ${this.hass.localize( "ui.panel.config.automation.editor.triggers.rename" )} + - + ${this.hass.localize( "ui.panel.config.automation.editor.actions.duplicate" )} + - + + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.edit_id" + )} + + + +
  • + + + ${this.hass.localize("ui.panel.config.automation.editor.edit_ui")} + ${!yamlMode + ? html`` + : ``} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} + ${yamlMode + ? html`` + : ``} + + +
  • + + ${this.trigger.enabled === false ? this.hass.localize( "ui.panel.config.automation.editor.actions.enable" @@ -158,11 +193,22 @@ export default class HaAutomationTriggerRow extends LitElement { : this.hass.localize( "ui.panel.config.automation.editor.actions.disable" )} + - + ${this.hass.localize( "ui.panel.config.automation.editor.actions.delete" )} + @@ -334,23 +380,27 @@ export default class HaAutomationTriggerRow extends LitElement { private async _handleAction(ev: CustomEvent) { switch (ev.detail.index) { case 0: + await this._renameTrigger(); + break; + case 1: + fireEvent(this, "duplicate"); + break; + case 2: this._requestShowId = true; this.expand(); break; - case 1: + case 3: + this._switchUiMode(); + this.expand(); + break; + case 4: this._switchYamlMode(); this.expand(); break; - case 2: - await this._renameTrigger(); - break; - case 3: - fireEvent(this, "duplicate"); - break; - case 4: + case 5: this._onDisable(); break; - case 5: + case 6: this._onDelete(); break; } @@ -404,9 +454,14 @@ export default class HaAutomationTriggerRow extends LitElement { fireEvent(this, "value-changed", { value: ev.detail.value }); } + private _switchUiMode() { + this._warnings = undefined; + this._yamlMode = false; + } + private _switchYamlMode() { this._warnings = undefined; - this._yamlMode = !this._yamlMode; + this._yamlMode = true; } private _showTriggeredInfo() { diff --git a/src/translations/en.json b/src/translations/en.json index 631adac4fc..65559eb592 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1859,7 +1859,7 @@ "triggered": "Triggered", "add": "Add trigger", "id": "Trigger ID", - "edit_id": "Edit trigger ID", + "edit_id": "Edit ID", "duplicate": "Duplicate", "rename": "Rename", "change_alias": "Rename trigger", @@ -2066,7 +2066,7 @@ "learn_more": "Learn more about actions", "add": "Add action", "invalid_action": "Invalid action", - "run_action": "Run action", + "run": "Run", "run_action_error": "Error running action", "run_action_success": "Action run successfully", "duplicate": "[%key:ui::panel::config::automation::editor::triggers::duplicate%]", From a1bc748bc1d25259c248844ec1ee8fd19ffeae54 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Tue, 23 Aug 2022 23:45:44 -0400 Subject: [PATCH 076/134] Fix accessibility of settings and system navigation (#12845) --- src/components/ha-navigation-list.ts | 33 ++++++++++++------- .../core/ha-config-system-navigation.ts | 3 ++ .../config/dashboard/ha-config-navigation.ts | 17 +--------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/components/ha-navigation-list.ts b/src/components/ha-navigation-list.ts index f354e98001..048092379a 100644 --- a/src/components/ha-navigation-list.ts +++ b/src/components/ha-navigation-list.ts @@ -1,11 +1,12 @@ -import "@material/mwc-list/mwc-list"; -import "@material/mwc-list/mwc-list-item"; +import { ActionDetail } from "@material/mwc-list/mwc-list"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; +import { ifDefined } from "lit/directives/if-defined"; +import { navigate } from "../common/navigate"; import type { PageNavigation } from "../layouts/hass-tabs-subpage"; import type { HomeAssistant } from "../types"; -import "./ha-clickable-list-item"; import "./ha-icon-next"; +import "./ha-list-item"; import "./ha-svg-icon"; @customElement("ha-navigation-list") @@ -18,17 +19,22 @@ class HaNavigationList extends LitElement { @property({ type: Boolean }) public hasSecondary = false; + @property() public label?: string; + public render(): TemplateResult { return html` - + ${this.pages.map( (page) => html` -
    ` : ""} - + ` )} `; } - private _entryClicked(ev) { - ev.currentTarget.blur(); + private _handleListAction(ev: CustomEvent) { + const path = this.pages[ev.detail.index].path; + if (path.endsWith("#external-app-configuration")) { + this.hass.auth.external!.fireMessage({ type: "config_screen/show" }); + } else { + navigate(path); + } } static styles: CSSResultGroup = css` @@ -75,7 +86,7 @@ class HaNavigationList extends LitElement { .icon-background ha-svg-icon { color: #fff; } - ha-clickable-list-item { + ha-list-item { cursor: pointer; font-size: var(--navigation-list-item-title-font-size); padding: var(--navigation-list-item-padding) 0; diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index 210e9b3bf3..e69948c657 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -137,6 +137,9 @@ class HaConfigSystemNavigation extends LitElement { .narrow=${this.narrow} .pages=${pages} hasSecondary + .label=${this.hass.localize( + "ui.panel.config.dashboard.system.main" + )} > diff --git a/src/panels/config/dashboard/ha-config-navigation.ts b/src/panels/config/dashboard/ha-config-navigation.ts index 7a293b4225..39e098b5da 100644 --- a/src/panels/config/dashboard/ha-config-navigation.ts +++ b/src/panels/config/dashboard/ha-config-navigation.ts @@ -60,26 +60,11 @@ class HaConfigNavigation extends LitElement { .hass=${this.hass} .narrow=${this.narrow} .pages=${pages} - @click=${this._entryClicked} + .label=${this.hass.localize("panel.config")} > `; } - private _entryClicked(ev) { - const anchor = ev - .composedPath() - .find((n) => (n as HTMLElement).tagName === "A") as - | HTMLAnchorElement - | undefined; - - if (anchor?.href?.endsWith("#external-app-configuration")) { - ev.preventDefault(); - this.hass.auth.external!.fireMessage({ - type: "config_screen/show", - }); - } - } - static styles: CSSResultGroup = css` ha-navigation-list { --navigation-list-item-title-font-size: 16px; From 5ce81232b55efb621ee204db749809bd04cf49f4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Aug 2022 10:59:27 +0200 Subject: [PATCH 077/134] Fix automation/script conditions editor (#13465) --- .../types/ha-automation-condition-state.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts index ca5c923f85..10b7bda06c 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts @@ -66,7 +66,10 @@ export class HaStateCondition extends LitElement implements ConditionElement { }, { name: "state", - selector: { state: { entity_id: entityId, attribute: attribute } }, + required: true, + selector: { + state: { entity_id: entityId, attribute: attribute }, + }, }, { name: "for", selector: { duration: {} } }, ] as const @@ -105,15 +108,21 @@ export class HaStateCondition extends LitElement implements ConditionElement { private _valueChanged(ev: CustomEvent): void { ev.stopPropagation(); - const newTrigger = ev.detail.value; + const newCondition = ev.detail.value; - Object.keys(newTrigger).forEach((key) => - newTrigger[key] === undefined || newTrigger[key] === "" - ? delete newTrigger[key] + Object.keys(newCondition).forEach((key) => + newCondition[key] === undefined || newCondition[key] === "" + ? delete newCondition[key] : {} ); - fireEvent(this, "value-changed", { value: newTrigger }); + // We should not cleanup state in the above, as it is required. + // Set it to empty string if it is undefined. + if (!newCondition.state) { + newCondition.state = ""; + } + + fireEvent(this, "value-changed", { value: newCondition }); } private _computeLabelCallback = ( From 807bb10199f0b6b7a6ad968da57e1fb533829bae Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 24 Aug 2022 04:18:50 -0500 Subject: [PATCH 078/134] Add some Trigger Descriptions (#13460) Co-authored-by: Franck Nijhof Co-authored-by: Bram Kragten --- .../src/pages/automation/describe-trigger.ts | 70 ++++- src/data/automation_i18n.ts | 287 +++++++++++++++++- src/data/script_i18n.ts | 2 +- .../trigger/ha-automation-trigger-row.ts | 14 +- 4 files changed, 354 insertions(+), 19 deletions(-) diff --git a/gallery/src/pages/automation/describe-trigger.ts b/gallery/src/pages/automation/describe-trigger.ts index 41b589489b..081f6162a7 100644 --- a/gallery/src/pages/automation/describe-trigger.ts +++ b/gallery/src/pages/automation/describe-trigger.ts @@ -1,25 +1,56 @@ import { dump } from "js-yaml"; -import { html, css, LitElement, TemplateResult } from "lit"; -import { customElement, state } from "lit/decorators"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; import "../../../../src/components/ha-card"; import "../../../../src/components/ha-yaml-editor"; import { Trigger } from "../../../../src/data/automation"; import { describeTrigger } from "../../../../src/data/automation_i18n"; +import { getEntity } from "../../../../src/fake_data/entity"; +import { provideHass } from "../../../../src/fake_data/provide_hass"; +import { HomeAssistant } from "../../../../src/types"; + +const ENTITIES = [ + getEntity("light", "kitchen", "on", { + friendly_name: "Kitchen Light", + }), + getEntity("person", "person", "", { + friendly_name: "Person", + }), + getEntity("zone", "home", "", { + friendly_name: "Home", + }), +]; const triggers = [ - { platform: "state" }, + { platform: "state", entity_id: "light.kitchen", from: "off", to: "on" }, { platform: "mqtt" }, - { platform: "geo_location" }, - { platform: "homeassistant" }, - { platform: "numeric_state" }, - { platform: "sun" }, + { + platform: "geo_location", + source: "test_source", + zone: "zone.home", + event: "enter", + }, + { platform: "homeassistant", event: "start" }, + { + platform: "numeric_state", + entity_id: "light.kitchen", + attribute: "brightness", + below: 80, + above: 20, + }, + { platform: "sun", event: "sunset" }, { platform: "time_pattern" }, { platform: "webhook" }, - { platform: "zone" }, + { + platform: "zone", + entity_id: "person.person", + zone: "zone.home", + event: "enter", + }, { platform: "tag" }, - { platform: "time" }, + { platform: "time", at: "15:32" }, { platform: "template" }, - { platform: "event" }, + { platform: "event", event_type: "homeassistant_started" }, ]; const initialTrigger: Trigger = { @@ -29,14 +60,22 @@ const initialTrigger: Trigger = { @customElement("demo-automation-describe-trigger") export class DemoAutomationDescribeTrigger extends LitElement { + @property({ attribute: false }) hass!: HomeAssistant; + @state() _trigger = initialTrigger; protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + return html`
    - ${this._trigger ? describeTrigger(this._trigger) : ""} + ${this._trigger + ? describeTrigger(this._trigger, this.hass) + : ""} html`
    - ${describeTrigger(conf as any)} + ${describeTrigger(conf as any, this.hass)}
    ${dump(conf)}
    ` @@ -56,6 +95,13 @@ export class DemoAutomationDescribeTrigger extends LitElement { `; } + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + const hass = provideHass(this); + hass.updateTranslations(null, "en"); + hass.addEntities(ENTITIES); + } + private _dataChanged(ev: CustomEvent): void { ev.stopPropagation(); this._trigger = ev.detail.isValid ? ev.detail.value : undefined; diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index 909061103f..33bf8fa9d1 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -2,11 +2,296 @@ import secondsToDuration from "../common/datetime/seconds_to_duration"; import { computeStateName } from "../common/entity/compute_state_name"; import type { HomeAssistant } from "../types"; import { Condition, Trigger } from "./automation"; +import { formatAttributeName } from "./entity_attributes"; -export const describeTrigger = (trigger: Trigger, ignoreAlias = false) => { +export const describeTrigger = ( + trigger: Trigger, + hass: HomeAssistant, + ignoreAlias = false +) => { if (trigger.alias && !ignoreAlias) { return trigger.alias; } + + // Event Trigger + if (trigger.platform === "event" && trigger.event_type) { + let eventTypes = ""; + + if (Array.isArray(trigger.event_type)) { + for (const [index, state] of trigger.event_type.entries()) { + eventTypes += `${index > 0 ? "," : ""} ${ + trigger.event_type.length > 1 && + index === trigger.event_type.length - 1 + ? "or" + : "" + } ${state}`; + } + } else { + eventTypes = trigger.event_type.toString(); + } + + return `When ${eventTypes} event is fired`; + } + + // Home Assistant Trigger + if (trigger.platform === "homeassistant" && trigger.event) { + return `When Home Assistant is ${ + trigger.event === "start" ? "started" : "shutdown" + }`; + } + + // Numeric State Trigger + if (trigger.platform === "numeric_state" && trigger.entity_id) { + let base = "When"; + const stateObj = hass.states[trigger.entity_id]; + const entity = stateObj ? computeStateName(stateObj) : trigger.entity_id; + + if (trigger.attribute) { + base += ` ${formatAttributeName(trigger.attribute)} from`; + } + + base += ` ${entity} is`; + + if ("above" in trigger) { + base += ` above ${trigger.above}`; + } + + if ("below" in trigger && "above" in trigger) { + base += " and"; + } + + if ("below" in trigger) { + base += ` below ${trigger.below}`; + } + + return base; + } + + // State Trigger + if (trigger.platform === "state" && trigger.entity_id) { + let base = "When"; + let entities = ""; + + const states = hass.states; + + if (trigger.attribute) { + base += ` ${formatAttributeName(trigger.attribute)} from`; + } + + if (Array.isArray(trigger.entity_id)) { + for (const [index, entity] of trigger.entity_id.entries()) { + if (states[entity]) { + entities += `${index > 0 ? "," : ""} ${ + trigger.entity_id.length > 1 && + index === trigger.entity_id.length - 1 + ? "or" + : "" + } ${computeStateName(states[entity]) || entity}`; + } + } + } else { + entities = states[trigger.entity_id] + ? computeStateName(states[trigger.entity_id]) + : trigger.entity_id; + } + + base += ` ${entities} changes`; + + if (trigger.from) { + let from = ""; + if (Array.isArray(trigger.from)) { + for (const [index, state] of trigger.from.entries()) { + from += `${index > 0 ? "," : ""} ${ + trigger.from.length > 1 && index === trigger.from.length - 1 + ? "or" + : "" + } ${state}`; + } + } else { + from = trigger.from.toString(); + } + base += ` from ${from}`; + } + + if (trigger.to) { + let to = ""; + if (Array.isArray(trigger.to)) { + for (const [index, state] of trigger.to.entries()) { + to += `${index > 0 ? "," : ""} ${ + trigger.to.length > 1 && index === trigger.to.length - 1 ? "or" : "" + } ${state}`; + } + } else if (trigger.to) { + to = trigger.to.toString(); + } + + base += ` to ${to}`; + } + + if ("for" in trigger) { + let duration: string; + if (typeof trigger.for === "number") { + duration = `for ${secondsToDuration(trigger.for)!}`; + } else if (typeof trigger.for === "string") { + duration = `for ${trigger.for}`; + } else { + duration = `for ${JSON.stringify(trigger.for)}`; + } + + base += ` for ${duration}`; + } + + return base; + } + + // Sun Trigger + if (trigger.platform === "sun" && trigger.event) { + let base = `When the sun ${trigger.event === "sunset" ? "sets" : "rises"}`; + + if (trigger.offset) { + let duration = ""; + + if (trigger.offset) { + if (typeof trigger.offset === "number") { + duration = ` offset by ${secondsToDuration(trigger.offset)!}`; + } else if (typeof trigger.offset === "string") { + duration = ` offset by ${trigger.offset}`; + } else { + duration = ` offset by ${JSON.stringify(trigger.offset)}`; + } + } + base += duration; + } + + return base; + } + + // Tag Trigger + if (trigger.platform === "tag") { + return "When a tag is scanned"; + } + + // Time Trigger + if (trigger.platform === "time" && trigger.at) { + const at = trigger.at.includes(".") + ? hass.states[trigger.at] || trigger.at + : trigger.at; + + return `When the time is equal to ${at}`; + } + + // Time Patter Trigger + if (trigger.platform === "time_pattern") { + return "Time pattern trigger"; + } + + // Zone Trigger + if (trigger.platform === "zone" && trigger.entity_id && trigger.zone) { + let entities = ""; + let zones = ""; + let zonesPlural = false; + + const states = hass.states; + + if (Array.isArray(trigger.entity_id)) { + for (const [index, entity] of trigger.entity_id.entries()) { + if (states[entity]) { + entities += `${index > 0 ? "," : ""} ${ + trigger.entity_id.length > 1 && + index === trigger.entity_id.length - 1 + ? "or" + : "" + } ${computeStateName(states[entity]) || entity}`; + } + } + } else { + entities = states[trigger.entity_id] + ? computeStateName(states[trigger.entity_id]) + : trigger.entity_id; + } + + if (Array.isArray(trigger.zone)) { + if (trigger.zone.length > 1) { + zonesPlural = true; + } + + for (const [index, zone] of trigger.zone.entries()) { + if (states[zone]) { + zones += `${index > 0 ? "," : ""} ${ + trigger.zone.length > 1 && index === trigger.zone.length - 1 + ? "or" + : "" + } ${computeStateName(states[zone]) || zone}`; + } + } + } else { + zones = states[trigger.zone] + ? computeStateName(states[trigger.zone]) + : trigger.zone; + } + + return `When ${entities} ${trigger.event}s ${zones} ${ + zonesPlural ? "zones" : "zone" + }`; + } + + // Geo Location Trigger + if (trigger.platform === "geo_location" && trigger.source && trigger.zone) { + let sources = ""; + let zones = ""; + let zonesPlural = false; + const states = hass.states; + + if (Array.isArray(trigger.source)) { + for (const [index, source] of trigger.source.entries()) { + sources += `${index > 0 ? "," : ""} ${ + trigger.source.length > 1 && index === trigger.source.length - 1 + ? "or" + : "" + } ${source}`; + } + } else { + sources = trigger.source; + } + + if (Array.isArray(trigger.zone)) { + if (trigger.zone.length > 1) { + zonesPlural = true; + } + + for (const [index, zone] of trigger.zone.entries()) { + if (states[zone]) { + zones += `${index > 0 ? "," : ""} ${ + trigger.zone.length > 1 && index === trigger.zone.length - 1 + ? "or" + : "" + } ${computeStateName(states[zone]) || zone}`; + } + } + } else { + zones = states[trigger.zone] + ? computeStateName(states[trigger.zone]) + : trigger.zone; + } + + return `When ${sources} ${trigger.event}s ${zones} ${ + zonesPlural ? "zones" : "zone" + }`; + } + // MQTT Trigger + if (trigger.platform === "mqtt") { + return "When a MQTT payload has been received"; + } + + // Template Trigger + if (trigger.platform === "template") { + return "When a template triggers"; + } + + // Webhook Trigger + if (trigger.platform === "webhook") { + return "When a Webhook payload has been received"; + } return `${trigger.platform || "Unknown"} trigger`; }; diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index bef0eaf2dc..79993d9913 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -142,7 +142,7 @@ export const describeAction = ( if (actionType === "wait_for_trigger") { const config = action as WaitForTriggerAction; return `Wait for ${ensureArray(config.wait_for_trigger) - .map((trigger) => describeTrigger(trigger)) + .map((trigger) => describeTrigger(trigger, hass)) .join(", ")}`; } diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 5dc2d0c1e1..2113207c2f 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -16,6 +16,7 @@ import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; import { handleStructError } from "../../../../common/structs/handle-errors"; import { debounce } from "../../../../common/util/debounce"; import "../../../../components/ha-alert"; @@ -23,9 +24,10 @@ import "../../../../components/ha-button-menu"; import "../../../../components/ha-card"; import "../../../../components/ha-expansion-panel"; import "../../../../components/ha-icon-button"; -import { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import "../../../../components/ha-textfield"; +import { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import { subscribeTrigger, Trigger } from "../../../../data/automation"; +import { describeTrigger } from "../../../../data/automation_i18n"; import { validateConfig } from "../../../../data/config"; import { showAlertDialog, @@ -49,8 +51,6 @@ import "./types/ha-automation-trigger-time"; import "./types/ha-automation-trigger-time_pattern"; import "./types/ha-automation-trigger-webhook"; import "./types/ha-automation-trigger-zone"; -import { describeTrigger } from "../../../../data/automation_i18n"; -import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; export interface TriggerElement extends LitElement { trigger: Trigger; @@ -121,7 +121,9 @@ export default class HaAutomationTriggerRow extends LitElement { Date: Wed, 24 Aug 2022 05:25:29 -0400 Subject: [PATCH 079/134] Fix various issues in time/duration input (#13462) --- src/components/ha-base-time-input.ts | 39 +++++++++---------- src/components/ha-duration-input.ts | 8 ++-- .../ha-form-positive_time_period_dict.ts | 6 +-- .../ha-selector/ha-selector-datetime.ts | 4 +- .../ha-selector/ha-selector-duration.ts | 10 ++--- .../ha-selector/ha-selector-time.ts | 4 +- src/components/ha-time-input.ts | 2 +- .../controls/more-info-input_datetime.ts | 7 +--- .../types/ha-automation-action-delay.ts | 6 +-- .../ha-automation-action-wait_for_trigger.ts | 8 ++-- .../hui-input-datetime-entity-row.ts | 7 +--- 11 files changed, 46 insertions(+), 55 deletions(-) diff --git a/src/components/ha-base-time-input.ts b/src/components/ha-base-time-input.ts index f1efcf9df3..038dda86bb 100644 --- a/src/components/ha-base-time-input.ts +++ b/src/components/ha-base-time-input.ts @@ -1,10 +1,11 @@ import "@material/mwc-list/mwc-list-item"; import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; +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-textfield"; +import { HaTextField } from "./ha-textfield"; import "./ha-input-helper-text"; export interface TimeChangedEvent { @@ -36,7 +37,7 @@ export class HaBaseTimeInput extends LitElement { /** * determines if inputs are required */ - @property({ type: Boolean }) public required?: boolean; + @property({ type: Boolean }) public required = false; /** * 12 or 24 hr format @@ -123,11 +124,6 @@ export class HaBaseTimeInput extends LitElement { */ @property() amPm: "AM" | "PM" = "AM"; - /** - * Formatted time string - */ - @property() value?: string; - protected render(): TemplateResult { return html` ${this.label @@ -140,11 +136,11 @@ export class HaBaseTimeInput extends LitElement { id="day" type="number" inputmode="numeric" - .value=${this.days} + .value=${this.days.toFixed()} .label=${this.dayLabel} name="days" @input=${this._valueChanged} - @focus=${this._onFocus} + @focusin=${this._onFocus} no-spinner .required=${this.required} .autoValidate=${this.autoValidate} @@ -161,16 +157,16 @@ export class HaBaseTimeInput extends LitElement { id="hour" type="number" inputmode="numeric" - .value=${this.hours} + .value=${this.hours.toFixed()} .label=${this.hourLabel} name="hours" @input=${this._valueChanged} - @focus=${this._onFocus} + @focusin=${this._onFocus} no-spinner .required=${this.required} .autoValidate=${this.autoValidate} maxlength="2" - .max=${this._hourMax} + max=${ifDefined(this._hourMax)} min="0" .disabled=${this.disabled} suffix=":" @@ -184,7 +180,7 @@ export class HaBaseTimeInput extends LitElement { .value=${this._formatValue(this.minutes)} .label=${this.minLabel} @input=${this._valueChanged} - @focus=${this._onFocus} + @focusin=${this._onFocus} name="minutes" no-spinner .required=${this.required} @@ -205,7 +201,7 @@ export class HaBaseTimeInput extends LitElement { .value=${this._formatValue(this.seconds)} .label=${this.secLabel} @input=${this._valueChanged} - @focus=${this._onFocus} + @focusin=${this._onFocus} name="seconds" no-spinner .required=${this.required} @@ -226,7 +222,7 @@ export class HaBaseTimeInput extends LitElement { .value=${this._formatValue(this.milliseconds, 3)} .label=${this.millisecLabel} @input=${this._valueChanged} - @focus=${this._onFocus} + @focusin=${this._onFocus} name="milliseconds" no-spinner .required=${this.required} @@ -260,9 +256,10 @@ export class HaBaseTimeInput extends LitElement { `; } - private _valueChanged(ev) { - this[ev.target.name] = - ev.target.name === "amPm" ? ev.target.value : Number(ev.target.value); + private _valueChanged(ev: InputEvent) { + const textField = ev.currentTarget as HaTextField; + this[textField.name] = + textField.name === "amPm" ? textField.value : Number(textField.value); const value: TimeChangedEvent = { hours: this.hours, minutes: this.minutes, @@ -277,8 +274,8 @@ export class HaBaseTimeInput extends LitElement { }); } - private _onFocus(ev) { - ev.target.select(); + private _onFocus(ev: FocusEvent) { + (ev.currentTarget as HaTextField).select(); } /** @@ -293,7 +290,7 @@ export class HaBaseTimeInput extends LitElement { */ private get _hourMax() { if (this.noHoursLimit) { - return null; + return undefined; } if (this.format === 12) { return 12; diff --git a/src/components/ha-duration-input.ts b/src/components/ha-duration-input.ts index 408d1e85c7..7d35fe160f 100644 --- a/src/components/ha-duration-input.ts +++ b/src/components/ha-duration-input.ts @@ -14,17 +14,17 @@ export interface HaDurationData { @customElement("ha-duration-input") class HaDurationInput extends LitElement { - @property({ attribute: false }) public data!: HaDurationData; + @property({ attribute: false }) public data?: HaDurationData; @property() public label?: string; @property() public helper?: string; - @property({ type: Boolean }) public required?: boolean; + @property({ type: Boolean }) public required = false; - @property({ type: Boolean }) public enableMillisecond?: boolean; + @property({ type: Boolean }) public enableMillisecond = false; - @property({ type: Boolean }) public enableDay?: boolean; + @property({ type: Boolean }) public enableDay = false; @property({ type: Boolean }) public disabled = false; diff --git a/src/components/ha-form/ha-form-positive_time_period_dict.ts b/src/components/ha-form/ha-form-positive_time_period_dict.ts index a693ce2721..ea2d56b114 100644 --- a/src/components/ha-form/ha-form-positive_time_period_dict.ts +++ b/src/components/ha-form/ha-form-positive_time_period_dict.ts @@ -5,9 +5,9 @@ import { HaFormElement, HaFormTimeData, HaFormTimeSchema } from "./types"; @customElement("ha-form-positive_time_period_dict") export class HaFormTimePeriod extends LitElement implements HaFormElement { - @property() public schema!: HaFormTimeSchema; + @property({ attribute: false }) public schema!: HaFormTimeSchema; - @property() public data!: HaFormTimeData; + @property({ attribute: false }) public data!: HaFormTimeData; @property() public label!: string; @@ -25,7 +25,7 @@ export class HaFormTimePeriod extends LitElement implements HaFormElement { return html` diff --git a/src/components/ha-selector/ha-selector-datetime.ts b/src/components/ha-selector/ha-selector-datetime.ts index cdb15ccacc..327122b92a 100644 --- a/src/components/ha-selector/ha-selector-datetime.ts +++ b/src/components/ha-selector/ha-selector-datetime.ts @@ -11,9 +11,9 @@ import type { HaTimeInput } from "../ha-time-input"; @customElement("ha-selector-datetime") export class HaDateTimeSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: DateTimeSelector; + @property({ attribute: false }) public selector!: DateTimeSelector; @property() public value?: string; diff --git a/src/components/ha-selector/ha-selector-duration.ts b/src/components/ha-selector/ha-selector-duration.ts index 61bde6fa21..a2ad44a5d5 100644 --- a/src/components/ha-selector/ha-selector-duration.ts +++ b/src/components/ha-selector/ha-selector-duration.ts @@ -2,15 +2,15 @@ import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import type { DurationSelector } from "../../data/selector"; import type { HomeAssistant } from "../../types"; -import "../ha-duration-input"; +import { HaDurationData } from "../ha-duration-input"; @customElement("ha-selector-duration") export class HaTimeDuration extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: DurationSelector; + @property({ attribute: false }) public selector!: DurationSelector; - @property() public value?: string; + @property({ attribute: false }) public value?: HaDurationData; @property() public label?: string; @@ -28,7 +28,7 @@ export class HaTimeDuration extends LitElement { .data=${this.value} .disabled=${this.disabled} .required=${this.required} - .enableDay=${this.selector.duration.enable_day} + ?enableDay=${this.selector.duration.enable_day} > `; } diff --git a/src/components/ha-selector/ha-selector-time.ts b/src/components/ha-selector/ha-selector-time.ts index 80c753ba03..40982435a2 100644 --- a/src/components/ha-selector/ha-selector-time.ts +++ b/src/components/ha-selector/ha-selector-time.ts @@ -6,9 +6,9 @@ import "../ha-time-input"; @customElement("ha-selector-time") export class HaTimeSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: TimeSelector; + @property({ attribute: false }) public selector!: TimeSelector; @property() public value?: string; diff --git a/src/components/ha-time-input.ts b/src/components/ha-time-input.ts index eadbc744ce..cba4a0a95a 100644 --- a/src/components/ha-time-input.ts +++ b/src/components/ha-time-input.ts @@ -43,7 +43,7 @@ export class HaTimeInput extends LitElement { .minutes=${Number(parts[1])} .seconds=${Number(parts[2])} .format=${useAMPM ? 12 : 24} - .amPm=${useAMPM && (numberHours >= 12 ? "PM" : "AM")} + .amPm=${useAMPM && numberHours >= 12 ? "PM" : "AM"} .disabled=${this.disabled} @value-changed=${this._timeChanged} .enableSecond=${this.enableSecond} diff --git a/src/dialogs/more-info/controls/more-info-input_datetime.ts b/src/dialogs/more-info/controls/more-info-input_datetime.ts index 2bcb5f534e..fbe6917fd6 100644 --- a/src/dialogs/more-info/controls/more-info-input_datetime.ts +++ b/src/dialogs/more-info/controls/more-info-input_datetime.ts @@ -60,7 +60,7 @@ class MoreInfoInputDatetime extends LitElement { ev.stopPropagation(); } - private _timeChanged(ev): void { + private _timeChanged(ev: CustomEvent<{ value: string }>): void { setInputDateTimeValue( this.hass!, this.stateObj!.entity_id, @@ -69,10 +69,9 @@ class MoreInfoInputDatetime extends LitElement { ? this.stateObj!.state.split(" ")[0] : undefined ); - ev.target.blur(); } - private _dateChanged(ev): void { + private _dateChanged(ev: CustomEvent<{ value: string }>): void { setInputDateTimeValue( this.hass!, this.stateObj!.entity_id, @@ -81,8 +80,6 @@ class MoreInfoInputDatetime extends LitElement { : undefined, ev.detail.value ); - - ev.target.blur(); } static get styles(): CSSResultGroup { diff --git a/src/panels/config/automation/action/types/ha-automation-action-delay.ts b/src/panels/config/automation/action/types/ha-automation-action-delay.ts index 2d71065f8b..f88a098b4b 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-delay.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-delay.ts @@ -1,5 +1,5 @@ import { html, LitElement, PropertyValues } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { hasTemplate } from "../../../../../common/string/has-template"; import type { HaDurationData } from "../../../../../components/ha-duration-input"; @@ -13,9 +13,9 @@ import { createDurationData } from "../../../../../common/datetime/create_durati export class HaDelayAction extends LitElement implements ActionElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public action!: DelayAction; + @property({ attribute: false }) public action!: DelayAction; - @property() public _timeData?: HaDurationData; + @state() private _timeData?: HaDurationData; public static get defaultConfig() { return { delay: "" }; diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts index 8107017d54..f1162bf7aa 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts @@ -10,6 +10,7 @@ import { ActionElement, handleChangeEvent } from "../ha-automation-action-row"; import "../../../../../components/ha-duration-input"; import { createDurationData } from "../../../../../common/datetime/create_duration_data"; import { TimeChangedEvent } from "../../../../../components/ha-base-time-input"; +import { ensureArray } from "../../../../../common/ensure-array"; @customElement("ha-automation-action-wait_for_trigger") export class HaWaitForTriggerAction @@ -18,14 +19,13 @@ export class HaWaitForTriggerAction { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public action!: WaitForTriggerAction; + @property({ attribute: false }) public action!: WaitForTriggerAction; public static get defaultConfig() { return { wait_for_trigger: [] }; } protected render() { - const { wait_for_trigger, continue_on_timeout } = this.action; const timeData = createDurationData(this.action.timeout); return html` @@ -43,12 +43,12 @@ export class HaWaitForTriggerAction )} > ): void { const stateObj = this.hass!.states[this._config!.entity]; setInputDateTimeValue( this.hass!, @@ -105,10 +105,9 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow { ev.detail.value, stateObj.attributes.has_date ? stateObj.state.split(" ")[0] : undefined ); - ev.target.blur(); } - private _dateChanged(ev): void { + private _dateChanged(ev: CustomEvent<{ value: string }>): void { const stateObj = this.hass!.states[this._config!.entity]; setInputDateTimeValue( @@ -117,8 +116,6 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow { stateObj.attributes.has_time ? stateObj.state.split(" ")[1] : undefined, ev.detail.value ); - - ev.target.blur(); } static get styles(): CSSResultGroup { From 25e0c057232731872ac95fd44d192fd3b0245878 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 24 Aug 2022 11:53:51 +0200 Subject: [PATCH 080/134] Fix padding navigation list (#13466) --- src/components/ha-navigation-list.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ha-navigation-list.ts b/src/components/ha-navigation-list.ts index 048092379a..cb8c99d8d2 100644 --- a/src/components/ha-navigation-list.ts +++ b/src/components/ha-navigation-list.ts @@ -89,7 +89,6 @@ class HaNavigationList extends LitElement { ha-list-item { cursor: pointer; font-size: var(--navigation-list-item-title-font-size); - padding: var(--navigation-list-item-padding) 0; } `; } From ca91f71d2e4801195d5a54becb6827f73fcb06e7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Aug 2022 14:58:12 +0200 Subject: [PATCH 081/134] Remove default actions/conditions from parallel and if actions (#13467) --- .../automation/action/types/ha-automation-action-if.ts | 6 ++---- .../action/types/ha-automation-action-parallel.ts | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/panels/config/automation/action/types/ha-automation-action-if.ts b/src/panels/config/automation/action/types/ha-automation-action-if.ts index 6742adf13c..2a6ddd54e7 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-if.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-if.ts @@ -2,8 +2,6 @@ import { CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { Action, IfAction } from "../../../../../data/script"; -import { HaDeviceCondition } from "../../condition/types/ha-automation-condition-device"; -import { HaDeviceAction } from "./ha-automation-action-device_id"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant } from "../../../../../types"; import type { Condition } from "../../../../lovelace/common/validate-condition"; @@ -19,8 +17,8 @@ export class HaIfAction extends LitElement implements ActionElement { public static get defaultConfig() { return { - if: [{ ...HaDeviceCondition.defaultConfig, condition: "device" }], - then: [HaDeviceAction.defaultConfig], + if: [], + then: [], }; } diff --git a/src/panels/config/automation/action/types/ha-automation-action-parallel.ts b/src/panels/config/automation/action/types/ha-automation-action-parallel.ts index 0320bb2e26..37dbfb429e 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-parallel.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-parallel.ts @@ -2,7 +2,6 @@ import { CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { Action, ParallelAction } from "../../../../../data/script"; -import { HaDeviceAction } from "./ha-automation-action-device_id"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant } from "../../../../../types"; import "../ha-automation-action"; @@ -17,7 +16,7 @@ export class HaParallelAction extends LitElement implements ActionElement { public static get defaultConfig() { return { - parallel: [HaDeviceAction.defaultConfig], + parallel: [], }; } From 89c6fa7383040632e4449f64126f1c49598dd212 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Aug 2022 15:00:13 +0200 Subject: [PATCH 082/134] Conditionally add extra divider in integration card overflow menu (#13468) --- src/panels/config/integrations/ha-integration-card.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/panels/config/integrations/ha-integration-card.ts b/src/panels/config/integrations/ha-integration-card.ts index 7da9ce51d7..2023e21298 100644 --- a/src/panels/config/integrations/ha-integration-card.ts +++ b/src/panels/config/integrations/ha-integration-card.ts @@ -367,9 +367,12 @@ export class HaIntegrationCard extends LitElement { ` : ""} - -
  • - + ${this.manifest && + (this.manifest.is_built_in || + this.manifest.issue_tracker || + this.manifest.documentation) + ? html`
  • ` + : ""} ${this.manifest ? html` Date: Wed, 24 Aug 2022 15:00:33 +0200 Subject: [PATCH 083/134] Change integration enable/disable icons to match automations (#13472) --- src/panels/config/integrations/ha-integration-card.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/panels/config/integrations/ha-integration-card.ts b/src/panels/config/integrations/ha-integration-card.ts index 2023e21298..567246cf1a 100644 --- a/src/panels/config/integrations/ha-integration-card.ts +++ b/src/panels/config/integrations/ha-integration-card.ts @@ -11,10 +11,10 @@ import { mdiDotsVertical, mdiDownload, mdiOpenInNew, + mdiPlayCircleOutline, mdiReload, mdiRenameBox, - mdiToggleSwitch, - mdiToggleSwitchOff, + mdiStopCircleOutline, } from "@mdi/js"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox"; @@ -432,7 +432,7 @@ export class HaIntegrationCard extends LitElement { ${this.hass.localize("ui.common.enable")} ` : item.source !== "system" @@ -445,7 +445,7 @@ export class HaIntegrationCard extends LitElement { ` : ""} From c2542a3baa83ccfbd30acbf5516f6d004852c692 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Aug 2022 15:27:35 +0200 Subject: [PATCH 084/134] Use yarn to resolve lint-staged in pre-commit (#13470) --- .husky/pre-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index b088d5ae66..cf5c994491 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -lint-staged --relative --shell "/bin/bash" +yarn run lint-staged --relative --shell "/bin/bash" From d64ade38480c3bf4e10bff3bb53c6ea15104b0dc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 24 Aug 2022 09:58:29 -0400 Subject: [PATCH 085/134] Scroll to new added trigger/condition/row (#13473) --- .../config/automation/action/ha-automation-action.ts | 8 ++++++-- .../automation/condition/ha-automation-condition.ts | 8 ++++++-- .../config/automation/trigger/ha-automation-trigger.ts | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index b34d814cdf..9c65fa1fba 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -92,8 +92,11 @@ export default class HaAutomationAction extends LitElement { const row = this.shadowRoot!.querySelector( "ha-automation-action-row:last-of-type" )!; - row.expand(); - row.focus(); + row.updateComplete.then(() => { + row.expand(); + row.scrollIntoView(); + row.focus(); + }); } } @@ -178,6 +181,7 @@ export default class HaAutomationAction extends LitElement { ha-automation-action-row { display: block; margin-bottom: 16px; + scroll-margin-top: 48px; } ha-svg-icon { height: 20px; diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 52ce971bbc..67233bbfe2 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -69,8 +69,11 @@ export default class HaAutomationCondition extends LitElement { const row = this.shadowRoot!.querySelector( "ha-automation-condition-row:last-of-type" )!; - row.expand(); - row.focus(); + row.updateComplete.then(() => { + row.expand(); + row.scrollIntoView(); + row.focus(); + }); } } @@ -187,6 +190,7 @@ export default class HaAutomationCondition extends LitElement { ha-automation-condition-row { display: block; margin-bottom: 16px; + scroll-margin-top: 48px; } ha-svg-icon { height: 20px; diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index 7ba92d87c2..3a9b936f53 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -88,8 +88,11 @@ export default class HaAutomationTrigger extends LitElement { const row = this.shadowRoot!.querySelector( "ha-automation-trigger-row:last-of-type" )!; - row.expand(); - row.focus(); + row.updateComplete.then(() => { + row.expand(); + row.scrollIntoView(); + row.focus(); + }); } } @@ -167,6 +170,7 @@ export default class HaAutomationTrigger extends LitElement { ha-automation-trigger-row { display: block; margin-bottom: 16px; + scroll-margin-top: 48px; } ha-svg-icon { height: 20px; From 166d6f1c88a5dd5e752132b3c48256c51051ac17 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 24 Aug 2022 10:18:45 -0400 Subject: [PATCH 086/134] Show graph for zone in more info (#13475) --- src/data/history.ts | 1 + src/data/logbook.ts | 2 +- src/dialogs/more-info/ha-more-info-history.ts | 2 +- src/dialogs/more-info/ha-more-info-logbook.ts | 2 +- src/translations/en.json | 3 +++ 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/data/history.ts b/src/data/history.ts index dea32efec3..39d9ba4884 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -412,6 +412,7 @@ export const computeHistory = ( unit = stateWithUnitorStateClass.a.unit_of_measurement || " "; } else { unit = { + zone: localize("ui.dialogs.more_info_control.zone.graph_unit"), climate: hass.config.unit_system.temperature, counter: "#", humidifier: "%", diff --git a/src/data/logbook.ts b/src/data/logbook.ts index 1b1f9ce980..db67e2ce06 100644 --- a/src/data/logbook.ts +++ b/src/data/logbook.ts @@ -13,7 +13,7 @@ import { HomeAssistant } from "../types"; import { UNAVAILABLE_STATES } from "./entity"; const LOGBOOK_LOCALIZE_PATH = "ui.components.logbook.messages"; -export const CONTINUOUS_DOMAINS = ["counter", "proximity", "sensor"]; +export const CONTINUOUS_DOMAINS = ["counter", "proximity", "sensor", "zone"]; export interface LogbookStreamMessage { events: LogbookEntry[]; diff --git a/src/dialogs/more-info/ha-more-info-history.ts b/src/dialogs/more-info/ha-more-info-history.ts index 89e6f7ef43..6a316a0ae6 100644 --- a/src/dialogs/more-info/ha-more-info-history.ts +++ b/src/dialogs/more-info/ha-more-info-history.ts @@ -104,7 +104,7 @@ export class MoreInfoHistory extends LitElement { } private _close(): void { - setTimeout(() => fireEvent(this, "closed"), 500); + setTimeout(() => fireEvent(this, "close-dialog"), 500); } static get styles() { diff --git a/src/dialogs/more-info/ha-more-info-logbook.ts b/src/dialogs/more-info/ha-more-info-logbook.ts index 9a30d7929a..4777ad64a8 100644 --- a/src/dialogs/more-info/ha-more-info-logbook.ts +++ b/src/dialogs/more-info/ha-more-info-logbook.ts @@ -61,7 +61,7 @@ export class MoreInfoLogbook extends LitElement { } private _close(): void { - setTimeout(() => fireEvent(this, "closed"), 500); + setTimeout(() => fireEvent(this, "close-dialog"), 500); } static get styles() { diff --git a/src/translations/en.json b/src/translations/en.json index 65559eb592..cc38651af5 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -803,6 +803,9 @@ "open_tilt_cover": "Open cover tilt", "close_tilt_cover": "Close cover tilt", "stop_cover": "Stop cover" + }, + "zone": { + "graph_unit": "People home" } }, "entity_registry": { From 669f7efa972cd38e0f712ea6662291dede2c52c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20R=C3=BCchel?= Date: Thu, 25 Aug 2022 18:31:52 +0930 Subject: [PATCH 087/134] Fix scripts docs link (#13484) In home-assistant/home-assistant.io#22391 the editor docs were removed making the current link invalid. This points the docs link to the syntax docs one level up. --- src/panels/config/script/ha-script-picker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index 25a31fef6a..8131ca19e8 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -267,7 +267,7 @@ class HaScriptPicker extends LitElement { ${this.hass.localize("ui.panel.config.script.picker.introduction")}

    From 255cb23c7df57234fd53b1f806bc1b03e0a56519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 25 Aug 2022 11:26:56 +0200 Subject: [PATCH 088/134] Show homeassistant when creating partial backup (#13482) --- hassio/src/components/supervisor-backup-content.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hassio/src/components/supervisor-backup-content.ts b/hassio/src/components/supervisor-backup-content.ts index 03df51bbe2..549f3abe51 100644 --- a/hassio/src/components/supervisor-backup-content.ts +++ b/hassio/src/components/supervisor-backup-content.ts @@ -171,7 +171,7 @@ export class SupervisorBackupContent extends LitElement { : ""} ${this.backupType === "partial" ? html`

    ` : ""} - + +
    + + ${capitalizeFirstLetter(describeAction(this.hass, this.action))} +
    + ${this.index !== 0 ? html` ` : ""} - + +
    + + ${capitalizeFirstLetter( + describeCondition(this.condition, this.hass) + )} +
    + ${this.hass.localize( "ui.panel.config.automation.editor.conditions.test" @@ -398,6 +404,10 @@ export default class HaAutomationConditionRow extends LitElement { --expansion-panel-summary-padding: 0 0 0 8px; --expansion-panel-content-padding: 0; } + .condition-icon { + color: var(--sidebar-icon-color); + padding-right: 8px; + } .card-content { padding: 16px; } diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 2113207c2f..1af5b04e49 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -29,6 +29,7 @@ import { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import { subscribeTrigger, Trigger } from "../../../../data/automation"; import { describeTrigger } from "../../../../data/automation_i18n"; import { validateConfig } from "../../../../data/config"; +import { TRIGGER_TYPES } from "../../../../data/trigger"; import { showAlertDialog, showConfirmationDialog, @@ -119,12 +120,14 @@ export default class HaAutomationTriggerRow extends LitElement { ` : ""} - + +
    + + ${capitalizeFirstLetter(describeTrigger(this.trigger, this.hass))} +
    Date: Thu, 25 Aug 2022 07:09:31 -0400 Subject: [PATCH 090/134] Move restored entity warning up and use ha-alert (#13477) --- .../src/pages/components/ha-alert.markdown | 7 ++ src/components/ha-alert.ts | 1 - src/dialogs/more-info/ha-more-info-dialog.ts | 1 + src/dialogs/more-info/ha-more-info-info.ts | 71 +++++++++---------- src/translations/en.json | 6 +- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/gallery/src/pages/components/ha-alert.markdown b/gallery/src/pages/components/ha-alert.markdown index e37a205481..2f608d1b17 100644 --- a/gallery/src/pages/components/ha-alert.markdown +++ b/gallery/src/pages/components/ha-alert.markdown @@ -3,6 +3,13 @@ title: Alerts subtitle: An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task. --- + + # Alert `` The alert offers four severity levels that set a distinctive icon and color. diff --git a/src/components/ha-alert.ts b/src/components/ha-alert.ts index aa6848a48b..d49c9959ac 100644 --- a/src/components/ha-alert.ts +++ b/src/components/ha-alert.ts @@ -83,7 +83,6 @@ class HaAlert extends LitElement { position: relative; padding: 8px; display: flex; - margin: 4px 0; } .issue-type.rtl { flex-direction: row-reverse; diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index eb5f3d857d..6bbf6bee94 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -152,6 +152,7 @@ export class MoreInfoDialog extends LitElement { ` : ""}
    +
    ${cache( this._currTab === "info" diff --git a/src/dialogs/more-info/ha-more-info-info.ts b/src/dialogs/more-info/ha-more-info-info.ts index d66adaed41..df3fe1f901 100644 --- a/src/dialogs/more-info/ha-more-info-info.ts +++ b/src/dialogs/more-info/ha-more-info-info.ts @@ -1,9 +1,12 @@ import { LitElement, html, css } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { computeDomain } from "../../common/entity/compute_domain"; -import { removeEntityRegistryEntry } from "../../data/entity_registry"; +import { subscribeOne } from "../../common/util/subscribe-one"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../../data/entity_registry"; import type { HomeAssistant } from "../../types"; -import { showConfirmationDialog } from "../generic/show-dialog-box"; import { computeShowHistoryComponent, computeShowLogBookComponent, @@ -19,12 +22,24 @@ export class MoreInfoInfo extends LitElement { @property() public entityId!: string; + @state() private _entityEntry?: EntityRegistryEntry; + protected render() { const entityId = this.entityId; const stateObj = this.hass.states[entityId]; const domain = computeDomain(entityId); return html` + ${stateObj.attributes.restored && this._entityEntry + ? html` + ${this.hass.localize( + "ui.dialogs.more_info_control.restored.no_longer_provided", + { + integration: this._entityEntry.platform, + } + )} + ` + : ""} ${DOMAINS_NO_INFO.includes(domain) ? "" : html` @@ -52,43 +67,18 @@ export class MoreInfoInfo extends LitElement { .stateObj=${stateObj} .hass=${this.hass} > - ${stateObj.attributes.restored - ? html` -

    - ${this.hass.localize( - "ui.dialogs.more_info_control.restored.not_provided" - )} -

    -

    - ${this.hass.localize( - "ui.dialogs.more_info_control.restored.remove_intro" - )} -

    - - ${this.hass.localize( - "ui.dialogs.more_info_control.restored.remove_action" - )} - - ` - : ""} `; } - private _removeEntity() { - const entityId = this.entityId!; - showConfirmationDialog(this, { - title: this.hass.localize( - "ui.dialogs.more_info_control.restored.confirm_remove_title" - ), - text: this.hass.localize( - "ui.dialogs.more_info_control.restored.confirm_remove_text" - ), - confirmText: this.hass.localize("ui.common.remove"), - dismissText: this.hass.localize("ui.common.cancel"), - confirm: () => { - removeEntityRegistryEntry(this.hass, entityId); - }, - }); + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + subscribeOne(this.hass.connection, subscribeEntityRegistry).then( + (entries) => { + this._entityEntry = entries.find( + (entry) => entry.entity_id === this.entityId + ); + } + ); } static get styles() { @@ -99,6 +89,13 @@ export class MoreInfoInfo extends LitElement { display: block; margin-bottom: 16px; } + + ha-alert { + display: block; + margin: calc(-1 * var(--dialog-content-padding, 24px)) + calc(-1 * var(--dialog-content-padding, 24px)) 16px + calc(-1 * var(--dialog-content-padding, 24px)); + } `; } } diff --git a/src/translations/en.json b/src/translations/en.json index cc38651af5..d82e11a5e7 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -776,11 +776,7 @@ "activity": "Current activity" }, "restored": { - "not_provided": "This entity is currently unavailable and is an orphan to a removed, changed or dysfunctional integration or device.", - "remove_intro": "If the entity is no longer in use, you can clean it up by removing it.", - "remove_action": "Remove entity", - "confirm_remove_title": "Remove entity?", - "confirm_remove_text": "Are you sure you want to remove this entity?" + "no_longer_provided": "This entity is no longer being provided by the {integration} integration. If the entity is no longer in use, delete it in settings." }, "vacuum": { "status": "Status", From d64c81a1231022a0d79a7b3415d2747cc4c9899c Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 25 Aug 2022 13:47:16 +0200 Subject: [PATCH 091/134] Use name from metadata in statistics card (#13451) * Use name from metadata in statistics card * Refactor to use getStatisticLabel --- src/components/chart/statistics-chart.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts index 9f793a6977..7107c2f2c3 100644 --- a/src/components/chart/statistics-chart.ts +++ b/src/components/chart/statistics-chart.ts @@ -15,13 +15,13 @@ import { import { customElement, property, state } from "lit/decorators"; import { getGraphColorByIndex } from "../../common/color/colors"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; -import { computeStateName } from "../../common/entity/compute_state_name"; import { formatNumber, numberFormatToLocale, } from "../../common/number/format_number"; import { getStatisticIds, + getStatisticLabel, Statistics, statisticsHaveType, StatisticsMetaData, @@ -233,19 +233,17 @@ class StatisticsChart extends LitElement { const names = this.names || {}; statisticsData.forEach((stats) => { const firstStat = stats[0]; - let name = names[firstStat.statistic_id]; - if (!name) { - const entityState = this.hass.states[firstStat.statistic_id]; - if (entityState) { - name = computeStateName(entityState); - } else { - name = firstStat.statistic_id; - } - } - const meta = this.statisticIds!.find( (stat) => stat.statistic_id === firstStat.statistic_id ); + let name = names[firstStat.statistic_id]; + if (!name) { + const tmp: Record = {}; + if (meta) { + tmp[firstStat.statistic_id] = meta; + } + name = getStatisticLabel(this.hass, firstStat.statistic_id, tmp); + } if (!this.unit) { if (unit === undefined) { From 6446534e0b418fd73cf9588985869d041271c97a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 25 Aug 2022 13:47:32 +0200 Subject: [PATCH 092/134] Use name from statistics metadata in energy dashboard (#13469) --- src/data/history.ts | 4 +- .../energy/hui-energy-devices-graph-card.ts | 26 ++++++--- .../energy/hui-energy-solar-graph-card.ts | 13 +++-- .../energy/hui-energy-sources-table-card.ts | 58 +++++++++++-------- 4 files changed, 62 insertions(+), 39 deletions(-) diff --git a/src/data/history.ts b/src/data/history.ts index 39d9ba4884..813b7e6cc9 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -569,12 +569,12 @@ export const adjustStatisticsSum = ( export const getStatisticLabel = ( hass: HomeAssistant, statisticsId: string, - statisticsMetaData: Record + statisticsMetaData: Record | undefined ): string => { const entity = hass.states[statisticsId]; if (entity) { return computeStateName(entity); } - const statisticMetaData = statisticsMetaData[statisticsId]; + const statisticMetaData = statisticsMetaData?.[statisticsId]; return statisticMetaData?.name || statisticsId; }; diff --git a/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts index 621d579c93..8fad8ee7f5 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts @@ -14,7 +14,6 @@ import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; import { getColorByIndex } from "../../../../common/color/colors"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { computeStateName } from "../../../../common/entity/compute_state_name"; import { formatNumber, numberFormatToLocale, @@ -26,6 +25,7 @@ import { EnergyData, getEnergyDataCollection } from "../../../../data/energy"; import { calculateStatisticSumGrowth, fetchStatistics, + getStatisticLabel, Statistics, } from "../../../../data/history"; import { FrontendLocaleData } from "../../../../data/translation"; @@ -45,6 +45,8 @@ export class HuiEnergyDevicesGraphCard @state() private _chartData: ChartData = { datasets: [] }; + @state() private _data?: EnergyData; + @query("ha-chart-base") private _chart?: HaChartBase; protected hassSubscribeRequiredHostProps = ["_config"]; @@ -53,7 +55,10 @@ export class HuiEnergyDevicesGraphCard return [ getEnergyDataCollection(this.hass, { key: this._config?.collection_key, - }).subscribe((data) => this._getStatistics(data)), + }).subscribe((data) => { + this._data = data; + this._getStatistics(data); + }), ]; } @@ -105,11 +110,14 @@ export class HuiEnergyDevicesGraphCard ticks: { autoSkip: false, callback: (index) => { - const entityId = ( + const statisticId = ( this._chartData.datasets[0].data[index] as ScatterDataPoint ).y; - const entity = this.hass.states[entityId]; - return entity ? computeStateName(entity) : entityId; + return getStatisticLabel( + this.hass, + statisticId as any, + this._data?.statsMetadata + ); }, }, }, @@ -126,8 +134,12 @@ export class HuiEnergyDevicesGraphCard mode: "nearest", callbacks: { title: (item) => { - const entity = this.hass.states[item[0].label]; - return entity ? computeStateName(entity) : item[0].label; + const statisticId = item[0].label; + return getStatisticLabel( + this.hass, + statisticId, + this._data?.statsMetadata + ); }, label: (context) => `${context.dataset.label}: ${formatNumber( diff --git a/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts index c4fb6c3196..e184dbce06 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts @@ -26,7 +26,6 @@ import { import { labBrighten, labDarken } from "../../../../common/color/lab"; import { formatDateShort } from "../../../../common/datetime/format_date"; import { formatTime } from "../../../../common/datetime/format_time"; -import { computeStateName } from "../../../../common/entity/compute_state_name"; import { formatNumber, numberFormatToLocale, @@ -327,6 +326,7 @@ export class HuiEnergySolarGraphCard if (forecasts) { datasets.push( ...this._processForecast( + energyData.statsMetadata, forecasts, solarSources, computedStyles.getPropertyValue("--primary-text-color"), @@ -422,6 +422,7 @@ export class HuiEnergySolarGraphCard } private _processForecast( + statisticsMetaData: Record, forecasts: EnergySolarForecasts, solarSources: SolarSourceTypeEnergyPreference[], borderColor: string, @@ -435,8 +436,6 @@ export class HuiEnergySolarGraphCard // Process solar forecast data. solarSources.forEach((source) => { if (source.config_entry_solar_forecast) { - const entity = this.hass.states[source.stat_energy_from]; - const forecastsData: Record | undefined = {}; source.config_entry_solar_forecast.forEach((configEntryId) => { if (!forecasts![configEntryId]) { @@ -481,9 +480,11 @@ export class HuiEnergySolarGraphCard label: this.hass.localize( "ui.panel.lovelace.cards.energy.energy_solar_graph.forecast", { - name: entity - ? computeStateName(entity) - : source.stat_energy_from, + name: getStatisticLabel( + this.hass, + source.stat_energy_from, + statisticsMetaData + ), } ), fill: false, diff --git a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts index cf584d0d73..a30e7ae5aa 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts @@ -18,7 +18,6 @@ import { hex2rgb, } from "../../../../common/color/convert-color"; import { labBrighten, labDarken } from "../../../../common/color/lab"; -import { computeStateName } from "../../../../common/entity/compute_state_name"; import { formatNumber } from "../../../../common/number/format_number"; import "../../../../components/chart/statistics-chart"; import "../../../../components/ha-card"; @@ -28,7 +27,10 @@ import { getEnergyDataCollection, getEnergyGasUnit, } from "../../../../data/energy"; -import { calculateStatisticSumGrowth } from "../../../../data/history"; +import { + calculateStatisticSumGrowth, + getStatisticLabel, +} from "../../../../data/history"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../../../types"; import { LovelaceCard } from "../../types"; @@ -199,7 +201,6 @@ export class HuiEnergySourcesTableCard ${types.solar?.map((source, idx) => { - const entity = this.hass.states[source.stat_energy_from]; const energy = calculateStatisticSumGrowth( this._data!.stats[source.stat_energy_from] @@ -235,9 +236,11 @@ export class HuiEnergySourcesTableCard >
    - ${entity - ? computeStateName(entity) - : source.stat_energy_from} + ${getStatisticLabel( + this.hass, + source.stat_energy_from, + this._data?.statsMetadata + )} ${compare ? html`` : ""} ${types.battery?.map((source, idx) => { - const entityFrom = this.hass.states[source.stat_energy_from]; - const entityTo = this.hass.states[source.stat_energy_to]; const energyFrom = calculateStatisticSumGrowth( this._data!.stats[source.stat_energy_from] @@ -343,9 +344,11 @@ export class HuiEnergySourcesTableCard >
    - ${entityFrom - ? computeStateName(entityFrom) - : source.stat_energy_from} + ${getStatisticLabel( + this.hass, + source.stat_energy_from, + this._data?.statsMetadata + )} ${compare ? html`
    - ${entityTo - ? computeStateName(entityTo) - : source.stat_energy_from} + ${getStatisticLabel( + this.hass, + source.stat_energy_to, + this._data?.statsMetadata + )} ${compare ? html` html`${source.flow_from.map((flow, idx) => { - const entity = this.hass.states[flow.stat_energy_from]; const energy = calculateStatisticSumGrowth( this._data!.stats[flow.stat_energy_from] @@ -498,9 +502,11 @@ export class HuiEnergySourcesTableCard >
    - ${entity - ? computeStateName(entity) - : flow.stat_energy_from} + ${getStatisticLabel( + this.hass, + flow.stat_energy_from, + this._data?.statsMetadata + )} ${compare ? html``; })} ${source.flow_to.map((flow, idx) => { - const entity = this.hass.states[flow.stat_energy_to]; const energy = (calculateStatisticSumGrowth( this._data!.stats[flow.stat_energy_to] @@ -602,7 +607,11 @@ export class HuiEnergySourcesTableCard >
    - ${entity ? computeStateName(entity) : flow.stat_energy_to} + ${getStatisticLabel( + this.hass, + flow.stat_energy_to, + this._data?.statsMetadata + )} ${compare ? html`` : ""} ${types.gas?.map((source, idx) => { - const entity = this.hass.states[source.stat_energy_from]; const energy = calculateStatisticSumGrowth( this._data!.stats[source.stat_energy_from] @@ -752,9 +760,11 @@ export class HuiEnergySourcesTableCard >
    - ${entity - ? computeStateName(entity) - : source.stat_energy_from} + ${getStatisticLabel( + this.hass, + source.stat_energy_from, + this._data?.statsMetadata + )} ${compare ? html` Date: Thu, 25 Aug 2022 13:47:44 +0200 Subject: [PATCH 093/134] Use name from statistics metadata in energy config panel (#13474) --- src/data/energy.ts | 104 ++++++++++-------- .../components/ha-energy-battery-settings.ts | 25 +++-- .../components/ha-energy-device-settings.ts | 16 ++- .../components/ha-energy-gas-settings.ts | 16 ++- .../components/ha-energy-grid-settings.ts | 24 ++-- .../components/ha-energy-solar-settings.ts | 16 ++- src/panels/config/energy/ha-config-energy.ts | 27 +++++ 7 files changed, 157 insertions(+), 71 deletions(-) diff --git a/src/data/energy.ts b/src/data/energy.ts index b4fb94845e..68d18a8e43 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -248,6 +248,62 @@ export interface EnergyData { fossilEnergyConsumptionCompare?: FossilEnergyConsumption; } +export const getReferencedStatisticIds = ( + prefs: EnergyPreferences, + info: EnergyInfo +): string[] => { + const statIDs: string[] = []; + + for (const source of prefs.energy_sources) { + if (source.type === "solar") { + statIDs.push(source.stat_energy_from); + continue; + } + + if (source.type === "gas") { + statIDs.push(source.stat_energy_from); + if (source.stat_cost) { + statIDs.push(source.stat_cost); + } + const costStatId = info.cost_sensors[source.stat_energy_from]; + if (costStatId) { + statIDs.push(costStatId); + } + continue; + } + + if (source.type === "battery") { + statIDs.push(source.stat_energy_from); + statIDs.push(source.stat_energy_to); + continue; + } + + // grid source + for (const flowFrom of source.flow_from) { + statIDs.push(flowFrom.stat_energy_from); + if (flowFrom.stat_cost) { + statIDs.push(flowFrom.stat_cost); + } + const costStatId = info.cost_sensors[flowFrom.stat_energy_from]; + if (costStatId) { + statIDs.push(costStatId); + } + } + for (const flowTo of source.flow_to) { + statIDs.push(flowTo.stat_energy_to); + if (flowTo.stat_compensation) { + statIDs.push(flowTo.stat_compensation); + } + const costStatId = info.cost_sensors[flowTo.stat_energy_to]; + if (costStatId) { + statIDs.push(costStatId); + } + } + } + + return statIDs; +}; + const getEnergyData = async ( hass: HomeAssistant, prefs: EnergyPreferences, @@ -285,55 +341,15 @@ const getEnergyData = async ( } const consumptionStatIDs: string[] = []; - const statIDs: string[] = []; - for (const source of prefs.energy_sources) { - if (source.type === "solar") { - statIDs.push(source.stat_energy_from); - continue; - } - - if (source.type === "gas") { - statIDs.push(source.stat_energy_from); - if (source.stat_cost) { - statIDs.push(source.stat_cost); - } - const costStatId = info.cost_sensors[source.stat_energy_from]; - if (costStatId) { - statIDs.push(costStatId); - } - continue; - } - - if (source.type === "battery") { - statIDs.push(source.stat_energy_from); - statIDs.push(source.stat_energy_to); - continue; - } - // grid source - for (const flowFrom of source.flow_from) { - consumptionStatIDs.push(flowFrom.stat_energy_from); - statIDs.push(flowFrom.stat_energy_from); - if (flowFrom.stat_cost) { - statIDs.push(flowFrom.stat_cost); - } - const costStatId = info.cost_sensors[flowFrom.stat_energy_from]; - if (costStatId) { - statIDs.push(costStatId); - } - } - for (const flowTo of source.flow_to) { - statIDs.push(flowTo.stat_energy_to); - if (flowTo.stat_compensation) { - statIDs.push(flowTo.stat_compensation); - } - const costStatId = info.cost_sensors[flowTo.stat_energy_to]; - if (costStatId) { - statIDs.push(costStatId); + if (source.type === "grid") { + for (const flowFrom of source.flow_from) { + consumptionStatIDs.push(flowFrom.stat_energy_from); } } } + const statIDs = getReferencedStatisticIds(prefs, info); const dayDifference = differenceInDays(end || new Date(), start); const period = diff --git a/src/panels/config/energy/components/ha-energy-battery-settings.ts b/src/panels/config/energy/components/ha-energy-battery-settings.ts index 6879a34c96..e674658b43 100644 --- a/src/panels/config/energy/components/ha-energy-battery-settings.ts +++ b/src/panels/config/energy/components/ha-energy-battery-settings.ts @@ -3,7 +3,6 @@ import { mdiBatteryHigh, mdiDelete, mdiPencil } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { computeStateName } from "../../../../common/entity/compute_state_name"; import "../../../../components/ha-card"; import "../../../../components/ha-icon-button"; import "../../../../components/ha-settings-row"; @@ -14,6 +13,10 @@ import { EnergyValidationIssue, saveEnergyPreferences, } from "../../../../data/energy"; +import { + StatisticsMetaData, + getStatisticLabel, +} from "../../../../data/history"; import { showAlertDialog, showConfirmationDialog, @@ -32,6 +35,9 @@ export class EnergyBatterySettings extends LitElement { @property({ attribute: false }) public preferences!: EnergyPreferences; + @property({ attribute: false }) + public statsMetadata!: Record; + @property({ attribute: false }) public validationResult?: EnergyPreferencesValidation; @@ -85,7 +91,6 @@ export class EnergyBatterySettings extends LitElement { )} ${batterySources.map((source) => { - const fromEntityState = this.hass.states[source.stat_energy_from]; const toEntityState = this.hass.states[source.stat_energy_to]; return html`
    @@ -96,14 +101,18 @@ export class EnergyBatterySettings extends LitElement { : html``}
    ${toEntityState - ? computeStateName(toEntityState) - : source.stat_energy_from}${getStatisticLabel( + this.hass, + source.stat_energy_from, + this.statsMetadata + )} ${fromEntityState - ? computeStateName(fromEntityState) - : source.stat_energy_to}${getStatisticLabel( + this.hass, + source.stat_energy_to, + this.statsMetadata + )}
    ; + @property({ attribute: false }) public validationResult?: EnergyPreferencesValidation; @@ -81,9 +87,11 @@ export class EnergyDeviceSettings extends LitElement {
    ${entityState - ? computeStateName(entityState) - : device.stat_consumption}${getStatisticLabel( + this.hass, + device.stat_consumption, + this.statsMetadata + )} ; + @property({ attribute: false }) public validationResult?: EnergyPreferencesValidation; @@ -89,9 +95,11 @@ export class EnergyGasSettings extends LitElement { >` : html``} ${entityState - ? computeStateName(entityState) - : source.stat_energy_from}${getStatisticLabel( + this.hass, + source.stat_energy_from, + this.statsMetadata + )} ; + @property({ attribute: false }) public validationResult?: EnergyPreferencesValidation; @@ -127,9 +133,11 @@ export class EnergyGridSettings extends LitElement { .path=${mdiHomeImportOutline} >`} ${entityState - ? computeStateName(entityState) - : flow.stat_energy_from}${getStatisticLabel( + this.hass, + flow.stat_energy_from, + this.statsMetadata + )} `} ${entityState - ? computeStateName(entityState) - : flow.stat_energy_to}${getStatisticLabel( + this.hass, + flow.stat_energy_to, + this.statsMetadata + )} ; + @property({ attribute: false }) public validationResult?: EnergyPreferencesValidation; @@ -97,9 +103,11 @@ export class EnergySolarSettings extends LitElement { >` : html``} ${entityState - ? computeStateName(entityState) - : source.stat_energy_from}${getStatisticLabel( + this.hass, + source.stat_energy_from, + this.statsMetadata + )} ${this.info ? html` diff --git a/src/panels/config/energy/ha-config-energy.ts b/src/panels/config/energy/ha-config-energy.ts index 44c0f14d66..c6a2d5b150 100644 --- a/src/panels/config/energy/ha-config-energy.ts +++ b/src/panels/config/energy/ha-config-energy.ts @@ -8,7 +8,12 @@ import { EnergyPreferences, getEnergyInfo, getEnergyPreferences, + getReferencedStatisticIds, } from "../../../data/energy"; +import { + getStatisticMetadata, + StatisticsMetaData, +} from "../../../data/history"; import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-subpage"; import { haStyle } from "../../../resources/styles"; @@ -47,6 +52,8 @@ class HaConfigEnergy extends LitElement { @state() private _error?: string; + @state() private _statsMetadata?: Record; + protected firstUpdated() { this._fetchConfig(); } @@ -83,12 +90,14 @@ class HaConfigEnergy extends LitElement { @@ -136,6 +148,7 @@ class HaConfigEnergy extends LitElement { this._error = err.message; } this._info = await energyInfoPromise; + await this._fetchMetaData(); } private async _prefsChanged(ev: CustomEvent) { @@ -147,6 +160,20 @@ class HaConfigEnergy extends LitElement { this._error = err.message; } this._info = await getEnergyInfo(this.hass); + await this._fetchMetaData(); + } + + private async _fetchMetaData() { + if (!this._preferences || !this._info) { + return; + } + const statIDs = getReferencedStatisticIds(this._preferences, this._info); + const statsMetadataArray = await getStatisticMetadata(this.hass, statIDs); + const statsMetadata: Record = {}; + statsMetadataArray.forEach((x) => { + statsMetadata[x.statistic_id] = x; + }); + this._statsMetadata = statsMetadata; } static get styles(): CSSResultGroup { From 5d1536030a16d1c0cd80cac3b8e18c38169488c9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 25 Aug 2022 14:53:16 +0200 Subject: [PATCH 094/134] Refactor getStatisticLabel (#13486) --- src/components/chart/statistics-chart.ts | 6 +----- src/data/history.ts | 5 ++--- .../energy/components/ha-energy-battery-settings.ts | 4 ++-- .../energy/components/ha-energy-device-settings.ts | 2 +- .../energy/components/ha-energy-gas-settings.ts | 2 +- .../energy/components/ha-energy-grid-settings.ts | 4 ++-- .../energy/components/ha-energy-solar-settings.ts | 2 +- .../cards/energy/hui-energy-devices-graph-card.ts | 4 ++-- .../cards/energy/hui-energy-gas-graph-card.ts | 2 +- .../cards/energy/hui-energy-solar-graph-card.ts | 4 ++-- .../cards/energy/hui-energy-sources-table-card.ts | 12 ++++++------ .../cards/energy/hui-energy-usage-graph-card.ts | 6 +++++- 12 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts index 7107c2f2c3..da1dac1c1d 100644 --- a/src/components/chart/statistics-chart.ts +++ b/src/components/chart/statistics-chart.ts @@ -238,11 +238,7 @@ class StatisticsChart extends LitElement { ); let name = names[firstStat.statistic_id]; if (!name) { - const tmp: Record = {}; - if (meta) { - tmp[firstStat.statistic_id] = meta; - } - name = getStatisticLabel(this.hass, firstStat.statistic_id, tmp); + name = getStatisticLabel(this.hass, firstStat.statistic_id, meta); } if (!this.unit) { diff --git a/src/data/history.ts b/src/data/history.ts index 813b7e6cc9..de7f7ccbcd 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -569,12 +569,11 @@ export const adjustStatisticsSum = ( export const getStatisticLabel = ( hass: HomeAssistant, statisticsId: string, - statisticsMetaData: Record | undefined + statisticsMetaData: StatisticsMetaData | undefined ): string => { const entity = hass.states[statisticsId]; if (entity) { return computeStateName(entity); } - const statisticMetaData = statisticsMetaData?.[statisticsId]; - return statisticMetaData?.name || statisticsId; + return statisticsMetaData?.name || statisticsId; }; diff --git a/src/panels/config/energy/components/ha-energy-battery-settings.ts b/src/panels/config/energy/components/ha-energy-battery-settings.ts index e674658b43..376ace7323 100644 --- a/src/panels/config/energy/components/ha-energy-battery-settings.ts +++ b/src/panels/config/energy/components/ha-energy-battery-settings.ts @@ -104,14 +104,14 @@ export class EnergyBatterySettings extends LitElement { >${getStatisticLabel( this.hass, source.stat_energy_from, - this.statsMetadata + this.statsMetadata[source.stat_energy_from] )} ${getStatisticLabel( this.hass, source.stat_energy_to, - this.statsMetadata + this.statsMetadata[source.stat_energy_to] )}
    diff --git a/src/panels/config/energy/components/ha-energy-device-settings.ts b/src/panels/config/energy/components/ha-energy-device-settings.ts index 71600031e8..bac990fbc8 100644 --- a/src/panels/config/energy/components/ha-energy-device-settings.ts +++ b/src/panels/config/energy/components/ha-energy-device-settings.ts @@ -90,7 +90,7 @@ export class EnergyDeviceSettings extends LitElement { >${getStatisticLabel( this.hass, device.stat_consumption, - this.statsMetadata + this.statsMetadata[device.stat_consumption] )} ${getStatisticLabel( this.hass, source.stat_energy_from, - this.statsMetadata + this.statsMetadata[source.stat_energy_from] )} ${getStatisticLabel( this.hass, flow.stat_energy_from, - this.statsMetadata + this.statsMetadata[flow.stat_energy_from] )} ${getStatisticLabel( this.hass, flow.stat_energy_to, - this.statsMetadata + this.statsMetadata[flow.stat_energy_to] )} ${getStatisticLabel( this.hass, source.stat_energy_from, - this.statsMetadata + this.statsMetadata[source.stat_energy_from] )} ${this.info diff --git a/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts index 8fad8ee7f5..a1419466c5 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts @@ -116,7 +116,7 @@ export class HuiEnergyDevicesGraphCard return getStatisticLabel( this.hass, statisticId as any, - this._data?.statsMetadata + this._data?.statsMetadata[statisticId] ); }, }, @@ -138,7 +138,7 @@ export class HuiEnergyDevicesGraphCard return getStatisticLabel( this.hass, statisticId, - this._data?.statsMetadata + this._data?.statsMetadata[statisticId] ); }, label: (context) => diff --git a/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts index 1d4b87943e..3803a69a23 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts @@ -382,7 +382,7 @@ export class HuiEnergyGasGraphCard label: getStatisticLabel( this.hass, source.stat_energy_from, - statisticsMetaData + statisticsMetaData[source.stat_energy_from] ), borderColor: compare ? borderColor + "7F" : borderColor, backgroundColor: compare ? borderColor + "32" : borderColor + "7F", diff --git a/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts index e184dbce06..5b29e94c53 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts @@ -405,7 +405,7 @@ export class HuiEnergySolarGraphCard name: getStatisticLabel( this.hass, source.stat_energy_from, - statisticsMetaData + statisticsMetaData[source.stat_energy_from] ), } ), @@ -483,7 +483,7 @@ export class HuiEnergySolarGraphCard name: getStatisticLabel( this.hass, source.stat_energy_from, - statisticsMetaData + statisticsMetaData[source.stat_energy_from] ), } ), diff --git a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts index a30e7ae5aa..6116c25e4c 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts @@ -239,7 +239,7 @@ export class HuiEnergySourcesTableCard ${getStatisticLabel( this.hass, source.stat_energy_from, - this._data?.statsMetadata + this._data?.statsMetadata[source.stat_energy_from] )} ${compare @@ -347,7 +347,7 @@ export class HuiEnergySourcesTableCard ${getStatisticLabel( this.hass, source.stat_energy_from, - this._data?.statsMetadata + this._data?.statsMetadata[source.stat_energy_from] )} ${compare @@ -384,7 +384,7 @@ export class HuiEnergySourcesTableCard ${getStatisticLabel( this.hass, source.stat_energy_to, - this._data?.statsMetadata + this._data?.statsMetadata[source.stat_energy_to] )} ${compare @@ -505,7 +505,7 @@ export class HuiEnergySourcesTableCard ${getStatisticLabel( this.hass, flow.stat_energy_from, - this._data?.statsMetadata + this._data?.statsMetadata[flow.stat_energy_from] )} ${compare @@ -610,7 +610,7 @@ export class HuiEnergySourcesTableCard ${getStatisticLabel( this.hass, flow.stat_energy_to, - this._data?.statsMetadata + this._data?.statsMetadata[flow.stat_energy_to] )} ${compare @@ -763,7 +763,7 @@ export class HuiEnergySourcesTableCard ${getStatisticLabel( this.hass, source.stat_energy_from, - this._data?.statsMetadata + this._data?.statsMetadata[source.stat_energy_from] )} ${compare diff --git a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts index f761ed0a88..1e315bcc37 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts @@ -620,7 +620,11 @@ export class HuiEnergyUsageGraphCard label: type in labels ? labels[type] - : getStatisticLabel(this.hass, statId, statisticsMetaData), + : getStatisticLabel( + this.hass, + statId, + statisticsMetaData[statId] + ), order: type === "used_solar" ? 1 From 1c05bc6380a599623ea9b99f961d3efa80869543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 25 Aug 2022 15:02:30 +0200 Subject: [PATCH 095/134] Catch reload issues (#13487) --- hassio/src/addon-store/hassio-addon-store.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/hassio/src/addon-store/hassio-addon-store.ts b/hassio/src/addon-store/hassio-addon-store.ts index 2c373a4e9f..0623e2bf44 100644 --- a/hassio/src/addon-store/hassio-addon-store.ts +++ b/hassio/src/addon-store/hassio-addon-store.ts @@ -22,8 +22,10 @@ import { HassioAddonRepository, reloadHassioAddons, } from "../../../src/data/hassio/addon"; +import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { StoreAddon } from "../../../src/data/supervisor/store"; import { Supervisor } from "../../../src/data/supervisor/supervisor"; +import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-subpage"; import { HomeAssistant, Route } from "../../../src/types"; @@ -59,8 +61,15 @@ class HassioAddonStore extends LitElement { @state() private _filter?: string; public async refreshData() { - await reloadHassioAddons(this.hass); - await this._loadData(); + try { + await reloadHassioAddons(this.hass); + } catch (err) { + showAlertDialog(this, { + text: extractApiErrorMessage(err), + }); + } finally { + await this._loadData(); + } } protected render(): TemplateResult { From a3d80f1280670afc7589f636f837ba5db37b4ec8 Mon Sep 17 00:00:00 2001 From: Yosi Levy <37745463+yosilevy@users.noreply.github.com> Date: Thu, 25 Aug 2022 17:20:04 +0300 Subject: [PATCH 096/134] RTL heater+state fixes (#13366) --- src/components/ha-water_heater-control.js | 1 + src/components/ha-water_heater-state.js | 9 +++++++-- src/dialogs/quick-bar/ha-quick-bar.ts | 24 ++++++++++------------- src/state-summary/state-card-display.ts | 17 ++++------------ 4 files changed, 22 insertions(+), 29 deletions(-) diff --git a/src/components/ha-water_heater-control.js b/src/components/ha-water_heater-control.js index f92034266e..a2521e5d8a 100644 --- a/src/components/ha-water_heater-control.js +++ b/src/components/ha-water_heater-control.js @@ -26,6 +26,7 @@ class HaWaterHeaterControl extends EventsMixin(PolymerElement) { #target_temperature { @apply --layout-self-center; font-size: 200%; + direction: ltr; } .control-buttons { font-size: 200%; diff --git a/src/components/ha-water_heater-state.js b/src/components/ha-water_heater-state.js index c9bd1147ac..4fb1330fcb 100644 --- a/src/components/ha-water_heater-state.js +++ b/src/components/ha-water_heater-state.js @@ -31,11 +31,16 @@ class HaWaterHeaterState extends LocalizeMixin(PolymerElement) { font-weight: bold; text-transform: capitalize; } + + .label { + direction: ltr; + display: inline-block; + }
    - [[_localizeState(stateObj)]] - [[computeTarget(hass, stateObj)]] + [[_localizeState(stateObj)]] + [[computeTarget(hass, stateObj)]]