diff --git a/src/data/automation.ts b/src/data/automation.ts index ca288a8fa6..aeeaa356d2 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -379,3 +379,9 @@ export const testCondition = ( condition, variables, }); + +export type Clipboard = { + trigger?: Trigger; + condition?: Condition; + action?: Action; +}; 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 b66d7f4d3d..12799d6491 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -3,6 +3,8 @@ import "@material/mwc-list/mwc-list-item"; import { mdiCheck, mdiContentDuplicate, + mdiContentCopy, + mdiContentCut, mdiDelete, mdiDotsVertical, mdiPlay, @@ -31,6 +33,7 @@ import { EntityRegistryEntry, subscribeEntityRegistry, } from "../../../../data/entity_registry"; +import { Clipboard } from "../../../../data/automation"; import { Action, getActionType } from "../../../../data/script"; import { describeAction } from "../../../../data/script_i18n"; 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_template"; -const getType = (action: Action | undefined) => { +export const getType = (action: Action | undefined) => { if (!action) { return undefined; } @@ -112,6 +115,8 @@ export default class HaAutomationActionRow extends LitElement { @property({ type: Boolean }) public reOrderMode = false; + @property() public clipboard?: Clipboard; + @state() private _entityReg: EntityRegistryEntry[] = []; @state() private _warnings?: string[]; @@ -214,6 +219,9 @@ export default class HaAutomationActionRow extends LitElement { )} + +
  • + ${this.hass.localize( "ui.panel.config.automation.editor.actions.duplicate" @@ -224,6 +232,26 @@ export default class HaAutomationActionRow extends LitElement { > + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.copy" + )} + + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.cut" + )} + + +
  • `} @@ -378,17 +407,24 @@ export default class HaAutomationActionRow extends LitElement { fireEvent(this, "duplicate"); break; 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.expand(); break; - case 5: + case 7: this._switchYamlMode(); this.expand(); break; - case 6: + case 8: this._onDisable(); break; - case 7: + case 9: this._onDelete(); break; } diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index 98b10bbffa..dcd2f3971d 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -1,8 +1,21 @@ import "@material/mwc-button"; 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 { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; +import { + css, + CSSResultGroup, + html, + LitElement, + nothing, + PropertyValues, +} from "lit"; import { customElement, property } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; import memoizeOne from "memoize-one"; @@ -16,13 +29,14 @@ import type { HaSelect } from "../../../../components/ha-select"; import "../../../../components/ha-svg-icon"; import { ACTION_TYPES } from "../../../../data/action"; import { Action } from "../../../../data/script"; +import { Clipboard } from "../../../../data/automation"; import { sortableStyles } from "../../../../resources/ha-sortable-style"; import { loadSortable, SortableInstance, } from "../../../../resources/sortable.ondemand"; 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 "./types/ha-automation-action-activate_scene"; 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_template"; +const PASTE_VALUE = "__paste__"; + @customElement("ha-automation-action") export default class HaAutomationAction extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -53,6 +69,8 @@ export default class HaAutomationAction extends LitElement { @property({ type: Boolean }) public reOrderMode = false; + @property() public clipboard?: Clipboard; + private _focusLastActionOnChange = false; private _actionKeys = new WeakMap(); @@ -95,6 +113,7 @@ export default class HaAutomationAction extends LitElement { @duplicate=${this._duplicateAction} @value-changed=${this._actionChanged} @re-order=${this._enterReOrderMode} + .clipboard=${this.clipboard} .hass=${this.hass} > ${this.reOrderMode @@ -139,6 +158,19 @@ export default class HaAutomationAction extends LitElement { > + ${this.clipboard?.action + ? html` + ${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` + )}) + ` + : nothing} ${this._processedTypes(this.hass.localize).map( ([opt, label, icon]) => html` @@ -221,13 +253,19 @@ export default class HaAutomationAction extends LitElement { private _addAction(ev: CustomEvent) { 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({ - ...elClass.defaultConfig, - }); + let actions: Action[]; + 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; fireEvent(this, "value-changed", { value: actions }); } 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 e515b49d1c..563f6cc14d 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 @@ -5,7 +5,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event"; import { ensureArray } from "../../../../../common/array/ensure-array"; import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-button"; -import { Condition } from "../../../../../data/automation"; +import { Condition, Clipboard } from "../../../../../data/automation"; import { Action, ChooseAction } from "../../../../../data/script"; import { haStyle } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; @@ -23,6 +23,8 @@ export class HaChooseAction extends LitElement implements ActionElement { @state() private _showDefault = false; + @property() public clipboard?: Clipboard; + public static get defaultConfig() { return { choose: [{ conditions: [], sequence: [] }] }; } @@ -63,6 +65,7 @@ export class HaChooseAction extends LitElement implements ActionElement { .hass=${this.hass} .idx=${idx} @value-changed=${this._conditionChanged} + .clipboard=${this.clipboard} >

    ${this.hass.localize( @@ -77,6 +80,7 @@ export class HaChooseAction extends LitElement implements ActionElement { .hass=${this.hass} .idx=${idx} @value-changed=${this._actionChanged} + .clipboard=${this.clipboard} > ` @@ -105,6 +109,7 @@ export class HaChooseAction extends LitElement implements ActionElement { .disabled=${this.disabled} @value-changed=${this._defaultChanged} .hass=${this.hass} + .clipboard=${this.clipboard} > ` : html` @@ -109,6 +114,7 @@ export class HaRepeatAction extends LitElement implements ActionElement { .reOrderMode=${this.reOrderMode} .disabled=${this.disabled} @value-changed=${this._actionChanged} + .clipboard=${this.clipboard} .hass=${this.hass} > `; diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts index 89fdb53585..d5c21b5989 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts @@ -4,6 +4,7 @@ import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-formfield"; import { WaitForTriggerAction } from "../../../../../data/script"; +import type { Clipboard } from "../../../../../data/automation"; import { HomeAssistant } from "../../../../../types"; import "../../trigger/ha-automation-trigger"; import { ActionElement, handleChangeEvent } from "../ha-automation-action-row"; @@ -25,6 +26,8 @@ export class HaWaitForTriggerAction @property({ type: Boolean }) public reOrderMode = false; + @property() public clipboard?: Clipboard; + public static get defaultConfig() { return { wait_for_trigger: [] }; } @@ -62,6 +65,7 @@ export class HaWaitForTriggerAction .name=${"wait_for_trigger"} .reOrderMode=${this.reOrderMode} @value-changed=${this._valueChanged} + .clipboard=${this.clipboard} > `; } 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 eb4558569e..2adf0512aa 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -4,7 +4,7 @@ import memoizeOne from "memoize-one"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-yaml-editor"; -import type { Condition } from "../../../../data/automation"; +import type { Condition, Clipboard } from "../../../../data/automation"; import { expandConditionWithShorthand } from "../../../../data/automation"; import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; @@ -32,6 +32,8 @@ export default class HaAutomationConditionEditor extends LitElement { @property({ type: Boolean }) public reOrderMode = false; + @property() public clipboard?: Clipboard; + private _processedCondition = memoizeOne((condition) => expandConditionWithShorthand(condition) ); @@ -70,6 +72,7 @@ export default class HaAutomationConditionEditor extends LitElement { condition: condition, reOrderMode: this.reOrderMode, disabled: this.disabled, + clipboard: this.clipboard, } )} 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 1b1f915bfa..c716391085 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -3,6 +3,8 @@ import "@material/mwc-list/mwc-list-item"; import { mdiCheck, mdiContentDuplicate, + mdiContentCopy, + mdiContentCut, mdiDelete, mdiDotsVertical, mdiFlask, @@ -22,6 +24,7 @@ import "../../../../components/ha-card"; import "../../../../components/ha-expansion-panel"; import "../../../../components/ha-icon-button"; import { Condition, testCondition } from "../../../../data/automation"; +import type { Clipboard } from "../../../../data/automation"; import { describeCondition } from "../../../../data/automation_i18n"; import { CONDITION_TYPES } from "../../../../data/condition"; import { validateConfig } from "../../../../data/config"; @@ -77,6 +80,8 @@ export default class HaAutomationConditionRow extends LitElement { @property({ type: Boolean }) public disabled = false; + @property() public clipboard?: Clipboard; + @state() private _yamlMode = false; @state() private _warnings?: string[]; @@ -149,6 +154,8 @@ export default class HaAutomationConditionRow extends LitElement { +
  • + ${this.hass.localize( "ui.panel.config.automation.editor.actions.duplicate" @@ -159,6 +166,26 @@ export default class HaAutomationConditionRow extends LitElement { > + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.copy" + )} + + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.cut" + )} + + +
  • @@ -255,6 +282,7 @@ export default class HaAutomationConditionRow extends LitElement { .hass=${this.hass} .condition=${this.condition} .reOrderMode=${this.reOrderMode} + .clipboard=${this.clipboard} > @@ -307,17 +335,24 @@ export default class HaAutomationConditionRow extends LitElement { fireEvent(this, "duplicate"); break; 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.expand(); break; - case 5: + case 7: this._switchYamlMode(); this.expand(); break; - case 6: + case 8: this._onDisable(); break; - case 7: + case 9: this._onDelete(); break; } diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 12b4fd3bbb..f19cd4685e 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -1,6 +1,12 @@ import "@material/mwc-button"; 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 { css, @@ -18,7 +24,7 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-button"; import "../../../../components/ha-button-menu"; import "../../../../components/ha-svg-icon"; -import type { Condition } from "../../../../data/automation"; +import type { Condition, Clipboard } from "../../../../data/automation"; import type { HomeAssistant } from "../../../../types"; import "./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-zone"; +const PASTE_VALUE = "__paste__"; + @customElement("ha-automation-condition") export default class HaAutomationCondition extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -56,6 +64,8 @@ export default class HaAutomationCondition extends LitElement { @property({ type: Boolean }) public reOrderMode = false; + @property() public clipboard?: Clipboard; + private _focusLastConditionOnChange = false; private _conditionKeys = new WeakMap(); @@ -147,6 +157,7 @@ export default class HaAutomationCondition extends LitElement { @move-condition=${this._move} @value-changed=${this._conditionChanged} @re-order=${this._enterReOrderMode} + .clipboard=${this.clipboard} .hass=${this.hass} > ${this.reOrderMode @@ -191,6 +202,17 @@ export default class HaAutomationCondition extends LitElement { > + ${this.clipboard?.condition + ? html` + ${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` + )}) + ` + : nothing} ${this._processedTypes(this.hass.localize).map( ([opt, label, icon]) => html` @@ -251,19 +273,25 @@ export default class HaAutomationCondition extends LitElement { } private _addCondition(ev: CustomEvent) { - const condition = (ev.currentTarget as HaSelect).items[ev.detail.index] - .value as Condition["condition"]; + const value = (ev.currentTarget as HaSelect).items[ev.detail.index].value; - const elClass = customElements.get( - `ha-automation-condition-${condition}` - ) as CustomElementConstructor & { - defaultConfig: Omit; - }; + let conditions: Condition[]; + if (value === PASTE_VALUE) { + conditions = this.conditions.concat(deepClone(this.clipboard!.condition)); + } else { + const condition = value as Condition["condition"]; - const conditions = this.conditions.concat({ - condition: condition as any, - ...elClass.defaultConfig, - }); + const elClass = customElements.get( + `ha-automation-condition-${condition}` + ) as CustomElementConstructor & { + defaultConfig: Omit; + }; + + conditions = this.conditions.concat({ + condition: condition as any, + ...elClass.defaultConfig, + }); + } this._focusLastConditionOnChange = true; fireEvent(this, "value-changed", { value: conditions }); } 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 a7aa3fb9a6..527e2b7234 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts @@ -1,7 +1,10 @@ import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; 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 "../ha-automation-condition"; 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() public clipboard?: Clipboard; + public static get defaultConfig() { return { conditions: [], @@ -30,6 +35,7 @@ export class HaLogicalCondition extends LitElement implements ConditionElement { @value-changed=${this._valueChanged} .hass=${this.hass} .disabled=${this.disabled} + .clipboard=${this.clipboard} .reOrderMode=${this.reOrderMode} > `; diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index fbf33c631f..3aa074c345 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -46,7 +46,10 @@ import { saveAutomationConfig, showAutomationEditor, triggerAutomationActions, + Trigger, + Condition, } from "../../../data/automation"; +import { Action } from "../../../data/script"; import { fetchEntityRegistry } from "../../../data/entity_registry"; import { showAlertDialog, @@ -76,6 +79,11 @@ declare global { "ui-mode-not-available": Error; duplicate: undefined; "re-order": undefined; + "set-clipboard": { + trigger?: Trigger; + condition?: Condition; + action?: Action; + }; } } diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index e0417269b6..c2e768c725 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -2,7 +2,8 @@ import "@material/mwc-button/mwc-button"; import { mdiHelpCircle } 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 deepClone from "deep-clone-simple"; import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/ha-card"; import "../../../components/ha-icon-button"; @@ -10,6 +11,7 @@ import { Condition, ManualAutomationConfig, Trigger, + Clipboard, } from "../../../data/automation"; import { Action } from "../../../data/script"; import { haStyle } from "../../../resources/styles"; @@ -33,6 +35,8 @@ export class HaManualAutomationEditor extends LitElement { @property({ attribute: false }) public stateObj?: HassEntity; + @state() private _clipboard: Clipboard = {}; + protected render() { return html` ${this.disabled @@ -91,6 +95,8 @@ export class HaManualAutomationEditor extends LitElement { @value-changed=${this._triggerChanged} .hass=${this.hass} .disabled=${this.disabled} + @set-clipboard=${this._setClipboard} + .clipboard=${this._clipboard} >
    @@ -120,6 +126,8 @@ export class HaManualAutomationEditor extends LitElement { @value-changed=${this._conditionChanged} .hass=${this.hass} .disabled=${this.disabled} + @set-clipboard=${this._setClipboard} + .clipboard=${this._clipboard} >
    @@ -152,6 +160,8 @@ export class HaManualAutomationEditor extends LitElement { .hass=${this.hass} .narrow=${this.narrow} .disabled=${this.disabled} + @set-clipboard=${this._setClipboard} + .clipboard=${this._clipboard} > `; } @@ -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 { ev.stopPropagation(); fireEvent(this, "value-changed", { 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 50b69d7765..0adbb662e6 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -3,6 +3,8 @@ import "@material/mwc-list/mwc-list-item"; import { mdiCheck, mdiContentDuplicate, + mdiContentCopy, + mdiContentCut, mdiDelete, mdiDotsVertical, mdiIdentifier, @@ -166,6 +168,18 @@ export default class HaAutomationTriggerRow extends LitElement { + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.edit_id" + )} + + + +
  • + ${this.hass.localize( "ui.panel.config.automation.editor.triggers.duplicate" @@ -178,11 +192,21 @@ export default class HaAutomationTriggerRow extends LitElement { ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.edit_id" + "ui.panel.config.automation.editor.triggers.copy" )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.cut" + )} + @@ -427,24 +451,31 @@ export default class HaAutomationTriggerRow extends LitElement { fireEvent(this, "re-order"); break; case 2: - fireEvent(this, "duplicate"); - break; - case 3: this._requestShowId = true; this.expand(); break; + case 3: + fireEvent(this, "duplicate"); + break; 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.expand(); break; - case 5: + case 7: this._switchYamlMode(); this.expand(); break; - case 6: + case 8: this._onDisable(); break; - case 7: + case 9: this._onDelete(); break; } diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index 7c8b8bd755..9993575921 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -1,8 +1,21 @@ import "@material/mwc-button"; 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 { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; +import { + css, + CSSResultGroup, + html, + LitElement, + nothing, + PropertyValues, +} from "lit"; import { customElement, property } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; import memoizeOne from "memoize-one"; @@ -14,7 +27,7 @@ import "../../../../components/ha-button-menu"; import "../../../../components/ha-button"; import type { HaSelect } from "../../../../components/ha-select"; import "../../../../components/ha-svg-icon"; -import { Trigger } from "../../../../data/automation"; +import { Trigger, Clipboard } from "../../../../data/automation"; import { TRIGGER_TYPES } from "../../../../data/trigger"; import { sortableStyles } from "../../../../resources/ha-sortable-style"; 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-zone"; +const PASTE_VALUE = "__paste__"; + @customElement("ha-automation-trigger") export default class HaAutomationTrigger extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -50,6 +65,8 @@ export default class HaAutomationTrigger extends LitElement { @property({ type: Boolean }) public reOrderMode = false; + @property() public clipboard?: Clipboard; + private _focusLastTriggerOnChange = false; private _triggerKeys = new WeakMap(); @@ -136,6 +153,22 @@ export default class HaAutomationTrigger extends LitElement { > + ${ + this.clipboard?.trigger + ? html` + ${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` + )}) + ` + : nothing + } ${this._processedTypes(this.hass.localize).map( ([opt, label, icon]) => html` @@ -222,19 +255,25 @@ export default class HaAutomationTrigger extends LitElement { } private _addTrigger(ev: CustomEvent) { - const platform = (ev.currentTarget as HaSelect).items[ev.detail.index] - .value as Trigger["platform"]; + const value = (ev.currentTarget as HaSelect).items[ev.detail.index].value; - const elClass = customElements.get( - `ha-automation-trigger-${platform}` - ) as CustomElementConstructor & { - defaultConfig: Omit; - }; + let triggers: Trigger[]; + if (value === PASTE_VALUE) { + triggers = this.triggers.concat(deepClone(this.clipboard!.trigger)); + } else { + const platform = value as Trigger["platform"]; - const triggers = this.triggers.concat({ - platform: platform as any, - ...elClass.defaultConfig, - }); + const elClass = customElements.get( + `ha-automation-trigger-${platform}` + ) as CustomElementConstructor & { + defaultConfig: Omit; + }; + + triggers = this.triggers.concat({ + platform: platform as any, + ...elClass.defaultConfig, + }); + } this._focusLastTriggerOnChange = true; fireEvent(this, "value-changed", { value: triggers }); } diff --git a/src/panels/config/script/manual-script-editor.ts b/src/panels/config/script/manual-script-editor.ts index d7d7ced9cf..130e8857e3 100644 --- a/src/panels/config/script/manual-script-editor.ts +++ b/src/panels/config/script/manual-script-editor.ts @@ -1,11 +1,13 @@ import "@material/mwc-button/mwc-button"; import { mdiHelpCircle } from "@mdi/js"; 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 "../../../components/ha-card"; import "../../../components/ha-icon-button"; import { Action, ScriptConfig } from "../../../data/script"; +import { Clipboard } from "../../../data/automation"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; @@ -23,6 +25,8 @@ export class HaManualScriptEditor extends LitElement { @property({ attribute: false }) public config!: ScriptConfig; + @state() private _clipboard: Clipboard = {}; + protected render() { return html` ${this.disabled @@ -59,6 +63,8 @@ export class HaManualScriptEditor extends LitElement { .hass=${this.hass} .narrow=${this.narrow} .disabled=${this.disabled} + @set-clipboard=${this._setClipboard} + .clipboard=${this._clipboard} > `; } @@ -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() { fireEvent(this, "duplicate"); } diff --git a/src/translations/en.json b/src/translations/en.json index 8422f0d143..1bed5b1348 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2230,6 +2230,9 @@ "duplicate": "[%key:ui::common::duplicate%]", "re_order": "Re-order", "rename": "Rename", + "cut": "Cut", + "copy": "Copy", + "paste": "Paste", "change_alias": "Rename trigger", "alias": "Trigger name", "delete": "[%key:ui::common::delete%]", @@ -2359,6 +2362,9 @@ "duplicate": "[%key:ui::common::duplicate%]", "re_order": "[%key:ui::panel::config::automation::editor::triggers::re_order%]", "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", "alias": "Condition name", "delete": "[%key:ui::common::delete%]", @@ -2456,6 +2462,9 @@ "duplicate": "[%key:ui::common::duplicate%]", "re_order": "[%key:ui::panel::config::automation::editor::triggers::re_order%]", "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", "alias": "Action name", "enable": "Enable",