From a9d1feb1969e7c4e789a8badf7679bbcc43afaf3 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sat, 3 Sep 2022 11:41:24 +0200 Subject: [PATCH 01/24] Hide soundmode when mediaplayer is off or unavailable (#13347) --- src/dialogs/more-info/controls/more-info-media_player.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dialogs/more-info/controls/more-info-media_player.ts b/src/dialogs/more-info/controls/more-info-media_player.ts index afd1854fcc..395254d4d4 100644 --- a/src/dialogs/more-info/controls/more-info-media_player.ts +++ b/src/dialogs/more-info/controls/more-info-media_player.ts @@ -167,7 +167,8 @@ class MoreInfoMediaPlayer extends LitElement { ` : ""} - ${supportsFeature(stateObj, SUPPORT_SELECT_SOUND_MODE) && + ${![UNAVAILABLE, UNKNOWN, "off"].includes(stateObj.state) && + supportsFeature(stateObj, SUPPORT_SELECT_SOUND_MODE) && stateObj.attributes.sound_mode_list?.length ? html`
From 43f9c9ebc94ac0e64ef837e1cb2583456e3d0af0 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Sat, 3 Sep 2022 11:44:02 +0200 Subject: [PATCH 02/24] Add mediadescription for channel media type (#13434) --- src/data/media-player.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/data/media-player.ts b/src/data/media-player.ts index 38a288d17e..4470156588 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -51,6 +51,7 @@ interface MediaPlayerEntityAttributes extends HassEntityAttributeBase { media_duration?: number; media_position?: number; media_title?: string; + media_channel?: string; icon?: string; entity_picture_local?: string; is_volume_muted?: boolean; @@ -235,6 +236,9 @@ export const computeMediaDescription = ( } } break; + case "channel": + secondaryTitle = stateObj.attributes.media_channel!; + break; default: secondaryTitle = stateObj.attributes.app_name || ""; } From 81cc745c0a7636ba615ebc09cf978610ae0bfe9c Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Sat, 3 Sep 2022 19:22:42 +0200 Subject: [PATCH 03/24] Align wording in automation and script editor overflow menus (#13575) --- src/panels/config/script/ha-script-editor.ts | 11 +++---- src/translations/en.json | 30 ++++++++++---------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index f740f48421..08fa9ef75d 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -192,7 +192,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { "ui.panel.config.automation.editor.edit_ui" )} graphic="icon" - ?activated=${this._mode === "gui"} > ${this.hass.localize("ui.panel.config.automation.editor.edit_ui")} ${this._mode === "gui" @@ -228,13 +227,11 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { - ${this.hass.localize( - "ui.panel.config.script.picker.duplicate_script" - )} + ${this.hass.localize("ui.panel.config.script.picker.duplicate")} - ${this.hass.localize("ui.panel.config.script.editor.delete_script")} + ${this.hass.localize("ui.panel.config.script.picker.delete")} Date: Sun, 4 Sep 2022 22:25:03 -0400 Subject: [PATCH 04/24] Fix ZHA visualization page (#13584) --- .../zha/zha-config-dashboard-router.ts | 2 +- .../zha/zha-network-visualization-page.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard-router.ts b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard-router.ts index 8b0963b190..758f8acf0e 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard-router.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard-router.ts @@ -60,7 +60,7 @@ class ZHAConfigDashboardRouter extends HassRouterPage { } else if (this._currentPage === "device") { el.ieee = this.routeTail.path.substr(1); } else if (this._currentPage === "visualization") { - el.zoomedDeviceId = this.routeTail.path.substr(1); + el.zoomedDeviceIdFromURL = this.routeTail.path.substr(1); } const searchParams = new URLSearchParams(window.location.search); diff --git a/src/panels/config/integrations/integration-panels/zha/zha-network-visualization-page.ts b/src/panels/config/integrations/integration-panels/zha/zha-network-visualization-page.ts index 82af56463b..b321222084 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-network-visualization-page.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-network-visualization-page.ts @@ -37,7 +37,10 @@ export class ZHANetworkVisualizationPage extends LitElement { @property({ type: Boolean }) public isWide!: boolean; @property() - public zoomedDeviceId?: string; + public zoomedDeviceIdFromURL?: string; + + @state() + private zoomedDeviceId?: string; @query("#visualization", true) private _visualization?: HTMLElement; @@ -64,6 +67,11 @@ export class ZHANetworkVisualizationPage extends LitElement { protected firstUpdated(changedProperties: PropertyValues): void { super.firstUpdated(changedProperties); + // prevent zoomedDeviceIdFromURL from being restored to zoomedDeviceId after the user clears it + if (this.zoomedDeviceIdFromURL) { + this.zoomedDeviceId = this.zoomedDeviceIdFromURL; + } + if (this.hass) { this._fetchData(); } From 8ffe6768275f12835381377cf70e2262590f2159 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 Sep 2022 04:49:07 -0400 Subject: [PATCH 05/24] Move edit description into rename dialog (#13580) --- .../dialog-automation-rename.ts | 125 ++++++++++++++++++ .../show-dialog-automation-rename.ts | 22 +++ .../automation/blueprint-automation-editor.ts | 39 +----- .../config/automation/ha-automation-editor.ts | 117 ++++++---------- .../automation/manual-automation-editor.ts | 12 -- src/translations/en.json | 1 - 6 files changed, 190 insertions(+), 126 deletions(-) create mode 100644 src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts create mode 100644 src/panels/config/automation/automation-rename-dialog/show-dialog-automation-rename.ts diff --git a/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts b/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts new file mode 100644 index 0000000000..e24ebc9e6c --- /dev/null +++ b/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts @@ -0,0 +1,125 @@ +import "@material/mwc-button"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { createCloseHeading } from "../../../../components/ha-dialog"; +import { HassDialog } from "../../../../dialogs/make-dialog-manager"; +import { haStyle, haStyleDialog } from "../../../../resources/styles"; +import type { HomeAssistant } from "../../../../types"; +import type { AutomationRenameDialog } from "./show-dialog-automation-rename"; + +@customElement("ha-dialog-automation-rename") +class DialogAutomationRename extends LitElement implements HassDialog { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _opened = false; + + private _params!: AutomationRenameDialog; + + private _newName?: string; + + private _newDescription?: string; + + public showDialog(params: AutomationRenameDialog): void { + this._opened = true; + this._params = params; + this._newName = params.config.alias || ""; + this._newDescription = params.config.description || ""; + } + + public closeDialog(): void { + this._params.onClose(); + + if (this._opened) { + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + this._opened = false; + } + + protected render(): TemplateResult { + if (!this._opened) { + return html``; + } + return html` + + + + + + + ${this.hass.localize("ui.dialogs.generic.cancel")} + + + ${this.hass.localize("ui.panel.config.automation.editor.rename")} + + + `; + } + + private _valueChanged(ev: CustomEvent) { + ev.stopPropagation(); + const target = ev.target as any; + if (target.name === "description") { + this._newDescription = target.value; + } else { + this._newName = target.value; + } + } + + private _save(): void { + this._params.updateAutomation({ + ...this._params.config, + alias: this._newName, + description: this._newDescription, + }); + this.closeDialog(); + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + haStyleDialog, + css` + ha-textfield, + ha-textarea { + display: block; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-dialog-automation-rename": DialogAutomationRename; + } +} diff --git a/src/panels/config/automation/automation-rename-dialog/show-dialog-automation-rename.ts b/src/panels/config/automation/automation-rename-dialog/show-dialog-automation-rename.ts new file mode 100644 index 0000000000..cf0fd4dd36 --- /dev/null +++ b/src/panels/config/automation/automation-rename-dialog/show-dialog-automation-rename.ts @@ -0,0 +1,22 @@ +import { fireEvent } from "../../../../common/dom/fire_event"; +import type { AutomationConfig } from "../../../../data/automation"; + +export const loadAutomationRenameDialog = () => + import("./dialog-automation-rename"); + +export interface AutomationRenameDialog { + config: AutomationConfig; + updateAutomation: (config: AutomationConfig) => void; + onClose: () => void; +} + +export const showAutomationRenameDialog = ( + element: HTMLElement, + dialogParams: AutomationRenameDialog +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "ha-dialog-automation-rename", + dialogImport: loadAutomationRenameDialog, + dialogParams, + }); +}; diff --git a/src/panels/config/automation/blueprint-automation-editor.ts b/src/panels/config/automation/blueprint-automation-editor.ts index be9c0ec9c1..b481754aa1 100644 --- a/src/panels/config/automation/blueprint-automation-editor.ts +++ b/src/panels/config/automation/blueprint-automation-editor.ts @@ -49,26 +49,6 @@ export class HaBlueprintAutomationEditor extends LitElement { protected render() { const blueprint = this._blueprint; return html` -

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

- -
- -
-
- ${this._config!.alias || - this.hass.localize( - "ui.panel.config.automation.editor.default_name" - )}` - : ""}
${this._errors}
` : ""} ${this._mode === "gui" - ? html` - ${this.narrow - ? "" - : html` -
-

- ${this._config!.alias || - this.hass.localize( - "ui.panel.config.automation.editor.default_name" - )} -

- -
- `} - ${"use_blueprint" in this._config - ? html` - - ` - : html` - - `} - ` + ? "use_blueprint" in this._config + ? html` + + ` + : html` + + ` : this._mode === "yaml" ? html` ${!this.narrow @@ -559,32 +533,26 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { this._mode = "yaml"; } - private async _promptAutomationAlias(): Promise { - const result = await showPromptDialog(this, { - title: this.hass.localize( - "ui.panel.config.automation.editor.automation_alias" - ), - inputLabel: this.hass.localize("ui.panel.config.automation.editor.alias"), - inputType: "string", - placeholder: this.hass.localize( - "ui.panel.config.automation.editor.default_name" - ), - defaultValue: this._config!.alias, - confirmText: this.hass.localize("ui.common.submit"), + private async _promptAutomationAlias(): Promise { + return new Promise((resolve) => { + showAutomationRenameDialog(this, { + config: this._config!, + updateAutomation: (config) => { + this._config = config; + this._dirty = true; + this.requestUpdate(); + resolve(); + }, + onClose: () => resolve(), + }); }); - if (result) { - this._config!.alias = result; - this._dirty = true; - this.requestUpdate(); - } - return result; } private async _saveAutomation(): Promise { const id = this.automationId || String(Date.now()); if (!this._config!.alias) { - const alias = await this._promptAutomationAlias(); - if (!alias) { + await this._promptAutomationAlias(); + if (!this._config!.alias) { showAlertDialog(this, { text: this.hass.localize( "ui.panel.config.automation.editor.missing_name" @@ -592,7 +560,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { }); return; } - this._config!.alias = alias; } this.hass!.callApi( diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index 2ac1affe3a..3e99317869 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -54,18 +54,6 @@ export class HaManualAutomationEditor extends LitElement { )}
- Date: Mon, 5 Sep 2022 14:03:00 +0200 Subject: [PATCH 06/24] Remove tabs from scene and script edit page (#13592) --- src/panels/config/scene/ha-scene-dashboard.ts | 18 ++++++++-- src/panels/config/scene/ha-scene-editor.ts | 36 +++++++++---------- src/panels/config/script/ha-script-editor.ts | 13 +++---- src/panels/config/script/ha-script-picker.ts | 14 ++++++-- src/translations/en.json | 1 - 5 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/panels/config/scene/ha-scene-dashboard.ts b/src/panels/config/scene/ha-scene-dashboard.ts index 07ca40e110..b112b0047c 100644 --- a/src/panels/config/scene/ha-scene-dashboard.ts +++ b/src/panels/config/scene/ha-scene-dashboard.ts @@ -11,9 +11,13 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import memoizeOne from "memoize-one"; -import { fireEvent } from "../../../common/dom/fire_event"; +import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; -import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table"; +import { navigate } from "../../../common/navigate"; +import { + 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"; @@ -165,6 +169,8 @@ class HaSceneDashboard extends LitElement { )} @clear-filter=${this._clearFilter} hasFab + clickable + @row-click=${this._handleRowClicked} > ) { + const scene = this.scenes.find((a) => a.entity_id === ev.detail.id); + + if (scene?.attributes.id) { + navigate(`/config/scene/edit/${scene?.attributes.id}`); + } + } + private _relatedFilterChanged(ev: CustomEvent) { this._filterValue = ev.detail.value; if (!this._filterValue) { diff --git a/src/panels/config/scene/ha-scene-editor.ts b/src/panels/config/scene/ha-scene-editor.ts index 584792533f..d693bf2417 100644 --- a/src/panels/config/scene/ha-scene-editor.ts +++ b/src/panels/config/scene/ha-scene-editor.ts @@ -63,13 +63,13 @@ import { showAlertDialog, showConfirmationDialog, } from "../../../dialogs/generic/show-dialog-box"; +import "../../../layouts/hass-subpage"; import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; import { showToast } from "../../../util/toast"; import "../ha-config-section"; -import { configSections } from "../ha-panel-config"; interface DeviceEntities { id: string; @@ -214,17 +214,16 @@ export class HaSceneEditor extends SubscribeMixin( this._deviceEntityLookup, this._deviceRegistryEntries ); - const name = this._scene - ? computeStateName(this._scene) - : this.hass.localize("ui.panel.config.scene.editor.default_name"); return html` - ${this._errors ? html`
${this._errors}
` : ""} - ${this.narrow ? html` ${name} ` : ""}
${this._config ? html` - - ${!this.narrow - ? html` ${name} ` - : ""} -
- ${this.hass.localize( - "ui.panel.config.scene.editor.introduction" - )} -
+
- +
@@ -486,7 +476,7 @@ export class HaSceneEditor extends SubscribeMixin( > - + `; } @@ -963,6 +953,16 @@ export class HaSceneEditor extends SubscribeMixin( ha-card { overflow: hidden; } + .container { + display: flex; + justify-content: center; + margin-top: 24px; + } + .container > * { + max-width: 1040px; + flex: 1 1 auto; + } + .errors { padding: 20px; font-weight: bold; diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index 08fa9ef75d..acfee65f3f 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -18,7 +18,7 @@ import { PropertyValues, TemplateResult, } from "lit"; -import { property, state, query } from "lit/decorators"; +import { property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; import { computeObjectId } from "../../../common/entity/compute_object_id"; @@ -51,13 +51,13 @@ import { } from "../../../data/script"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/ha-app-layout"; +import "../../../layouts/hass-subpage"; import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin"; import { haStyle } from "../../../resources/styles"; 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 { configSections } from "../ha-panel-config"; import "./blueprint-script-editor"; export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { @@ -168,12 +168,12 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { }; return html` - - ${this.narrow - ? html`${this._config?.alias}` - : ""}
- + `; } diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index 8131ca19e8..ab828534bd 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -11,10 +11,14 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { formatDateTime } from "../../../common/datetime/format_date_time"; -import { fireEvent } from "../../../common/dom/fire_event"; +import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; +import { navigate } from "../../../common/navigate"; import { computeRTL } from "../../../common/util/compute_rtl"; -import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table"; +import { + 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"; @@ -191,6 +195,8 @@ class HaScriptPicker extends LitElement { )} @clear-filter=${this._clearFilter} hasFab + clickable + @row-click=${this._handleRowClicked} > ) { + navigate(`/config/script/edit/${ev.detail.id}`); + } + private _runScript = async (ev) => { ev.stopPropagation(); const script = ev.currentTarget.script as HassEntity; diff --git a/src/translations/en.json b/src/translations/en.json index bb807ef13a..2bfdcb136d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2331,7 +2331,6 @@ } }, "editor": { - "introduction": "Use scenes to bring your home to life.", "default_name": "New Scene", "load_error_not_editable": "Only scenes in scenes.yaml are editable.", "load_error_unknown": "Error loading scene ({err_no}).", From 310df387e7927da92e23bf279619042d13a703dd Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 5 Sep 2022 14:08:52 +0200 Subject: [PATCH 07/24] Fix tag trigger (#13588) --- .../automation/trigger/types/ha-automation-trigger-tag.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 12cb696d80..18c702bf16 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 @@ -58,9 +58,9 @@ export class HaTagTrigger extends LitElement implements TriggerElement { private _tagChanged(ev) { if ( - !ev.detail.value || + !ev.target.value || !this._tags || - this.trigger.tag_id === ev.detail.value + this.trigger.tag_id === ev.target.value ) { return; } From 02d608b7049ad29adfa73b2f450fe7db31049650 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 5 Sep 2022 14:12:04 +0200 Subject: [PATCH 08/24] only show cpu and mem when available (#13589) --- .../config/hardware/ha-config-hardware.ts | 99 +++++++++---------- 1 file changed, 49 insertions(+), 50 deletions(-) diff --git a/src/panels/config/hardware/ha-config-hardware.ts b/src/panels/config/hardware/ha-config-hardware.ts index 14795a97e3..6806eb5cb0 100644 --- a/src/panels/config/hardware/ha-config-hardware.ts +++ b/src/panels/config/hardware/ha-config-hardware.ts @@ -284,38 +284,38 @@ class HaConfigHardware extends SubscribeMixin(LitElement) { ` : ""} - - -
-
- ${this.hass.localize("ui.panel.config.hardware.processor")} -
-
- ${this._systemStatusData?.cpu_percent || "-"}% -
-
-
- -
-
- -
-
- ${this.hass.localize("ui.panel.config.hardware.memory")} -
-
- ${this._systemStatusData - ? html` + ${this._systemStatusData + ? html` +
+
+ ${this.hass.localize( + "ui.panel.config.hardware.processor" + )} +
+
+ ${this._systemStatusData.cpu_percent || "-"}% +
+
+
+ +
+
+ +
+
+ ${this.hass.localize("ui.panel.config.hardware.memory")} +
+
${round(this._systemStatusData.memory_used_mb / 1024, 1)} GB / ${round( @@ -325,24 +325,23 @@ class HaConfigHardware extends SubscribeMixin(LitElement) { 0 )} GB - ` - : "- GB / - GB"} -
-
-
- -
-
+
+
+
+ +
+
` + : ""}
`; From ab745f6e8e9d186e7a0cddac7d5e6edebe902917 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 5 Sep 2022 14:19:38 +0200 Subject: [PATCH 09/24] Reorder automation elements (#13548) --- .../action/ha-automation-action-row.ts | 239 ++++++++---------- .../automation/action/ha-automation-action.ts | 177 ++++++++++--- .../types/ha-automation-action-choose.ts | 4 + .../action/types/ha-automation-action-if.ts | 7 +- .../types/ha-automation-action-parallel.ts | 3 + .../types/ha-automation-action-repeat.ts | 3 + .../ha-automation-condition-editor.ts | 8 +- .../condition/ha-automation-condition-row.ts | 183 ++++++++------ .../condition/ha-automation-condition.ts | 181 ++++++++++--- .../types/ha-automation-condition-logical.ts | 3 + .../automation/manual-automation-editor.ts | 52 +++- .../trigger/ha-automation-trigger-row.ts | 181 +++++++------ .../trigger/ha-automation-trigger.ts | 209 +++++++++++---- 13 files changed, 819 insertions(+), 431 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 310b38c90d..c742eefc01 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -1,8 +1,6 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; import { - mdiArrowDown, - mdiArrowUp, mdiCheck, mdiContentDuplicate, mdiDelete, @@ -17,13 +15,15 @@ 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 "../../../../components/ha-alert"; import "../../../../components/ha-button-menu"; import "../../../../components/ha-card"; -import "../../../../components/ha-icon-button"; import "../../../../components/ha-expansion-panel"; +import "../../../../components/ha-icon-button"; import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; +import { ACTION_TYPES } from "../../../../data/action"; import { validateConfig } from "../../../../data/config"; import { Action, getActionType } from "../../../../data/script"; import { describeAction } from "../../../../data/script_i18n"; @@ -50,8 +50,6 @@ 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 { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; const getType = (action: Action | undefined) => { if (!action) { @@ -66,13 +64,6 @@ const getType = (action: Action | undefined) => { return Object.keys(ACTION_TYPES).find((option) => option in action); }; -declare global { - // for fire event - interface HASSDomEvents { - "move-action": { direction: "up" | "down" }; - } -} - export interface ActionElement extends LitElement { action: Action; } @@ -107,12 +98,12 @@ export default class HaAutomationActionRow extends LitElement { @property() public action!: Action; - @property() public index!: number; - - @property() public totalActions!: number; - @property({ type: Boolean }) public narrow = false; + @property({ type: Boolean }) public hideMenu = false; + + @property({ type: Boolean }) public reOrderMode = false; + @state() private _warnings?: string[]; @state() private _uiModeAvailable = true; @@ -165,119 +156,112 @@ export default class HaAutomationActionRow extends LitElement { ${capitalizeFirstLetter(describeAction(this.hass, this.action))} - ${this.index !== 0 - ? html` - + ${this.hideMenu + ? "" + : html` + - ` - : ""} - ${this.index !== this.totalActions - 1 - ? html` - - ` - : ""} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.run" - )} - - + fixed + corner="BOTTOM_START" + @action=${this._handleAction} + @click=${preventDefault} + > + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.run" + )} + + - - ${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.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_ui" + )} + ${!yamlMode + ? html`` + : ``} + - - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - ${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" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + + +
    + `} - - ${this.action.enabled === false - ? this.hass.localize( - "ui.panel.config.automation.editor.actions.enable" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.actions.disable" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - -
    `} @@ -346,16 +331,6 @@ export default class HaAutomationActionRow extends LitElement { } } - private _moveUp(ev) { - ev.preventDefault(); - fireEvent(this, "move-action", { direction: "up" }); - } - - private _moveDown(ev) { - ev.preventDefault(); - fireEvent(this, "move-action", { direction: "down" }); - } - private async _handleAction(ev: CustomEvent) { switch (ev.detail.index) { case 0: diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index 9c65fa1fba..dd7b802aa1 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -1,15 +1,25 @@ -import { repeat } from "lit/directives/repeat"; -import { mdiPlus } from "@mdi/js"; -import deepClone from "deep-clone-simple"; import "@material/mwc-button"; import type { ActionDetail } from "@material/mwc-list"; -import memoizeOne from "memoize-one"; +import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js"; +import deepClone from "deep-clone-simple"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; +import { repeat } from "lit/directives/repeat"; +import memoizeOne from "memoize-one"; +import type { SortableEvent } from "sortablejs"; import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/ha-svg-icon"; +import { stringCompare } from "../../../../common/string/compare"; +import { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/ha-button-menu"; +import type { HaSelect } from "../../../../components/ha-select"; +import "../../../../components/ha-svg-icon"; +import { ACTION_TYPES } from "../../../../data/action"; import { Action } from "../../../../data/script"; +import { sortableStyles } from "../../../../resources/ha-sortable-style"; +import { + loadSortable, + SortableInstance, +} from "../../../../resources/sortable.ondemand"; import { HomeAssistant } from "../../../../types"; import "./ha-automation-action-row"; import type HaAutomationActionRow from "./ha-automation-action-row"; @@ -27,10 +37,6 @@ 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 { @@ -40,28 +46,62 @@ export default class HaAutomationAction extends LitElement { @property() public actions!: Action[]; + @property({ type: Boolean }) public reOrderMode = false; + private _focusLastActionOnChange = false; private _actionKeys = new WeakMap(); + private _sortable?: SortableInstance; + protected render() { return html` - ${repeat( - this.actions, - (action) => this._getKey(action), - (action, idx) => html` - - ` - )} +
    + ${repeat( + this.actions, + (action) => this._getKey(action), + (action, idx) => html` + + ${this.reOrderMode + ? html` + + +
    + +
    + ` + : ""} +
    + ` + )} +
    { + (evt.item as any).placeholder = + document.createComment("sort-placeholder"); + evt.item.after((evt.item as any).placeholder); + }, + onEnd: (evt: SortableEvent) => { + // put back in original location + if ((evt.item as any).placeholder) { + (evt.item as any).placeholder.replaceWith(evt.item); + delete (evt.item as any).placeholder; + } + this._dragged(evt); + }, + }); + } + + private _destroySortable() { + this._sortable?.destroy(); + this._sortable = undefined; + } + private _getKey(action: Action) { if (!this._actionKeys.has(action)) { this._actionKeys.set(action, Math.random().toString()); @@ -121,12 +195,24 @@ export default class HaAutomationAction extends LitElement { fireEvent(this, "value-changed", { value: actions }); } - private _move(ev: CustomEvent) { - // Prevent possible parent action-row from also moving - ev.stopPropagation(); - + private _moveUp(ev) { const index = (ev.target as any).index; - const newIndex = ev.detail.direction === "up" ? index - 1 : index + 1; + const newIndex = index - 1; + this._move(index, newIndex); + } + + private _moveDown(ev) { + const index = (ev.target as any).index; + const newIndex = index + 1; + this._move(index, newIndex); + } + + private _dragged(ev: SortableEvent): void { + if (ev.oldIndex === ev.newIndex) return; + this._move(ev.oldIndex!, ev.newIndex!); + } + + private _move(index: number, newIndex: number) { const actions = this.actions.concat(); const action = actions.splice(index, 1)[0]; actions.splice(newIndex, 0, action); @@ -177,16 +263,27 @@ export default class HaAutomationAction extends LitElement { ); static get styles(): CSSResultGroup { - return css` - ha-automation-action-row { - display: block; - margin-bottom: 16px; - scroll-margin-top: 48px; - } - ha-svg-icon { - height: 20px; - } - `; + return [ + sortableStyles, + css` + ha-automation-action-row { + display: block; + margin-bottom: 16px; + scroll-margin-top: 48px; + } + ha-svg-icon { + height: 20px; + } + .handle { + cursor: move; + padding: 12px; + } + .handle ha-svg-icon { + pointer-events: none; + height: 24px; + } + `, + ]; } } diff --git a/src/panels/config/automation/action/types/ha-automation-action-choose.ts b/src/panels/config/automation/action/types/ha-automation-action-choose.ts index 620d8e39ad..67a70e4304 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-choose.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-choose.ts @@ -17,6 +17,8 @@ export class HaChooseAction extends LitElement implements ActionElement { @property() public action!: ChooseAction; + @property({ type: Boolean }) public reOrderMode = false; + @state() private _showDefault = false; public static get defaultConfig() { @@ -52,6 +54,7 @@ export class HaChooseAction extends LitElement implements ActionElement { 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 0424b8cf79..8725e52dc0 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 @@ -15,6 +15,8 @@ export class HaIfAction extends LitElement implements ActionElement { @property({ attribute: false }) public action!: IfAction; + @property({ type: Boolean }) public reOrderMode = false; + @state() private _showElse = false; public static get defaultConfig() { @@ -35,8 +37,9 @@ export class HaIfAction extends LitElement implements ActionElement {

    @@ -46,6 +49,7 @@ export class HaIfAction extends LitElement implements ActionElement {

    @@ -58,6 +62,7 @@ export class HaIfAction extends LitElement implements ActionElement { 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 37dbfb429e..f4d56bbf91 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 @@ -14,6 +14,8 @@ export class HaParallelAction extends LitElement implements ActionElement { @property({ attribute: false }) public action!: ParallelAction; + @property({ type: Boolean }) public reOrderMode = false; + public static get defaultConfig() { return { parallel: [], @@ -26,6 +28,7 @@ export class HaParallelAction extends LitElement implements ActionElement { return html` 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 6b78e6c0cf..c7dc7c8eab 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 @@ -25,6 +25,8 @@ export class HaRepeatAction extends LitElement implements ActionElement { @property({ attribute: false }) public action!: RepeatAction; + @property({ type: Boolean }) public reOrderMode = false; + public static get defaultConfig() { return { repeat: { count: 2, sequence: [] } }; } @@ -95,6 +97,7 @@ export class HaRepeatAction extends LitElement implements ActionElement { 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 bcb3727209..3cd54473cb 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -28,6 +28,8 @@ export default class HaAutomationConditionEditor extends LitElement { @property({ type: Boolean }) public yamlMode = false; + @property({ type: Boolean }) public reOrderMode = false; + private _processedCondition = memoizeOne((condition) => expandConditionWithShorthand(condition) ); @@ -60,7 +62,11 @@ export default class HaAutomationConditionEditor extends LitElement {
    ${dynamicElement( `ha-automation-condition-${condition.condition}`, - { hass: this.hass, condition: condition } + { + hass: this.hass, + condition: condition, + reOrderMode: this.reOrderMode, + } )}
    `} 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 8c2f1204b5..dcc93b8015 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -70,6 +70,10 @@ export default class HaAutomationConditionRow extends LitElement { @property() public condition!: Condition; + @property({ type: Boolean }) public hideMenu = false; + + @property({ type: Boolean }) public reOrderMode = false; + @state() private _yamlMode = false; @state() private _warnings?: string[]; @@ -103,96 +107,106 @@ export default class HaAutomationConditionRow extends LitElement { )} - - - + + ${this.hideMenu + ? "" + : html` + + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.conditions.test" - )} - - - - ${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.conditions.test" + )} + + + + ${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_ui" + )} + ${!this._yamlMode + ? html`` + : ``} + - - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - ${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" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.actions.disable" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - -
    + + ${this.condition.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + + +
    + `}
    diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 67233bbfe2..8815d15cb1 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -1,14 +1,15 @@ -import { mdiPlus } from "@mdi/js"; -import { repeat } from "lit/directives/repeat"; -import deepClone from "deep-clone-simple"; import "@material/mwc-button"; +import type { ActionDetail } from "@material/mwc-list"; +import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js"; +import deepClone from "deep-clone-simple"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; +import { repeat } from "lit/directives/repeat"; import memoizeOne from "memoize-one"; -import type { ActionDetail } from "@material/mwc-list"; +import type { SortableEvent } from "sortablejs"; import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/ha-svg-icon"; import "../../../../components/ha-button-menu"; +import "../../../../components/ha-svg-icon"; import type { Condition } from "../../../../data/automation"; import type { HomeAssistant } from "../../../../types"; import "./ha-automation-condition-row"; @@ -16,6 +17,14 @@ 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 { stringCompare } from "../../../../common/string/compare"; +import type { LocalizeFunc } from "../../../../common/translations/localize"; +import type { HaSelect } from "../../../../components/ha-select"; +import { CONDITION_TYPES } from "../../../../data/condition"; +import { + loadSortable, + SortableInstance, +} from "../../../../resources/sortable.ondemand"; import "./types/ha-automation-condition-and"; import "./types/ha-automation-condition-device"; import "./types/ha-automation-condition-numeric_state"; @@ -25,10 +34,7 @@ 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"; +import { sortableStyles } from "../../../../resources/ha-sortable-style"; @customElement("ha-automation-condition") export default class HaAutomationCondition extends LitElement { @@ -36,11 +42,23 @@ export default class HaAutomationCondition extends LitElement { @property() public conditions!: Condition[]; + @property({ type: Boolean }) public reOrderMode = false; + private _focusLastConditionOnChange = false; private _conditionKeys = new WeakMap(); + private _sortable?: SortableInstance; + protected updated(changedProperties: PropertyValues) { + if (changedProperties.has("reOrderMode")) { + if (this.reOrderMode) { + this._createSortable(); + } else { + this._destroySortable(); + } + } + if (!changedProperties.has("conditions")) { return; } @@ -82,19 +100,53 @@ export default class HaAutomationCondition extends LitElement { return html``; } return html` - ${repeat( - this.conditions, - (condition) => this._getKey(condition), - (cond, idx) => html` - - ` - )} +
    + ${repeat( + this.conditions, + (condition) => this._getKey(condition), + (cond, idx) => html` + + ${this.reOrderMode + ? html` + + +
    + +
    + ` + : ""} +
    + ` + )} +
    { + (evt.item as any).placeholder = + document.createComment("sort-placeholder"); + evt.item.after((evt.item as any).placeholder); + }, + onEnd: (evt: SortableEvent) => { + // put back in original location + if ((evt.item as any).placeholder) { + (evt.item as any).placeholder.replaceWith(evt.item); + delete (evt.item as any).placeholder; + } + this._dragged(evt); + }, + } + ); + } + + private _destroySortable() { + this._sortable?.destroy(); + this._sortable = undefined; + } + private _getKey(condition: Condition) { if (!this._conditionKeys.has(condition)) { this._conditionKeys.set(condition, Math.random().toString()); @@ -142,6 +224,30 @@ export default class HaAutomationCondition extends LitElement { fireEvent(this, "value-changed", { value: conditions }); } + private _moveUp(ev) { + const index = (ev.target as any).index; + const newIndex = index - 1; + this._move(index, newIndex); + } + + private _moveDown(ev) { + const index = (ev.target as any).index; + const newIndex = index + 1; + this._move(index, newIndex); + } + + private _dragged(ev: SortableEvent): void { + if (ev.oldIndex === ev.newIndex) return; + this._move(ev.oldIndex!, ev.newIndex!); + } + + private _move(index: number, newIndex: number) { + const conditions = this.conditions.concat(); + const condition = conditions.splice(index, 1)[0]; + conditions.splice(newIndex, 0, condition); + fireEvent(this, "value-changed", { value: conditions }); + } + private _conditionChanged(ev: CustomEvent) { ev.stopPropagation(); const conditions = [...this.conditions]; @@ -186,16 +292,27 @@ export default class HaAutomationCondition extends LitElement { ); static get styles(): CSSResultGroup { - return css` - ha-automation-condition-row { - display: block; - margin-bottom: 16px; - scroll-margin-top: 48px; - } - ha-svg-icon { - height: 20px; - } - `; + return [ + sortableStyles, + css` + ha-automation-condition-row { + display: block; + margin-bottom: 16px; + scroll-margin-top: 48px; + } + ha-svg-icon { + height: 20px; + } + .handle { + cursor: move; + padding: 12px; + } + .handle ha-svg-icon { + pointer-events: none; + height: 24px; + } + `, + ]; } } 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 628c050663..0838feffc3 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 @@ -12,6 +12,8 @@ export class HaLogicalCondition extends LitElement implements ConditionElement { @property({ attribute: false }) public condition!: LogicalCondition; + @property({ type: Boolean }) public reOrderMode = false; + public static get defaultConfig() { return { conditions: [], @@ -24,6 +26,7 @@ export class HaLogicalCondition extends LitElement implements ConditionElement { .conditions=${this.condition.conditions || []} @value-changed=${this._valueChanged} .hass=${this.hass} + .reOrderMode=${this.reOrderMode} >
    `; } diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index 3e99317869..a3c7c36985 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -1,8 +1,8 @@ import "@material/mwc-button/mwc-button"; -import { mdiHelpCircle, mdiRobot } from "@mdi/js"; +import { mdiHelpCircle, mdiRobot, mdiSort, mdiTextBoxEdit } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/entity/ha-entity-toggle"; import "../../../components/ha-card"; @@ -35,6 +35,12 @@ export class HaManualAutomationEditor extends LitElement { @property({ attribute: false }) public stateObj?: HassEntity; + @state() private _reOrderMode = false; + + private _toggleReOrderMode() { + this._reOrderMode = !this._reOrderMode; + } + protected render() { return html` @@ -108,6 +114,13 @@ export class HaManualAutomationEditor extends LitElement { "ui.panel.config.automation.editor.triggers.header" )} +
    @@ -136,6 +150,13 @@ export class HaManualAutomationEditor extends LitElement { "ui.panel.config.automation.editor.conditions.header" )} + `; } 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 b3c32cdc92..2674664af9 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -87,6 +87,8 @@ export default class HaAutomationTriggerRow extends LitElement { @property({ attribute: false }) public trigger!: Trigger; + @property({ type: Boolean }) public hideMenu = false; + @state() private _warnings?: string[]; @state() private _yamlMode = false; @@ -128,97 +130,110 @@ export default class HaAutomationTriggerRow extends LitElement { > ${capitalizeFirstLetter(describeTrigger(this.trigger, this.hass))} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.rename" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.duplicate" - )} - - + + ${this.hideMenu + ? "" + : html` + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.edit_id" - )} - - + + ${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.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" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.actions.disable" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - -
    +
  • + + ${this.trigger.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + + +
    + `}
    (); + private _sortable?: SortableInstance; + protected render() { return html` - ${repeat( - this.triggers, - (trigger) => this._getKey(trigger), - (trg, idx) => html` - - ` - )} - - - - - ${this._processedTypes(this.hass.localize).map( - ([opt, label, icon]) => html` - - ${label} +
    + ${repeat( + this.triggers, + (trigger) => this._getKey(trigger), + (trg, idx) => html` + + ${this.reOrderMode + ? html` + + +
    + +
    + ` + : ""} +
    ` )} - +
    + + + + + ${this._processedTypes(this.hass.localize).map( + ([opt, label, icon]) => html` + + ${label} + ` + )} + +
    `; } protected updated(changedProps: PropertyValues) { super.updated(changedProps); + if (changedProps.has("reOrderMode")) { + if (this.reOrderMode) { + this._createSortable(); + } else { + this._destroySortable(); + } + } + if (changedProps.has("triggers") && this._focusLastTriggerOnChange) { this._focusLastTriggerOnChange = false; @@ -96,6 +144,36 @@ export default class HaAutomationTrigger extends LitElement { } } + private async _createSortable() { + const Sortable = await loadSortable(); + this._sortable = new Sortable( + this.shadowRoot!.querySelector(".triggers")!, + { + animation: 150, + fallbackClass: "sortable-fallback", + handle: ".handle", + onChoose: (evt: SortableEvent) => { + (evt.item as any).placeholder = + document.createComment("sort-placeholder"); + evt.item.after((evt.item as any).placeholder); + }, + onEnd: (evt: SortableEvent) => { + // put back in original location + if ((evt.item as any).placeholder) { + (evt.item as any).placeholder.replaceWith(evt.item); + delete (evt.item as any).placeholder; + } + this._dragged(evt); + }, + } + ); + } + + private _destroySortable() { + this._sortable?.destroy(); + this._sortable = undefined; + } + private _getKey(action: Trigger) { if (!this._triggerKeys.has(action)) { this._triggerKeys.set(action, Math.random().toString()); @@ -122,6 +200,30 @@ export default class HaAutomationTrigger extends LitElement { fireEvent(this, "value-changed", { value: triggers }); } + private _moveUp(ev) { + const index = (ev.target as any).index; + const newIndex = index - 1; + this._move(index, newIndex); + } + + private _moveDown(ev) { + const index = (ev.target as any).index; + const newIndex = index + 1; + this._move(index, newIndex); + } + + private _dragged(ev: SortableEvent): void { + if (ev.oldIndex === ev.newIndex) return; + this._move(ev.oldIndex!, ev.newIndex!); + } + + private _move(index: number, newIndex: number) { + const triggers = this.triggers.concat(); + const trigger = triggers.splice(index, 1)[0]; + triggers.splice(newIndex, 0, trigger); + fireEvent(this, "value-changed", { value: triggers }); + } + private _triggerChanged(ev: CustomEvent) { ev.stopPropagation(); const triggers = [...this.triggers]; @@ -166,16 +268,27 @@ export default class HaAutomationTrigger extends LitElement { ); static get styles(): CSSResultGroup { - return css` - ha-automation-trigger-row { - display: block; - margin-bottom: 16px; - scroll-margin-top: 48px; - } - ha-svg-icon { - height: 20px; - } - `; + return [ + sortableStyles, + css` + ha-automation-trigger-row { + display: block; + margin-bottom: 16px; + scroll-margin-top: 48px; + } + ha-svg-icon { + height: 20px; + } + .handle { + cursor: move; + padding: 12px; + } + .handle ha-svg-icon { + pointer-events: none; + height: 24px; + } + `, + ]; } } From a817faae5488ed3f76439d6c5b170105843cb98f Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Mon, 5 Sep 2022 14:49:21 +0200 Subject: [PATCH 10/24] Add back blank before percent sign ("%") based on language (#13590) Co-authored-by: Paulus Schoutsen --- src/common/entity/compute_state_display.ts | 9 +++++---- .../translations/blank_before_percent.ts | 18 ++++++++++++++++++ src/components/ha-gauge.ts | 7 ++++++- 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 src/common/translations/blank_before_percent.ts diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index ba759b197f..de3acbabe8 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -2,17 +2,18 @@ import { HassEntity } from "home-assistant-js-websocket"; import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { FrontendLocaleData } from "../../data/translation"; import { - UPDATE_SUPPORT_PROGRESS, updateIsInstallingFromAttributes, + UPDATE_SUPPORT_PROGRESS, } from "../../data/update"; +import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration"; import { formatDate } from "../datetime/format_date"; import { formatDateTime } from "../datetime/format_date_time"; import { formatTime } from "../datetime/format_time"; import { formatNumber, isNumericFromAttributes } from "../number/format_number"; +import { blankBeforePercent } from "../translations/blank_before_percent"; import { LocalizeFunc } from "../translations/localize"; -import { supportsFeatureFromAttributes } from "./supports-feature"; -import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration"; import { computeDomain } from "./compute_domain"; +import { supportsFeatureFromAttributes } from "./supports-feature"; export const computeStateDisplay = ( localize: LocalizeFunc, @@ -67,7 +68,7 @@ export const computeStateDisplayFromEntityAttributes = ( const unit = !attributes.unit_of_measurement ? "" : attributes.unit_of_measurement === "%" - ? "%" + ? blankBeforePercent(locale) + "%" : ` ${attributes.unit_of_measurement}`; return `${formatNumber(state, locale)}${unit}`; } diff --git a/src/common/translations/blank_before_percent.ts b/src/common/translations/blank_before_percent.ts new file mode 100644 index 0000000000..4c489c96c8 --- /dev/null +++ b/src/common/translations/blank_before_percent.ts @@ -0,0 +1,18 @@ +import { FrontendLocaleData } from "../../data/translation"; + +// Logic based on https://en.wikipedia.org/wiki/Percent_sign#Form_and_spacing +export const blankBeforePercent = ( + localeOptions: FrontendLocaleData +): string => { + switch (localeOptions.language) { + case "cz": + case "de": + case "fi": + case "fr": + case "sk": + case "sv": + return " "; + default: + return ""; + } +}; diff --git a/src/components/ha-gauge.ts b/src/components/ha-gauge.ts index e37d80a0b1..6aaf31e3d5 100644 --- a/src/components/ha-gauge.ts +++ b/src/components/ha-gauge.ts @@ -2,6 +2,7 @@ import { css, LitElement, PropertyValues, svg, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import { formatNumber } from "../common/number/format_number"; +import { blankBeforePercent } from "../common/translations/blank_before_percent"; import { afterNextRender } from "../common/util/render-status"; import { FrontendLocaleData } from "../data/translation"; import { getValueInPercentage, normalize } from "../util/calculate"; @@ -133,7 +134,11 @@ export class Gauge extends LitElement { ? this._segment_label : this.valueText || formatNumber(this.value, this.locale) }${ - this._segment_label ? "" : this.label === "%" ? "%" : ` ${this.label}` + this._segment_label + ? "" + : this.label === "%" + ? blankBeforePercent(this.locale) + "%" + : ` ${this.label}` } `; From 37f6b4f6be8edda15372f58c7b9f679d65ae2455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 5 Sep 2022 16:47:28 +0200 Subject: [PATCH 11/24] Show hardware if hassio or hardware (#13594) --- src/panels/config/ha-panel-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 61687ed80c..eb952f4b6c 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -319,7 +319,7 @@ export const configSections: { [name: string]: PageNavigation[] } = { translationKey: "hardware", iconPath: mdiMemory, iconColor: "#301A8E", - component: "hassio", + components: ["hassio", "hardware"], }, ], about: [ From 3ef567dcd5d6ed013ec0af7ddafcd530a1806cb8 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 5 Sep 2022 17:43:34 +0200 Subject: [PATCH 12/24] Automation change mode dialog (#13591) --- src/data/automation.ts | 1 + .../dialog-automation-mode.ts | 166 ++++++++++++++++++ .../show-dialog-automation-mode.ts | 22 +++ .../dialog-automation-rename.ts | 4 +- .../automation/blueprint-automation-editor.ts | 28 +++ .../config/automation/ha-automation-editor.ts | 65 +++++-- .../automation/manual-automation-editor.ts | 142 +++------------ src/translations/en.json | 1 + 8 files changed, 300 insertions(+), 129 deletions(-) create mode 100644 src/panels/config/automation/automation-mode-dialog/dialog-automation-mode.ts create mode 100644 src/panels/config/automation/automation-mode-dialog/show-dialog-automation-mode.ts diff --git a/src/data/automation.ts b/src/data/automation.ts index 91591a595b..4f7d31d1eb 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -9,6 +9,7 @@ import { DeviceCondition, DeviceTrigger } from "./device_automation"; import { Action, MODES } from "./script"; export const AUTOMATION_DEFAULT_MODE: typeof MODES[number] = "single"; +export const AUTOMATION_DEFAULT_MAX = 10; export interface AutomationEntity extends HassEntityBase { attributes: HassEntityAttributeBase & { diff --git a/src/panels/config/automation/automation-mode-dialog/dialog-automation-mode.ts b/src/panels/config/automation/automation-mode-dialog/dialog-automation-mode.ts new file mode 100644 index 0000000000..a9af861658 --- /dev/null +++ b/src/panels/config/automation/automation-mode-dialog/dialog-automation-mode.ts @@ -0,0 +1,166 @@ +import "@material/mwc-button"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { createCloseHeading } from "../../../../components/ha-dialog"; +import "../../../../components/ha-textfield"; +import "../../../../components/ha-select"; +import { HassDialog } from "../../../../dialogs/make-dialog-manager"; +import { haStyle, haStyleDialog } from "../../../../resources/styles"; +import type { HomeAssistant } from "../../../../types"; +import type { AutomationModeDialog } from "./show-dialog-automation-mode"; +import { + AUTOMATION_DEFAULT_MAX, + AUTOMATION_DEFAULT_MODE, +} from "../../../../data/automation"; +import { documentationUrl } from "../../../../util/documentation-url"; +import { isMaxMode, MODES } from "../../../../data/script"; +import "@material/mwc-list/mwc-list-item"; +import { stopPropagation } from "../../../../common/dom/stop_propagation"; + +@customElement("ha-dialog-automation-mode") +class DialogAutomationMode extends LitElement implements HassDialog { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _opened = false; + + private _params!: AutomationModeDialog; + + @state() private _newMode: typeof MODES[number] = AUTOMATION_DEFAULT_MODE; + + @state() private _newMax?: number; + + public showDialog(params: AutomationModeDialog): void { + this._opened = true; + this._params = params; + this._newMode = params.config.mode || AUTOMATION_DEFAULT_MODE; + this._newMax = isMaxMode(this._newMode) + ? params.config.max || AUTOMATION_DEFAULT_MAX + : undefined; + } + + public closeDialog(): void { + this._params.onClose(); + + if (this._opened) { + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + this._opened = false; + } + + protected render(): TemplateResult { + if (!this._opened) { + return html``; + } + + return 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} + + ` + )} + + ${isMaxMode(this._newMode) + ? html` +
    + + ` + : html``} + + + ${this.hass.localize("ui.dialogs.generic.cancel")} + + + ${this.hass.localize("ui.panel.config.automation.editor.change_mode")} + +
    + `; + } + + private _modeChanged(ev) { + const mode = ev.target.value; + this._newMode = mode; + if (!isMaxMode(mode)) { + this._newMax = undefined; + } else if (!this._newMax) { + this._newMax = AUTOMATION_DEFAULT_MAX; + } + } + + private _valueChanged(ev: CustomEvent) { + ev.stopPropagation(); + const target = ev.target as any; + if (target.name === "max") { + this._newMax = Number(target.value); + } + } + + private _save(): void { + this._params.updateAutomation({ + ...this._params.config, + mode: this._newMode, + max: this._newMax, + }); + this.closeDialog(); + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + haStyleDialog, + css` + ha-select, + ha-textfield { + display: block; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-dialog-automation-mode": DialogAutomationMode; + } +} diff --git a/src/panels/config/automation/automation-mode-dialog/show-dialog-automation-mode.ts b/src/panels/config/automation/automation-mode-dialog/show-dialog-automation-mode.ts new file mode 100644 index 0000000000..3847ec252d --- /dev/null +++ b/src/panels/config/automation/automation-mode-dialog/show-dialog-automation-mode.ts @@ -0,0 +1,22 @@ +import { fireEvent } from "../../../../common/dom/fire_event"; +import type { AutomationConfig } from "../../../../data/automation"; + +export const loadAutomationModeDialog = () => + import("./dialog-automation-mode"); + +export interface AutomationModeDialog { + config: AutomationConfig; + updateAutomation: (config: AutomationConfig) => void; + onClose: () => void; +} + +export const showAutomationModeDialog = ( + element: HTMLElement, + dialogParams: AutomationModeDialog +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "ha-dialog-automation-mode", + dialogImport: loadAutomationModeDialog, + dialogParams, + }); +}; diff --git a/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts b/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts index e24ebc9e6c..2d55bfd117 100644 --- a/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts +++ b/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts @@ -7,6 +7,8 @@ import { HassDialog } from "../../../../dialogs/make-dialog-manager"; import { haStyle, haStyleDialog } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; import type { AutomationRenameDialog } from "./show-dialog-automation-rename"; +import "../../../../components/ha-textarea"; +import "../../../../components/ha-textfield"; @customElement("ha-dialog-automation-rename") class DialogAutomationRename extends LitElement implements HassDialog { @@ -51,7 +53,7 @@ class DialogAutomationRename extends LitElement implements HassDialog { > + ${this.hass.localize( + "ui.panel.config.automation.editor.disabled" + )} + + ${this.hass.localize( + "ui.panel.config.automation.editor.enable" + )} + + + ` + : ""} { + if (!this.hass || !this.stateObj) { + return; + } + await this.hass.callService("automation", "turn_on", { + entity_id: this.stateObj.entity_id, + }); + } + static get styles(): CSSResultGroup { return [ haStyle, @@ -220,6 +244,10 @@ export class HaBlueprintAutomationEditor extends LitElement { --settings-row-prefix-display: contents; border-top: 1px solid var(--divider-color); } + ha-alert { + margin-bottom: 16px; + display: block; + } `, ]; } diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 316dbe05d9..5abf51d1c6 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -3,6 +3,7 @@ import { mdiCheck, mdiContentDuplicate, mdiContentSave, + mdiDebugStepOver, mdiDelete, mdiDotsVertical, mdiInformationOutline, @@ -55,6 +56,7 @@ import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; import { showToast } from "../../../util/toast"; import "../ha-config-section"; +import { showAutomationModeDialog } from "./automation-mode-dialog/show-dialog-automation-mode"; import { showAutomationRenameDialog } from "./automation-rename-dialog/show-dialog-automation-rename"; import "./blueprint-automation-editor"; import "./manual-automation-editor"; @@ -161,11 +163,33 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { ` : ""} - + ${this.hass.localize("ui.panel.config.automation.editor.rename")} + ${this._config && !("use_blueprint" in this._config) + ? html` + + ${this.hass.localize( + "ui.panel.config.automation.editor.change_mode" + )} + + + ` + : ""} + - ${!stateObj || stateObj.state === "off" + ${stateObj?.state === "off" ? this.hass.localize("ui.panel.config.automation.editor.enable") : this.hass.localize("ui.panel.config.automation.editor.disable")} @@ -270,18 +294,20 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { ` : this._mode === "yaml" ? html` - ${!this.narrow + ${stateObj?.state === "off" ? html` - -
    - ${this._config.alias || - this.hass.localize( - "ui.panel.config.automation.editor.default_name" + + ${this.hass.localize( + "ui.panel.config.automation.editor.disabled" + )} + + ${this.hass.localize( + "ui.panel.config.automation.editor.enable" )} -
    -
    + + ` - : ``} + : ""} { + return new Promise((resolve) => { + showAutomationModeDialog(this, { + config: this._config!, + updateAutomation: (config) => { + this._config = config; + this._dirty = true; + this.requestUpdate(); + resolve(); + }, + onClose: () => resolve(), + }); + }); + } + private async _saveAutomation(): Promise { const id = this.automationId || String(Date.now()); if (!this._config!.alias) { diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index a3c7c36985..e6092cf251 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -1,21 +1,19 @@ import "@material/mwc-button/mwc-button"; -import { mdiHelpCircle, mdiRobot, mdiSort, mdiTextBoxEdit } from "@mdi/js"; +import { mdiHelpCircle, mdiSort, mdiTextBoxEdit } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/entity/ha-entity-toggle"; import "../../../components/ha-card"; -import "../../../components/ha-textarea"; -import "../../../components/ha-textfield"; import "../../../components/ha-icon-button"; +import "../../../components/ha-alert"; import { - AUTOMATION_DEFAULT_MODE, Condition, ManualAutomationConfig, Trigger, } from "../../../data/automation"; -import { Action, isMaxMode, MODES } from "../../../data/script"; +import { Action } from "../../../data/script"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; @@ -43,71 +41,20 @@ export class HaManualAutomationEditor extends LitElement { protected render() { return html` - - ${this.stateObj && this.stateObj.state === "off" - ? html`
    + ${this.stateObj?.state === "off" + ? html` + ${this.hass.localize( "ui.panel.config.automation.editor.disabled" )} -
    ` - : ""} - - -

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

    -
    - ${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.hass.localize( + "ui.panel.config.automation.editor.enable" + )} + + + ` + : ""}

    ${this.hass.localize( @@ -221,48 +168,6 @@ export class HaManualAutomationEditor extends LitElement { `; } - private _valueChanged(ev: CustomEvent) { - ev.stopPropagation(); - const target = ev.target as any; - const name = target.name; - if (!name) { - return; - } - let newVal = target.value; - if (target.type === "number") { - newVal = Number(newVal); - } - if ((this.config![name] || "") === newVal) { - return; - } - fireEvent(this, "value-changed", { - value: { ...this.config!, [name]: newVal }, - }); - } - - private _modeChanged(ev) { - const mode = ev.target.value; - - if ( - mode === this.config!.mode || - (!this.config!.mode && mode === MODES[0]) - ) { - return; - } - const value = { - ...this.config!, - mode, - }; - - if (!isMaxMode(mode)) { - delete value.max; - } - - fireEvent(this, "value-changed", { - value, - }); - } - private _triggerChanged(ev: CustomEvent): void { ev.stopPropagation(); fireEvent(this, "value-changed", { @@ -287,6 +192,15 @@ export class HaManualAutomationEditor extends LitElement { }); } + private async _enable(): Promise { + if (!this.hass || !this.stateObj) { + return; + } + await this.hass.callService("automation", "turn_on", { + entity_id: this.stateObj.entity_id, + }); + } + static get styles(): CSSResultGroup { return [ haStyle, @@ -300,10 +214,6 @@ export class HaManualAutomationEditor extends LitElement { .link-button-row { padding: 14px; } - ha-textarea, - ha-textfield { - display: block; - } p { margin-bottom: 0; @@ -320,6 +230,9 @@ export class HaManualAutomationEditor extends LitElement { display: flex; align-items: center; } + .header:first-child { + margin-top: -16px; + } .header .name { font-size: 20px; font-weight: 400; @@ -340,9 +253,6 @@ export class HaManualAutomationEditor extends LitElement { .card-content { padding: 16px; } - .card-content ha-textarea:first-child { - margin-top: -16px; - } .settings-icon { display: none; } diff --git a/src/translations/en.json b/src/translations/en.json index 2bfdcb136d..63dda9251d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1847,6 +1847,7 @@ "no_blueprints": "You don't have any blueprints", "no_inputs": "This blueprint doesn't have any inputs." }, + "change_mode": "Change mode", "modes": { "label": "Mode", "learn_more": "Learn about modes", From 8ee9655bd5643ae577870a3367137d91bb4389ef Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 5 Sep 2022 17:44:10 +0200 Subject: [PATCH 13/24] On larger screens, move traces button out of the overflow menu (#13597) --- .../config/automation/ha-automation-editor.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 5abf51d1c6..d1af4e2947 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -1,3 +1,4 @@ +import "@material/mwc-button"; import "@material/mwc-list/mwc-list-item"; import { mdiCheck, @@ -125,6 +126,21 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { "ui.panel.config.automation.editor.default_name" )} > + ${this._config?.id && !this.narrow + ? html` + + + ${this.hass.localize( + "ui.panel.config.automation.editor.show_trace" + )} + + + ` + : ""} - ${stateObj && this._config + ${stateObj && this._config && this.narrow ? html` ${this.hass.localize( @@ -659,6 +675,9 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { flex-direction: column; padding-bottom: 0; } + .trace-link { + text-decoration: none; + } manual-automation-editor, blueprint-automation-editor { margin: 0 auto; From 5842b10a10c6af6cdebe9e65c35e9973a7282e98 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Sep 2022 10:47:48 -0500 Subject: [PATCH 14/24] Handle logbook updates where the new records are in the middle of the old records (#13595) --- src/panels/logbook/ha-logbook.ts | 36 +++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/panels/logbook/ha-logbook.ts b/src/panels/logbook/ha-logbook.ts index f0885b4ca7..57ae5ac00a 100644 --- a/src/panels/logbook/ha-logbook.ts +++ b/src/panels/logbook/ha-logbook.ts @@ -377,16 +377,32 @@ export class HaLogbook extends LitElement { return; } const nonExpiredRecords = this._nonExpiredRecords(purgeBeforePythonTime); - this._logbookEntries = !nonExpiredRecords.length - ? // All existing entries expired - newEntries - : newEntries[0].when >= nonExpiredRecords[0].when - ? // The new records are newer than the old records - // append the old records to the end of the new records - newEntries.concat(nonExpiredRecords) - : // The new records are older than the old records - // append the new records to the end of the old records - nonExpiredRecords.concat(newEntries); + + // Entries are sorted in descending order with newest first. + if (!nonExpiredRecords.length) { + // We have no records left, so we can just replace the list + this._logbookEntries = newEntries; + } else if ( + newEntries[newEntries.length - 1].when > // oldest new entry + nonExpiredRecords[0].when // newest old entry + ) { + // The new records are newer than the old records + // append the old records to the end of the new records + this._logbookEntries = newEntries.concat(nonExpiredRecords); + } else if ( + nonExpiredRecords[nonExpiredRecords.length - 1].when > // oldest old entry + newEntries[0].when // newest new entry + ) { + // The new records are older than the old records + // append the new records to the end of the old records + this._logbookEntries = nonExpiredRecords.concat(newEntries); + } else { + // The new records are in the middle of the old records + // so we need to re-sort them + this._logbookEntries = nonExpiredRecords + .concat(newEntries) + .sort((a, b) => b.when - a.when); + } }; private _updateTraceContexts = throttle(async () => { From a57d7813e7aa004fa9aef024ba6771003d6e1376 Mon Sep 17 00:00:00 2001 From: chpego <38792705+chpego@users.noreply.github.com> Date: Mon, 5 Sep 2022 17:55:48 +0200 Subject: [PATCH 15/24] typo dialogs (#13598) --- gallery/src/pages/components/dialogs.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/src/pages/components/dialogs.markdown b/gallery/src/pages/components/dialogs.markdown index 69d6edf758..472265b091 100644 --- a/gallery/src/pages/components/dialogs.markdown +++ b/gallery/src/pages/components/dialogs.markdown @@ -1,5 +1,5 @@ --- -title: Dialgos +title: Dialogs subtitle: Dialogs provide important prompts in a user flow. --- From 1f003ae3be5d62a09f472c69571c19d3a30ae63e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 5 Sep 2022 18:18:32 +0200 Subject: [PATCH 16/24] Remove tabs from area and device pages (#13600) --- .../config/areas/ha-config-area-page.ts | 54 ++++---------- .../config/devices/ha-config-device-page.ts | 71 ++++++------------- 2 files changed, 38 insertions(+), 87 deletions(-) diff --git a/src/panels/config/areas/ha-config-area-page.ts b/src/panels/config/areas/ha-config-area-page.ts index 74aec4c709..c211dccd9b 100644 --- a/src/panels/config/areas/ha-config-area-page.ts +++ b/src/panels/config/areas/ha-config-area-page.ts @@ -45,13 +45,14 @@ 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 { HomeAssistant } from "../../../types"; import "../../logbook/ha-logbook"; -import { configSections } from "../ha-panel-config"; import { loadAreaRegistryDetailDialog, showAreaRegistryDetailDialog, } from "./show-dialog-area-registry-detail"; +import "../../../layouts/hass-error-screen"; +import "../../../layouts/hass-subpage"; declare type NameAndEntity = { name: string; @@ -66,11 +67,9 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { @property({ type: Boolean, reflect: true }) public narrow!: boolean; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide!: boolean; - @property() public showAdvanced!: boolean; - - @property() public route!: Route; + @property({ type: Boolean }) public showAdvanced!: boolean; @state() public _areas!: AreaRegistryEntry[]; @@ -242,43 +241,20 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { } return html` - - ${this.narrow - ? html` ${area.name} - ` - : ""} +
    - ${!this.narrow - ? html` -
    -

    - ${area.name} - -

    -
    - ` - : ""}
    ${area.picture ? html`
    @@ -504,7 +480,7 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { : ""}
    - + `; } diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index ddb82c4ca6..dae63448dd 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -60,12 +60,11 @@ import { import "../../../layouts/hass-error-screen"; import "../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../resources/styles"; -import type { HomeAssistant, Route } from "../../../types"; +import type { HomeAssistant } from "../../../types"; import { brandsUrl } from "../../../util/brands-url"; import { fileDownload } from "../../../util/file_download"; import "../../logbook/ha-logbook"; import "../ha-config-section"; -import { configSections } from "../ha-panel-config"; import "./device-detail/ha-device-entities-card"; import "./device-detail/ha-device-info-card"; import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation"; @@ -73,6 +72,7 @@ import { loadDeviceRegistryDetailDialog, showDeviceRegistryDetailDialog, } from "./device-registry-detail/show-dialog-device-registry-detail"; +import "../../../layouts/hass-subpage"; export interface EntityRegistryStateEntry extends EntityRegistryEntry { stateName?: string | null; @@ -96,23 +96,21 @@ export interface DeviceAlert { export class HaConfigDevicePage extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public devices!: DeviceRegistryEntry[]; + @property({ attribute: false }) public devices!: DeviceRegistryEntry[]; - @property() public entries!: ConfigEntry[]; + @property({ attribute: false }) public entries!: ConfigEntry[]; - @property() public entities!: EntityRegistryEntry[]; + @property({ attribute: false }) public entities!: EntityRegistryEntry[]; - @property() public areas!: AreaRegistryEntry[]; + @property({ attribute: false }) public areas!: AreaRegistryEntry[]; @property() public deviceId!: string; @property({ type: Boolean, reflect: true }) public narrow!: boolean; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide!: boolean; - @property() public showAdvanced!: boolean; - - @property() public route!: Route; + @property({ type: Boolean }) public showAdvanced!: boolean; @state() private _related?: RelatedResult; @@ -609,16 +607,12 @@ export class HaConfigDevicePage extends LitElement { : ""; return html` - - ${ - this.narrow - ? html` - ${deviceName} + - ` - : "" - }
    - `; + `; } private async _getDiagnosticButtons(requestId: number): Promise { From 28d11703fc514fcb1107852e9a40ca9a6f83c623 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 5 Sep 2022 18:18:58 +0200 Subject: [PATCH 17/24] Reorder overflow menu (#13596) --- .../config/automation/ha-automation-editor.ts | 28 ++++++-- .../automation/manual-automation-editor.ts | 67 ++++++++++--------- src/translations/en.json | 6 ++ 3 files changed, 66 insertions(+), 35 deletions(-) diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index d1af4e2947..739559533d 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -11,6 +11,7 @@ import { mdiPlay, mdiPlayCircleOutline, mdiRenameBox, + mdiSort, mdiStopCircleOutline, mdiTransitConnection, } from "@mdi/js"; @@ -25,7 +26,7 @@ import { PropertyValues, TemplateResult, } from "lit"; -import { property, state, query } from "lit/decorators"; +import { property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../../common/dom/fire_event"; import { navigate } from "../../../common/navigate"; @@ -61,6 +62,7 @@ import { showAutomationModeDialog } from "./automation-mode-dialog/show-dialog-a import { showAutomationRenameDialog } from "./automation-rename-dialog/show-dialog-automation-rename"; import "./blueprint-automation-editor"; import "./manual-automation-editor"; +import type { HaManualAutomationEditor } from "./manual-automation-editor"; declare global { interface HTMLElementTagNameMap { @@ -100,7 +102,10 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { @state() private _mode: "gui" | "yaml" = "gui"; - @query("ha-yaml-editor", true) private _editor?: HaYamlEditor; + @query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor; + + @query("manual-automation-editor") + private _manualEditor?: HaManualAutomationEditor; private _configSubscriptions: Record< string, @@ -206,6 +211,15 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { ` : ""} + + ${this.hass.localize("ui.panel.config.automation.editor.re_order")} + + + { - if (this._editor?.yaml) { - await copyToClipboard(this._editor.yaml); + if (this._yamlEditor?.yaml) { + await copyToClipboard(this._yamlEditor.yaml); showToast(this, { message: this.hass.localize("ui.common.copied_clipboard"), }); @@ -575,6 +589,12 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { this._mode = "yaml"; } + private _toggleReOrderMode() { + if (this._manualEditor) { + this._manualEditor.reOrderMode = !this._manualEditor.reOrderMode; + } + } + private async _promptAutomationAlias(): Promise { return new Promise((resolve) => { showAutomationRenameDialog(this, { diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index e6092cf251..340b9e33eb 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -1,13 +1,15 @@ import "@material/mwc-button/mwc-button"; -import { mdiHelpCircle, mdiSort, mdiTextBoxEdit } from "@mdi/js"; +import { mdiHelpCircle } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/entity/ha-entity-toggle"; import "../../../components/ha-card"; import "../../../components/ha-icon-button"; import "../../../components/ha-alert"; +import "../../../components/ha-textarea"; +import "../../../components/ha-textfield"; import { Condition, ManualAutomationConfig, @@ -33,11 +35,8 @@ export class HaManualAutomationEditor extends LitElement { @property({ attribute: false }) public stateObj?: HassEntity; - @state() private _reOrderMode = false; - - private _toggleReOrderMode() { - this._reOrderMode = !this._reOrderMode; - } + @property({ type: Boolean, reflect: true, attribute: "re-order-mode" }) + public reOrderMode = false; protected render() { return html` @@ -55,19 +54,31 @@ export class HaManualAutomationEditor extends LitElement { ` : ""} + ${this.reOrderMode + ? html` + + ${this.hass.localize( + "ui.panel.config.automation.editor.re_order_mode.description" + )} + + ${this.hass.localize( + "ui.panel.config.automation.editor.re_order_mode.exit" + )} + + + ` + : ""}

    -
    @@ -134,13 +138,6 @@ export class HaManualAutomationEditor extends LitElement { )}
    - `; } + private _exitReOrderMode() { + this.reOrderMode = !this.reOrderMode; + } + private _triggerChanged(ev: CustomEvent): void { ev.stopPropagation(); fireEvent(this, "value-changed", { @@ -270,6 +271,10 @@ export class HaManualAutomationEditor extends LitElement { border-top-right-radius: var(--ha-card-border-radius); border-top-left-radius: var(--ha-card-border-radius); } + ha-alert { + display: block; + margin-bottom: 16px; + } `, ]; } diff --git a/src/translations/en.json b/src/translations/en.json index 63dda9251d..c73f2ac15a 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1836,6 +1836,12 @@ "automation_settings": "Automation settings", "move_up": "Move up", "move_down": "Move down", + "re_order": "Re-order", + "re_order_mode": { + "title": "Re-order mode", + "description": "You are in re-order mode, you can re-order your triggers, conditions and actions.", + "exit": "Exit" + }, "description": { "label": "Description", "placeholder": "Optional description", From 847d163cc876f948435a4c137b94fab0aceebe5c Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 5 Sep 2022 18:26:32 +0200 Subject: [PATCH 18/24] Disable close on outside click for rename + mode dialogs (#13602) --- .../automation/automation-mode-dialog/dialog-automation-mode.ts | 1 + .../automation-rename-dialog/dialog-automation-rename.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/panels/config/automation/automation-mode-dialog/dialog-automation-mode.ts b/src/panels/config/automation/automation-mode-dialog/dialog-automation-mode.ts index a9af861658..270eaed01e 100644 --- a/src/panels/config/automation/automation-mode-dialog/dialog-automation-mode.ts +++ b/src/panels/config/automation/automation-mode-dialog/dialog-automation-mode.ts @@ -56,6 +56,7 @@ class DialogAutomationMode extends LitElement implements HassDialog { return html` Date: Mon, 5 Sep 2022 18:28:54 +0200 Subject: [PATCH 19/24] Add remove, delete, add and create guidelines (#13593) Co-authored-by: Philip Allgaier --- .../Text/remove-delete-add-create.markdown | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 gallery/src/pages/Text/remove-delete-add-create.markdown diff --git a/gallery/src/pages/Text/remove-delete-add-create.markdown b/gallery/src/pages/Text/remove-delete-add-create.markdown new file mode 100644 index 0000000000..395adc673c --- /dev/null +++ b/gallery/src/pages/Text/remove-delete-add-create.markdown @@ -0,0 +1,56 @@ +--- +title: When to use remove, delete, add and create +subtitle: The difference between remove/delete and add/create. +--- + +# Remove vs Delete +Remove and Delete are quite similar, but can be frustrating if used inconsistently. + +## Remove +Take away and set aside, but kept in existence. + +For example: +* Removing a user's permission +* Removing a user from a group +* Removing links between items +* Removing a widget +* Removing a link +* Removing an item from a cart + +## Delete +Erase, rendered nonexistent or nonrecoverable. + +For example: +* Deleting a field +* Deleting a value in a field +* Deleting a task +* Deleting a group +* Deleting a permission +* Deleting a calendar event + +# Add vs Create +In most cases, Create can be paired with Delete, and Add can be paired with Remove. + +## Add +An already-exisiting item. + +For example: +* Adding a permission to a user +* Adding a user to a group +* Adding links between items +* Adding a widget +* Adding a link +* Adding an item to a cart + +## Create +Something made from scratch. + +For example: +* Creating a new field +* Creating a new value in a field +* Creating a new task +* Creating a new group +* Creating a new permission +* Creating a new calendar event + +Based on this is [UX magazine article](https://uxmag.com/articles/ui-copy-remove-vs-delete2-banner). From f3449c4d9b772008436960ea18b421de1a60d2ac Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 5 Sep 2022 19:00:54 +0200 Subject: [PATCH 20/24] Add description to automation editor (#13603) --- .../config/automation/blueprint-automation-editor.ts | 6 ++++++ src/panels/config/automation/ha-automation-editor.ts | 2 +- src/panels/config/automation/manual-automation-editor.ts | 7 ++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/panels/config/automation/blueprint-automation-editor.ts b/src/panels/config/automation/blueprint-automation-editor.ts index 069376c4ba..cb69701510 100644 --- a/src/panels/config/automation/blueprint-automation-editor.ts +++ b/src/panels/config/automation/blueprint-automation-editor.ts @@ -64,6 +64,9 @@ export class HaBlueprintAutomationEditor extends LitElement { ` : ""} + ${this.config.description + ? html`

    ${this.config.description}

    ` + : ""} { const id = this.automationId || String(Date.now()); - if (!this._config!.alias) { + if (!this.automationId) { await this._promptAutomationAlias(); if (!this._config!.alias) { showAlertDialog(this, { diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index 340b9e33eb..ebe14f3e1d 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -73,6 +73,9 @@ export class HaManualAutomationEditor extends LitElement { ` : ""} + ${this.config.description + ? html`

    ${this.config.description}

    ` + : ""}

    ${this.hass.localize( @@ -215,7 +218,9 @@ export class HaManualAutomationEditor extends LitElement { .link-button-row { padding: 14px; } - + .description { + margin: 0; + } p { margin-bottom: 0; } From aa6ee0f6d264d8db435aee7c0050439e28b5f102 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 5 Sep 2022 19:01:20 +0200 Subject: [PATCH 21/24] Fix icon picker reset by hass update (#13587) * Fix icon picker reset by hass update * Update src/components/ha-icon-picker.ts Co-authored-by: Bram Kragten * Only update when not opened Co-authored-by: Bram Kragten --- src/components/ha-icon-picker.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ha-icon-picker.ts b/src/components/ha-icon-picker.ts index 871dc3e477..657953e816 100644 --- a/src/components/ha-icon-picker.ts +++ b/src/components/ha-icon-picker.ts @@ -1,4 +1,4 @@ -import { css, html, LitElement, TemplateResult } from "lit"; +import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; @@ -123,6 +123,10 @@ export class HaIconPicker extends LitElement { } } + protected shouldUpdate(changedProps: PropertyValues) { + return !this._opened || changedProps.has("_opened"); + } + private _valueChanged(ev: PolymerChangedEvent) { ev.stopPropagation(); this._setValue(ev.detail.value); From e510e5b3714f677394baa66bc4405461df472849 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 5 Sep 2022 19:36:09 +0200 Subject: [PATCH 22/24] Link logbook row to trace if available (#13599) * Link logbook row to trace if available * Update ha-logbook-renderer.ts --- src/panels/logbook/ha-logbook-renderer.ts | 145 ++++++++++++++-------- 1 file changed, 93 insertions(+), 52 deletions(-) diff --git a/src/panels/logbook/ha-logbook-renderer.ts b/src/panels/logbook/ha-logbook-renderer.ts index 81d4424974..c79bdc9e34 100644 --- a/src/panels/logbook/ha-logbook-renderer.ts +++ b/src/panels/logbook/ha-logbook-renderer.ts @@ -34,6 +34,8 @@ import { } from "../../resources/styles"; import { HomeAssistant } from "../../types"; import { brandsUrl } from "../../util/brands-url"; +import "../../components/ha-icon-next"; +import { navigate } from "../../common/navigate"; declare global { interface HASSDomEvents { @@ -156,8 +158,26 @@ class HaLogbookRenderer extends LitElement { }) : undefined; + const traceContext = + triggerDomains.includes(item.domain!) && + item.context_id! in this.traceContexts + ? this.traceContexts[item.context_id!] + : undefined; + + const hasTrace = traceContext !== undefined; + return html` -
    +
    ${index === 0 || (item?.when && previous?.when && @@ -186,15 +206,16 @@ class HaLogbookRenderer extends LitElement {
    + ${hasTrace ? html`` : ""}

    `; @@ -258,7 +261,8 @@ class HaLogbookRenderer extends LitElement { item: LogbookEntry, seenEntityIds: string[], domain?: string, - historicStateObj?: HassEntity + historicStateObj?: HassEntity, + noLink?: boolean ) { if (item.entity_id) { if (item.state) { @@ -291,7 +295,8 @@ class HaLogbookRenderer extends LitElement { ? stripEntityId(message, item.context_entity_id) : message, seenEntityIds, - undefined + undefined, + noLink ) : ""; } @@ -307,7 +312,8 @@ class HaLogbookRenderer extends LitElement { private _renderUnseenContextSourceEntity( item: LogbookEntry, - seenEntityIds: string[] + seenEntityIds: string[], + noLink: boolean ) { if ( !item.context_entity_id || @@ -320,11 +326,16 @@ class HaLogbookRenderer extends LitElement { // described event. return html` (${this._renderEntity( item.context_entity_id, - item.context_entity_id_name + item.context_entity_id_name, + noLink )})`; } - private _renderContextMessage(item: LogbookEntry, seenEntityIds: string[]) { + private _renderContextMessage( + item: LogbookEntry, + seenEntityIds: string[], + noLink: boolean + ) { // State change if (item.context_state) { const historicStateObj = @@ -337,7 +348,11 @@ class HaLogbookRenderer extends LitElement { return html`${this.hass.localize( "ui.components.logbook.triggered_by_state_of" )} - ${this._renderEntity(item.context_entity_id, item.context_entity_id_name)} + ${this._renderEntity( + item.context_entity_id, + item.context_entity_id_name, + noLink + )} ${historicStateObj ? localizeStateMessage( this.hass, @@ -379,11 +394,17 @@ class HaLogbookRenderer extends LitElement { ? "ui.components.logbook.triggered_by_automation" : "ui.components.logbook.triggered_by_script" )} - ${this._renderEntity(item.context_entity_id, item.context_entity_id_name)} + ${this._renderEntity( + item.context_entity_id, + item.context_entity_id_name, + noLink + )} ${item.context_message ? this._formatMessageWithPossibleEntity( contextTriggerSource, - seenEntityIds + seenEntityIds, + undefined, + noLink ) : ""}`; } @@ -394,14 +415,16 @@ class HaLogbookRenderer extends LitElement { ${this._formatMessageWithPossibleEntity( item.context_message, seenEntityIds, - item.context_entity_id + item.context_entity_id, + noLink )} - ${this._renderUnseenContextSourceEntity(item, seenEntityIds)}`; + ${this._renderUnseenContextSourceEntity(item, seenEntityIds, noLink)}`; } private _renderEntity( entityId: string | undefined, - entityName: string | undefined + entityName: string | undefined, + noLink?: boolean ) { const hasState = entityId && entityId in this.hass.states; const displayName = @@ -412,19 +435,22 @@ class HaLogbookRenderer extends LitElement { if (!hasState) { return displayName; } - return html``; + return noLink + ? displayName + : html``; } private _formatMessageWithPossibleEntity( message: string, seenEntities: string[], - possibleEntity?: string + possibleEntity?: string, + noLink?: boolean ) { // // As we are looking at a log(book), we are doing entity_id @@ -449,7 +475,8 @@ class HaLogbookRenderer extends LitElement { return html`${messageParts.join(" ")} ${this._renderEntity( entityId, - this.hass.states[entityId].attributes.friendly_name + this.hass.states[entityId].attributes.friendly_name, + noLink )} ${messageEnd.join(" ")}`; } @@ -475,7 +502,7 @@ class HaLogbookRenderer extends LitElement { message.length - possibleEntityName.length ); return html`${message} - ${this._renderEntity(possibleEntity, possibleEntityName)}`; + ${this._renderEntity(possibleEntity, possibleEntityName, noLink)}`; } } return message; @@ -494,8 +521,12 @@ class HaLogbookRenderer extends LitElement { }); } - private _close(): void { - setTimeout(() => fireEvent(this, "closed"), 500); + _handleClick(ev) { + if (!ev.currentTarget.traceLink) { + return; + } + navigate(ev.currentTarget.traceLink); + fireEvent(this, "closed"); } static get styles(): CSSResultGroup { @@ -520,10 +551,20 @@ class HaLogbookRenderer extends LitElement { padding: 8px 16px; box-sizing: border-box; border-top: 1px solid var(--divider-color); + justify-content: space-between; + align-items: center; } - .entry.no-entity, - .no-name .entry { + ha-icon-next { + color: var(--secondary-text-color); + } + + .clickable { + cursor: pointer; + } + + :not(.clickable) .entry.no-entity, + :not(.clickable) .no-name .entry { cursor: default; } From 8a7f35cee66e080bb30b9aff3b21a9c184488bd2 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 5 Sep 2022 19:38:51 +0200 Subject: [PATCH 23/24] Improve rename automation dialog (#13601) --- .../dialog-automation-rename.ts | 35 +++++++++++++++++-- .../config/automation/ha-automation-editor.ts | 17 ++------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts b/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts index ffcc401094..e97976e3f7 100644 --- a/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts +++ b/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts @@ -8,6 +8,7 @@ import { haStyle, haStyleDialog } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; import type { AutomationRenameDialog } from "./show-dialog-automation-rename"; import "../../../../components/ha-textarea"; +import "../../../../components/ha-alert"; import "../../../../components/ha-textfield"; @customElement("ha-dialog-automation-rename") @@ -16,6 +17,8 @@ class DialogAutomationRename extends LitElement implements HassDialog { @state() private _opened = false; + @state() private _error?: string; + private _params!: AutomationRenameDialog; private _newName?: string; @@ -25,7 +28,9 @@ class DialogAutomationRename extends LitElement implements HassDialog { public showDialog(params: AutomationRenameDialog): void { this._opened = true; this._params = params; - this._newName = params.config.alias || ""; + this._newName = + params.config.alias || + this.hass.localize("ui.panel.config.automation.editor.default_name"); this._newDescription = params.config.description || ""; } @@ -49,9 +54,20 @@ class DialogAutomationRename extends LitElement implements HassDialog { @closed=${this.closeDialog} .heading=${createCloseHeading( this.hass, - this.hass.localize("ui.panel.config.automation.editor.rename") + this.hass.localize( + this._params.config.alias + ? "ui.panel.config.automation.editor.rename" + : "ui.panel.config.automation.editor.save" + ) )} > + ${this._error + ? html`${this.hass.localize( + "ui.panel.config.automation.editor.missing_name" + )}` + : ""} @@ -82,7 +99,11 @@ class DialogAutomationRename extends LitElement implements HassDialog { ${this.hass.localize("ui.dialogs.generic.cancel")} - ${this.hass.localize("ui.panel.config.automation.editor.rename")} + ${this.hass.localize( + this._params.config.alias + ? "ui.panel.config.automation.editor.rename" + : "ui.panel.config.automation.editor.save" + )} `; @@ -99,6 +120,10 @@ class DialogAutomationRename extends LitElement implements HassDialog { } private _save(): void { + if (!this._newName) { + this._error = "Name is required"; + return; + } this._params.updateAutomation({ ...this._params.config, alias: this._newName, @@ -116,6 +141,10 @@ class DialogAutomationRename extends LitElement implements HassDialog { ha-textarea { display: block; } + ha-alert { + display: block; + margin-bottom: 16px; + } `, ]; } diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index bbe0c81d1f..936c45eb3c 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -298,7 +298,9 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { @subscribe-automation-config=${this._subscribeAutomationConfig} > ${this._errors - ? html`
    ${this._errors}
    ` + ? html` + ${this._errors} + ` : ""} ${this._mode === "gui" ? "use_blueprint" in this._config @@ -629,14 +631,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { const id = this.automationId || String(Date.now()); if (!this.automationId) { await this._promptAutomationAlias(); - if (!this._config!.alias) { - showAlertDialog(this, { - text: this.hass.localize( - "ui.panel.config.automation.editor.missing_name" - ), - }); - return; - } } this.hass!.callApi( @@ -681,11 +675,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { ha-card { overflow: hidden; } - .errors { - padding: 20px; - font-weight: bold; - color: var(--error-color); - } .content { padding-bottom: 20px; } From ccd617d68e3fd242dd1dd9cbcb15e4fb8bef7254 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 5 Sep 2022 19:39:30 +0200 Subject: [PATCH 24/24] Bumped version to 20220905.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 38b0d8ad8a..399da64a98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20220902.0" +version = "20220905.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md"