Add cut / copy / paste support to automation & script editor (#16488)

This commit is contained in:
karwosts 2023-05-24 02:47:33 -07:00 committed by GitHub
parent 31c89d70c5
commit 29846a168e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 349 additions and 57 deletions

View File

@ -379,3 +379,9 @@ export const testCondition = (
condition, condition,
variables, variables,
}); });
export type Clipboard = {
trigger?: Trigger;
condition?: Condition;
action?: Action;
};

View File

@ -3,6 +3,8 @@ import "@material/mwc-list/mwc-list-item";
import { import {
mdiCheck, mdiCheck,
mdiContentDuplicate, mdiContentDuplicate,
mdiContentCopy,
mdiContentCut,
mdiDelete, mdiDelete,
mdiDotsVertical, mdiDotsVertical,
mdiPlay, mdiPlay,
@ -31,6 +33,7 @@ import {
EntityRegistryEntry, EntityRegistryEntry,
subscribeEntityRegistry, subscribeEntityRegistry,
} from "../../../../data/entity_registry"; } from "../../../../data/entity_registry";
import { Clipboard } from "../../../../data/automation";
import { Action, getActionType } from "../../../../data/script"; import { Action, getActionType } from "../../../../data/script";
import { describeAction } from "../../../../data/script_i18n"; import { describeAction } from "../../../../data/script_i18n";
import { callExecuteScript } from "../../../../data/service"; import { callExecuteScript } from "../../../../data/service";
@ -57,7 +60,7 @@ import "./types/ha-automation-action-stop";
import "./types/ha-automation-action-wait_for_trigger"; import "./types/ha-automation-action-wait_for_trigger";
import "./types/ha-automation-action-wait_template"; import "./types/ha-automation-action-wait_template";
const getType = (action: Action | undefined) => { export const getType = (action: Action | undefined) => {
if (!action) { if (!action) {
return undefined; return undefined;
} }
@ -112,6 +115,8 @@ export default class HaAutomationActionRow extends LitElement {
@property({ type: Boolean }) public reOrderMode = false; @property({ type: Boolean }) public reOrderMode = false;
@property() public clipboard?: Clipboard;
@state() private _entityReg: EntityRegistryEntry[] = []; @state() private _entityReg: EntityRegistryEntry[] = [];
@state() private _warnings?: string[]; @state() private _warnings?: string[];
@ -214,6 +219,9 @@ export default class HaAutomationActionRow extends LitElement {
)} )}
<ha-svg-icon slot="graphic" .path=${mdiSort}></ha-svg-icon> <ha-svg-icon slot="graphic" .path=${mdiSort}></ha-svg-icon>
</mwc-list-item> </mwc-list-item>
<li divider role="separator"></li>
<mwc-list-item graphic="icon" .disabled=${this.disabled}> <mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate" "ui.panel.config.automation.editor.actions.duplicate"
@ -224,6 +232,26 @@ export default class HaAutomationActionRow extends LitElement {
></ha-svg-icon> ></ha-svg-icon>
</mwc-list-item> </mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentCopy}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentCut}
></ha-svg-icon>
</mwc-list-item>
<li divider role="separator"></li> <li divider role="separator"></li>
<mwc-list-item <mwc-list-item
@ -344,6 +372,7 @@ export default class HaAutomationActionRow extends LitElement {
narrow: this.narrow, narrow: this.narrow,
reOrderMode: this.reOrderMode, reOrderMode: this.reOrderMode,
disabled: this.disabled, disabled: this.disabled,
clipboard: this.clipboard,
})} })}
</div> </div>
`} `}
@ -378,17 +407,24 @@ export default class HaAutomationActionRow extends LitElement {
fireEvent(this, "duplicate"); fireEvent(this, "duplicate");
break; break;
case 4: case 4:
fireEvent(this, "set-clipboard", { action: this.action });
break;
case 5:
fireEvent(this, "set-clipboard", { action: this.action });
fireEvent(this, "value-changed", { value: null });
break;
case 6:
this._switchUiMode(); this._switchUiMode();
this.expand(); this.expand();
break; break;
case 5: case 7:
this._switchYamlMode(); this._switchYamlMode();
this.expand(); this.expand();
break; break;
case 6: case 8:
this._onDisable(); this._onDisable();
break; break;
case 7: case 9:
this._onDelete(); this._onDelete();
break; break;
} }

View File

