Automation-keybindings (#26762)

Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Wendelin
2025-09-01 21:41:50 +02:00
committed by GitHub
parent eff352cde1
commit 7c6c92c856
25 changed files with 857 additions and 188 deletions

View File

@@ -81,6 +81,10 @@ export class HaAutomationRow extends LitElement {
!( !(
(this.sortSelected || ev.altKey) && (this.sortSelected || ev.altKey) &&
(ev.key === "ArrowUp" || ev.key === "ArrowDown") (ev.key === "ArrowUp" || ev.key === "ArrowDown")
) &&
!(
(ev.ctrlKey || ev.metaKey) &&
(ev.key === "c" || ev.key === "x" || ev.key === "Delete")
) )
) { ) {
return; return;
@@ -101,6 +105,22 @@ export class HaAutomationRow extends LitElement {
return; return;
} }
if (ev.ctrlKey || ev.metaKey) {
if (ev.key === "c") {
fireEvent(this, "copy-row");
return;
}
if (ev.key === "x") {
fireEvent(this, "cut-row");
return;
}
if (ev.key === "Delete") {
fireEvent(this, "delete-row");
return;
}
}
this.click(); this.click();
} }
@@ -196,5 +216,8 @@ declare global {
interface HASSDomEvents { interface HASSDomEvents {
"toggle-collapsed": undefined; "toggle-collapsed": undefined;
"stop-sort-selection": undefined; "stop-sort-selection": undefined;
"copy-row": undefined;
"cut-row": undefined;
"delete-row": undefined;
} }
} }

View File

@@ -3,11 +3,11 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined"; import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-button";
import "../../components/ha-dialog-header";
import "../../components/ha-md-dialog"; import "../../components/ha-md-dialog";
import type { HaMdDialog } from "../../components/ha-md-dialog"; import type { HaMdDialog } from "../../components/ha-md-dialog";
import "../../components/ha-dialog-header";
import "../../components/ha-svg-icon"; import "../../components/ha-svg-icon";
import "../../components/ha-button";
import "../../components/ha-textfield"; import "../../components/ha-textfield";
import type { HaTextField } from "../../components/ha-textfield"; import type { HaTextField } from "../../components/ha-textfield";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
@@ -52,7 +52,7 @@ class DialogBox extends LitElement {
return nothing; return nothing;
} }
const confirmPrompt = this._params.confirmation || this._params.prompt; const confirmPrompt = this._params.confirmation || !!this._params.prompt;
const dialogTitle = const dialogTitle =
this._params.title || this._params.title ||
@@ -62,7 +62,7 @@ class DialogBox extends LitElement {
return html` return html`
<ha-md-dialog <ha-md-dialog
open open
.disableCancelAction=${confirmPrompt || false} .disableCancelAction=${confirmPrompt}
@closed=${this._dialogClosed} @closed=${this._dialogClosed}
type="alert" type="alert"
aria-labelledby="dialog-box-title" aria-labelledby="dialog-box-title"
@@ -100,23 +100,22 @@ class DialogBox extends LitElement {
: ""} : ""}
</div> </div>
<div slot="actions"> <div slot="actions">
${confirmPrompt && ${confirmPrompt
html` ? html`
<ha-button <ha-button
@click=${this._dismiss} @click=${this._dismiss}
?dialogInitialFocus=${!this._params.prompt && ?autofocus=${!this._params.prompt && this._params.destructive}
this._params.destructive}
appearance="plain" appearance="plain"
> >
${this._params.dismissText ${this._params.dismissText
? this._params.dismissText ? this._params.dismissText
: this.hass.localize("ui.common.cancel")} : this.hass.localize("ui.common.cancel")}
</ha-button> </ha-button>
`} `
: nothing}
<ha-button <ha-button
@click=${this._confirm} @click=${this._confirm}
?dialogInitialFocus=${!this._params.prompt && ?autofocus=${!this._params.prompt && !this._params.destructive}
!this._params.destructive}
variant=${this._params.destructive ? "danger" : "brand"} variant=${this._params.destructive ? "danger" : "brand"}
> >
${this._params.confirmText ${this._params.confirmText

View File

@@ -14,20 +14,46 @@ export const KeyboardShortcutMixin = <T extends Constructor<LitElement>>(
if ((event.ctrlKey || event.metaKey) && event.key in supportedShortcuts) { if ((event.ctrlKey || event.metaKey) && event.key in supportedShortcuts) {
event.preventDefault(); event.preventDefault();
supportedShortcuts[event.key](); supportedShortcuts[event.key]();
return;
}
const supportedSingleKeyShortcuts = this.supportedSingleKeyShortcuts();
if (event.key in supportedSingleKeyShortcuts) {
event.preventDefault();
supportedSingleKeyShortcuts[event.key]();
} }
}; };
private _listenersAdded = false;
public connectedCallback() { public connectedCallback() {
super.connectedCallback(); super.connectedCallback();
window.addEventListener("keydown", this._keydownEvent); this.addKeyboardShortcuts();
} }
public disconnectedCallback() { public disconnectedCallback() {
window.removeEventListener("keydown", this._keydownEvent); this.removeKeyboardShortcuts();
super.disconnectedCallback(); super.disconnectedCallback();
} }
public addKeyboardShortcuts() {
if (this._listenersAdded) {
return;
}
this._listenersAdded = true;
window.addEventListener("keydown", this._keydownEvent);
}
public removeKeyboardShortcuts() {
this._listenersAdded = false;
window.removeEventListener("keydown", this._keydownEvent);
}
protected supportedShortcuts(): SupportedShortcuts { protected supportedShortcuts(): SupportedShortcuts {
return {}; return {};
} }
protected supportedSingleKeyShortcuts(): SupportedShortcuts {
return {};
}
}; };

View File

