Always allow reorder for triggers, conditions, actions and options (#19574)

* Move up and down action to overflow menu

* Always enable reorder mode on desktop

* Use media query helper
This commit is contained in:
Paul Bottein 2024-01-30 15:06:02 +01:00 committed by GitHub
parent 568e9ebc38
commit 64fc58ddd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 642 additions and 886 deletions

View File

@ -32,7 +32,6 @@ import {
expandDeviceTarget,
Selector,
} from "../data/selector";
import { ReorderModeMixin } from "../state/reorder-mode-mixin";
import { HomeAssistant, ValueChangedEvent } from "../types";
import { documentationUrl } from "../util/documentation-url";
import "./ha-checkbox";
@ -77,7 +76,7 @@ interface ExtHassService extends Omit<HassService, "fields"> {
}
@customElement("ha-service-control")
export class HaServiceControl extends ReorderModeMixin(LitElement) {
export class HaServiceControl extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public value?: {
@ -441,7 +440,6 @@ export class HaServiceControl extends ReorderModeMixin(LitElement) {
allow-custom-entity
></ha-entity-picker>`
: ""}
${this._renderReorderModeAlert()}
${shouldRenderServiceDataYaml
? html`<ha-yaml-editor
.hass=${this.hass}
@ -522,34 +520,6 @@ export class HaServiceControl extends ReorderModeMixin(LitElement) {
})}`;
}
private _renderReorderModeAlert() {
if (!this._reorderMode.active) {
return nothing;
}
return html`
<ha-alert
class="re-order"
alert-type="info"
.title=${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.title"
)}
>
${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.description_all"
)}
<ha-button slot="action" @click=${this._exitReOrderMode}>
${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.exit"
)}
</ha-button>
</ha-alert>
`;
}
private async _exitReOrderMode() {
this._reorderMode.exit();
}
private _localizeValueCallback = (key: string) => {
if (!this._value?.service) {
return "";

View File

@ -3,6 +3,8 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import {
mdiAlertCircleCheck,
mdiArrowDown,
mdiArrowUp,
mdiCheck,
mdiContentCopy,
mdiContentCut,
@ -12,7 +14,6 @@ import {
mdiPlay,
mdiPlayCircleOutline,
mdiRenameBox,
mdiSort,
mdiStopCircleOutline,
} from "@mdi/js";
import deepClone from "deep-clone-simple";
@ -34,9 +35,9 @@ import { handleStructError } from "../../../../common/structs/handle-errors";
import "../../../../components/ha-alert";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-card";
import "../../../../components/ha-service-icon";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-service-icon";
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import { ACTION_ICONS, YAML_ONLY_ACTION_TYPES } from "../../../../data/action";
import { AutomationClipboard } from "../../../../data/automation";
@ -56,10 +57,6 @@ import {
showPromptDialog,
} from "../../../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../resources/styles";
import {
ReorderMode,
reorderModeContext,
} from "../../../../state/reorder-mode-mixin";
import type { HomeAssistant, ItemPath } from "../../../../types";
import { showToast } from "../../../../util/toast";
import "./types/ha-automation-action-activate_scene";
@ -73,10 +70,10 @@ import "./types/ha-automation-action-parallel";
import "./types/ha-automation-action-play_media";
import "./types/ha-automation-action-repeat";
import "./types/ha-automation-action-service";
import "./types/ha-automation-action-set_conversation_response";
import "./types/ha-automation-action-stop";
import "./types/ha-automation-action-wait_for_trigger";
import "./types/ha-automation-action-wait_template";
import "./types/ha-automation-action-set_conversation_response";
export const getType = (action: Action | undefined) => {
if (!action) {
@ -131,10 +128,12 @@ export default class HaAutomationActionRow extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public hideMenu = false;
@property({ type: Array }) public path?: ItemPath;
@property({ type: Boolean }) public first?: boolean;
@property({ type: Boolean }) public last?: boolean;
@storage({
key: "automationClipboard",
state: false,
@ -147,10 +146,6 @@ export default class HaAutomationActionRow extends LitElement {
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
@state()
@consume({ context: reorderModeContext, subscribe: true })
private _reorderMode?: ReorderMode;
@state() private _warnings?: string[];
@state() private _uiModeAvailable = true;
@ -189,17 +184,17 @@ export default class HaAutomationActionRow extends LitElement {
const type = getType(this.action);
const yamlMode = this._yamlMode;
const noReorderModeAvailable = this._reorderMode === undefined;
return html`
<ha-card outlined>
${this.action.enabled === false
? html`<div class="disabled-bar">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.disabled"
)}
</div>`
: ""}
? html`
<div class="disabled-bar">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.disabled"
)}
</div>
`
: nothing}
<ha-expansion-panel leftChevron>
<h3 slot="header">
${type === "service" &&
@ -220,6 +215,7 @@ export default class HaAutomationActionRow extends LitElement {
</h3>
<slot name="icons" slot="icons"></slot>
${type !== "condition" &&
(this.action as NonConditionAction).continue_on_error === true
? html`<div slot="icons">
@ -231,146 +227,134 @@ export default class HaAutomationActionRow extends LitElement {
</simple-tooltip>
</div> `
: nothing}
${this.hideMenu
? ""
: html`
<ha-button-menu
slot="icons"
@action=${this._handleAction}
@click=${preventDefault}
fixed
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.run"
)}
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.rename"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiRenameBox}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
graphic="icon"
.disabled=${this.disabled}
class=${classMap({ hidden: noReorderModeAvailable })}
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.re_order"
)}
<ha-svg-icon slot="graphic" .path=${mdiSort}></ha-svg-icon>
</mwc-list-item>
<ha-button-menu
slot="icons"
@action=${this._handleAction}
@click=${preventDefault}
fixed
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.run"
)}
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
</mwc-list-item>
<li divider role="separator"></li>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.rename"
)}
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentDuplicate}
></ha-svg-icon>
</mwc-list-item>
<li divider role="separator"></li>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentCopy}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentDuplicate}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentCut}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
)}
<ha-svg-icon slot="graphic" .path=${mdiContentCopy}></ha-svg-icon>
</mwc-list-item>
<li divider role="separator"></li>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
)}
<ha-svg-icon slot="graphic" .path=${mdiContentCut}></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
.disabled=${!this._uiModeAvailable}
graphic="icon"
>
${this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)}
${!yamlMode
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
<mwc-list-item
graphic="icon"
.disabled=${this.disabled || this.first}
>
${this.hass.localize("ui.panel.config.automation.editor.move_up")}
<ha-svg-icon slot="graphic" .path=${mdiArrowUp}></ha-svg-icon
></mwc-list-item>
<mwc-list-item
.disabled=${!this._uiModeAvailable}
graphic="icon"
>
${this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
${yamlMode
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
<mwc-list-item
graphic="icon"
.disabled=${this.disabled || this.last}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon slot="graphic" .path=${mdiArrowDown}></ha-svg-icon
></mwc-list-item>
<li divider role="separator"></li>
<li divider role="separator"></li>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.action.enabled === false
? this.hass.localize(
"ui.panel.config.automation.editor.actions.enable"
)
: this.hass.localize(
"ui.panel.config.automation.editor.actions.disable"
)}
<ha-svg-icon
slot="graphic"
.path=${this.action.enabled === false
? mdiPlayCircleOutline
: mdiStopCircleOutline}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
class="warning"
graphic="icon"
.disabled=${this.disabled}
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
<ha-svg-icon
class="warning"
slot="graphic"
.path=${mdiDelete}
></ha-svg-icon>
</mwc-list-item>
</ha-button-menu>
`}
<mwc-list-item .disabled=${!this._uiModeAvailable} graphic="icon">
${this.hass.localize("ui.panel.config.automation.editor.edit_ui")}
${!yamlMode
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
<mwc-list-item .disabled=${!this._uiModeAvailable} graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
${yamlMode
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
<li divider role="separator"></li>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.action.enabled === false
? this.hass.localize(
"ui.panel.config.automation.editor.actions.enable"
)
: this.hass.localize(
"ui.panel.config.automation.editor.actions.disable"
)}
<ha-svg-icon
slot="graphic"
.path=${this.action.enabled === false
? mdiPlayCircleOutline
: mdiStopCircleOutline}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
class="warning"
graphic="icon"
.disabled=${this.disabled}
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
<ha-svg-icon
class="warning"
slot="graphic"
.path=${mdiDelete}
></ha-svg-icon>
</mwc-list-item>
</ha-button-menu>
<div
class=${classMap({
@ -453,30 +437,33 @@ export default class HaAutomationActionRow extends LitElement {
await this._renameAction();
break;
case 2:
this._reorderMode?.enter();
fireEvent(this, "duplicate");
break;
case 3:
fireEvent(this, "duplicate");
this._setClipboard();
break;
case 4:
this._setClipboard();
break;
case 5:
this._setClipboard();
fireEvent(this, "value-changed", { value: null });
break;
case 5:
fireEvent(this, "move-up");
break;
case 6:
fireEvent(this, "move-down");
break;
case 7:
this._switchUiMode();
this.expand();
break;
case 7:
case 8:
this._switchYamlMode();
this.expand();
break;
case 8:
case 9:
this._onDisable();
break;
case 9:
case 10:
this._onDelete();
break;
}

View File

@ -1,11 +1,18 @@
import { consume } from "@lit-labs/context";
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
import { mdiDrag, mdiPlus } from "@mdi/js";
import deepClone from "deep-clone-simple";
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
import {
CSSResultGroup,
LitElement,
PropertyValues,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import { storage } from "../../../../common/decorators/storage";
import { fireEvent } from "../../../../common/dom/fire_event";
import { listenMediaQuery } from "../../../../common/dom/media_query";
import { nestedArrayMove } from "../../../../common/util/array-move";
import "../../../../components/ha-button";
import "../../../../components/ha-sortable";
@ -13,10 +20,6 @@ import "../../../../components/ha-svg-icon";
import { getService, isService } from "../../../../data/action";
import type { AutomationClipboard } from "../../../../data/automation";
import { Action } from "../../../../data/script";
import {
ReorderMode,
reorderModeContext,
} from "../../../../state/reorder-mode-mixin";
import { HomeAssistant, ItemPath } from "../../../../types";
import {
PASTE_VALUE,
@ -37,9 +40,7 @@ export default class HaAutomationAction extends LitElement {
@property({ attribute: false }) public actions!: Action[];
@state()
@consume({ context: reorderModeContext, subscribe: true })
private _reorderMode?: ReorderMode;
@state() private _showReorder: boolean = false;
@storage({
key: "automationClipboard",
@ -53,6 +54,21 @@ export default class HaAutomationAction extends LitElement {
private _actionKeys = new WeakMap<Action, string>();
private _unsubMql?: () => void;
public connectedCallback() {
super.connectedCallback();
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
this._showReorder = matches;
});
}
public disconnectedCallback() {
super.disconnectedCallback();
this._unsubMql?.();
this._unsubMql = undefined;
}
private get nested() {
return this.path !== undefined;
}
@ -61,7 +77,7 @@ export default class HaAutomationAction extends LitElement {
return html`
<ha-sortable
handle-selector=".handle"
.disabled=${!this._reorderMode?.active}
.disabled=${!this._showReorder}
@item-moved=${this._actionMoved}
group="actions"
.path=${this.path}
@ -74,44 +90,28 @@ export default class HaAutomationAction extends LitElement {
<ha-automation-action-row
.path=${[...(this.path ?? []), idx]}
.index=${idx}
.first=${idx === 0}
.last=${idx === this.actions.length - 1}
.action=${action}
.narrow=${this.narrow}
.disabled=${this.disabled}
.hideMenu=${Boolean(this._reorderMode?.active)}
@duplicate=${this._duplicateAction}
@move-down=${this._moveDown}
@move-up=${this._moveUp}
@value-changed=${this._actionChanged}
.hass=${this.hass}
>
${this._reorderMode?.active
${this._showReorder
? html`
<ha-icon-button
.index=${idx}
slot="icons"
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
.path=${mdiArrowUp}
@click=${this._moveUp}
.disabled=${idx === 0}
></ha-icon-button>
<ha-icon-button
.index=${idx}
slot="icons"
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
.path=${mdiArrowDown}
@click=${this._moveDown}
.disabled=${idx === this.actions.length - 1}
></ha-icon-button>
<div class="handle" slot="icons">
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
</div>
`
: ""}
: nothing}
</ha-automation-action-row>
`
)}
</div>
</div>
</ha-sortable>
<div class="buttons">
@ -281,7 +281,7 @@ export default class HaAutomationAction extends LitElement {
overflow: hidden;
}
.handle {
padding: 12px;
padding: 12px 4px;
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
}

View File

@ -9,15 +9,21 @@ import {
mdiDrag,
mdiPlus,
mdiRenameBox,
mdiSort,
} from "@mdi/js";
import deepClone from "deep-clone-simple";
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
import {
CSSResultGroup,
LitElement,
PropertyValues,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat";
import { ensureArray } from "../../../../../common/array/ensure-array";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { listenMediaQuery } from "../../../../../common/dom/media_query";
import { capitalizeFirstLetter } from "../../../../../common/string/capitalize-first-letter";
import "../../../../../components/ha-button";
import "../../../../../components/ha-button-menu";
@ -37,10 +43,6 @@ import {
showPromptDialog,
} from "../../../../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../../resources/styles";
import {
ReorderMode,
reorderModeContext,
} from "../../../../../state/reorder-mode-mixin";
import { HomeAssistant, ItemPath } from "../../../../../types";
import { ActionElement } from "../ha-automation-action-row";
@ -64,12 +66,25 @@ export class HaChooseAction extends LitElement implements ActionElement {
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
@state()
@consume({ context: reorderModeContext, subscribe: true })
private _reorderMode?: ReorderMode;
@state() private _showReorder: boolean = false;
private _expandLast = false;
private _unsubMql?: () => void;
public connectedCallback() {
super.connectedCallback();
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
this._showReorder = matches;
});
}
public disconnectedCallback() {
super.disconnectedCallback();
this._unsubMql?.();
this._unsubMql = undefined;
}
public static get defaultConfig() {
return { choose: [{ conditions: [], sequence: [] }] };
}
@ -104,12 +119,10 @@ export class HaChooseAction extends LitElement implements ActionElement {
protected render() {
const action = this.action;
const noReorderModeAvailable = this._reorderMode === undefined;
return html`
<ha-sortable
handle-selector=".handle"
.disabled=${!this._reorderMode?.active}
.disabled=${!this._showReorder}
group="choose-options"
.path=${[...(this.path ?? []), "choose"]}
>
@ -135,103 +148,89 @@ export class HaChooseAction extends LitElement implements ActionElement {
? ""
: this._getDescription(option))}
</h3>
${this._reorderMode?.active
${this._showReorder
? html`
<ha-icon-button
.index=${idx}
slot="icons"
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
.path=${mdiArrowUp}
@click=${this._moveUp}
.disabled=${idx === 0}
></ha-icon-button>
<ha-icon-button
.index=${idx}
slot="icons"
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
.path=${mdiArrowDown}
@click=${this._moveDown}
.disabled=${idx ===
ensureArray(this.action.choose).length - 1}
></ha-icon-button>
<div class="handle" slot="icons">
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
</div>
`
: html`
<ha-button-menu
slot="icons"
.idx=${idx}
@action=${this._handleAction}
@click=${preventDefault}
fixed
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item
graphic="icon"
.disabled=${this.disabled}
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.rename"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiRenameBox}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
graphic="icon"
.disabled=${this.disabled}
class=${classMap({
hidden: noReorderModeAvailable,
})}
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.re_order"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiSort}
></ha-svg-icon>
</mwc-list-item>
: nothing}
<mwc-list-item
graphic="icon"
.disabled=${this.disabled}
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentDuplicate}
></ha-svg-icon>
</mwc-list-item>
<ha-button-menu
slot="icons"
.idx=${idx}
@action=${this._handleAction}
@click=${preventDefault}
fixed
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.rename"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiRenameBox}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentDuplicate}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
graphic="icon"
.disabled=${this.disabled || idx === 0}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiArrowUp}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
graphic="icon"
.disabled=${this.disabled ||
idx === ensureArray(this.action.choose).length - 1}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiArrowDown}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
class="warning"
graphic="icon"
.disabled=${this.disabled}
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
)}
<ha-svg-icon
class="warning"
slot="graphic"
.path=${mdiDelete}
></ha-svg-icon>
</mwc-list-item>
</ha-button-menu>
<mwc-list-item
class="warning"
graphic="icon"
.disabled=${this.disabled}
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
)}
<ha-svg-icon
class="warning"
slot="graphic"
.path=${mdiDelete}
></ha-svg-icon>
</mwc-list-item>
</ha-button-menu>
`}
<div class="card-content">
<h4>
${this.hass.localize(
@ -324,12 +323,15 @@ export class HaChooseAction extends LitElement implements ActionElement {
await this._renameAction(ev);
break;
case 1:
this._reorderMode?.enter();
break;
case 2:
this._duplicateOption(ev);
break;
case 2:
this._moveUp(ev);
break;
case 3:
this._moveDown(ev);
break;
case 4:
this._removeOption(ev);
break;
}
@ -433,13 +435,13 @@ export class HaChooseAction extends LitElement implements ActionElement {
}
private _moveUp(ev) {
const index = (ev.target as any).index;
const index = (ev.target as any).idx;
const newIndex = index - 1;
this._move(index, newIndex);
}
private _moveDown(ev) {
const index = (ev.target as any).index;
const index = (ev.target as any).idx;
const newIndex = index + 1;
this._move(index, newIndex);
}
@ -537,7 +539,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
padding: 0 16px 16px 16px;
}
.handle {
padding: 12px;
padding: 12px 4px;
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
}

View File

@ -1,6 +1,6 @@
import "@material/mwc-button/mwc-button";
import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { nestedArrayMove } from "../../../common/util/array-move";
@ -18,12 +18,11 @@ import {
fetchBlueprints,
} from "../../../data/blueprint";
import { haStyle } from "../../../resources/styles";
import { ReorderModeMixin } from "../../../state/reorder-mode-mixin";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
@customElement("blueprint-automation-editor")
export class HaBlueprintAutomationEditor extends ReorderModeMixin(LitElement) {
export class HaBlueprintAutomationEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public isWide = false;
@ -78,7 +77,6 @@ export class HaBlueprintAutomationEditor extends ReorderModeMixin(LitElement) {
${this.config.description
? html`<p class="description">${this.config.description}</p>`
: ""}
${this._renderReorderModeAlert()}
<ha-card
outlined
class="blueprint"
@ -173,34 +171,6 @@ export class HaBlueprintAutomationEditor extends ReorderModeMixin(LitElement) {
`;
}
private _renderReorderModeAlert() {
if (!this._reorderMode.active) {
return nothing;
}
return html`
<ha-alert
class="re-order"
alert-type="info"
.title=${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.title"
)}
>
${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.description_all"
)}
<ha-button slot="action" @click=${this._exitReOrderMode}>
${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.exit"
)}
</ha-button>
</ha-alert>
`;
}
private async _exitReOrderMode() {
this._reorderMode.exit();
}
private async _getBlueprints() {
this._blueprints = await fetchBlueprints(this.hass, "automation");
}

View File

@ -2,6 +2,8 @@ import { consume } from "@lit-labs/context";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import {
mdiArrowDown,
mdiArrowUp,
mdiCheck,
mdiContentCopy,
mdiContentCut,
@ -11,7 +13,6 @@ import {
mdiFlask,
mdiPlayCircleOutline,
mdiRenameBox,
mdiSort,
mdiStopCircleOutline,
} from "@mdi/js";
import deepClone from "deep-clone-simple";
@ -41,10 +42,6 @@ import {
import { haStyle } from "../../../../resources/styles";
import { HomeAssistant, ItemPath } from "../../../../types";
import "./ha-automation-condition-editor";
import {
ReorderMode,
reorderModeContext,
} from "../../../../state/reorder-mode-mixin";
export interface ConditionElement extends LitElement {
condition: Condition;
@ -83,12 +80,14 @@ export default class HaAutomationConditionRow extends LitElement {
@property({ attribute: false }) public condition!: Condition;
@property({ type: Boolean }) public hideMenu = false;
@property({ type: Boolean }) public disabled = false;
@property({ type: Array }) public path?: ItemPath;
@property({ type: Boolean }) public first?: boolean;
@property({ type: Boolean }) public last?: boolean;
@storage({
key: "automationClipboard",
state: false,
@ -109,25 +108,21 @@ export default class HaAutomationConditionRow extends LitElement {
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
@state()
@consume({ context: reorderModeContext, subscribe: true })
private _reorderMode?: ReorderMode;
protected render() {
if (!this.condition) {
return nothing;
}
const noReorderModeAvailable = this._reorderMode === undefined;
return html`
<ha-card outlined>
${this.condition.enabled === false
? html`<div class="disabled-bar">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.disabled"
)}
</div>`
? html`
<div class="disabled-bar">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.disabled"
)}
</div>
`
: ""}
<ha-expansion-panel leftChevron>
@ -142,142 +137,135 @@ export default class HaAutomationConditionRow extends LitElement {
</h3>
<slot name="icons" slot="icons"></slot>
${this.hideMenu
? ""
: html`
<ha-button-menu
slot="icons"
@action=${this._handleAction}
@click=${preventDefault}
fixed
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
>
</ha-icon-button>
<mwc-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.test"
)}
<ha-svg-icon slot="graphic" .path=${mdiFlask}></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.rename"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiRenameBox}
></ha-svg-icon>
</mwc-list-item>
<ha-button-menu
slot="icons"
@action=${this._handleAction}
@click=${preventDefault}
fixed
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
>
</ha-icon-button>
<mwc-list-item
graphic="icon"
.disabled=${this.disabled}
class=${classMap({ hidden: noReorderModeAvailable })}
>
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.re_order"
)}
<ha-svg-icon slot="graphic" .path=${mdiSort}></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.test"
)}
<ha-svg-icon slot="graphic" .path=${mdiFlask}></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.rename"
)}
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
</mwc-list-item>
<li divider role="separator"></li>
<li divider role="separator"></li>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentDuplicate}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentDuplicate}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentCopy}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
)}
<ha-svg-icon slot="graphic" .path=${mdiContentCopy}></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentCut}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
)}
<ha-svg-icon slot="graphic" .path=${mdiContentCut}></ha-svg-icon>
</mwc-list-item>
<li divider role="separator"></li>
<mwc-list-item
graphic="icon"
.disabled=${this.disabled || this.first}
>
${this.hass.localize("ui.panel.config.automation.editor.move_up")}
<ha-svg-icon slot="graphic" .path=${mdiArrowUp}></ha-svg-icon
></mwc-list-item>
<mwc-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)}
${!this._yamlMode
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
<mwc-list-item
graphic="icon"
.disabled=${this.disabled || this.last}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon slot="graphic" .path=${mdiArrowDown}></ha-svg-icon
></mwc-list-item>
<mwc-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
${this._yamlMode
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
<li divider role="separator"></li>
<li divider role="separator"></li>
<mwc-list-item graphic="icon">
${this.hass.localize("ui.panel.config.automation.editor.edit_ui")}
${!this._yamlMode
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.condition.enabled === false
? this.hass.localize(
"ui.panel.config.automation.editor.actions.enable"
)
: this.hass.localize(
"ui.panel.config.automation.editor.actions.disable"
)}
<ha-svg-icon
slot="graphic"
.path=${this.condition.enabled === false
? mdiPlayCircleOutline
: mdiStopCircleOutline}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
class="warning"
graphic="icon"
.disabled=${this.disabled}
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
<ha-svg-icon
class="warning"
slot="graphic"
.path=${mdiDelete}
></ha-svg-icon>
</mwc-list-item>
</ha-button-menu>
`}
<mwc-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
${this._yamlMode
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
<li divider role="separator"></li>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.condition.enabled === false
? this.hass.localize(
"ui.panel.config.automation.editor.actions.enable"
)
: this.hass.localize(
"ui.panel.config.automation.editor.actions.disable"
)}
<ha-svg-icon
slot="graphic"
.path=${this.condition.enabled === false
? mdiPlayCircleOutline
: mdiStopCircleOutline}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
class="warning"
graphic="icon"
.disabled=${this.disabled}
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
<ha-svg-icon
class="warning"
slot="graphic"
.path=${mdiDelete}
></ha-svg-icon>
</mwc-list-item>
</ha-button-menu>
<div
class=${classMap({
@ -359,30 +347,33 @@ export default class HaAutomationConditionRow extends LitElement {
await this._renameCondition();
break;
case 2:
this._reorderMode?.enter();
fireEvent(this, "duplicate");
break;
case 3:
fireEvent(this, "duplicate");
this._setClipboard();
break;
case 4:
this._setClipboard();
break;
case 5:
this._setClipboard();
fireEvent(this, "value-changed", { value: null });
break;
case 5:
fireEvent(this, "move-up");
break;
case 6:
fireEvent(this, "move-down");
break;
case 7:
this._switchUiMode();
this.expand();
break;
case 7:
case 8:
this._switchYamlMode();
this.expand();
break;
case 8:
case 9:
this._onDisable();
break;
case 9:
case 10:
this._onDelete();
break;
}

View File

@ -1,5 +1,4 @@
import { consume } from "@lit-labs/context";
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
import { mdiDrag, mdiPlus } from "@mdi/js";
import deepClone from "deep-clone-simple";
import {
CSSResultGroup,
@ -13,6 +12,7 @@ import { customElement, property, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import { storage } from "../../../../common/decorators/storage";
import { fireEvent } from "../../../../common/dom/fire_event";
import { listenMediaQuery } from "../../../../common/dom/media_query";
import { nestedArrayMove } from "../../../../common/util/array-move";
import "../../../../components/ha-button";
import "../../../../components/ha-button-menu";
@ -22,10 +22,6 @@ import type {
AutomationClipboard,
Condition,
} from "../../../../data/automation";
import {
ReorderMode,
reorderModeContext,
} from "../../../../state/reorder-mode-mixin";
import type { HomeAssistant, ItemPath } from "../../../../types";
import {
PASTE_VALUE,
@ -44,9 +40,7 @@ export default class HaAutomationCondition extends LitElement {
@property({ type: Array }) public path?: ItemPath;
@state()
@consume({ context: reorderModeContext, subscribe: true })
private _reorderMode?: ReorderMode;
@state() private _showReorder: boolean = false;
@storage({
key: "automationClipboard",
@ -60,6 +54,21 @@ export default class HaAutomationCondition extends LitElement {
private _conditionKeys = new WeakMap<Condition, string>();
private _unsubMql?: () => void;
public connectedCallback() {
super.connectedCallback();
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
this._showReorder = matches;
});
}
public disconnectedCallback() {
super.disconnectedCallback();
this._unsubMql?.();
this._unsubMql = undefined;
}
protected updated(changedProperties: PropertyValues) {
if (!changedProperties.has("conditions")) {
return;
@ -108,7 +117,7 @@ export default class HaAutomationCondition extends LitElement {
return html`
<ha-sortable
handle-selector=".handle"
.disabled=${!this._reorderMode?.active}
.disabled=${!this._showReorder}
@item-moved=${this._conditionMoved}
group="conditions"
.path=${this.path}
@ -121,42 +130,24 @@ export default class HaAutomationCondition extends LitElement {
<ha-automation-condition-row
.path=${[...(this.path ?? []), idx]}
.index=${idx}
.first=${idx === 0}
.last=${idx === this.conditions.length - 1}
.totalConditions=${this.conditions.length}
.condition=${cond}
.hideMenu=${Boolean(this._reorderMode?.active)}
.disabled=${this.disabled}
@duplicate=${this._duplicateCondition}
@move-condition=${this._move}
@move-down=${this._moveDown}
@move-up=${this._moveUp}
@value-changed=${this._conditionChanged}
.hass=${this.hass}
>
${this._reorderMode?.active
${this._showReorder
? html`
<ha-icon-button
.index=${idx}
slot="icons"
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
.path=${mdiArrowUp}
@click=${this._moveUp}
.disabled=${idx === 0}
></ha-icon-button>
<ha-icon-button
.index=${idx}
slot="icons"
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
.path=${mdiArrowDown}
@click=${this._moveDown}
.disabled=${idx === this.conditions.length - 1}
></ha-icon-button>
<div class="handle" slot="icons">
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
</div>
`
: ""}
: nothing}
</ha-automation-condition-row>
`
)}
@ -315,7 +306,7 @@ export default class HaAutomationCondition extends LitElement {
overflow: hidden;
}
.handle {
padding: 12px;
padding: 12px 4px;
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
}

View File

@ -76,7 +76,8 @@ declare global {
};
"ui-mode-not-available": Error;
duplicate: undefined;
"re-order": undefined;
"move-down": undefined;
"move-up": undefined;
}
}

View File

@ -16,7 +16,6 @@ import {
} from "../../../data/automation";
import { Action } from "../../../data/script";
import { haStyle } from "../../../resources/styles";
import { ReorderModeMixin } from "../../../state/reorder-mode-mixin";
import type { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import "./action/ha-automation-action";
@ -24,7 +23,7 @@ import "./condition/ha-automation-condition";
import "./trigger/ha-automation-trigger";
@customElement("manual-automation-editor")
export class HaManualAutomationEditor extends ReorderModeMixin(LitElement) {
export class HaManualAutomationEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public isWide = false;
@ -94,7 +93,6 @@ export class HaManualAutomationEditor extends ReorderModeMixin(LitElement) {
)}
</p>`
: nothing}
${this._renderReorderModeAlert("triggers")}
<ha-automation-trigger
role="region"
@ -137,7 +135,6 @@ export class HaManualAutomationEditor extends ReorderModeMixin(LitElement) {
)}
</p>`
: nothing}
${this._renderReorderModeAlert("conditions")}
<ha-automation-condition
role="region"
@ -178,7 +175,6 @@ export class HaManualAutomationEditor extends ReorderModeMixin(LitElement) {
)}
</p>`
: nothing}
${this._renderReorderModeAlert("actions")}
<ha-automation-action
role="region"
@ -194,34 +190,6 @@ export class HaManualAutomationEditor extends ReorderModeMixin(LitElement) {
`;
}
private _renderReorderModeAlert(type: "conditions" | "actions" | "triggers") {
if (!this._reorderMode.active) {
return nothing;
}
return html`
<ha-alert
class="re-order"
alert-type="info"
.title=${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.title"
)}
>
${this.hass.localize(
`ui.panel.config.automation.editor.re_order_mode.description_${type}`
)}
<ha-button slot="action" @click=${this._exitReOrderMode}>
${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.exit"
)}
</ha-button>
</ha-alert>
`;
}
private async _exitReOrderMode() {
this._reorderMode.exit();
}
private _triggerChanged(ev: CustomEvent): void {
ev.stopPropagation();
fireEvent(this, "value-changed", {

View File

@ -2,6 +2,8 @@ import { consume } from "@lit-labs/context";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import {
mdiArrowDown,
mdiArrowUp,
mdiCheck,
mdiContentCopy,
mdiContentCut,
@ -11,7 +13,6 @@ import {
mdiIdentifier,
mdiPlayCircleOutline,
mdiRenameBox,
mdiSort,
mdiStopCircleOutline,
} from "@mdi/js";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
@ -53,6 +54,7 @@ import {
import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant, ItemPath } from "../../../../types";
import "./types/ha-automation-trigger-calendar";
import "./types/ha-automation-trigger-conversation";
import "./types/ha-automation-trigger-device";
import "./types/ha-automation-trigger-event";
import "./types/ha-automation-trigger-geo_location";
@ -60,7 +62,6 @@ import "./types/ha-automation-trigger-homeassistant";
import "./types/ha-automation-trigger-mqtt";
import "./types/ha-automation-trigger-numeric_state";
import "./types/ha-automation-trigger-persistent_notification";
import "./types/ha-automation-trigger-conversation";
import "./types/ha-automation-trigger-state";
import "./types/ha-automation-trigger-sun";
import "./types/ha-automation-trigger-tag";
@ -69,10 +70,6 @@ import "./types/ha-automation-trigger-time";
import "./types/ha-automation-trigger-time_pattern";
import "./types/ha-automation-trigger-webhook";
import "./types/ha-automation-trigger-zone";
import {
ReorderMode,
reorderModeContext,
} from "../../../../state/reorder-mode-mixin";
export interface TriggerElement extends LitElement {
trigger: Trigger;
@ -108,12 +105,14 @@ export default class HaAutomationTriggerRow extends LitElement {
@property({ attribute: false }) public trigger!: Trigger;
@property({ type: Boolean }) public hideMenu = false;
@property({ type: Boolean }) public disabled = false;
@property({ type: Array }) public path?: ItemPath;
@property({ type: Boolean }) public first?: boolean;
@property({ type: Boolean }) public last?: boolean;
@state() private _warnings?: string[];
@state() private _yamlMode = false;
@ -138,17 +137,11 @@ export default class HaAutomationTriggerRow extends LitElement {
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
@state()
@consume({ context: reorderModeContext, subscribe: true })
private _reorderMode?: ReorderMode;
private _triggerUnsub?: Promise<UnsubscribeFunc>;
protected render() {
if (!this.trigger) return nothing;
const noReorderModeAvailable = this._reorderMode === undefined;
const supported =
customElements.get(`ha-automation-trigger-${this.trigger.platform}`) !==
undefined;
@ -165,7 +158,7 @@ export default class HaAutomationTriggerRow extends LitElement {
)}
</div>
`
: ""}
: nothing}
<ha-expansion-panel leftChevron>
<h3 slot="header">
@ -177,145 +170,136 @@ export default class HaAutomationTriggerRow extends LitElement {
</h3>
<slot name="icons" slot="icons"></slot>
${this.hideMenu
? ""
: html`
<ha-button-menu
slot="icons"
@action=${this._handleAction}
@click=${preventDefault}
fixed
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiRenameBox}
></ha-svg-icon>
</mwc-list-item>
<ha-button-menu
slot="icons"
@action=${this._handleAction}
@click=${preventDefault}
fixed
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item
graphic="icon"
.disabled=${this.disabled}
class=${classMap({ hidden: noReorderModeAvailable })}
>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.re_order"
)}
<ha-svg-icon slot="graphic" .path=${mdiSort}></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)}
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiIdentifier}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id"
)}
<ha-svg-icon slot="graphic" .path=${mdiIdentifier}></ha-svg-icon>
</mwc-list-item>
<li divider role="separator"></li>
<li divider role="separator"></li>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.duplicate"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentDuplicate}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.duplicate"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentDuplicate}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentCopy}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
)}
<ha-svg-icon slot="graphic" .path=${mdiContentCopy}></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiContentCut}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
)}
<ha-svg-icon slot="graphic" .path=${mdiContentCut}></ha-svg-icon>
</mwc-list-item>
<li divider role="separator"></li>
<mwc-list-item
graphic="icon"
.disabled=${this.disabled || this.first}
>
${this.hass.localize("ui.panel.config.automation.editor.move_up")}
<ha-svg-icon slot="graphic" .path=${mdiArrowUp}></ha-svg-icon
></mwc-list-item>
<mwc-list-item .disabled=${!supported} graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)}
${!yamlMode
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
<mwc-list-item
graphic="icon"
.disabled=${this.disabled || this.last}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon slot="graphic" .path=${mdiArrowDown}></ha-svg-icon
></mwc-list-item>
<mwc-list-item .disabled=${!supported} graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
${yamlMode
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
<li divider role="separator"></li>
<li divider role="separator"></li>
<mwc-list-item .disabled=${!supported} graphic="icon">
${this.hass.localize("ui.panel.config.automation.editor.edit_ui")}
${!yamlMode
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
<mwc-list-item .disabled=${!supported} graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
${yamlMode
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
<li divider role="separator"></li>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.trigger.enabled === false
? this.hass.localize(
"ui.panel.config.automation.editor.actions.enable"
)
: this.hass.localize(
"ui.panel.config.automation.editor.actions.disable"
)}
<ha-svg-icon
slot="graphic"
.path=${this.trigger.enabled === false
? mdiPlayCircleOutline
: mdiStopCircleOutline}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
class="warning"
graphic="icon"
.disabled=${this.disabled}
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
<ha-svg-icon
class="warning"
slot="graphic"
.path=${mdiDelete}
></ha-svg-icon>
</mwc-list-item>
</ha-button-menu>
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
${this.trigger.enabled === false
? this.hass.localize(
"ui.panel.config.automation.editor.actions.enable"
)
: this.hass.localize(
"ui.panel.config.automation.editor.actions.disable"
)}
<ha-svg-icon
slot="graphic"
.path=${this.trigger.enabled === false
? mdiPlayCircleOutline
: mdiStopCircleOutline}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
class="warning"
graphic="icon"
.disabled=${this.disabled}
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
<ha-svg-icon
class="warning"
slot="graphic"
.path=${mdiDelete}
></ha-svg-icon>
</mwc-list-item>
</ha-button-menu>
`}
<div
class=${classMap({
"card-content": true,
@ -496,34 +480,37 @@ export default class HaAutomationTriggerRow extends LitElement {
await this._renameTrigger();
break;
case 1:
this._reorderMode?.enter();
break;
case 2:
this._requestShowId = true;
this.expand();
break;
case 3:
case 2:
fireEvent(this, "duplicate");
break;
case 3:
this._setClipboard();
break;
case 4:
this._setClipboard();
break;
case 5:
this._setClipboard();
fireEvent(this, "value-changed", { value: null });
break;
case 5:
fireEvent(this, "move-up");
break;
case 6:
fireEvent(this, "move-down");
break;
case 7:
this._switchUiMode();
this.expand();
break;
case 7:
case 8:
this._switchYamlMode();
this.expand();
break;
case 8:
case 9:
this._onDisable();
break;
case 9:
case 10:
this._onDelete();
break;
}

View File

@ -1,21 +1,24 @@
import { consume } from "@lit-labs/context";
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
import { mdiDrag, mdiPlus } from "@mdi/js";
import deepClone from "deep-clone-simple";
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
import {
CSSResultGroup,
LitElement,
PropertyValues,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import { storage } from "../../../../common/decorators/storage";
import { fireEvent } from "../../../../common/dom/fire_event";
import { listenMediaQuery } from "../../../../common/dom/media_query";
import { nestedArrayMove } from "../../../../common/util/array-move";
import "../../../../components/ha-button";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-sortable";
import "../../../../components/ha-svg-icon";
import { AutomationClipboard, Trigger } from "../../../../data/automation";
import {
ReorderMode,
reorderModeContext,
} from "../../../../state/reorder-mode-mixin";
import { HomeAssistant, ItemPath } from "../../../../types";
import {
PASTE_VALUE,
@ -34,9 +37,7 @@ export default class HaAutomationTrigger extends LitElement {
@property({ type: Array }) public path?: ItemPath;
@state()
@consume({ context: reorderModeContext, subscribe: true })
private _reorderMode?: ReorderMode;
@state() private _showReorder: boolean = false;
@storage({
key: "automationClipboard",
@ -50,6 +51,21 @@ export default class HaAutomationTrigger extends LitElement {
private _triggerKeys = new WeakMap<Trigger, string>();
private _unsubMql?: () => void;
public connectedCallback() {
super.connectedCallback();
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
this._showReorder = matches;
});
}
public disconnectedCallback() {
super.disconnectedCallback();
this._unsubMql?.();
this._unsubMql = undefined;
}
private get nested() {
return this.path !== undefined;
}
@ -58,7 +74,7 @@ export default class HaAutomationTrigger extends LitElement {
return html`
<ha-sortable
handle-selector=".handle"
.disabled=${!this._reorderMode?.active}
.disabled=${!this._showReorder}
@item-moved=${this._triggerMoved}
group="triggers"
.path=${this.path}
@ -71,40 +87,23 @@ export default class HaAutomationTrigger extends LitElement {
<ha-automation-trigger-row
.path=${[...(this.path ?? []), idx]}
.index=${idx}
.first=${idx === 0}
.last=${idx === this.triggers.length - 1}
.trigger=${trg}
.hideMenu=${Boolean(this._reorderMode?.active)}
@duplicate=${this._duplicateTrigger}
@move-down=${this._moveDown}
@move-up=${this._moveUp}
@value-changed=${this._triggerChanged}
.hass=${this.hass}
.disabled=${this.disabled}
>
${this._reorderMode?.active
${this._showReorder
? html`
<ha-icon-button
.index=${idx}
slot="icons"
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
.path=${mdiArrowUp}
@click=${this._moveUp}
.disabled=${idx === 0}
></ha-icon-button>
<ha-icon-button
.index=${idx}
slot="icons"
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
.path=${mdiArrowDown}
@click=${this._moveDown}
.disabled=${idx === this.triggers.length - 1}
></ha-icon-button>
<div class="handle" slot="icons">
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
</div>
`
: ""}
: nothing}
</ha-automation-trigger-row>
`
)}
@ -256,7 +255,7 @@ export default class HaAutomationTrigger extends LitElement {
overflow: hidden;
}
.handle {
padding: 12px;
padding: 12px 4px;
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
}

View File

@ -1,4 +1,4 @@
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { nestedArrayMove } from "../../../common/util/array-move";
@ -18,10 +18,9 @@ import { BlueprintScriptConfig } from "../../../data/script";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import { ReorderModeMixin } from "../../../state/reorder-mode-mixin";
@customElement("blueprint-script-editor")
export class HaBlueprintScriptEditor extends ReorderModeMixin(LitElement) {
export class HaBlueprintScriptEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public isWide = false;
@ -57,7 +56,6 @@ export class HaBlueprintScriptEditor extends ReorderModeMixin(LitElement) {
</mwc-button>
</ha-alert>`
: ""}
${this._renderReorderModeAlert()}
<ha-card
outlined
class="blueprint"
@ -151,34 +149,6 @@ export class HaBlueprintScriptEditor extends ReorderModeMixin(LitElement) {
`;
}
private _renderReorderModeAlert() {
if (!this._reorderMode.active) {
return nothing;
}
return html`
<ha-alert
class="re-order"
alert-type="info"
.title=${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.title"
)}
>
${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.description_all"
)}
<ha-button slot="action" @click=${this._exitReOrderMode}>
${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.exit"
)}
</ha-button>
</ha-alert>
`;
}
private async _exitReOrderMode() {
this._reorderMode.exit();
}
private async _getBlueprints() {
this._blueprints = await fetchBlueprints(this.hass, "script");
}

View File

@ -8,7 +8,6 @@ import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import { Action, Fields, ScriptConfig } from "../../../data/script";
import { haStyle } from "../../../resources/styles";
import { ReorderModeMixin } from "../../../state/reorder-mode-mixin";
import type { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import "../automation/action/ha-automation-action";
@ -16,7 +15,7 @@ import "./ha-script-fields";
import type HaScriptFields from "./ha-script-fields";
@customElement("manual-script-editor")
export class HaManualScriptEditor extends ReorderModeMixin(LitElement) {
export class HaManualScriptEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public isWide = false;
@ -120,8 +119,6 @@ export class HaManualScriptEditor extends ReorderModeMixin(LitElement) {
</a>
</div>
${this._renderReorderModeAlert()}
<ha-automation-action
role="region"
aria-labelledby="sequence-heading"
@ -136,34 +133,6 @@ export class HaManualScriptEditor extends ReorderModeMixin(LitElement) {
`;
}
private _renderReorderModeAlert() {
if (!this._reorderMode.active) {
return nothing;
}
return html`
<ha-alert
class="re-order"
alert-type="info"
.title=${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.title"
)}
>
${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.description_all"
)}
<ha-button slot="action" @click=${this._exitReOrderMode}>
${this.hass.localize(
"ui.panel.config.automation.editor.re_order_mode.exit"
)}
</ha-button>
</ha-alert>
`;
}
private async _exitReOrderMode() {
this._reorderMode.exit();
}
private _fieldsChanged(ev: CustomEvent): void {
ev.stopPropagation();
fireEvent(this, "value-changed", {

View File

@ -1,40 +0,0 @@
import { ContextProvider, createContext } from "@lit-labs/context";
import { LitElement } from "lit";
import { Constructor } from "../types";
export type ReorderMode = {
active: boolean;
enter: () => void;
exit: () => void;
};
export const reorderModeContext = createContext<ReorderMode>("reorder-mode");
export const ReorderModeMixin = <T extends Constructor<LitElement>>(
superClass: T
) =>
class extends superClass {
private _reorderModeProvider = new ContextProvider(this, {
context: reorderModeContext,
initialValue: {
active: false,
enter: () => {
this._reorderModeProvider.setValue({
...this._reorderModeProvider.value,
active: true,
});
this.requestUpdate("_reorderMode");
},
exit: () => {
this._reorderModeProvider.setValue({
...this._reorderModeProvider.value,
active: false,
});
this.requestUpdate("_reorderMode");
},
},
});
get _reorderMode() {
return this._reorderModeProvider.value;
}
};

View File

@ -2446,15 +2446,6 @@
"automation_settings": "Automation settings",
"move_up": "Move up",
"move_down": "Move down",
"re_order": "Re-order",
"re_order_mode": {
"title": "Re-order mode",
"description_triggers": "You are in re-order mode, you can re-order your triggers.",
"description_conditions": "You are in re-order mode, you can re-order your conditions.",
"description_actions": "You are in re-order mode, you can re-order your actions.",
"description_all": "You are in re-order mode, you can re-order your triggers, conditions and actions.",
"exit": "Exit"
},
"description": {
"label": "Description",
"placeholder": "Optional description",