mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-21 13:13:39 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ce30fdcaf | ||
|
|
d7a166a9ae | ||
|
|
e7aa0e06a6 | ||
|
|
3fdd9fd5e9 |
No files matched your search
@@ -116,6 +116,7 @@ export class HaTracePathDetails extends LitElement {
|
||||
showIndices: false,
|
||||
select: () => undefined,
|
||||
fixDuplicateIds: async () => undefined,
|
||||
updateTriggerId: () => false,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user