@ -1,8 +1,21 @@
import "@material/mwc-button"; import "@material/mwc-button";
import type { ActionDetail } from "@material/mwc-list"; import type { ActionDetail } from "@material/mwc-list";
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js"; import {
mdiArrowDown,
mdiArrowUp,
mdiDrag,
mdiPlus,
mdiContentPaste,
} from "@mdi/js";
import deepClone from "deep-clone-simple"; import deepClone from "deep-clone-simple";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
} from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { repeat } from "lit/directives/repeat"; import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
@ -16,13 +29,14 @@ import type { HaSelect } from "../../../../components/ha-select";
import "../../../../components/ha-svg-icon"; import "../../../../components/ha-svg-icon";
import { ACTION_TYPES } from "../../../../data/action"; import { ACTION_TYPES } from "../../../../data/action";
import { Action } from "../../../../data/script"; import { Action } from "../../../../data/script";
import { Clipboard } from "../../../../data/automation";
import { sortableStyles } from "../../../../resources/ha-sortable-style"; import { sortableStyles } from "../../../../resources/ha-sortable-style";
import { import {
loadSortable, loadSortable,
SortableInstance, SortableInstance,
} from "../../../../resources/sortable.ondemand"; } from "../../../../resources/sortable.ondemand";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import "./ha-automation-action-row"; import { getType } from "./ha-automation-action-row";
import type HaAutomationActionRow from "./ha-automation-action-row"; import type HaAutomationActionRow from "./ha-automation-action-row";
import "./types/ha-automation-action-activate_scene"; import "./types/ha-automation-action-activate_scene";
import "./types/ha-automation-action-choose"; import "./types/ha-automation-action-choose";
@ -39,6 +53,8 @@ import "./types/ha-automation-action-stop";
import "./types/ha-automation-action-wait_for_trigger"; import "./types/ha-automation-action-wait_for_trigger";
import "./types/ha-automation-action-wait_template"; import "./types/ha-automation-action-wait_template";
const PASTE_VALUE = "__paste__";
@customElement("ha-automation-action") @customElement("ha-automation-action")
export default class HaAutomationAction extends LitElement { export default class HaAutomationAction extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -53,6 +69,8 @@ export default class HaAutomationAction extends LitElement {
@property({ type: Boolean }) public reOrderMode = false; @property({ type: Boolean }) public reOrderMode = false;
@property() public clipboard?: Clipboard;
private _focusLastActionOnChange = false; private _focusLastActionOnChange = false;
private _actionKeys = new WeakMap<Action, string>(); private _actionKeys = new WeakMap<Action, string>();
@ -95,6 +113,7 @@ export default class HaAutomationAction extends LitElement {
@duplicate=${this._duplicateAction} @duplicate=${this._duplicateAction}
@value-changed=${this._actionChanged} @value-changed=${this._actionChanged}
@re-order=${this._enterReOrderMode} @re-order=${this._enterReOrderMode}
.clipboard=${this.clipboard}
.hass=${this.hass} .hass=${this.hass}
> >
${this.reOrderMode ${this.reOrderMode
@ -139,6 +158,19 @@ export default class HaAutomationAction extends LitElement {
> >
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon> <ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button> </ha-button>
${this.clipboard?.action
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.paste"
)}
(${this.hass.localize(
`ui.panel.config.automation.editor.actions.type.${getType(
this.clipboard.action
)}.label`
)})
<ha-svg-icon slot="graphic" .path=${mdiContentPaste}></ha-svg-icon
></mwc-list-item>`
: nothing}
${this._processedTypes(this.hass.localize).map( ${this._processedTypes(this.hass.localize).map(
([opt, label, icon]) => html` ([opt, label, icon]) => html`
<mwc-list-item .value=${opt} graphic="icon"> <mwc-list-item .value=${opt} graphic="icon">
@ -221,13 +253,19 @@ export default class HaAutomationAction extends LitElement {
private _addAction(ev: CustomEvent<ActionDetail>) { private _addAction(ev: CustomEvent<ActionDetail>) {
const action = (ev.currentTarget as HaSelect).items[ev.detail.index].value; const action = (ev.currentTarget as HaSelect).items[ev.detail.index].value;
const elClass = customElements.get(
`ha-automation-action-${action}`
) as CustomElementConstructor & { defaultConfig: Action };
const actions = this.actions.concat({ let actions: Action[];
...elClass.defaultConfig, if (action === PASTE_VALUE) {
}); actions = this.actions.concat(deepClone(this.clipboard!.action));
} else {
const elClass = customElements.get(
`ha-automation-action-${action}`
) as CustomElementConstructor & { defaultConfig: Action };
actions = this.actions.concat({
...elClass.defaultConfig,
});
}
this._focusLastActionOnChange = true; this._focusLastActionOnChange = true;
fireEvent(this, "value-changed", { value: actions }); fireEvent(this, "value-changed", { value: actions });
} }

View File

@ -5,7 +5,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
import { ensureArray } from "../../../../../common/array/ensure-array"; import { ensureArray } from "../../../../../common/array/ensure-array";
import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-button"; import "../../../../../components/ha-button";
import { Condition } from "../../../../../data/automation"; import { Condition, Clipboard } from "../../../../../data/automation";
import { Action, ChooseAction } from "../../../../../data/script"; import { Action, ChooseAction } from "../../../../../data/script";
import { haStyle } from "../../../../../resources/styles"; import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
@ -23,6 +23,8 @@ export class HaChooseAction extends LitElement implements ActionElement {
@state() private _showDefault = false; @state() private _showDefault = false;
@property() public clipboard?: Clipboard;
public static get defaultConfig() { public static get defaultConfig() {
return { choose: [{ conditions: [], sequence: [] }] }; return { choose: [{ conditions: [], sequence: [] }] };
} }
@ -63,6 +65,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
.hass=${this.hass} .hass=${this.hass}
.idx=${idx} .idx=${idx}
@value-changed=${this._conditionChanged} @value-changed=${this._conditionChanged}
.clipboard=${this.clipboard}
></ha-automation-condition> ></ha-automation-condition>
<h3> <h3>
${this.hass.localize( ${this.hass.localize(
@ -77,6 +80,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
.hass=${this.hass} .hass=${this.hass}
.idx=${idx} .idx=${idx}
@value-changed=${this._actionChanged} @value-changed=${this._actionChanged}
.clipboard=${this.clipboard}
></ha-automation-action> ></ha-automation-action>
</div> </div>
</ha-card>` </ha-card>`
@ -105,6 +109,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
.disabled=${this.disabled} .disabled=${this.disabled}
@value-changed=${this._defaultChanged} @value-changed=${this._defaultChanged}
.hass=${this.hass} .hass=${this.hass}
.clipboard=${this.clipboard}
></ha-automation-action> ></ha-automation-action>
` `
: html`<div class="link-button-row"> : html`<div class="link-button-row">

View File

@ -6,7 +6,7 @@ import { stringCompare } from "../../../../../common/string/compare";
import type { LocalizeFunc } from "../../../../../common/translations/localize"; import type { LocalizeFunc } from "../../../../../common/translations/localize";
import "../../../../../components/ha-select"; import "../../../../../components/ha-select";
import type { HaSelect } from "../../../../../components/ha-select"; import type { HaSelect } from "../../../../../components/ha-select";
import type { Condition } from "../../../../../data/automation"; import type { Condition, Clipboard } from "../../../../../data/automation";
import { CONDITION_TYPES } from "../../../../../data/condition"; import { CONDITION_TYPES } from "../../../../../data/condition";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
import "../../condition/ha-automation-condition-editor"; import "../../condition/ha-automation-condition-editor";
@ -20,6 +20,8 @@ export class HaConditionAction extends LitElement implements ActionElement {
@property() public action!: Condition; @property() public action!: Condition;
@property() public clipboard?: Clipboard;
public static get defaultConfig() { public static get defaultConfig() {
return { condition: "state" }; return { condition: "state" };
} }
@ -49,6 +51,7 @@ export class HaConditionAction extends LitElement implements ActionElement {
.disabled=${this.disabled} .disabled=${this.disabled}
.hass=${this.hass} .hass=${this.hass}
@value-changed=${this._conditionChanged} @value-changed=${this._conditionChanged}
.clipboard=${this.clipboard}
></ha-automation-condition-editor> ></ha-automation-condition-editor>
`; `;
} }

View File

@ -2,6 +2,7 @@ import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import { Action, IfAction } from "../../../../../data/script"; import { Action, IfAction } from "../../../../../data/script";
import type { Clipboard } from "../../../../../data/automation";
import { haStyle } from "../../../../../resources/styles"; import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
import type { Condition } from "../../../../lovelace/common/validate-condition"; import type { Condition } from "../../../../lovelace/common/validate-condition";
@ -19,6 +20,8 @@ export class HaIfAction extends LitElement implements ActionElement {
@property({ type: Boolean }) public reOrderMode = false; @property({ type: Boolean }) public reOrderMode = false;
@property() public clipboard?: Clipboard;
@state() private _showElse = false; @state() private _showElse = false;
public static get defaultConfig() { public static get defaultConfig() {
@ -43,6 +46,7 @@ export class HaIfAction extends LitElement implements ActionElement {
.reOrderMode=${this.reOrderMode} .reOrderMode=${this.reOrderMode}
.disabled=${this.disabled} .disabled=${this.disabled}
@value-changed=${this._ifChanged} @value-changed=${this._ifChanged}
.clipboard=${this.clipboard}
.hass=${this.hass} .hass=${this.hass}
></ha-automation-condition> ></ha-automation-condition>
@ -57,6 +61,7 @@ export class HaIfAction extends LitElement implements ActionElement {
.reOrderMode=${this.reOrderMode} .reOrderMode=${this.reOrderMode}
.disabled=${this.disabled} .disabled=${this.disabled}
@value-changed=${this._thenChanged} @value-changed=${this._thenChanged}
.clipboard=${this.clipboard}
.hass=${this.hass} .hass=${this.hass}
></ha-automation-action> ></ha-automation-action>
${this._showElse || action.else ${this._showElse || action.else

View File

@ -2,6 +2,7 @@ import { CSSResultGroup, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import { Action, ParallelAction } from "../../../../../data/script"; import { Action, ParallelAction } from "../../../../../data/script";
import type { Clipboard } from "../../../../../data/automation";
import { haStyle } from "../../../../../resources/styles"; import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
import "../ha-automation-action"; import "../ha-automation-action";
@ -18,6 +19,8 @@ export class HaParallelAction extends LitElement implements ActionElement {
@property({ type: Boolean }) public reOrderMode = false; @property({ type: Boolean }) public reOrderMode = false;
@property() public clipboard?: Clipboard;
public static get defaultConfig() { public static get defaultConfig() {
return { return {
parallel: [], parallel: [],
@ -34,6 +37,7 @@ export class HaParallelAction extends LitElement implements ActionElement {
.reOrderMode=${this.reOrderMode} .reOrderMode=${this.reOrderMode}
.disabled=${this.disabled} .disabled=${this.disabled}
@value-changed=${this._actionsChanged} @value-changed=${this._actionsChanged}
.clipboard=${this.clipboard}
.hass=${this.hass} .hass=${this.hass}
></ha-automation-action> ></ha-automation-action>
`; `;

View File

@ -8,6 +8,7 @@ import {
UntilRepeat, UntilRepeat,
WhileRepeat, WhileRepeat,
} from "../../../../../data/script"; } from "../../../../../data/script";
import type { Clipboard } from "../../../../../data/automation";
import { haStyle } from "../../../../../resources/styles"; import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
import type { Condition } from "../../../../lovelace/common/validate-condition"; import type { Condition } from "../../../../lovelace/common/validate-condition";
@ -29,6 +30,8 @@ export class HaRepeatAction extends LitElement implements ActionElement {
@property({ type: Boolean }) public reOrderMode = false; @property({ type: Boolean }) public reOrderMode = false;
@property() public clipboard?: Clipboard;
public static get defaultConfig() { public static get defaultConfig() {
return { repeat: { count: 2, sequence: [] } }; return { repeat: { count: 2, sequence: [] } };
} }
@ -82,6 +85,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
.hass=${this.hass} .hass=${this.hass}
.disabled=${this.disabled} .disabled=${this.disabled}
@value-changed=${this._conditionChanged} @value-changed=${this._conditionChanged}
.clipboard=${this.clipboard}
></ha-automation-condition>` ></ha-automation-condition>`
: type === "until" : type === "until"
? html` <h3> ? html` <h3>
@ -95,6 +99,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
.hass=${this.hass} .hass=${this.hass}
.disabled=${this.disabled} .disabled=${this.disabled}
@value-changed=${this._conditionChanged} @value-changed=${this._conditionChanged}
.clipboard=${this.clipboard}
></ha-automation-condition>` ></ha-automation-condition>`
: ""} : ""}
</div> </div>
@ -109,6 +114,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
.reOrderMode=${this.reOrderMode} .reOrderMode=${this.reOrderMode}
.disabled=${this.disabled} .disabled=${this.disabled}
@value-changed=${this._actionChanged} @value-changed=${this._actionChanged}
.clipboard=${this.clipboard}
.hass=${this.hass} .hass=${this.hass}
></ha-automation-action> ></ha-automation-action>
`; `;

View File

@ -4,6 +4,7 @@ import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-formfield"; import "../../../../../components/ha-formfield";
import { WaitForTriggerAction } from "../../../../../data/script"; import { WaitForTriggerAction } from "../../../../../data/script";
import type { Clipboard } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
import "../../trigger/ha-automation-trigger"; import "../../trigger/ha-automation-trigger";
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row"; import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
@ -25,6 +26,8 @@ export class HaWaitForTriggerAction
@property({ type: Boolean }) public reOrderMode = false; @property({ type: Boolean }) public reOrderMode = false;
@property() public clipboard?: Clipboard;
public static get defaultConfig() { public static get defaultConfig() {
return { wait_for_trigger: [] }; return { wait_for_trigger: [] };
} }
@ -62,6 +65,7 @@ export class HaWaitForTriggerAction
.name=${"wait_for_trigger"} .name=${"wait_for_trigger"}
.reOrderMode=${this.reOrderMode} .reOrderMode=${this.reOrderMode}
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
.clipboard=${this.clipboard}
></ha-automation-trigger> ></ha-automation-trigger>
`; `;
} }

View File

@ -4,7 +4,7 @@ import memoizeOne from "memoize-one";
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-yaml-editor"; import "../../../../components/ha-yaml-editor";
import type { Condition } from "../../../../data/automation"; import type { Condition, Clipboard } from "../../../../data/automation";
import { expandConditionWithShorthand } from "../../../../data/automation"; import { expandConditionWithShorthand } from "../../../../data/automation";
import { haStyle } from "../../../../resources/styles"; import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
@ -32,6 +32,8 @@ export default class HaAutomationConditionEditor extends LitElement {
@property({ type: Boolean }) public reOrderMode = false; @property({ type: Boolean }) public reOrderMode = false;
@property() public clipboard?: Clipboard;
private _processedCondition = memoizeOne((condition) => private _processedCondition = memoizeOne((condition) =>
expandConditionWithShorthand(condition) expandConditionWithShorthand(condition)
); );
@ -70,6 +72,7 @@ export default class HaAutomationConditionEditor extends LitElement {
condition: condition, condition: condition,
reOrderMode: this.reOrderMode, reOrderMode: this.reOrderMode,
disabled: this.disabled, disabled: this.disabled,
clipboard: this.clipboard,
} }
)} )}
</div> </div>

View File

@ -3,6 +3,8 @@ import "@material/mwc-list/mwc-list-item";
import { import {
mdiCheck, mdiCheck,
mdiContentDuplicate, mdiContentDuplicate,
mdiContentCopy,
mdiContentCut,
mdiDelete, mdiDelete,
mdiDotsVertical, mdiDotsVertical,
mdiFlask, mdiFlask,
@ -22,6 +24,7 @@ import "../../../../components/ha-card";
import "../../../../components/ha-expansion-panel"; import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button"; import "../../../../components/ha-icon-button";
import { Condition, testCondition } from "../../../../data/automation"; import { Condition, testCondition } from "../../../../data/automation";
import type { Clipboard } from "../../../../data/automation";
import { describeCondition } from "../../../../data/automation_i18n"; import { describeCondition } from "../../../../data/automation_i18n";
import { CONDITION_TYPES } from "../../../../data/condition"; import { CONDITION_TYPES } from "../../../../data/condition";
import { validateConfig } from "../../../../data/config"; import { validateConfig } from "../../../../data/config";
@ -77,6 +80,8 @@ export default class HaAutomationConditionRow extends LitElement {
@property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public disabled = false;
@property() public clipboard?: Clipboard;
@state() private _yamlMode = false; @state() private _yamlMode = false;
@state() private _warnings?: string[]; @state() private _warnings?: string[];
@ -149,6 +154,8 @@ export default class HaAutomationConditionRow extends LitElement {
<ha-svg-icon slot="graphic" .path=${mdiSort}></ha-svg-icon> <ha-svg-icon slot="graphic" .path=${mdiSort}></ha-svg-icon>
</mwc-list-item> </mwc-list-item>
<li divider role="separator"></li>
<mwc-list-item graphic="icon" .disabled=${this.disabled}> <mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate" "ui.panel.config.automation.editor.actions.duplicate"
@ -159,6 +166,26 @@ export default class HaAutomationConditionRow extends LitElement {
></ha-svg-icon> ></ha-svg-icon>
</mwc-list-item> </mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentCopy}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentCut}
></ha-svg-icon>
</mwc-list-item>
<li divider role="separator"></li> <li divider role="separator"></li>
<mwc-list-item graphic="icon"> <mwc-list-item graphic="icon">
@ -255,6 +282,7 @@ export default class HaAutomationConditionRow extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.condition=${this.condition} .condition=${this.condition}
.reOrderMode=${this.reOrderMode} .reOrderMode=${this.reOrderMode}
.clipboard=${this.clipboard}
></ha-automation-condition-editor> ></ha-automation-condition-editor>
</div> </div>
</ha-expansion-panel> </ha-expansion-panel>
@ -307,17 +335,24 @@ export default class HaAutomationConditionRow extends LitElement {
fireEvent(this, "duplicate"); fireEvent(this, "duplicate");
break; break;
case 4: case 4:
fireEvent(this, "set-clipboard", { condition: this.condition });
break;
case 5:
fireEvent(this, "set-clipboard", { condition: this.condition });
fireEvent(this, "value-changed", { value: null });
break;
case 6:
this._switchUiMode(); this._switchUiMode();
this.expand(); this.expand();
break; break;
case 5: case 7:
this._switchYamlMode(); this._switchYamlMode();
this.expand(); this.expand();
break; break;
case 6: case 8:
this._onDisable(); this._onDisable();
break; break;
case 7: case 9:
this._onDelete(); this._onDelete();
break; break;
} }

View File

@ -1,6 +1,12 @@
import "@material/mwc-button"; import "@material/mwc-button";
import type { ActionDetail } from "@material/mwc-list"; import type { ActionDetail } from "@material/mwc-list";
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js"; import {
mdiArrowDown,
mdiArrowUp,
mdiDrag,
mdiPlus,
mdiContentPaste,
} from "@mdi/js";
import deepClone from "deep-clone-simple"; import deepClone from "deep-clone-simple";
import { import {
css, css,
@ -18,7 +24,7 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-button"; import "../../../../components/ha-button";
import "../../../../components/ha-button-menu"; import "../../../../components/ha-button-menu";
import "../../../../components/ha-svg-icon"; import "../../../../components/ha-svg-icon";
import type { Condition } from "../../../../data/automation"; import type { Condition, Clipboard } from "../../../../data/automation";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import "./ha-automation-condition-row"; import "./ha-automation-condition-row";
import type HaAutomationConditionRow from "./ha-automation-condition-row"; import type HaAutomationConditionRow from "./ha-automation-condition-row";
@ -44,6 +50,8 @@ import "./types/ha-automation-condition-time";
import "./types/ha-automation-condition-trigger"; import "./types/ha-automation-condition-trigger";
import "./types/ha-automation-condition-zone"; import "./types/ha-automation-condition-zone";
const PASTE_VALUE = "__paste__";
@customElement("ha-automation-condition") @customElement("ha-automation-condition")
export default class HaAutomationCondition extends LitElement { export default class HaAutomationCondition extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -56,6 +64,8 @@ export default class HaAutomationCondition extends LitElement {
@property({ type: Boolean }) public reOrderMode = false; @property({ type: Boolean }) public reOrderMode = false;
@property() public clipboard?: Clipboard;
private _focusLastConditionOnChange = false; private _focusLastConditionOnChange = false;
private _conditionKeys = new WeakMap<Condition, string>(); private _conditionKeys = new WeakMap<Condition, string>();
@ -147,6 +157,7 @@ export default class HaAutomationCondition extends LitElement {
@move-condition=${this._move} @move-condition=${this._move}
@value-changed=${this._conditionChanged} @value-changed=${this._conditionChanged}
@re-order=${this._enterReOrderMode} @re-order=${this._enterReOrderMode}
.clipboard=${this.clipboard}
.hass=${this.hass} .hass=${this.hass}
> >
${this.reOrderMode ${this.reOrderMode
@ -191,6 +202,17 @@ export default class HaAutomationCondition extends LitElement {
> >
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon> <ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button> </ha-button>
${this.clipboard?.condition
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.paste"
)}
(${this.hass.localize(
`ui.panel.config.automation.editor.conditions.type.${this.clipboard.condition.condition}.label`
)})
<ha-svg-icon slot="graphic" .path=${mdiContentPaste}></ha-svg-icon
></mwc-list-item>`
: nothing}
${this._processedTypes(this.hass.localize).map( ${this._processedTypes(this.hass.localize).map(
([opt, label, icon]) => html` ([opt, label, icon]) => html`
<mwc-list-item .value=${opt} graphic="icon"> <mwc-list-item .value=${opt} graphic="icon">
@ -251,19 +273,25 @@ export default class HaAutomationCondition extends LitElement {
} }
private _addCondition(ev: CustomEvent<ActionDetail>) { private _addCondition(ev: CustomEvent<ActionDetail>) {
const condition = (ev.currentTarget as HaSelect).items[ev.detail.index] const value = (ev.currentTarget as HaSelect).items[ev.detail.index].value;
.value as Condition["condition"];
const elClass = customElements.get( let conditions: Condition[];
`ha-automation-condition-${condition}` if (value === PASTE_VALUE) {
) as CustomElementConstructor & { conditions = this.conditions.concat(deepClone(this.clipboard!.condition));
defaultConfig: Omit<Condition, "condition">; } else {
}; const condition = value as Condition["condition"];
const conditions = this.conditions.concat({ const elClass = customElements.get(
condition: condition as any, `ha-automation-condition-${condition}`
...elClass.defaultConfig, ) as CustomElementConstructor & {
}); defaultConfig: Omit<Condition, "condition">;
};
conditions = this.conditions.concat({
condition: condition as any,
...elClass.defaultConfig,
});
}
this._focusLastConditionOnChange = true; this._focusLastConditionOnChange = true;
fireEvent(this, "value-changed", { value: conditions }); fireEvent(this, "value-changed", { value: conditions });
} }

View File

@ -1,7 +1,10 @@
import { html, LitElement } from "lit"; import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import type { LogicalCondition } from "../../../../../data/automation"; import type {
LogicalCondition,
Clipboard,
} from "../../../../../data/automation";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
import "../ha-automation-condition"; import "../ha-automation-condition";
import type { ConditionElement } from "../ha-automation-condition-row"; import type { ConditionElement } from "../ha-automation-condition-row";
@ -16,6 +19,8 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
@property({ type: Boolean }) public reOrderMode = false; @property({ type: Boolean }) public reOrderMode = false;
@property() public clipboard?: Clipboard;
public static get defaultConfig() { public static get defaultConfig() {
return { return {
conditions: [], conditions: [],
@ -30,6 +35,7 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
.hass=${this.hass} .hass=${this.hass}
.disabled=${this.disabled} .disabled=${this.disabled}
.clipboard=${this.clipboard}
.reOrderMode=${this.reOrderMode} .reOrderMode=${this.reOrderMode}
></ha-automation-condition> ></ha-automation-condition>
`; `;

View File

@ -46,7 +46,10 @@ import {
saveAutomationConfig, saveAutomationConfig,
showAutomationEditor, showAutomationEditor,
triggerAutomationActions, triggerAutomationActions,
Trigger,
Condition,
} from "../../../data/automation"; } from "../../../data/automation";
import { Action } from "../../../data/script";
import { fetchEntityRegistry } from "../../../data/entity_registry"; import { fetchEntityRegistry } from "../../../data/entity_registry";
import { import {
showAlertDialog, showAlertDialog,
@ -76,6 +79,11 @@ declare global {
"ui-mode-not-available": Error; "ui-mode-not-available": Error;
duplicate: undefined; duplicate: undefined;
"re-order": undefined; "re-order": undefined;
"set-clipboard": {
trigger?: Trigger;
condition?: Condition;
action?: Action;
};
} }
} }

View File

@ -2,7 +2,8 @@ import "@material/mwc-button/mwc-button";
import { mdiHelpCircle } from "@mdi/js"; import { mdiHelpCircle } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement } from "lit"; import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import deepClone from "deep-clone-simple";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
@ -10,6 +11,7 @@ import {
Condition, Condition,
ManualAutomationConfig, ManualAutomationConfig,
Trigger, Trigger,
Clipboard,
} from "../../../data/automation"; } from "../../../data/automation";
import { Action } from "../../../data/script"; import { Action } from "../../../data/script";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
@ -33,6 +35,8 @@ export class HaManualAutomationEditor extends LitElement {
@property({ attribute: false }) public stateObj?: HassEntity; @property({ attribute: false }) public stateObj?: HassEntity;
@state() private _clipboard: Clipboard = {};
protected render() { protected render() {
return html` return html`
${this.disabled ${this.disabled
@ -91,6 +95,8 @@ export class HaManualAutomationEditor extends LitElement {
@value-changed=${this._triggerChanged} @value-changed=${this._triggerChanged}
.hass=${this.hass} .hass=${this.hass}
.disabled=${this.disabled} .disabled=${this.disabled}
@set-clipboard=${this._setClipboard}
.clipboard=${this._clipboard}
></ha-automation-trigger> ></ha-automation-trigger>
<div class="header"> <div class="header">
@ -120,6 +126,8 @@ export class HaManualAutomationEditor extends LitElement {
@value-changed=${this._conditionChanged} @value-changed=${this._conditionChanged}
.hass=${this.hass} .hass=${this.hass}
.disabled=${this.disabled} .disabled=${this.disabled}
@set-clipboard=${this._setClipboard}
.clipboard=${this._clipboard}
></ha-automation-condition> ></ha-automation-condition>
<div class="header"> <div class="header">
@ -152,6 +160,8 @@ export class HaManualAutomationEditor extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}
.disabled=${this.disabled} .disabled=${this.disabled}
@set-clipboard=${this._setClipboard}
.clipboard=${this._clipboard}
></ha-automation-action> ></ha-automation-action>
`; `;
} }
@ -163,6 +173,11 @@ export class HaManualAutomationEditor extends LitElement {
}); });
} }
private _setClipboard(ev: CustomEvent): void {
ev.stopPropagation();
this._clipboard = { ...this._clipboard, ...deepClone(ev.detail) };
}
private _conditionChanged(ev: CustomEvent): void { private _conditionChanged(ev: CustomEvent): void {
ev.stopPropagation(); ev.stopPropagation();
fireEvent(this, "value-changed", { fireEvent(this, "value-changed", {

View File

@ -3,6 +3,8 @@ import "@material/mwc-list/mwc-list-item";
import { import {
mdiCheck, mdiCheck,
mdiContentDuplicate, mdiContentDuplicate,
mdiContentCopy,
mdiContentCut,
mdiDelete, mdiDelete,
mdiDotsVertical, mdiDotsVertical,
mdiIdentifier, mdiIdentifier,
@ -166,6 +168,18 @@ export default class HaAutomationTriggerRow extends LitElement {
<ha-svg-icon slot="graphic" .path=${mdiSort}></ha-svg-icon> <ha-svg-icon slot="graphic" .path=${mdiSort}></ha-svg-icon>
</mwc-list-item> </mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiIdentifier}
></ha-svg-icon>
</mwc-list-item>
<li divider role="separator"></li>
<mwc-list-item graphic="icon" .disabled=${this.disabled}> <mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.triggers.duplicate" "ui.panel.config.automation.editor.triggers.duplicate"
@ -178,11 +192,21 @@ export default class HaAutomationTriggerRow extends LitElement {
<mwc-list-item graphic="icon" .disabled=${this.disabled}> <mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id" "ui.panel.config.automation.editor.triggers.copy"
)} )}
<ha-svg-icon <ha-svg-icon
slot="graphic" slot="graphic"
.path=${mdiIdentifier} .path=${mdiContentCopy}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentCut}
></ha-svg-icon> ></ha-svg-icon>
</mwc-list-item> </mwc-list-item>
@ -427,24 +451,31 @@ export default class HaAutomationTriggerRow extends LitElement {
fireEvent(this, "re-order"); fireEvent(this, "re-order");
break; break;
case 2: case 2:
fireEvent(this, "duplicate");
break;
case 3:
this._requestShowId = true; this._requestShowId = true;
this.expand(); this.expand();
break; break;
case 3:
fireEvent(this, "duplicate");
break;
case 4: case 4:
fireEvent(this, "set-clipboard", { trigger: this.trigger });
break;
case 5:
fireEvent(this, "set-clipboard", { trigger: this.trigger });
fireEvent(this, "value-changed", { value: null });
break;
case 6:
this._switchUiMode(); this._switchUiMode();
this.expand(); this.expand();
break; break;
case 5: case 7:
this._switchYamlMode(); this._switchYamlMode();
this.expand(); this.expand();
break; break;
case 6: case 8:
this._onDisable(); this._onDisable();
break; break;
case 7: case 9:
this._onDelete(); this._onDelete();
break; break;
} }

View File

@ -1,8 +1,21 @@
import "@material/mwc-button"; import "@material/mwc-button";
import type { ActionDetail } from "@material/mwc-list"; import type { ActionDetail } from "@material/mwc-list";
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js"; import {
mdiArrowDown,
mdiArrowUp,
mdiDrag,
mdiPlus,
mdiContentPaste,
} from "@mdi/js";
import deepClone from "deep-clone-simple"; import deepClone from "deep-clone-simple";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
} from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { repeat } from "lit/directives/repeat"; import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
@ -14,7 +27,7 @@ import "../../../../components/ha-button-menu";
import "../../../../components/ha-button"; import "../../../../components/ha-button";
import type { HaSelect } from "../../../../components/ha-select"; import type { HaSelect } from "../../../../components/ha-select";
import "../../../../components/ha-svg-icon"; import "../../../../components/ha-svg-icon";
import { Trigger } from "../../../../data/automation"; import { Trigger, Clipboard } from "../../../../data/automation";
import { TRIGGER_TYPES } from "../../../../data/trigger"; import { TRIGGER_TYPES } from "../../../../data/trigger";
import { sortableStyles } from "../../../../resources/ha-sortable-style"; import { sortableStyles } from "../../../../resources/ha-sortable-style";
import { SortableInstance } from "../../../../resources/sortable"; import { SortableInstance } from "../../../../resources/sortable";
@ -38,6 +51,8 @@ import "./types/ha-automation-trigger-time_pattern";
import "./types/ha-automation-trigger-webhook"; import "./types/ha-automation-trigger-webhook";
import "./types/ha-automation-trigger-zone"; import "./types/ha-automation-trigger-zone";
const PASTE_VALUE = "__paste__";
@customElement("ha-automation-trigger") @customElement("ha-automation-trigger")
export default class HaAutomationTrigger extends LitElement { export default class HaAutomationTrigger extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -50,6 +65,8 @@ export default class HaAutomationTrigger extends LitElement {
@property({ type: Boolean }) public reOrderMode = false; @property({ type: Boolean }) public reOrderMode = false;
@property() public clipboard?: Clipboard;
private _focusLastTriggerOnChange = false; private _focusLastTriggerOnChange = false;
private _triggerKeys = new WeakMap<Trigger, string>(); private _triggerKeys = new WeakMap<Trigger, string>();
@ -136,6 +153,22 @@ export default class HaAutomationTrigger extends LitElement {
> >
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon> <ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button> </ha-button>
${
this.clipboard?.trigger
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.paste"
)}
(${this.hass.localize(
`ui.panel.config.automation.editor.triggers.type.${this.clipboard.trigger.platform}.label`
)})
<ha-svg-icon
slot="graphic"
.path=${mdiContentPaste}
></ha-svg-icon
></mwc-list-item>`
: nothing
}
${this._processedTypes(this.hass.localize).map( ${this._processedTypes(this.hass.localize).map(
([opt, label, icon]) => html` ([opt, label, icon]) => html`
<mwc-list-item .value=${opt} graphic="icon"> <mwc-list-item .value=${opt} graphic="icon">
@ -222,19 +255,25 @@ export default class HaAutomationTrigger extends LitElement {
} }
private _addTrigger(ev: CustomEvent<ActionDetail>) { private _addTrigger(ev: CustomEvent<ActionDetail>) {
const platform = (ev.currentTarget as HaSelect).items[ev.detail.index] const value = (ev.currentTarget as HaSelect).items[ev.detail.index].value;
.value as Trigger["platform"];
const elClass = customElements.get( let triggers: Trigger[];
`ha-automation-trigger-${platform}` if (value === PASTE_VALUE) {
) as CustomElementConstructor & { triggers = this.triggers.concat(deepClone(this.clipboard!.trigger));
defaultConfig: Omit<Trigger, "platform">; } else {
}; const platform = value as Trigger["platform"];
const triggers = this.triggers.concat({ const elClass = customElements.get(
platform: platform as any, `ha-automation-trigger-${platform}`
...elClass.defaultConfig, ) as CustomElementConstructor & {
}); defaultConfig: Omit<Trigger, "platform">;
};
triggers = this.triggers.concat({
platform: platform as any,
...elClass.defaultConfig,
});
}
this._focusLastTriggerOnChange = true; this._focusLastTriggerOnChange = true;
fireEvent(this, "value-changed", { value: triggers }); fireEvent(this, "value-changed", { value: triggers });
} }

View File

@ -1,11 +1,13 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import { mdiHelpCircle } from "@mdi/js"; import { mdiHelpCircle } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement } from "lit"; import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import deepClone from "deep-clone-simple";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import { Action, ScriptConfig } from "../../../data/script"; import { Action, ScriptConfig } from "../../../data/script";
import { Clipboard } from "../../../data/automation";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url"; import { documentationUrl } from "../../../util/documentation-url";
@ -23,6 +25,8 @@ export class HaManualScriptEditor extends LitElement {
@property({ attribute: false }) public config!: ScriptConfig; @property({ attribute: false }) public config!: ScriptConfig;
@state() private _clipboard: Clipboard = {};
protected render() { protected render() {
return html` return html`
${this.disabled ${this.disabled
@ -59,6 +63,8 @@ export class HaManualScriptEditor extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}
.disabled=${this.disabled} .disabled=${this.disabled}
@set-clipboard=${this._setClipboard}
.clipboard=${this._clipboard}
></ha-automation-action> ></ha-automation-action>
`; `;
} }
@ -70,6 +76,11 @@ export class HaManualScriptEditor extends LitElement {
}); });
} }
private _setClipboard(ev: CustomEvent): void {
ev.stopPropagation();
this._clipboard = { ...this._clipboard, ...deepClone(ev.detail) };
}
private _duplicate() { private _duplicate() {
fireEvent(this, "duplicate"); fireEvent(this, "duplicate");
} }

View File

@ -2230,6 +2230,9 @@
"duplicate": "[%key:ui::common::duplicate%]", "duplicate": "[%key:ui::common::duplicate%]",
"re_order": "Re-order", "re_order": "Re-order",
"rename": "Rename", "rename": "Rename",
"cut": "Cut",
"copy": "Copy",
"paste": "Paste",
"change_alias": "Rename trigger", "change_alias": "Rename trigger",
"alias": "Trigger name", "alias": "Trigger name",
"delete": "[%key:ui::common::delete%]", "delete": "[%key:ui::common::delete%]",
@ -2359,6 +2362,9 @@
"duplicate": "[%key:ui::common::duplicate%]", "duplicate": "[%key:ui::common::duplicate%]",
"re_order": "[%key:ui::panel::config::automation::editor::triggers::re_order%]", "re_order": "[%key:ui::panel::config::automation::editor::triggers::re_order%]",
"rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]", "rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]",
"cut": "[%key:ui::panel::config::automation::editor::triggers::cut%]",
"copy": "[%key:ui::panel::config::automation::editor::triggers::copy%]",
"paste": "[%key:ui::panel::config::automation::editor::triggers::paste%]",
"change_alias": "Rename condition", "change_alias": "Rename condition",
"alias": "Condition name", "alias": "Condition name",
"delete": "[%key:ui::common::delete%]", "delete": "[%key:ui::common::delete%]",
@ -2456,6 +2462,9 @@
"duplicate": "[%key:ui::common::duplicate%]", "duplicate": "[%key:ui::common::duplicate%]",
"re_order": "[%key:ui::panel::config::automation::editor::triggers::re_order%]", "re_order": "[%key:ui::panel::config::automation::editor::triggers::re_order%]",
"rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]", "rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]",
"cut": "[%key:ui::panel::config::automation::editor::triggers::cut%]",
"copy": "[%key:ui::panel::config::automation::editor::triggers::copy%]",
"paste": "[%key:ui::panel::config::automation::editor::triggers::paste%]",
"change_alias": "Rename action", "change_alias": "Rename action",
"alias": "Action name", "alias": "Action name",
"enable": "Enable", "enable": "Enable",