@@ -5,12 +5,12 @@ import {
mdiArrowUp, mdiArrowUp,
mdiContentCopy, mdiContentCopy,
mdiContentCut, mdiContentCut,
mdiContentDuplicate,
mdiDelete, mdiDelete,
mdiDotsVertical, mdiDotsVertical,
mdiPlay, mdiPlay,
mdiPlayCircleOutline, mdiPlayCircleOutline,
mdiPlaylistEdit, mdiPlaylistEdit,
mdiPlusCircleMultipleOutline,
mdiRenameBox, mdiRenameBox,
mdiStopCircleOutline, mdiStopCircleOutline,
} from "@mdi/js"; } from "@mdi/js";
@@ -89,6 +89,7 @@ import "./types/ha-automation-action-set_conversation_response";
import "./types/ha-automation-action-stop"; 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";
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
export const getAutomationActionType = memoizeOne( export const getAutomationActionType = memoizeOne(
(action: Action | undefined) => { (action: Action | undefined) => {
@@ -195,6 +196,10 @@ export default class HaAutomationActionRow extends LitElement {
@query("ha-automation-row") @query("ha-automation-row")
private _automationRowElement?: HaAutomationRow; private _automationRowElement?: HaAutomationRow;
get selected() {
return this._selected;
}
protected firstUpdated(changedProperties: PropertyValues): void { protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties); super.firstUpdated(changedProperties);
@@ -305,7 +310,7 @@ export default class HaAutomationActionRow extends LitElement {
)} )}
<ha-svg-icon <ha-svg-icon
slot="start" slot="start"
.path=${mdiContentDuplicate} .path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon> ></ha-svg-icon>
</ha-md-menu-item> </ha-md-menu-item>
@@ -438,7 +443,6 @@ export default class HaAutomationActionRow extends LitElement {
${this.optionsInSidebar ${this.optionsInSidebar
? html`<ha-automation-row ? html`<ha-automation-row
.disabled=${this.action.enabled === false} .disabled=${this.action.enabled === false}
@click=${this._toggleSidebar}
.leftChevron=${[ .leftChevron=${[
...ACTION_BUILDING_BLOCKS, ...ACTION_BUILDING_BLOCKS,
...ACTION_COMBINED_BLOCKS, ...ACTION_COMBINED_BLOCKS,
@@ -450,12 +454,16 @@ export default class HaAutomationActionRow extends LitElement {
.collapsed=${this._collapsed} .collapsed=${this._collapsed}
.selected=${this._selected} .selected=${this._selected}
.highlight=${this.highlight} .highlight=${this.highlight}
@toggle-collapsed=${this._toggleCollapse}
.buildingBlock=${[ .buildingBlock=${[
...ACTION_BUILDING_BLOCKS, ...ACTION_BUILDING_BLOCKS,
...ACTION_COMBINED_BLOCKS, ...ACTION_COMBINED_BLOCKS,
].includes(blockType!)} ].includes(blockType!)}
.sortSelected=${this.sortSelected} .sortSelected=${this.sortSelected}
@click=${this._toggleSidebar}
@toggle-collapsed=${this._toggleCollapse}
@copy-row=${this._copyAction}
@cut-row=${this._cutAction}
@delete-row=${this._onDelete}
>${this._renderRow()}</ha-automation-row >${this._renderRow()}</ha-automation-row
>` >`
: html` : html`
@@ -500,6 +508,7 @@ export default class HaAutomationActionRow extends LitElement {
...this._clipboard, ...this._clipboard,
action: deepClone(this.action), action: deepClone(this.action),
}; };
copyToClipboard(JSON.stringify(this.action));
} }
private _onDisable = () => { private _onDisable = () => {
@@ -514,6 +523,15 @@ export default class HaAutomationActionRow extends LitElement {
}; };
private _runAction = async () => { private _runAction = async () => {
requestAnimationFrame(() => {
// @ts-ignore is supported in all browsers except firefox
if (this.scrollIntoViewIfNeeded) {
// @ts-ignore is supported in all browsers except firefox
this.scrollIntoViewIfNeeded();
return;
}
this.scrollIntoView();
});
const validated = await validateConfig(this.hass, { const validated = await validateConfig(this.hass, {
actions: this.action, actions: this.action,
}); });
@@ -623,6 +641,12 @@ export default class HaAutomationActionRow extends LitElement {
private _copyAction = () => { private _copyAction = () => {
this._setClipboard(); this._setClipboard();
showToast(this, {
message: this.hass.localize(
"ui.panel.config.automation.editor.actions.copied_to_clipboard"
),
duration: 2000,
});
}; };
private _cutAction = () => { private _cutAction = () => {
@@ -631,6 +655,12 @@ export default class HaAutomationActionRow extends LitElement {
if (this._selected) { if (this._selected) {
fireEvent(this, "close-sidebar"); fireEvent(this, "close-sidebar");
} }
showToast(this, {
message: this.hass.localize(
"ui.panel.config.automation.editor.actions.cut_to_clipboard"
),
duration: 2000,
});
}; };
private _moveUp = () => { private _moveUp = () => {
@@ -761,10 +791,6 @@ export default class HaAutomationActionRow extends LitElement {
this._collapsed = !this._collapsed; this._collapsed = !this._collapsed;
} }
public isSelected() {
return this._selected;
}
public focus() { public focus() {
this._automationRowElement?.focus(); this._automationRowElement?.focus();
} }

View File

@@ -300,7 +300,7 @@ export default class HaAutomationAction extends LitElement {
ev.stopPropagation(); ev.stopPropagation();
const { index, data } = ev.detail; const { index, data } = ev.detail;
const item = ev.detail.item as HaAutomationActionRow; const item = ev.detail.item as HaAutomationActionRow;
const selected = item.isSelected(); const selected = item.selected;
let actions = [ let actions = [
...this.actions.slice(0, index), ...this.actions.slice(0, index),

View File

@@ -1,4 +1,9 @@
import { mdiClose, mdiContentPaste, mdiPlus } from "@mdi/js"; import {
mdiAppleKeyboardCommand,
mdiClose,
mdiContentPaste,
mdiPlus,
} from "@mdi/js";
import Fuse from "fuse.js"; import Fuse from "fuse.js";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit"; import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit"; import { LitElement, css, html, nothing } from "lit";
@@ -41,11 +46,14 @@ import {
} from "../../../data/integration"; } from "../../../data/integration";
import { TRIGGER_GROUPS, TRIGGER_ICONS } from "../../../data/trigger"; import { TRIGGER_GROUPS, TRIGGER_ICONS } from "../../../data/trigger";
import type { HassDialog } from "../../../dialogs/make-dialog-manager"; import type { HassDialog } from "../../../dialogs/make-dialog-manager";
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
import { HaFuse } from "../../../resources/fuse";
import { haStyle, haStyleDialog } from "../../../resources/styles"; import { haStyle, haStyleDialog } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { isMac } from "../../../util/is_mac";
import { showToast } from "../../../util/toast";
import type { AddAutomationElementDialogParams } from "./show-add-automation-element-dialog"; import type { AddAutomationElementDialogParams } from "./show-add-automation-element-dialog";
import { PASTE_VALUE } from "./show-add-automation-element-dialog"; import { PASTE_VALUE } from "./show-add-automation-element-dialog";
import { HaFuse } from "../../../resources/fuse";
const TYPES = { const TYPES = {
trigger: { groups: TRIGGER_GROUPS, icons: TRIGGER_ICONS }, trigger: { groups: TRIGGER_GROUPS, icons: TRIGGER_ICONS },
@@ -85,7 +93,10 @@ const ENTITY_DOMAINS_OTHER = new Set([
const ENTITY_DOMAINS_MAIN = new Set(["notify"]); const ENTITY_DOMAINS_MAIN = new Set(["notify"]);
@customElement("add-automation-element-dialog") @customElement("add-automation-element-dialog")
class DialogAddAutomationElement extends LitElement implements HassDialog { class DialogAddAutomationElement
extends KeyboardShortcutMixin(LitElement)
implements HassDialog
{
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: AddAutomationElementDialogParams; @state() private _params?: AddAutomationElementDialogParams;
@@ -108,9 +119,14 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
@state() private _height?: number; @state() private _height?: number;
@state() private _narrow = false;
public showDialog(params): void { public showDialog(params): void {
this._params = params; this._params = params;
this._group = params.group; this._group = params.group;
this.addKeyboardShortcuts();
if (this._params?.type === "action") { if (this._params?.type === "action") {
this.hass.loadBackendTranslation("services"); this.hass.loadBackendTranslation("services");
this._fetchManifests(); this._fetchManifests();
@@ -120,9 +136,12 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
this._fullScreen = matchMedia( this._fullScreen = matchMedia(
"all and (max-width: 450px), all and (max-height: 500px)" "all and (max-width: 450px), all and (max-height: 500px)"
).matches; ).matches;
this._narrow = matchMedia("(max-width: 870px)").matches;
} }
public closeDialog() { public closeDialog() {
this.removeKeyboardShortcuts();
if (this._params) { if (this._params) {
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
@@ -555,15 +574,37 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
.value=${PASTE_VALUE} .value=${PASTE_VALUE}
@click=${this._selected} @click=${this._selected}
> >
<div class="shortcut-label">
<div class="label">
<div>
${this.hass.localize( ${this.hass.localize(
`ui.panel.config.automation.editor.${this._params.type}s.paste` `ui.panel.config.automation.editor.${this._params.type}s.paste`
)} )}
<span slot="supporting-text" </div>
>${this.hass.localize( <div class="supporting-text">
${this.hass.localize(
// @ts-ignore // @ts-ignore
`ui.panel.config.automation.editor.${this._params.type}s.type.${this._params.clipboardItem}.label` `ui.panel.config.automation.editor.${this._params.type}s.type.${this._params.clipboardItem}.label`
)}
</div>
</div>
${!this._narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span )}</span
> >
<span>+</span>
<span>V</span>
</span>`
: nothing}
</div>
<ha-svg-icon <ha-svg-icon
slot="start" slot="start"
.path=${mdiContentPaste} .path=${mdiContentPaste}
@@ -571,7 +612,7 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
><ha-svg-icon slot="end" .path=${mdiPlus}></ha-svg-icon> ><ha-svg-icon slot="end" .path=${mdiPlus}></ha-svg-icon>
</ha-md-list-item> </ha-md-list-item>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>` <ha-md-divider role="separator" tabindex="-1"></ha-md-divider>`
: ""} : nothing}
${repeat( ${repeat(
items, items,
(item) => item.key, (item) => item.key,
@@ -637,6 +678,30 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _addClipboard = () => {
if (this._params?.clipboardItem) {
this._params!.add(PASTE_VALUE);
showToast(this, {
message: this.hass.localize(
"ui.panel.config.automation.editor.item_pasted",
{
item: this.hass.localize(
// @ts-ignore
`ui.panel.config.automation.editor.${this._params.type}s.type.${this._params.clipboardItem}.label`
),
}
),
});
this.closeDialog();
}
};
protected supportedShortcuts(): SupportedShortcuts {
return {
v: () => this._addClipboard(),
};
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
@@ -660,6 +725,7 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
max-width: 100vw; max-width: 100vw;
--md-list-item-leading-space: 24px; --md-list-item-leading-space: 24px;
--md-list-item-trailing-space: 24px; --md-list-item-trailing-space: 24px;
--md-list-item-supporting-text-font: var(--ha-font-size-s);
} }
ha-md-list-item img { ha-md-list-item img {
width: 24px; width: 24px;
@@ -668,6 +734,27 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
display: block; display: block;
margin: 0 16px; margin: 0 16px;
} }
.shortcut-label {
display: flex;
gap: 12px;
justify-content: space-between;
}
.shortcut-label .supporting-text {
color: var(--secondary-text-color);
font-size: var(--ha-font-size-s);
}
.shortcut-label .shortcut {
--mdc-icon-size: 12px;
display: inline-flex;
flex-direction: row;
align-items: center;
gap: 2px;
}
.shortcut-label .shortcut span {
font-size: var(--ha-font-size-s);
font-family: var(--ha-font-family-code);
color: var(--ha-color-text-secondary);
}
`, `,
]; ];
} }

View File

@@ -4,12 +4,12 @@ import {
mdiArrowUp, mdiArrowUp,
mdiContentCopy, mdiContentCopy,
mdiContentCut, mdiContentCut,
mdiContentDuplicate,
mdiDelete, mdiDelete,
mdiDotsVertical, mdiDotsVertical,
mdiFlask, mdiFlask,
mdiPlayCircleOutline, mdiPlayCircleOutline,
mdiPlaylistEdit, mdiPlaylistEdit,
mdiPlusCircleMultipleOutline,
mdiRenameBox, mdiRenameBox,
mdiStopCircleOutline, mdiStopCircleOutline,
} from "@mdi/js"; } from "@mdi/js";
@@ -53,6 +53,7 @@ import {
showPromptDialog, showPromptDialog,
} from "../../../../dialogs/generic/show-dialog-box"; } from "../../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { showToast } from "../../../../util/toast";
import "../ha-automation-editor-warning"; import "../ha-automation-editor-warning";
import { rowStyles } from "../styles"; import { rowStyles } from "../styles";
import "./ha-automation-condition-editor"; import "./ha-automation-condition-editor";
@@ -68,6 +69,7 @@ import "./types/ha-automation-condition-template";
import "./types/ha-automation-condition-time"; 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";
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
export interface ConditionElement extends LitElement { export interface ConditionElement extends LitElement {
condition: Condition; condition: Condition;
@@ -154,6 +156,10 @@ export default class HaAutomationConditionRow extends LitElement {
@query("ha-automation-row") @query("ha-automation-row")
private _automationRowElement?: HaAutomationRow; private _automationRowElement?: HaAutomationRow;
get selected() {
return this._selected;
}
private _renderRow() { private _renderRow() {
return html` return html`
<ha-svg-icon <ha-svg-icon
@@ -214,7 +220,7 @@ export default class HaAutomationConditionRow extends LitElement {
)} )}
<ha-svg-icon <ha-svg-icon
slot="start" slot="start"
.path=${mdiContentDuplicate} .path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon> ></ha-svg-icon>
</ha-md-menu-item> </ha-md-menu-item>
@@ -358,12 +364,15 @@ export default class HaAutomationConditionRow extends LitElement {
.collapsed=${this._collapsed} .collapsed=${this._collapsed}
.selected=${this._selected} .selected=${this._selected}
.highlight=${this.highlight} .highlight=${this.highlight}
@click=${this._toggleSidebar}
@toggle-collapsed=${this._toggleCollapse}
.buildingBlock=${CONDITION_BUILDING_BLOCKS.includes( .buildingBlock=${CONDITION_BUILDING_BLOCKS.includes(
this.condition.condition this.condition.condition
)} )}
.sortSelected=${this.sortSelected} .sortSelected=${this.sortSelected}
@click=${this._toggleSidebar}
@toggle-collapsed=${this._toggleCollapse}
@copy-row=${this._copyCondition}
@cut-row=${this._cutCondition}
@delete-row=${this._onDelete}
>${this._renderRow()}</ha-automation-row >${this._renderRow()}</ha-automation-row
>` >`
: html` : html`
@@ -432,6 +441,7 @@ export default class HaAutomationConditionRow extends LitElement {
...this._clipboard, ...this._clipboard,
condition: deepClone(this.condition), condition: deepClone(this.condition),
}; };
copyToClipboard(JSON.stringify(this.condition));
} }
private _onDisable = () => { private _onDisable = () => {
@@ -480,6 +490,15 @@ export default class HaAutomationConditionRow extends LitElement {
this._testingResult = undefined; this._testingResult = undefined;
this._testing = true; this._testing = true;
const condition = this.condition; const condition = this.condition;
requestAnimationFrame(() => {
// @ts-ignore is supported in all browsers expect firefox
if (this.scrollIntoViewIfNeeded) {
// @ts-ignore is supported in all browsers expect firefox
this.scrollIntoViewIfNeeded();
return;
}
this.scrollIntoView();
});
try { try {
const validateResult = await validateConfig(this.hass, { const validateResult = await validateConfig(this.hass, {
@@ -570,6 +589,12 @@ export default class HaAutomationConditionRow extends LitElement {
private _copyCondition = () => { private _copyCondition = () => {
this._setClipboard(); this._setClipboard();
showToast(this, {
message: this.hass.localize(
"ui.panel.config.automation.editor.conditions.copied_to_clipboard"
),
duration: 2000,
});
}; };
private _cutCondition = () => { private _cutCondition = () => {
@@ -578,6 +603,12 @@ export default class HaAutomationConditionRow extends LitElement {
if (this._selected) { if (this._selected) {
fireEvent(this, "close-sidebar"); fireEvent(this, "close-sidebar");
} }
showToast(this, {
message: this.hass.localize(
"ui.panel.config.automation.editor.conditions.cut_to_clipboard"
),
duration: 2000,
});
}; };
private _moveUp = () => { private _moveUp = () => {
@@ -697,10 +728,6 @@ export default class HaAutomationConditionRow extends LitElement {
this._collapsed = !this._collapsed; this._collapsed = !this._collapsed;
} }
public isSelected() {
return this._selected;
}
public focus() { public focus() {
this._automationRowElement?.focus(); this._automationRowElement?.focus();
} }

View File

@@ -318,7 +318,7 @@ export default class HaAutomationCondition extends LitElement {
ev.stopPropagation(); ev.stopPropagation();
const { index, data } = ev.detail; const { index, data } = ev.detail;
const item = ev.detail.item as HaAutomationConditionRow; const item = ev.detail.item as HaAutomationConditionRow;
const selected = item.isSelected(); const selected = item.selected;
let conditions = [ let conditions = [
...this.conditions.slice(0, index), ...this.conditions.slice(0, index),
data, data,

View File

@@ -1,7 +1,6 @@
import { consume } from "@lit/context"; import { consume } from "@lit/context";
import { import {
mdiCog, mdiCog,
mdiContentDuplicate,
mdiContentSave, mdiContentSave,
mdiDebugStepOver, mdiDebugStepOver,
mdiDelete, mdiDelete,
@@ -11,6 +10,7 @@ import {
mdiPlay, mdiPlay,
mdiPlayCircleOutline, mdiPlayCircleOutline,
mdiPlaylistEdit, mdiPlaylistEdit,
mdiPlusCircleMultipleOutline,
mdiRenameBox, mdiRenameBox,
mdiRobotConfused, mdiRobotConfused,
mdiStopCircleOutline, mdiStopCircleOutline,
@@ -337,7 +337,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
)} )}
<ha-svg-icon <ha-svg-icon
slot="graphic" slot="graphic"
.path=${mdiContentDuplicate} .path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon> ></ha-svg-icon>
</ha-list-item> </ha-list-item>
@@ -1138,6 +1138,9 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
protected supportedShortcuts(): SupportedShortcuts { protected supportedShortcuts(): SupportedShortcuts {
return { return {
s: () => this._handleSaveAutomation(), s: () => this._handleSaveAutomation(),
c: () => this._copySelectedRow(),
x: () => this._cutSelectedRow(),
Delete: () => this._deleteSelectedRow(),
}; };
} }
@@ -1157,6 +1160,18 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
this._manualEditor?.expandAll(); this._manualEditor?.expandAll();
} }
private _copySelectedRow() {
this._manualEditor?.copySelectedRow();
}
private _cutSelectedRow() {
this._manualEditor?.cutSelectedRow();
}
private _deleteSelectedRow() {
this._manualEditor?.deleteSelectedRow();
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@@ -3,7 +3,13 @@ import type { HassEntity } from "home-assistant-js-websocket";
import { load } from "js-yaml"; import { load } from "js-yaml";
import type { CSSResultGroup, PropertyValues } from "lit"; import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import {
customElement,
property,
query,
queryAll,
state,
} from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { import {
any, any,
@@ -28,6 +34,7 @@ import "../../../components/ha-fab";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-markdown"; import "../../../components/ha-markdown";
import type { import type {
ActionSidebarConfig,
AutomationConfig, AutomationConfig,
Condition, Condition,
ManualAutomationConfig, ManualAutomationConfig,
@@ -96,6 +103,11 @@ export class HaManualAutomationEditor extends LitElement {
@query("ha-automation-sidebar") private _sidebarElement?: HaAutomationSidebar; @query("ha-automation-sidebar") private _sidebarElement?: HaAutomationSidebar;
@queryAll("ha-automation-action, ha-automation-condition")
private _collapsableElements?: NodeListOf<
HaAutomationAction | HaAutomationCondition
>;
private _previousConfig?: ManualAutomationConfig; private _previousConfig?: ManualAutomationConfig;
public connectedCallback() { public connectedCallback() {
@@ -478,7 +490,20 @@ export class HaManualAutomationEditor extends LitElement {
if (normalized) { if (normalized) {
ev.preventDefault(); ev.preventDefault();
if (this.dirty) { if (
Object.keys(normalized).length === 1 &&
ensureArray(normalized[Object.keys(normalized)[0]]).length === 1
) {
this._appendToExistingConfig(normalized);
return;
}
if (
this.dirty ||
ensureArray(this.config.triggers)?.length ||
ensureArray(this.config.conditions)?.length ||
ensureArray(this.config.actions)?.length
) {
const result = await new Promise<boolean>((resolve) => { const result = await new Promise<boolean>((resolve) => {
showPasteReplaceDialog(this, { showPasteReplaceDialog(this, {
domain: "automation", domain: "automation",
@@ -587,24 +612,36 @@ export class HaManualAutomationEditor extends LitElement {
}); });
} }
private _getCollapsableElements() {
return this.shadowRoot!.querySelectorAll<
HaAutomationAction | HaAutomationCondition
>("ha-automation-action, ha-automation-condition");
}
public expandAll() { public expandAll() {
this._getCollapsableElements().forEach((element) => { this._collapsableElements?.forEach((element) => {
element.expandAll(); element.expandAll();
}); });
} }
public collapseAll() { public collapseAll() {
this._getCollapsableElements().forEach((element) => { this._collapsableElements?.forEach((element) => {
element.collapseAll(); element.collapseAll();
}); });
} }
public copySelectedRow() {
if ((this._sidebarConfig as ActionSidebarConfig)?.copy) {
(this._sidebarConfig as ActionSidebarConfig).copy();
}
}
public cutSelectedRow() {
if ((this._sidebarConfig as ActionSidebarConfig)?.cut) {
(this._sidebarConfig as ActionSidebarConfig).cut();
}
}
public deleteSelectedRow() {
if ((this._sidebarConfig as ActionSidebarConfig)?.delete) {
(this._sidebarConfig as ActionSidebarConfig).delete();
}
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
saveFabStyles, saveFabStyles,

View File

@@ -2,9 +2,9 @@ import { consume } from "@lit/context";
import { import {
mdiArrowDown, mdiArrowDown,
mdiArrowUp, mdiArrowUp,
mdiContentDuplicate,
mdiDelete, mdiDelete,
mdiDotsVertical, mdiDotsVertical,
mdiPlusCircleMultipleOutline,
mdiRenameBox, mdiRenameBox,
} from "@mdi/js"; } from "@mdi/js";
import type { CSSResultGroup } from "lit"; import type { CSSResultGroup } from "lit";
@@ -86,6 +86,10 @@ export default class HaAutomationOptionRow extends LitElement {
@query("ha-automation-row") @query("ha-automation-row")
private _automationRowElement?: HaAutomationRow; private _automationRowElement?: HaAutomationRow;
get selected() {
return this._selected;
}
private _expandedChanged(ev) { private _expandedChanged(ev) {
if (ev.currentTarget.id !== "option") { if (ev.currentTarget.id !== "option") {
return; return;
@@ -167,7 +171,7 @@ export default class HaAutomationOptionRow extends LitElement {
)} )}
<ha-svg-icon <ha-svg-icon
slot="start" slot="start"
.path=${mdiContentDuplicate} .path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon> ></ha-svg-icon>
</ha-md-menu-item> </ha-md-menu-item>
@@ -271,9 +275,10 @@ export default class HaAutomationOptionRow extends LitElement {
left-chevron left-chevron
.collapsed=${this._collapsed} .collapsed=${this._collapsed}
.selected=${this._selected} .selected=${this._selected}
.sortSelected=${this.sortSelected}
@click=${this._toggleSidebar} @click=${this._toggleSidebar}
@toggle-collapsed=${this._toggleCollapse} @toggle-collapsed=${this._toggleCollapse}
.sortSelected=${this.sortSelected} @delete-row=${this._removeOption}
>${this._renderRow()}</ha-automation-row >${this._renderRow()}</ha-automation-row
>` >`
: html` : html`
@@ -445,10 +450,6 @@ export default class HaAutomationOptionRow extends LitElement {
this._collapsed = !this._collapsed; this._collapsed = !this._collapsed;
} }
public isSelected() {
return this._selected;
}
public focus() { public focus() {
this._automationRowElement?.focus(); this._automationRowElement?.focus();
} }

View File

@@ -240,7 +240,7 @@ export default class HaAutomationOption extends LitElement {
ev.stopPropagation(); ev.stopPropagation();
const { index, data } = ev.detail; const { index, data } = ev.detail;
const item = ev.detail.item as HaAutomationOptionRow; const item = ev.detail.item as HaAutomationOptionRow;
const selected = item.isSelected(); const selected = item.selected;
const options = [ const options = [
...this.options.slice(0, index), ...this.options.slice(0, index),

View File

@@ -1,15 +1,16 @@
import { import {
mdiAppleKeyboardCommand,
mdiContentCopy, mdiContentCopy,
mdiContentCut, mdiContentCut,
mdiContentDuplicate,
mdiDelete, mdiDelete,
mdiPlay, mdiPlay,
mdiPlayCircleOutline, mdiPlayCircleOutline,
mdiPlaylistEdit, mdiPlaylistEdit,
mdiPlusCircleMultipleOutline,
mdiRenameBox, mdiRenameBox,
mdiStopCircleOutline, mdiStopCircleOutline,
} from "@mdi/js"; } from "@mdi/js";
import { html, LitElement } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed"; import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
@@ -21,6 +22,7 @@ import { ACTION_BUILDING_BLOCKS } from "../../../../data/action";
import type { ActionSidebarConfig } from "../../../../data/automation"; import type { ActionSidebarConfig } from "../../../../data/automation";
import type { RepeatAction } from "../../../../data/script"; import type { RepeatAction } from "../../../../data/script";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor"; import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
import { getAutomationActionType } from "../action/ha-automation-action-row"; import { getAutomationActionType } from "../action/ha-automation-action-row";
import { getRepeatType } from "../action/types/ha-automation-action-repeat"; import { getRepeatType } from "../action/types/ha-automation-action-repeat";
@@ -103,18 +105,24 @@ export default class HaAutomationSidebarAction extends LitElement {
<span slot="subtitle">${subtitle}</span> <span slot="subtitle">${subtitle}</span>
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.run}> <ha-md-menu-item slot="menu-items" .clickAction=${this.config.run}>
${this.hass.localize("ui.panel.config.automation.editor.actions.run")}
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon> <ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize("ui.panel.config.automation.editor.actions.run")}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
slot="menu-items" slot="menu-items"
.clickAction=${this.config.rename} .clickAction=${this.config.rename}
.disabled=${!!disabled} .disabled=${!!disabled}
> >
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename" "ui.panel.config.automation.editor.triggers.rename"
)} )}
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon> <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-divider <ha-md-divider
slot="menu-items" slot="menu-items"
@@ -126,36 +134,85 @@ export default class HaAutomationSidebarAction extends LitElement {
.clickAction=${this.config.duplicate} .clickAction=${this.config.duplicate}
.disabled=${this.disabled} .disabled=${this.disabled}
> >
<ha-svg-icon
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate" "ui.panel.config.automation.editor.actions.duplicate"
)} )}
<ha-svg-icon slot="start" .path=${mdiContentDuplicate}></ha-svg-icon> <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
slot="menu-items" slot="menu-items"
.clickAction=${this.config.copy} .clickAction=${this.config.copy}
.disabled=${this.disabled} .disabled=${this.disabled}
> >
${this.hass.localize("ui.panel.config.automation.editor.triggers.copy")}
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon> <ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
)}
${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span>C</span>
</span>`
: nothing}
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
slot="menu-items" slot="menu-items"
.clickAction=${this.config.cut} .clickAction=${this.config.cut}
.disabled=${this.disabled} .disabled=${this.disabled}
> >
${this.hass.localize("ui.panel.config.automation.editor.triggers.cut")}
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon> <ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
)}
${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span>X</span>
</span>`
: nothing}
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
slot="menu-items" slot="menu-items"
.clickAction=${this._toggleYamlMode} .clickAction=${this._toggleYamlMode}
.disabled=${!this.config.uiSupported || !!this._warnings} .disabled=${!this.config.uiSupported || !!this._warnings}
> >
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}` `ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)} )}
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon> <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-divider <ha-md-divider
slot="menu-items" slot="menu-items"
@@ -163,13 +220,16 @@ export default class HaAutomationSidebarAction extends LitElement {
tabindex="-1" tabindex="-1"
></ha-md-divider> ></ha-md-divider>
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.disable}> <ha-md-menu-item slot="menu-items" .clickAction=${this.config.disable}>
${this.hass.localize(
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
)}
<ha-svg-icon <ha-svg-icon
slot="start" slot="start"
.path=${this.disabled ? mdiPlayCircleOutline : mdiStopCircleOutline} .path=${this.disabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
></ha-svg-icon> ></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
slot="menu-items" slot="menu-items"
@@ -177,10 +237,32 @@ export default class HaAutomationSidebarAction extends LitElement {
.disabled=${this.disabled} .disabled=${this.disabled}
class="warning" class="warning"
> >
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete" "ui.panel.config.automation.editor.actions.delete"
)} )}
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon> ${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span
>${this.hass.localize(
"ui.panel.config.automation.editor.del"
)}</span
>
</span>`
: nothing}
</div>
</ha-md-menu-item> </ha-md-menu-item>
${description && !this.yamlMode ${description && !this.yamlMode
? html`<div class="description">${description}</div>` ? html`<div class="description">${description}</div>`

View File

@@ -1,15 +1,16 @@
import { import {
mdiAppleKeyboardCommand,
mdiContentCopy, mdiContentCopy,
mdiContentCut, mdiContentCut,
mdiContentDuplicate,
mdiDelete, mdiDelete,
mdiFlask, mdiFlask,
mdiPlayCircleOutline, mdiPlayCircleOutline,
mdiPlaylistEdit, mdiPlaylistEdit,
mdiPlusCircleMultipleOutline,
mdiRenameBox, mdiRenameBox,
mdiStopCircleOutline, mdiStopCircleOutline,
} from "@mdi/js"; } from "@mdi/js";
import { css, html, LitElement } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { keyed } from "lit/directives/keyed"; import { keyed } from "lit/directives/keyed";
@@ -22,6 +23,7 @@ import {
import { CONDITION_BUILDING_BLOCKS } from "../../../../data/condition"; import { CONDITION_BUILDING_BLOCKS } from "../../../../data/condition";
import { validateConfig } from "../../../../data/config"; import { validateConfig } from "../../../../data/config";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
import { showAlertDialog } from "../../../lovelace/custom-card-helpers"; import { showAlertDialog } from "../../../lovelace/custom-card-helpers";
import "../condition/ha-automation-condition-editor"; import "../condition/ha-automation-condition-editor";
import type HaAutomationConditionEditor from "../condition/ha-automation-condition-editor"; import type HaAutomationConditionEditor from "../condition/ha-automation-condition-editor";
@@ -99,20 +101,26 @@ export default class HaAutomationSidebarCondition extends LitElement {
<span slot="title">${title}</span> <span slot="title">${title}</span>
<span slot="subtitle">${subtitle}</span> <span slot="subtitle">${subtitle}</span>
<ha-md-menu-item slot="menu-items" .clickAction=${this._testCondition}> <ha-md-menu-item slot="menu-items" .clickAction=${this._testCondition}>
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.conditions.test" "ui.panel.config.automation.editor.conditions.test"
)} )}
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon> <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
slot="menu-items" slot="menu-items"
.clickAction=${this.config.rename} .clickAction=${this.config.rename}
.disabled=${!!disabled} .disabled=${!!disabled}
> >
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename" "ui.panel.config.automation.editor.triggers.rename"
)} )}
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon> <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-divider <ha-md-divider
@@ -126,10 +134,16 @@ export default class HaAutomationSidebarCondition extends LitElement {
.clickAction=${this.config.duplicate} .clickAction=${this.config.duplicate}
.disabled=${this.disabled} .disabled=${this.disabled}
> >
<ha-svg-icon
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate" "ui.panel.config.automation.editor.actions.duplicate"
)} )}
<ha-svg-icon slot="start" .path=${mdiContentDuplicate}></ha-svg-icon> <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
@@ -137,8 +151,28 @@ export default class HaAutomationSidebarCondition extends LitElement {
.clickAction=${this.config.copy} .clickAction=${this.config.copy}
.disabled=${this.disabled} .disabled=${this.disabled}
> >
${this.hass.localize("ui.panel.config.automation.editor.triggers.copy")}
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon> <ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
)}
${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span>C</span>
</span>`
: nothing}
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
@@ -146,18 +180,41 @@ export default class HaAutomationSidebarCondition extends LitElement {
.clickAction=${this.config.cut} .clickAction=${this.config.cut}
.disabled=${this.disabled} .disabled=${this.disabled}
> >
${this.hass.localize("ui.panel.config.automation.editor.triggers.cut")}
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon> <ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
)}
${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span>X</span>
</span>`
: nothing}
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
slot="menu-items" slot="menu-items"
.clickAction=${this._toggleYamlMode} .clickAction=${this._toggleYamlMode}
.disabled=${!this.config.uiSupported || !!this._warnings} .disabled=${!this.config.uiSupported || !!this._warnings}
> >
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}` `ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)} )}
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon> <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-divider <ha-md-divider
slot="menu-items" slot="menu-items"
@@ -165,13 +222,16 @@ export default class HaAutomationSidebarCondition extends LitElement {
tabindex="-1" tabindex="-1"
></ha-md-divider> ></ha-md-divider>
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.disable}> <ha-md-menu-item slot="menu-items" .clickAction=${this.config.disable}>
${this.hass.localize(
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
)}
<ha-svg-icon <ha-svg-icon
slot="start" slot="start"
.path=${this.disabled ? mdiPlayCircleOutline : mdiStopCircleOutline} .path=${this.disabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
></ha-svg-icon> ></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
slot="menu-items" slot="menu-items"
@@ -179,10 +239,32 @@ export default class HaAutomationSidebarCondition extends LitElement {
.disabled=${this.disabled} .disabled=${this.disabled}
class="warning" class="warning"
> >
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete" "ui.panel.config.automation.editor.actions.delete"
)} )}
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon> ${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span
>${this.hass.localize(
"ui.panel.config.automation.editor.del"
)}</span
>
</span>`
: nothing}
</div>
</ha-md-menu-item> </ha-md-menu-item>
${description && !this.yamlMode ${description && !this.yamlMode
? html`<div class="description">${description}</div>` ? html`<div class="description">${description}</div>`

View File

@@ -1,8 +1,14 @@
import { mdiContentDuplicate, mdiDelete, mdiRenameBox } from "@mdi/js"; import {
import { html, LitElement } from "lit"; mdiAppleKeyboardCommand,
mdiDelete,
mdiPlusCircleMultipleOutline,
mdiRenameBox,
} from "@mdi/js";
import { html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import type { OptionSidebarConfig } from "../../../../data/automation"; import type { OptionSidebarConfig } from "../../../../data/automation";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor"; import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
import { sidebarEditorStyles } from "../styles"; import { sidebarEditorStyles } from "../styles";
import "./ha-automation-sidebar-card"; import "./ha-automation-sidebar-card";
@@ -52,10 +58,13 @@ export default class HaAutomationSidebarOption extends LitElement {
.clickAction=${this.config.rename} .clickAction=${this.config.rename}
.disabled=${!!disabled} .disabled=${!!disabled}
> >
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename" "ui.panel.config.automation.editor.triggers.rename"
)} )}
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon> <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
@@ -63,13 +72,16 @@ export default class HaAutomationSidebarOption extends LitElement {
@click=${this.config.duplicate} @click=${this.config.duplicate}
.disabled=${this.disabled} .disabled=${this.disabled}
> >
<ha-svg-icon
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate" "ui.panel.config.automation.editor.actions.duplicate"
)} )}
<ha-svg-icon <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
slot="start" </div>
.path=${mdiContentDuplicate}
></ha-svg-icon>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-divider <ha-md-divider
slot="menu-items" slot="menu-items"
@@ -82,10 +94,32 @@ export default class HaAutomationSidebarOption extends LitElement {
.disabled=${this.disabled} .disabled=${this.disabled}
class="warning" class="warning"
> >
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.choose.remove_option" "ui.panel.config.automation.editor.actions.type.choose.remove_option"
)} )}
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon> ${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span
>${this.hass.localize(
"ui.panel.config.automation.editor.del"
)}</span
>
</span>`
: nothing}
</div>
</ha-md-menu-item> </ha-md-menu-item>
`} `}

View File

@@ -1,11 +1,12 @@
import { mdiDelete, mdiPlaylistEdit } from "@mdi/js"; import { mdiAppleKeyboardCommand, mdiDelete, mdiPlaylistEdit } from "@mdi/js";
import { html, LitElement } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed"; import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import type { LocalizeKeys } from "../../../../common/translations/localize"; import type { LocalizeKeys } from "../../../../common/translations/localize";
import type { ScriptFieldSidebarConfig } from "../../../../data/automation"; import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
import "../../script/ha-script-field-selector-editor"; import "../../script/ha-script-field-selector-editor";
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor"; import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
import { sidebarEditorStyles } from "../styles"; import { sidebarEditorStyles } from "../styles";
@@ -68,10 +69,13 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
.clickAction=${this._toggleYamlMode} .clickAction=${this._toggleYamlMode}
.disabled=${!!this._warnings} .disabled=${!!this._warnings}
> >
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}` `ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)} )}
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon> <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
slot="menu-items" slot="menu-items"
@@ -79,10 +83,32 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
.disabled=${this.disabled} .disabled=${this.disabled}
class="warning" class="warning"
> >
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete" "ui.panel.config.automation.editor.actions.delete"
)} )}
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon> ${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span
>${this.hass.localize(
"ui.panel.config.automation.editor.del"
)}</span
>
</span>`
: nothing}
</div>
</ha-md-menu-item> </ha-md-menu-item>
${keyed( ${keyed(
this.sidebarKey, this.sidebarKey,

View File

@@ -1,10 +1,11 @@
import { mdiDelete, mdiPlaylistEdit } from "@mdi/js"; import { mdiAppleKeyboardCommand, mdiDelete, mdiPlaylistEdit } from "@mdi/js";
import { html, LitElement } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed"; import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import type { ScriptFieldSidebarConfig } from "../../../../data/automation"; import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
import "../../script/ha-script-field-editor"; import "../../script/ha-script-field-editor";
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor"; import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
import { sidebarEditorStyles } from "../styles"; import { sidebarEditorStyles } from "../styles";
@@ -61,10 +62,13 @@ export default class HaAutomationSidebarScriptField extends LitElement {
.clickAction=${this._toggleYamlMode} .clickAction=${this._toggleYamlMode}
.disabled=${!!this._warnings} .disabled=${!!this._warnings}
> >
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}` `ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)} )}
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon> <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
slot="menu-items" slot="menu-items"
@@ -72,10 +76,32 @@ export default class HaAutomationSidebarScriptField extends LitElement {
.disabled=${this.disabled} .disabled=${this.disabled}
class="warning" class="warning"
> >
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete" "ui.panel.config.automation.editor.actions.delete"
)} )}
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon> ${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span
>${this.hass.localize(
"ui.panel.config.automation.editor.del"
)}</span
>
</span>`
: nothing}
</div>
</ha-md-menu-item> </ha-md-menu-item>
${keyed( ${keyed(
this.sidebarKey, this.sidebarKey,

View File

@@ -1,11 +1,12 @@
import { import {
mdiAppleKeyboardCommand,
mdiContentCopy, mdiContentCopy,
mdiContentCut, mdiContentCut,
mdiContentDuplicate,
mdiDelete, mdiDelete,
mdiIdentifier, mdiIdentifier,
mdiPlayCircleOutline, mdiPlayCircleOutline,
mdiPlaylistEdit, mdiPlaylistEdit,
mdiPlusCircleMultipleOutline,
mdiRenameBox, mdiRenameBox,
mdiStopCircleOutline, mdiStopCircleOutline,
} from "@mdi/js"; } from "@mdi/js";
@@ -17,6 +18,7 @@ import { handleStructError } from "../../../../common/structs/handle-errors";
import type { TriggerSidebarConfig } from "../../../../data/automation"; import type { TriggerSidebarConfig } from "../../../../data/automation";
import { isTriggerList } from "../../../../data/trigger"; import { isTriggerList } from "../../../../data/trigger";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
import { sidebarEditorStyles } from "../styles"; import { sidebarEditorStyles } from "../styles";
import "../trigger/ha-automation-trigger-editor"; import "../trigger/ha-automation-trigger-editor";
import type HaAutomationTriggerEditor from "../trigger/ha-automation-trigger-editor"; import type HaAutomationTriggerEditor from "../trigger/ha-automation-trigger-editor";
@@ -89,10 +91,13 @@ export default class HaAutomationSidebarTrigger extends LitElement {
.clickAction=${this.config.rename} .clickAction=${this.config.rename}
.disabled=${disabled || type === "list"} .disabled=${disabled || type === "list"}
> >
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename" "ui.panel.config.automation.editor.triggers.rename"
)} )}
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon> <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
${!this.yamlMode && ${!this.yamlMode &&
!("id" in this.config.config) && !("id" in this.config.config) &&
@@ -102,10 +107,13 @@ export default class HaAutomationSidebarTrigger extends LitElement {
.clickAction=${this._showTriggerId} .clickAction=${this._showTriggerId}
.disabled=${disabled || type === "list"} .disabled=${disabled || type === "list"}
> >
<ha-svg-icon slot="start" .path=${mdiIdentifier}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id" "ui.panel.config.automation.editor.triggers.edit_id"
)} )}
<ha-svg-icon slot="start" .path=${mdiIdentifier}></ha-svg-icon> <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item>` </ha-md-menu-item>`
: nothing} : nothing}
@@ -123,7 +131,10 @@ export default class HaAutomationSidebarTrigger extends LitElement {
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.triggers.duplicate" "ui.panel.config.automation.editor.triggers.duplicate"
)} )}
<ha-svg-icon slot="start" .path=${mdiContentDuplicate}></ha-svg-icon> <ha-svg-icon
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
@@ -131,10 +142,28 @@ export default class HaAutomationSidebarTrigger extends LitElement {
.clickAction=${this.config.copy} .clickAction=${this.config.copy}
.disabled=${this.disabled} .disabled=${this.disabled}
> >
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy" "ui.panel.config.automation.editor.triggers.copy"
)} )}
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon> ${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span>C</span>
</span>`
: nothing}
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
@@ -142,20 +171,41 @@ export default class HaAutomationSidebarTrigger extends LitElement {
.clickAction=${this.config.cut} .clickAction=${this.config.cut}
.disabled=${this.disabled} .disabled=${this.disabled}
> >
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut" "ui.panel.config.automation.editor.triggers.cut"
)} )}
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon> ${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span>X</span>
</span>`
: nothing}
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
slot="menu-items" slot="menu-items"
.clickAction=${this._toggleYamlMode} .clickAction=${this._toggleYamlMode}
.disabled=${!this.config.uiSupported || !!this._warnings} .disabled=${!this.config.uiSupported || !!this._warnings}
> >
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}` `ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)} )}
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon> <span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-divider <ha-md-divider
slot="menu-items" slot="menu-items"
@@ -167,13 +217,16 @@ export default class HaAutomationSidebarTrigger extends LitElement {
.clickAction=${this.config.disable} .clickAction=${this.config.disable}
.disabled=${type === "list"} .disabled=${type === "list"}
> >
${this.hass.localize(
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
)}
<ha-svg-icon <ha-svg-icon
slot="start" slot="start"
.path=${this.disabled ? mdiPlayCircleOutline : mdiStopCircleOutline} .path=${this.disabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
></ha-svg-icon> ></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-md-menu-item> </ha-md-menu-item>
<ha-md-menu-item <ha-md-menu-item
slot="menu-items" slot="menu-items"
@@ -181,10 +234,32 @@ export default class HaAutomationSidebarTrigger extends LitElement {
.disabled=${this.disabled} .disabled=${this.disabled}
class="warning" class="warning"
> >
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete" "ui.panel.config.automation.editor.actions.delete"
)} )}
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon> ${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span
>${this.hass.localize(
"ui.panel.config.automation.editor.del"
)}</span
>
</span>`
: nothing}
</div>
</ha-md-menu-item> </ha-md-menu-item>
${keyed( ${keyed(
this.sidebarKey, this.sidebarKey,

View File

@@ -218,4 +218,34 @@ export const sidebarEditorStyles = css`
.description { .description {
padding-top: 16px; padding-top: 16px;
} }
.overflow-label {
display: flex;
justify-content: space-between;
gap: 12px;
white-space: nowrap;
}
.overflow-label .shortcut {
--mdc-icon-size: 12px;
display: inline-flex;
flex-direction: row;
align-items: center;
gap: 2px;
}
.overflow-label .shortcut span {
font-size: var(--ha-font-size-s);
font-family: var(--ha-font-family-code);
color: var(--ha-color-text-secondary);
}
.shortcut-placeholder {
display: inline-block;
width: 60px;
}
.shortcut-placeholder.mac {
width: 46px;
}
@media all and (max-width: 870px) {
.shortcut-placeholder {
display: none;
}
}
`; `;

View File

@@ -4,12 +4,12 @@ import {
mdiArrowUp, mdiArrowUp,
mdiContentCopy, mdiContentCopy,
mdiContentCut, mdiContentCut,
mdiContentDuplicate,
mdiDelete, mdiDelete,
mdiDotsVertical, mdiDotsVertical,
mdiIdentifier, mdiIdentifier,
mdiPlayCircleOutline, mdiPlayCircleOutline,
mdiPlaylistEdit, mdiPlaylistEdit,
mdiPlusCircleMultipleOutline,
mdiRenameBox, mdiRenameBox,
mdiStopCircleOutline, mdiStopCircleOutline,
} from "@mdi/js"; } from "@mdi/js";
@@ -52,6 +52,7 @@ import {
showPromptDialog, showPromptDialog,
} from "../../../../dialogs/generic/show-dialog-box"; } from "../../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { showToast } from "../../../../util/toast";
import "../ha-automation-editor-warning"; import "../ha-automation-editor-warning";
import { rowStyles } from "../styles"; import { rowStyles } from "../styles";
import "./ha-automation-trigger-editor"; import "./ha-automation-trigger-editor";
@@ -74,6 +75,7 @@ import "./types/ha-automation-trigger-time";
import "./types/ha-automation-trigger-time_pattern"; 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";
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
export interface TriggerElement extends LitElement { export interface TriggerElement extends LitElement {
trigger: Trigger; trigger: Trigger;
@@ -153,6 +155,10 @@ export default class HaAutomationTriggerRow extends LitElement {
@consume({ context: fullEntitiesContext, subscribe: true }) @consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[]; _entityReg!: EntityRegistryEntry[];
get selected() {
return this._selected;
}
private _triggerUnsub?: Promise<UnsubscribeFunc>; private _triggerUnsub?: Promise<UnsubscribeFunc>;
private _renderRow() { private _renderRow() {
@@ -221,7 +227,7 @@ export default class HaAutomationTriggerRow extends LitElement {
)} )}
<ha-svg-icon <ha-svg-icon
slot="start" slot="start"
.path=${mdiContentDuplicate} .path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon> ></ha-svg-icon>
</ha-md-menu-item> </ha-md-menu-item>
@@ -349,10 +355,13 @@ export default class HaAutomationTriggerRow extends LitElement {
? html`<ha-automation-row ? html`<ha-automation-row
.disabled=${"enabled" in this.trigger && .disabled=${"enabled" in this.trigger &&
this.trigger.enabled === false} this.trigger.enabled === false}
@click=${this._toggleSidebar}
.selected=${this._selected} .selected=${this._selected}
.highlight=${this.highlight} .highlight=${this.highlight}
.sortSelected=${this.sortSelected} .sortSelected=${this.sortSelected}
@click=${this._toggleSidebar}
@copy-row=${this._copyTrigger}
@cut-row=${this._cutTrigger}
@delete-row=${this._onDelete}
>${this._selected >${this._selected
? "selected" ? "selected"
: nothing}${this._renderRow()}</ha-automation-row : nothing}${this._renderRow()}</ha-automation-row
@@ -525,6 +534,7 @@ export default class HaAutomationTriggerRow extends LitElement {
...this._clipboard, ...this._clipboard,
trigger: this.trigger, trigger: this.trigger,
}; };
copyToClipboard(JSON.stringify(this.trigger));
} }
private _onDelete = () => { private _onDelete = () => {
@@ -634,6 +644,12 @@ export default class HaAutomationTriggerRow extends LitElement {
private _copyTrigger = () => { private _copyTrigger = () => {
this._setClipboard(); this._setClipboard();
showToast(this, {
message: this.hass.localize(
"ui.panel.config.automation.editor.triggers.copied_to_clipboard"
),
duration: 2000,
});
}; };
private _cutTrigger = () => { private _cutTrigger = () => {
@@ -642,6 +658,12 @@ export default class HaAutomationTriggerRow extends LitElement {
if (this._selected) { if (this._selected) {
fireEvent(this, "close-sidebar"); fireEvent(this, "close-sidebar");
} }
showToast(this, {
message: this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut_to_clipboard"
),
duration: 2000,
});
}; };
private _moveUp = () => { private _moveUp = () => {
@@ -679,10 +701,6 @@ export default class HaAutomationTriggerRow extends LitElement {
customElements.get(`ha-automation-trigger-${type}`) !== undefined customElements.get(`ha-automation-trigger-${type}`) !== undefined
); );
public isSelected() {
return this._selected;
}
public focus() { public focus() {
this._automationRowElement?.focus(); this._automationRowElement?.focus();
} }

View File

@@ -259,7 +259,7 @@ export default class HaAutomationTrigger extends LitElement {
ev.stopPropagation(); ev.stopPropagation();
const { index, data } = ev.detail; const { index, data } = ev.detail;
const item = ev.detail.item as HaAutomationTriggerRow; const item = ev.detail.item as HaAutomationTriggerRow;
const selected = item.isSelected(); const selected = item.selected;
let triggers = [ let triggers = [
...this.triggers.slice(0, index), ...this.triggers.slice(0, index),

View File

@@ -1,7 +1,6 @@
import { consume } from "@lit/context"; import { consume } from "@lit/context";
import { import {
mdiCog, mdiCog,
mdiContentDuplicate,
mdiContentSave, mdiContentSave,
mdiDebugStepOver, mdiDebugStepOver,
mdiDelete, mdiDelete,
@@ -11,6 +10,7 @@ import {
mdiInformationOutline, mdiInformationOutline,
mdiPlay, mdiPlay,
mdiPlaylistEdit, mdiPlaylistEdit,
mdiPlusCircleMultipleOutline,
mdiRenameBox, mdiRenameBox,
mdiRobotConfused, mdiRobotConfused,
mdiTag, mdiTag,
@@ -308,7 +308,7 @@ export class HaScriptEditor extends SubscribeMixin(
)} )}
<ha-svg-icon <ha-svg-icon
slot="graphic" slot="graphic"
.path=${mdiContentDuplicate} .path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon> ></ha-svg-icon>
</ha-list-item> </ha-list-item>
@@ -1047,6 +1047,9 @@ export class HaScriptEditor extends SubscribeMixin(
protected supportedShortcuts(): SupportedShortcuts { protected supportedShortcuts(): SupportedShortcuts {
return { return {
s: () => this._handleSaveScript(), s: () => this._handleSaveScript(),
c: () => this._copySelectedRow(),
x: () => this._cutSelectedRow(),
Delete: () => this._deleteSelectedRow(),
}; };
} }
@@ -1066,6 +1069,18 @@ export class HaScriptEditor extends SubscribeMixin(
this._manualEditor?.expandAll(); this._manualEditor?.expandAll();
} }
private _copySelectedRow() {
this._manualEditor?.copySelectedRow();
}
private _cutSelectedRow() {
this._manualEditor?.cutSelectedRow();
}
private _deleteSelectedRow() {
this._manualEditor?.deleteSelectedRow();
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@@ -64,6 +64,7 @@ export default class HaScriptFieldRow extends LitElement {
@toggle-collapsed=${this._toggleCollapse} @toggle-collapsed=${this._toggleCollapse}
.collapsed=${this._collapsed} .collapsed=${this._collapsed}
.highlight=${this.highlight} .highlight=${this.highlight}
@delete-row=${this._onDelete}
> >
<h3 slot="header">${this.key}</h3> <h3 slot="header">${this.key}</h3>

View File

@@ -2,7 +2,13 @@ import { mdiContentSave, mdiHelpCircle } from "@mdi/js";
import { load } from "js-yaml"; import { load } from "js-yaml";
import type { CSSResultGroup, PropertyValues } from "lit"; import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import {
customElement,
property,
query,
queryAll,
state,
} from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { import {
any, any,
@@ -24,7 +30,10 @@ import {
} from "../../../common/url/search-params"; } from "../../../common/url/search-params";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-markdown"; import "../../../components/ha-markdown";
import type { SidebarConfig } from "../../../data/automation"; import type {
ActionSidebarConfig,
SidebarConfig,
} from "../../../data/automation";
import type { Action, Fields, ScriptConfig } from "../../../data/script"; import type { Action, Fields, ScriptConfig } from "../../../data/script";
import { import {
getActionType, getActionType,
@@ -80,6 +89,11 @@ export class HaManualScriptEditor extends LitElement {
@query("ha-automation-sidebar") private _sidebarElement?: HaAutomationSidebar; @query("ha-automation-sidebar") private _sidebarElement?: HaAutomationSidebar;
@queryAll("ha-automation-action, ha-script-fields")
private _collapsableElements?: NodeListOf<
HaAutomationAction | HaScriptFields
>;
private _previousConfig?: ScriptConfig; private _previousConfig?: ScriptConfig;
private _openFields = false; private _openFields = false;
@@ -357,7 +371,11 @@ export class HaManualScriptEditor extends LitElement {
if (normalized) { if (normalized) {
ev.preventDefault(); ev.preventDefault();
if (this.dirty) { if (
this.dirty ||
ensureArray(this.config.sequence)?.length ||
Object.keys(this.config.fields || {}).length
) {
const result = await new Promise<boolean>((resolve) => { const result = await new Promise<boolean>((resolve) => {
showPasteReplaceDialog(this, { showPasteReplaceDialog(this, {
domain: "script", domain: "script",
@@ -501,24 +519,36 @@ export class HaManualScriptEditor extends LitElement {
fireEvent(this, "save-script"); fireEvent(this, "save-script");
} }
private _getCollapsableElements() {
return this.shadowRoot!.querySelectorAll<
HaAutomationAction | HaScriptFields
>("ha-automation-action, ha-script-fields");
}
public expandAll() { public expandAll() {
this._getCollapsableElements().forEach((element) => { this._collapsableElements?.forEach((element) => {
element.expandAll(); element.expandAll();
}); });
} }
public collapseAll() { public collapseAll() {
this._getCollapsableElements().forEach((element) => { this._collapsableElements?.forEach((element) => {
element.collapseAll(); element.collapseAll();
}); });
} }
public copySelectedRow() {
if ((this._sidebarConfig as ActionSidebarConfig)?.copy) {
(this._sidebarConfig as ActionSidebarConfig).copy();
}
}
public cutSelectedRow() {
if ((this._sidebarConfig as ActionSidebarConfig)?.cut) {
(this._sidebarConfig as ActionSidebarConfig).cut();
}
}
public deleteSelectedRow() {
if ((this._sidebarConfig as ActionSidebarConfig)?.delete) {
(this._sidebarConfig as ActionSidebarConfig).delete();
}
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
saveFabStyles, saveFabStyles,

View File

@@ -3856,6 +3856,9 @@
"type_script_plural": "[%key:ui::panel::config::blueprint::overview::types_plural::script%]", "type_script_plural": "[%key:ui::panel::config::blueprint::overview::types_plural::script%]",
"new_automation_setup_failed_title": "New {type} setup failed", "new_automation_setup_failed_title": "New {type} setup failed",
"new_automation_setup_failed_text": "Your new {type} has saved, but waiting for it to setup has timed out. This could be due to errors parsing your configuration.yaml, please check the configuration in developer tools. Your {type} will not be visible until this is corrected, and {types} are reloaded. Changes to area, category, or labels were not saved and must be reapplied.", "new_automation_setup_failed_text": "Your new {type} has saved, but waiting for it to setup has timed out. This could be due to errors parsing your configuration.yaml, please check the configuration in developer tools. Your {type} will not be visible until this is corrected, and {types} are reloaded. Changes to area, category, or labels were not saved and must be reapplied.",
"item_pasted": "{item} pasted",
"ctrl": "Ctrl",
"del": "Del",
"triggers": { "triggers": {
"name": "Triggers", "name": "Triggers",
"header": "When", "header": "When",
@@ -3882,6 +3885,8 @@
"unknown_trigger": "[%key:ui::panel::config::devices::automation::triggers::unknown_trigger%]", "unknown_trigger": "[%key:ui::panel::config::devices::automation::triggers::unknown_trigger%]",
"triggering_event_detail": "Triggering event detail", "triggering_event_detail": "Triggering event detail",
"trigger": "Trigger", "trigger": "Trigger",
"copied_to_clipboard": "Trigger copied to clipboard",
"cut_to_clipboard": "Trigger cut to clipboard",
"groups": { "groups": {
"entity": { "entity": {
"label": "Entity", "label": "Entity",
@@ -4144,6 +4149,8 @@
"type_select": "Condition type", "type_select": "Condition type",
"unknown_condition": "[%key:ui::panel::config::devices::automation::conditions::unknown_condition%]", "unknown_condition": "[%key:ui::panel::config::devices::automation::conditions::unknown_condition%]",
"condition": "Condition", "condition": "Condition",
"copied_to_clipboard": "Condition copied to clipboard",
"cut_to_clipboard": "Condition cut to clipboard",
"groups": { "groups": {
"entity": { "entity": {
"label": "Entity", "label": "Entity",
@@ -4313,6 +4320,8 @@
"type_select": "Action type", "type_select": "Action type",
"continue_on_error": "Continue on error", "continue_on_error": "Continue on error",
"action": "Action", "action": "Action",
"copied_to_clipboard": "Action copied to clipboard",
"cut_to_clipboard": "Action cut to clipboard",
"groups": { "groups": {
"helpers": { "helpers": {
"label": "Helpers" "label": "Helpers"