Compare commits

...
9 changed files with 178 additions and 28 deletions

No files matched your search

@@ -116,6 +116,7 @@ export class HaTracePathDetails extends LitElement {
showIndices: false,
select: () => undefined,
fixDuplicateIds: async () => undefined,
updateTriggerId: () => false,
},
});
+1
View File
@@ -709,6 +709,7 @@ export interface BaseSidebarConfig {
export interface TriggerSidebarConfig extends BaseSidebarConfig {
save: (value: Trigger) => void;
rename: () => void;
editId: () => void;
disable: () => void;
duplicate: () => void;
cut: () => void;
+11 -1
View File
@@ -95,7 +95,7 @@ class DialogBox extends DirtyStateProviderMixin<DialogBoxDirtyState>()(
>
<ha-dialog-header slot="header">
${
!confirmPrompt
!confirmPrompt || this._params.showCloseButton
? html`<slot name="headerNavigationIcon" slot="navigationIcon">
<ha-icon-button
data-dialog="close"
@@ -169,6 +169,13 @@ class DialogBox extends DirtyStateProviderMixin<DialogBoxDirtyState>()(
`
: nothing
}
${
this._params.prompt && this._params.textAfterInput
? html`<div class="after-input">
${this._params.textAfterInput}
</div>`
: nothing
}
</div>
<ha-dialog-footer slot="footer">
${
@@ -307,6 +314,9 @@ class DialogBox extends DirtyStateProviderMixin<DialogBoxDirtyState>()(
ha-input {
width: 100%;
}
.after-input {
margin-top: var(--ha-space-4);
}
.title {
font-weight: inherit;
font-size: inherit;
+3
View File
@@ -5,6 +5,8 @@ interface BaseDialogBoxParams {
/** Whether back closes the dialog. Off while a navigation is in flight. */
addHistory?: boolean;
confirmText?: string;
/** Show the close button for confirmations and prompts, which hide it by default. */
showCloseButton?: boolean;
text?: string | TemplateResult;
title?: string;
subtitle?: string;
@@ -26,6 +28,7 @@ export interface ConfirmationDialogParams extends BaseDialogBoxParams {
export interface PromptDialogParams extends BaseDialogBoxParams {
inputLabel?: string;
inputSuffix?: string;
textAfterInput?: string | TemplateResult;
dismissText?: string;
inputType?: string;
defaultValue?: string;
@@ -5,6 +5,7 @@ import {
mdiContentCut,
mdiContentPaste,
mdiDelete,
mdiIdentifier,
mdiPlayCircleOutline,
mdiPlaylistEdit,
mdiPlusCircleMultipleOutline,
@@ -137,23 +138,38 @@ export default class HaAutomationSidebarTrigger extends LitElement {
${
type !== "list"
? html`<ha-dropdown-item
slot="menu-items"
value="edit_note"
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
.path=${mdiCommentEditOutline}
></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.note.${(this.config.config as Exclude<Trigger, TriggerList>).note ? "edit" : "add"}`
)}
<span
class="shortcut-placeholder ${isMac ? "mac" : ""}"
></span>
</div>
</ha-dropdown-item>`
slot="menu-items"
value="edit_note"
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
.path=${mdiCommentEditOutline}
></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.note.${(this.config.config as Exclude<Trigger, TriggerList>).note ? "edit" : "add"}`
)}
<span
class="shortcut-placeholder ${isMac ? "mac" : ""}"
></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
slot="menu-items"
value="edit_id"
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiIdentifier}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id"
)}
<span
class="shortcut-placeholder ${isMac ? "mac" : ""}"
></span>
</div>
</ha-dropdown-item>`
: nothing
}
<wa-divider slot="menu-items"></wa-divider>
@@ -387,6 +403,9 @@ export default class HaAutomationSidebarTrigger extends LitElement {
case "edit_note":
this.config.editNote();
break;
case "edit_id":
this.config.editId();
break;
case "duplicate":
this.config.duplicate();
break;
@@ -4,6 +4,7 @@ import memoizeOne from "memoize-one";
import type {
AutomationConfig,
SidebarConfig,
Trigger,
TriggerCondition,
} from "../../../../data/automation";
import { ContextSubscriptionController } from "../../../../common/decorators/consume";
@@ -19,6 +20,7 @@ import {
getExplicitTriggerIds,
isGeneratedTriggerId,
makeDuplicateTriggerIdsUnique,
replaceLeafTrigger,
updateTriggerCondition,
} from "./automation-trigger-id";
import { preserveAutomationRowKeys } from "../ha-automation-sortable-list-mixin";
@@ -70,6 +72,7 @@ export class AutomationTriggerController implements ReactiveController {
showIndices: false,
select: this._selectTriggerIds,
fixDuplicateIds: this._fixDuplicateTriggerIds,
updateTriggerId: this._updateTriggerId,
},
});
_host.addController(this);
@@ -179,6 +182,23 @@ export class AutomationTriggerController implements ReactiveController {
}
};
/**
* Commit an explicit ID edit directly. Ordinary edits pass through cleanupRemovedIds,
* which would treat a changed generated ID as a deleted trigger and strip its references.
*/
private _updateTriggerId = (original: Trigger, updated: Trigger): boolean => {
const config = this._options.getConfig();
if (!config || !this._options.canEdit()) {
return false;
}
const triggers = replaceLeafTrigger(config.triggers, original, updated);
if (triggers === config.triggers) {
return false;
}
this._commit({ ...config, triggers });
return true;
};
private _commit(config: AutomationConfig) {
const current = this._options.getConfig();
if (config === current) {
@@ -47,6 +47,11 @@ export interface AutomationTriggerContext {
select: (condition: TriggerCondition, ids: string[]) => void;
/** Requests confirmation before migrating duplicate IDs throughout the automation. */
fixDuplicateIds: () => Promise<void>;
/**
* Replace a leaf trigger after an explicit ID edit, leaving references to the old ID
* dangling. Return false when the trigger is not one of the automation's triggers.
*/
updateTriggerId: (original: Trigger, updated: Trigger) => boolean;
}
export const automationTriggerContext =
@@ -163,6 +168,16 @@ const walkLeafTriggers = (
return callback(triggers);
};
/** Replace one leaf trigger by object identity; return the input unchanged if it is absent. */
export const replaceLeafTrigger = (
triggers: Trigger | Trigger[],
original: Trigger,
updated: Trigger
): Trigger | Trigger[] =>
walkLeafTriggers(triggers, (trigger) =>
trigger === original ? updated : trigger
);
/** Store only selected candidate IDs, matching options to their original trigger objects. */
export const assignGeneratedTriggerIds = (
triggers: Trigger | Trigger[],
@@ -9,6 +9,7 @@ import {
mdiContentPaste,
mdiDelete,
mdiDotsVertical,
mdiIdentifier,
mdiPlayCircleOutline,
mdiPlaylistEdit,
mdiPlusCircleMultipleOutline,
@@ -73,6 +74,7 @@ import {
showPromptDialog,
} from "../../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../../types";
import { documentationUrl } from "../../../../util/documentation-url";
import { isMac } from "../../../../util/is_mac";
import { showEditorToast } from "../editor-toast";
import "../ha-automation-editor-warning";
@@ -406,16 +408,24 @@ export default class HaAutomationTriggerRow extends LitElement {
${
type !== "list"
? html`<ha-dropdown-item value="edit_note">
<ha-svg-icon
slot="icon"
.path=${mdiCommentEditOutline}
></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
`ui.panel.config.automation.editor.note.${(this.trigger as Exclude<Trigger, TriggerList>).note ? "edit" : "add"}`
)
)}
</ha-dropdown-item>`
<ha-svg-icon
slot="icon"
.path=${mdiCommentEditOutline}
></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
`ui.panel.config.automation.editor.note.${(this.trigger as Exclude<Trigger, TriggerList>).note ? "edit" : "add"}`
)
)}
</ha-dropdown-item>
<ha-dropdown-item value="edit_id" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiIdentifier}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id"
)
)}
</ha-dropdown-item>`
: nothing
}
<wa-divider></wa-divider>
@@ -807,6 +817,7 @@ export default class HaAutomationTriggerRow extends LitElement {
this._renameTrigger();
},
editNote: this._editNoteTrigger,
editId: this._editIdTrigger,
toggleYamlMode: (yamlMode?: boolean) => {
if (yamlMode === undefined || yamlMode !== this._yamlMode) {
this._toggleYamlMode();
@@ -951,6 +962,69 @@ export default class HaAutomationTriggerRow extends LitElement {
}
};
private _editIdTrigger = async (): Promise<void> => {
if (isTriggerList(this.trigger)) return;
const trigger = this.trigger;
const result = await showPromptDialog(this, {
title: this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id_title"
),
textAfterInput: html`<ha-alert alert-type="info">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id_info",
{
link: html`<a
href=${documentationUrl(
this.hass,
"/docs/automation/trigger/#trigger-id"
)}
target="_blank"
rel="noreferrer"
>${this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id_learn_more"
)}</a
>`,
}
)}
</ha-alert>`,
inputLabel: this.hass.localize(
"ui.panel.config.automation.editor.triggers.id"
),
inputType: "string",
placeholder: this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id_placeholder"
),
defaultValue: trigger.id,
showCloseButton: true,
confirmText: this.hass.localize("ui.common.submit"),
});
if (result === null) {
return;
}
const id = result.trim() === "" ? undefined : result;
if (id === trigger.id) {
return;
}
const value = { ...trigger };
if (id === undefined) {
delete value.id;
} else {
value.id = id;
}
// Bypass the editor's generated ID cleanup so references to the old ID dangle
if (!this._triggers?.updateTriggerId(trigger, value)) {
fireEvent(this, "value-changed", {
value,
});
}
if (this._selected && this.optionsInSidebar) {
this.openSidebar(value); // refresh sidebar
} else if (this._yamlMode) {
this.triggerEditor?.yamlEditor?.setValue(value);
}
};
private _editNoteTrigger = async (): Promise<void> => {
if (isTriggerList(this.trigger)) return;
const trigger = this.trigger;
@@ -1105,6 +1179,9 @@ export default class HaAutomationTriggerRow extends LitElement {
case "edit_note":
this._editNoteTrigger();
break;
case "edit_id":
this._editIdTrigger();
break;
case "duplicate":
this._duplicateTrigger();
break;
+4
View File
@@ -5644,6 +5644,10 @@
"id": "Trigger ID",
"optional": "Optional",
"edit_id": "Edit ID",
"edit_id_title": "Edit trigger ID",
"edit_id_info": "Trigger IDs are generated automatically, so you usually don't need to set one yourself. {link}",
"edit_id_learn_more": "Learn more about trigger IDs",
"edit_id_placeholder": "e.g. sun_has_set",
"duplicate": "[%key:ui::common::duplicate%]",
"re_order": "Re-order",
"rename": "Rename",