Reorder automation elements (#13548)

This commit is contained in:
Paul Bottein 2022-09-05 14:19:38 +02:00 committed by GitHub
parent 02d608b704
commit ab745f6e8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 819 additions and 431 deletions

View File

@ -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:

View File

@ -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;
}
`,
];
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>
`}

View File

@ -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>

View File

@ -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;
}
`,
];
}
}

View File

@ -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>
`;
}

View File

@ -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>
`;
}

View File

@ -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,

View File

@ -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;
}
`,
];
}
}