mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 09:16:38 +00:00
Reorder automation elements (#13548)
This commit is contained in:
parent
02d608b704
commit
ab745f6e8e
@ -1,8 +1,6 @@
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
mdiArrowDown,
|
||||
mdiArrowUp,
|
||||
mdiCheck,
|
||||
mdiContentDuplicate,
|
||||
mdiDelete,
|
||||
@ -17,13 +15,15 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||
import { ACTION_TYPES } from "../../../../data/action";
|
||||
import { validateConfig } from "../../../../data/config";
|
||||
import { Action, getActionType } from "../../../../data/script";
|
||||
import { describeAction } from "../../../../data/script_i18n";
|
||||
@ -50,8 +50,6 @@ import "./types/ha-automation-action-service";
|
||||
import "./types/ha-automation-action-stop";
|
||||
import "./types/ha-automation-action-wait_for_trigger";
|
||||
import "./types/ha-automation-action-wait_template";
|
||||
import { ACTION_TYPES } from "../../../../data/action";
|
||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||
|
||||
const getType = (action: Action | undefined) => {
|
||||
if (!action) {
|
||||
@ -66,13 +64,6 @@ const getType = (action: Action | undefined) => {
|
||||
return Object.keys(ACTION_TYPES).find((option) => option in action);
|
||||
};
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"move-action": { direction: "up" | "down" };
|
||||
}
|
||||
}
|
||||
|
||||
export interface ActionElement extends LitElement {
|
||||
action: Action;
|
||||
}
|
||||
@ -107,12 +98,12 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
|
||||
@property() public action!: Action;
|
||||
|
||||
@property() public index!: number;
|
||||
|
||||
@property() public totalActions!: number;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean }) public hideMenu = false;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@state() private _uiModeAvailable = true;
|
||||
@ -165,119 +156,112 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
${capitalizeFirstLetter(describeAction(this.hass, this.action))}
|
||||
</h3>
|
||||
|
||||
${this.index !== 0
|
||||
? html`
|
||||
<ha-icon-button
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
${this.hideMenu
|
||||
? ""
|
||||
: html`
|
||||
<ha-button-menu
|
||||
slot="icons"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_up"
|
||||
)}
|
||||
.path=${mdiArrowUp}
|
||||
@click=${this._moveUp}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
${this.index !== this.totalActions - 1
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="icons"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
.path=${mdiArrowDown}
|
||||
@click=${this._moveDown}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
<ha-button-menu
|
||||
slot="icons"
|
||||
fixed
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handleAction}
|
||||
@click=${preventDefault}
|
||||
>
|
||||
<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>
|
||||
fixed
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handleAction}
|
||||
@click=${preventDefault}
|
||||
>
|
||||
<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">
|
||||
${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">
|
||||
${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">
|
||||
${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">
|
||||
${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>
|
||||
<li divider role="separator"></li>
|
||||
|
||||
<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_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>
|
||||
<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>
|
||||
<li divider role="separator"></li>
|
||||
|
||||
<mwc-list-item graphic="icon">
|
||||
${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">
|
||||
${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.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">
|
||||
${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,
|
||||
@ -327,6 +311,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
hass: this.hass,
|
||||
action: this.action,
|
||||
narrow: this.narrow,
|
||||
reOrderMode: this.reOrderMode,
|
||||
})}
|
||||
</div>
|
||||
`}
|
||||
@ -346,16 +331,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _moveUp(ev) {
|
||||
ev.preventDefault();
|
||||
fireEvent(this, "move-action", { direction: "up" });
|
||||
}
|
||||
|
||||
private _moveDown(ev) {
|
||||
ev.preventDefault();
|
||||
fireEvent(this, "move-action", { direction: "down" });
|
||||
}
|
||||
|
||||
private async _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
|
@ -1,15 +1,25 @@
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import "@material/mwc-button";
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import type { HaSelect } from "../../../../components/ha-select";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { ACTION_TYPES } from "../../../../data/action";
|
||||
import { Action } from "../../../../data/script";
|
||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||
import {
|
||||
loadSortable,
|
||||
SortableInstance,
|
||||
} from "../../../../resources/sortable.ondemand";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import "./ha-automation-action-row";
|
||||
import type HaAutomationActionRow from "./ha-automation-action-row";
|
||||
@ -27,10 +37,6 @@ import "./types/ha-automation-action-service";
|
||||
import "./types/ha-automation-action-stop";
|
||||
import "./types/ha-automation-action-wait_for_trigger";
|
||||
import "./types/ha-automation-action-wait_template";
|
||||
import { ACTION_TYPES } from "../../../../data/action";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import type { HaSelect } from "../../../../components/ha-select";
|
||||
|
||||
@customElement("ha-automation-action")
|
||||
export default class HaAutomationAction extends LitElement {
|
||||
@ -40,28 +46,62 @@ export default class HaAutomationAction extends LitElement {
|
||||
|
||||
@property() public actions!: Action[];
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
private _focusLastActionOnChange = false;
|
||||
|
||||
private _actionKeys = new WeakMap<Action, string>();
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${repeat(
|
||||
this.actions,
|
||||
(action) => this._getKey(action),
|
||||
(action, idx) => html`
|
||||
<ha-automation-action-row
|
||||
.index=${idx}
|
||||
.totalActions=${this.actions.length}
|
||||
.action=${action}
|
||||
.narrow=${this.narrow}
|
||||
@duplicate=${this._duplicateAction}
|
||||
@move-action=${this._move}
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action-row>
|
||||
`
|
||||
)}
|
||||
<div class="actions">
|
||||
${repeat(
|
||||
this.actions,
|
||||
(action) => this._getKey(action),
|
||||
(action, idx) => html`
|
||||
<ha-automation-action-row
|
||||
.index=${idx}
|
||||
.action=${action}
|
||||
.narrow=${this.narrow}
|
||||
.hideMenu=${this.reOrderMode}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
@duplicate=${this._duplicateAction}
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
${this.reOrderMode
|
||||
? 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>
|
||||
`
|
||||
: ""}
|
||||
</ha-automation-action-row>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<ha-button-menu fixed @action=${this._addAction}>
|
||||
<mwc-button
|
||||
slot="trigger"
|
||||
@ -86,6 +126,13 @@ export default class HaAutomationAction extends LitElement {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("reOrderMode")) {
|
||||
if (this.reOrderMode) {
|
||||
this._createSortable();
|
||||
} else {
|
||||
this._destroySortable();
|
||||
}
|
||||
}
|
||||
if (changedProps.has("actions") && this._focusLastActionOnChange) {
|
||||
this._focusLastActionOnChange = false;
|
||||
|
||||
@ -100,6 +147,33 @@ export default class HaAutomationAction extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = await loadSortable();
|
||||
this._sortable = new Sortable(this.shadowRoot!.querySelector(".actions")!, {
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
handle: ".handle",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._dragged(evt);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
private _getKey(action: Action) {
|
||||
if (!this._actionKeys.has(action)) {
|
||||
this._actionKeys.set(action, Math.random().toString());
|
||||
@ -121,12 +195,24 @@ export default class HaAutomationAction extends LitElement {
|
||||
fireEvent(this, "value-changed", { value: actions });
|
||||
}
|
||||
|
||||
private _move(ev: CustomEvent) {
|
||||
// Prevent possible parent action-row from also moving
|
||||
ev.stopPropagation();
|
||||
|
||||
private _moveUp(ev) {
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = ev.detail.direction === "up" ? index - 1 : index + 1;
|
||||
const newIndex = index - 1;
|
||||
this._move(index, newIndex);
|
||||
}
|
||||
|
||||
private _moveDown(ev) {
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = index + 1;
|
||||
this._move(index, newIndex);
|
||||
}
|
||||
|
||||
private _dragged(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) return;
|
||||
this._move(ev.oldIndex!, ev.newIndex!);
|
||||
}
|
||||
|
||||
private _move(index: number, newIndex: number) {
|
||||
const actions = this.actions.concat();
|
||||
const action = actions.splice(index, 1)[0];
|
||||
actions.splice(newIndex, 0, action);
|
||||
@ -177,16 +263,27 @@ export default class HaAutomationAction extends LitElement {
|
||||
);
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-automation-action-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
`;
|
||||
return [
|
||||
sortableStyles,
|
||||
css`
|
||||
ha-automation-action-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
.handle {
|
||||
cursor: move;
|
||||
padding: 12px;
|
||||
}
|
||||
.handle ha-svg-icon {
|
||||
pointer-events: none;
|
||||
height: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,8 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
|
||||
@property() public action!: ChooseAction;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@state() private _showDefault = false;
|
||||
|
||||
public static get defaultConfig() {
|
||||
@ -52,6 +54,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
</h3>
|
||||
<ha-automation-condition
|
||||
.conditions=${option.conditions}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.hass=${this.hass}
|
||||
.idx=${idx}
|
||||
@value-changed=${this._conditionChanged}
|
||||
@ -89,6 +92,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
</h2>
|
||||
<ha-automation-action
|
||||
.actions=${action.default || []}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
@value-changed=${this._defaultChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
|
@ -15,6 +15,8 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ attribute: false }) public action!: IfAction;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@state() private _showElse = false;
|
||||
|
||||
public static get defaultConfig() {
|
||||
@ -35,8 +37,9 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
</h3>
|
||||
<ha-automation-condition
|
||||
.conditions=${action.if}
|
||||
.hass=${this.hass}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
@value-changed=${this._ifChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-condition>
|
||||
|
||||
<h3>
|
||||
@ -46,6 +49,7 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
</h3>
|
||||
<ha-automation-action
|
||||
.actions=${action.then}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
@value-changed=${this._thenChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
@ -58,6 +62,7 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
</h3>
|
||||
<ha-automation-action
|
||||
.actions=${action.else || []}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
@value-changed=${this._elseChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
|
@ -14,6 +14,8 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ attribute: false }) public action!: ParallelAction;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return {
|
||||
parallel: [],
|
||||
@ -26,6 +28,7 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
||||
return html`
|
||||
<ha-automation-action
|
||||
.actions=${action.parallel}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
@value-changed=${this._actionsChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
|
@ -25,6 +25,8 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ attribute: false }) public action!: RepeatAction;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { repeat: { count: 2, sequence: [] } };
|
||||
}
|
||||
@ -95,6 +97,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
</h3>
|
||||
<ha-automation-action
|
||||
.actions=${action.sequence}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
|
@ -28,6 +28,8 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public yamlMode = false;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
private _processedCondition = memoizeOne((condition) =>
|
||||
expandConditionWithShorthand(condition)
|
||||
);
|
||||
@ -60,7 +62,11 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
<div>
|
||||
${dynamicElement(
|
||||
`ha-automation-condition-${condition.condition}`,
|
||||
{ hass: this.hass, condition: condition }
|
||||
{
|
||||
hass: this.hass,
|
||||
condition: condition,
|
||||
reOrderMode: this.reOrderMode,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
|
@ -70,6 +70,10 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
|
||||
@property() public condition!: Condition;
|
||||
|
||||
@property({ type: Boolean }) public hideMenu = false;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
@ -103,96 +107,106 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
)}
|
||||
</h3>
|
||||
|
||||
<ha-button-menu
|
||||
slot="icons"
|
||||
fixed
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handleAction}
|
||||
@click=${preventDefault}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
>
|
||||
</ha-icon-button>
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
${this.hideMenu
|
||||
? ""
|
||||
: html`
|
||||
<ha-button-menu
|
||||
slot="icons"
|
||||
fixed
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handleAction}
|
||||
@click=${preventDefault}
|
||||
>
|
||||
<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">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item graphic="icon">
|
||||
${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">
|
||||
${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">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.rename"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiRenameBox}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item graphic="icon">
|
||||
${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>
|
||||
<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">
|
||||
${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">
|
||||
${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>
|
||||
<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.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">
|
||||
${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.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">
|
||||
${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({
|
||||
@ -226,6 +240,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
.yamlMode=${this._yamlMode}
|
||||
.hass=${this.hass}
|
||||
.condition=${this.condition}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
></ha-automation-condition-editor>
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import "@material/mwc-button";
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type { Condition } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "./ha-automation-condition-row";
|
||||
@ -16,6 +17,14 @@ import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
||||
// Uncommenting these and this element doesn't load
|
||||
// import "./types/ha-automation-condition-not";
|
||||
// import "./types/ha-automation-condition-or";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import type { HaSelect } from "../../../../components/ha-select";
|
||||
import { CONDITION_TYPES } from "../../../../data/condition";
|
||||
import {
|
||||
loadSortable,
|
||||
SortableInstance,
|
||||
} from "../../../../resources/sortable.ondemand";
|
||||
import "./types/ha-automation-condition-and";
|
||||
import "./types/ha-automation-condition-device";
|
||||
import "./types/ha-automation-condition-numeric_state";
|
||||
@ -25,10 +34,7 @@ import "./types/ha-automation-condition-template";
|
||||
import "./types/ha-automation-condition-time";
|
||||
import "./types/ha-automation-condition-trigger";
|
||||
import "./types/ha-automation-condition-zone";
|
||||
import { CONDITION_TYPES } from "../../../../data/condition";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import type { HaSelect } from "../../../../components/ha-select";
|
||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||
|
||||
@customElement("ha-automation-condition")
|
||||
export default class HaAutomationCondition extends LitElement {
|
||||
@ -36,11 +42,23 @@ export default class HaAutomationCondition extends LitElement {
|
||||
|
||||
@property() public conditions!: Condition[];
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
private _focusLastConditionOnChange = false;
|
||||
|
||||
private _conditionKeys = new WeakMap<Condition, string>();
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("reOrderMode")) {
|
||||
if (this.reOrderMode) {
|
||||
this._createSortable();
|
||||
} else {
|
||||
this._destroySortable();
|
||||
}
|
||||
}
|
||||
|
||||
if (!changedProperties.has("conditions")) {
|
||||
return;
|
||||
}
|
||||
@ -82,19 +100,53 @@ export default class HaAutomationCondition extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${repeat(
|
||||
this.conditions,
|
||||
(condition) => this._getKey(condition),
|
||||
(cond, idx) => html`
|
||||
<ha-automation-condition-row
|
||||
.index=${idx}
|
||||
.condition=${cond}
|
||||
@duplicate=${this._duplicateCondition}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-condition-row>
|
||||
`
|
||||
)}
|
||||
<div class="conditions">
|
||||
${repeat(
|
||||
this.conditions,
|
||||
(condition) => this._getKey(condition),
|
||||
(cond, idx) => html`
|
||||
<ha-automation-condition-row
|
||||
.index=${idx}
|
||||
.totalConditions=${this.conditions.length}
|
||||
.condition=${cond}
|
||||
.hideMenu=${this.reOrderMode}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
@duplicate=${this._duplicateCondition}
|
||||
@move-condition=${this._move}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
${this.reOrderMode
|
||||
? 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>
|
||||
`
|
||||
: ""}
|
||||
</ha-automation-condition-row>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<ha-button-menu fixed @action=${this._addCondition}>
|
||||
<mwc-button
|
||||
slot="trigger"
|
||||
@ -116,6 +168,36 @@ export default class HaAutomationCondition extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = await loadSortable();
|
||||
this._sortable = new Sortable(
|
||||
this.shadowRoot!.querySelector(".conditions")!,
|
||||
{
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
handle: ".handle",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._dragged(evt);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
private _getKey(condition: Condition) {
|
||||
if (!this._conditionKeys.has(condition)) {
|
||||
this._conditionKeys.set(condition, Math.random().toString());
|
||||
@ -142,6 +224,30 @@ export default class HaAutomationCondition extends LitElement {
|
||||
fireEvent(this, "value-changed", { value: conditions });
|
||||
}
|
||||
|
||||
private _moveUp(ev) {
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = index - 1;
|
||||
this._move(index, newIndex);
|
||||
}
|
||||
|
||||
private _moveDown(ev) {
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = index + 1;
|
||||
this._move(index, newIndex);
|
||||
}
|
||||
|
||||
private _dragged(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) return;
|
||||
this._move(ev.oldIndex!, ev.newIndex!);
|
||||
}
|
||||
|
||||
private _move(index: number, newIndex: number) {
|
||||
const conditions = this.conditions.concat();
|
||||
const condition = conditions.splice(index, 1)[0];
|
||||
conditions.splice(newIndex, 0, condition);
|
||||
fireEvent(this, "value-changed", { value: conditions });
|
||||
}
|
||||
|
||||
private _conditionChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const conditions = [...this.conditions];
|
||||
@ -186,16 +292,27 @@ export default class HaAutomationCondition extends LitElement {
|
||||
);
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-automation-condition-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
`;
|
||||
return [
|
||||
sortableStyles,
|
||||
css`
|
||||
ha-automation-condition-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
.handle {
|
||||
cursor: move;
|
||||
padding: 12px;
|
||||
}
|
||||
.handle ha-svg-icon {
|
||||
pointer-events: none;
|
||||
height: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,8 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
|
||||
|
||||
@property({ attribute: false }) public condition!: LogicalCondition;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return {
|
||||
conditions: [],
|
||||
@ -24,6 +26,7 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
|
||||
.conditions=${this.condition.conditions || []}
|
||||
@value-changed=${this._valueChanged}
|
||||
.hass=${this.hass}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
></ha-automation-condition>
|
||||
`;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiHelpCircle, mdiRobot } from "@mdi/js";
|
||||
import { mdiHelpCircle, mdiRobot, mdiSort, mdiTextBoxEdit } from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/entity/ha-entity-toggle";
|
||||
import "../../../components/ha-card";
|
||||
@ -35,6 +35,12 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
@state() private _reOrderMode = false;
|
||||
|
||||
private _toggleReOrderMode() {
|
||||
this._reOrderMode = !this._reOrderMode;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-card outlined>
|
||||
@ -108,6 +114,13 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
"ui.panel.config.automation.editor.triggers.header"
|
||||
)}
|
||||
</h2>
|
||||
<ha-icon-button
|
||||
.path=${this._reOrderMode ? mdiTextBoxEdit : mdiSort}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.re_order"
|
||||
)}
|
||||
@click=${this._toggleReOrderMode}
|
||||
></ha-icon-button>
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/docs/automation/trigger/")}
|
||||
target="_blank"
|
||||
@ -128,6 +141,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
.triggers=${this.config.trigger}
|
||||
@value-changed=${this._triggerChanged}
|
||||
.hass=${this.hass}
|
||||
.reOrderMode=${this._reOrderMode}
|
||||
></ha-automation-trigger>
|
||||
|
||||
<div class="header">
|
||||
@ -136,6 +150,13 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
"ui.panel.config.automation.editor.conditions.header"
|
||||
)}
|
||||
</h2>
|
||||
<ha-icon-button
|
||||
.path=${this._reOrderMode ? mdiTextBoxEdit : mdiSort}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.re_order"
|
||||
)}
|
||||
@click=${this._toggleReOrderMode}
|
||||
></ha-icon-button>
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/docs/automation/condition/")}
|
||||
target="_blank"
|
||||
@ -156,6 +177,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
.conditions=${this.config.condition || []}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
.reOrderMode=${this._reOrderMode}
|
||||
></ha-automation-condition>
|
||||
|
||||
<div class="header">
|
||||
@ -164,18 +186,27 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.header"
|
||||
)}
|
||||
</h2>
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/docs/automation/action/")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div>
|
||||
<ha-icon-button
|
||||
.path=${mdiHelpCircle}
|
||||
.path=${this._reOrderMode ? mdiTextBoxEdit : mdiSort}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.learn_more"
|
||||
"ui.panel.config.automation.editor.actions.re_order"
|
||||
)}
|
||||
@click=${this._toggleReOrderMode}
|
||||
></ha-icon-button>
|
||||
</a>
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/docs/automation/action/")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<ha-icon-button
|
||||
.path=${mdiHelpCircle}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.learn_more"
|
||||
)}
|
||||
></ha-icon-button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ha-automation-action
|
||||
@ -185,6 +216,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.reOrderMode=${this._reOrderMode}
|
||||
></ha-automation-action>
|
||||
`;
|
||||
}
|
||||
|
@ -87,6 +87,8 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public trigger!: Trigger;
|
||||
|
||||
@property({ type: Boolean }) public hideMenu = false;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
@ -128,97 +130,110 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
></ha-svg-icon>
|
||||
${capitalizeFirstLetter(describeTrigger(this.trigger, this.hass))}
|
||||
</h3>
|
||||
<ha-button-menu
|
||||
slot="icons"
|
||||
fixed
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handleAction}
|
||||
@click=${preventDefault}
|
||||
>
|
||||
<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.triggers.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiContentDuplicate}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
${this.hideMenu
|
||||
? ""
|
||||
: html`
|
||||
<ha-button-menu
|
||||
slot="icons"
|
||||
fixed
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handleAction}
|
||||
@click=${preventDefault}
|
||||
>
|
||||
<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.triggers.edit_id"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiIdentifier}></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item graphic="icon">
|
||||
${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">
|
||||
${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">
|
||||
${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 .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>
|
||||
<li divider role="separator"></li>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<li divider role="separator"></li>
|
||||
<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>
|
||||
|
||||
<mwc-list-item graphic="icon">
|
||||
${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">
|
||||
${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>
|
||||
<li divider role="separator"></li>
|
||||
|
||||
<mwc-list-item graphic="icon">
|
||||
${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">
|
||||
${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,
|
||||
|
@ -1,22 +1,26 @@
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import memoizeOne from "memoize-one";
|
||||
import "@material/mwc-button";
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import type { HaSelect } from "../../../../components/ha-select";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { Trigger } from "../../../../data/automation";
|
||||
import { TRIGGER_TYPES } from "../../../../data/trigger";
|
||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||
import { SortableInstance } from "../../../../resources/sortable";
|
||||
import { loadSortable } from "../../../../resources/sortable.ondemand";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import "./ha-automation-trigger-row";
|
||||
import type HaAutomationTriggerRow from "./ha-automation-trigger-row";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import type { HaSelect } from "../../../../components/ha-select";
|
||||
import "./types/ha-automation-trigger-calendar";
|
||||
import "./types/ha-automation-trigger-device";
|
||||
import "./types/ha-automation-trigger-event";
|
||||
@ -39,49 +43,93 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
|
||||
@property() public triggers!: Trigger[];
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
private _focusLastTriggerOnChange = false;
|
||||
|
||||
private _triggerKeys = new WeakMap<Trigger, string>();
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${repeat(
|
||||
this.triggers,
|
||||
(trigger) => this._getKey(trigger),
|
||||
(trg, idx) => html`
|
||||
<ha-automation-trigger-row
|
||||
.index=${idx}
|
||||
.trigger=${trg}
|
||||
@duplicate=${this._duplicateTrigger}
|
||||
@value-changed=${this._triggerChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-trigger-row>
|
||||
`
|
||||
)}
|
||||
<ha-button-menu @action=${this._addTrigger}>
|
||||
<mwc-button
|
||||
slot="trigger"
|
||||
outlined
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.add"
|
||||
)}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</mwc-button>
|
||||
${this._processedTypes(this.hass.localize).map(
|
||||
([opt, label, icon]) => html`
|
||||
<mwc-list-item .value=${opt} aria-label=${label} graphic="icon">
|
||||
${label}<ha-svg-icon slot="graphic" .path=${icon}></ha-svg-icon
|
||||
></mwc-list-item>
|
||||
<div class="triggers">
|
||||
${repeat(
|
||||
this.triggers,
|
||||
(trigger) => this._getKey(trigger),
|
||||
(trg, idx) => html`
|
||||
<ha-automation-trigger-row
|
||||
.index=${idx}
|
||||
.trigger=${trg}
|
||||
.hideMenu=${this.reOrderMode}
|
||||
@duplicate=${this._duplicateTrigger}
|
||||
@value-changed=${this._triggerChanged}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
${this.reOrderMode
|
||||
? 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>
|
||||
`
|
||||
: ""}
|
||||
</ha-automation-trigger-row>
|
||||
`
|
||||
)}
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
<ha-button-menu @action=${this._addTrigger}>
|
||||
<mwc-button
|
||||
slot="trigger"
|
||||
outlined
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.add"
|
||||
)}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</mwc-button>
|
||||
${this._processedTypes(this.hass.localize).map(
|
||||
([opt, label, icon]) => html`
|
||||
<mwc-list-item .value=${opt} aria-label=${label} graphic="icon">
|
||||
${label}<ha-svg-icon slot="graphic" .path=${icon}></ha-svg-icon
|
||||
></mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("reOrderMode")) {
|
||||
if (this.reOrderMode) {
|
||||
this._createSortable();
|
||||
} else {
|
||||
this._destroySortable();
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProps.has("triggers") && this._focusLastTriggerOnChange) {
|
||||
this._focusLastTriggerOnChange = false;
|
||||
|
||||
@ -96,6 +144,36 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = await loadSortable();
|
||||
this._sortable = new Sortable(
|
||||
this.shadowRoot!.querySelector(".triggers")!,
|
||||
{
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
handle: ".handle",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._dragged(evt);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
private _getKey(action: Trigger) {
|
||||
if (!this._triggerKeys.has(action)) {
|
||||
this._triggerKeys.set(action, Math.random().toString());
|
||||
@ -122,6 +200,30 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
fireEvent(this, "value-changed", { value: triggers });
|
||||
}
|
||||
|
||||
private _moveUp(ev) {
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = index - 1;
|
||||
this._move(index, newIndex);
|
||||
}
|
||||
|
||||
private _moveDown(ev) {
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = index + 1;
|
||||
this._move(index, newIndex);
|
||||
}
|
||||
|
||||
private _dragged(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) return;
|
||||
this._move(ev.oldIndex!, ev.newIndex!);
|
||||
}
|
||||
|
||||
private _move(index: number, newIndex: number) {
|
||||
const triggers = this.triggers.concat();
|
||||
const trigger = triggers.splice(index, 1)[0];
|
||||
triggers.splice(newIndex, 0, trigger);
|
||||
fireEvent(this, "value-changed", { value: triggers });
|
||||
}
|
||||
|
||||
private _triggerChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const triggers = [...this.triggers];
|
||||
@ -166,16 +268,27 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
);
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-automation-trigger-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
`;
|
||||
return [
|
||||
sortableStyles,
|
||||
css`
|
||||
ha-automation-trigger-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
.handle {
|
||||
cursor: move;
|
||||
padding: 12px;
|
||||
}
|
||||
.handle ha-svg-icon {
|
||||
pointer-events: none;
|
||||
height: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user