From ab745f6e8e9d186e7a0cddac7d5e6edebe902917 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 5 Sep 2022 14:19:38 +0200 Subject: [PATCH] Reorder automation elements (#13548) --- .../action/ha-automation-action-row.ts | 239 ++++++++---------- .../automation/action/ha-automation-action.ts | 177 ++++++++++--- .../types/ha-automation-action-choose.ts | 4 + .../action/types/ha-automation-action-if.ts | 7 +- .../types/ha-automation-action-parallel.ts | 3 + .../types/ha-automation-action-repeat.ts | 3 + .../ha-automation-condition-editor.ts | 8 +- .../condition/ha-automation-condition-row.ts | 183 ++++++++------ .../condition/ha-automation-condition.ts | 181 ++++++++++--- .../types/ha-automation-condition-logical.ts | 3 + .../automation/manual-automation-editor.ts | 52 +++- .../trigger/ha-automation-trigger-row.ts | 181 +++++++------ .../trigger/ha-automation-trigger.ts | 209 +++++++++++---- 13 files changed, 819 insertions(+), 431 deletions(-) diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 310b38c90d..c742eefc01 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -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))} - ${this.index !== 0 - ? html` - + ${this.hideMenu + ? "" + : html` + - ` - : ""} - ${this.index !== this.totalActions - 1 - ? html` - - ` - : ""} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.run" - )} - - + fixed + corner="BOTTOM_START" + @action=${this._handleAction} + @click=${preventDefault} + > + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.run" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.rename" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.duplicate" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.rename" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.duplicate" + )} + + -
  • +
  • - - ${this.hass.localize("ui.panel.config.automation.editor.edit_ui")} - ${!yamlMode - ? html`` - : ``} - + + ${this.hass.localize( + "ui.panel.config.automation.editor.edit_ui" + )} + ${!yamlMode + ? html`` + : ``} + - - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - ${yamlMode - ? html`` - : ``} - + + ${this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} + ${yamlMode + ? html`` + : ``} + -
  • +
  • + + + ${this.action.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + + +
    + `} - - ${this.action.enabled === false - ? this.hass.localize( - "ui.panel.config.automation.editor.actions.enable" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.actions.disable" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - -
    `} @@ -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) { switch (ev.detail.index) { case 0: diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index 9c65fa1fba..dd7b802aa1 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -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(); + private _sortable?: SortableInstance; + protected render() { return html` - ${repeat( - this.actions, - (action) => this._getKey(action), - (action, idx) => html` - - ` - )} +
    + ${repeat( + this.actions, + (action) => this._getKey(action), + (action, idx) => html` + + ${this.reOrderMode + ? html` + + +
    + +
    + ` + : ""} +
    + ` + )} +
    { + (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; + } + `, + ]; } } diff --git a/src/panels/config/automation/action/types/ha-automation-action-choose.ts b/src/panels/config/automation/action/types/ha-automation-action-choose.ts index 620d8e39ad..67a70e4304 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-choose.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-choose.ts @@ -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 { diff --git a/src/panels/config/automation/action/types/ha-automation-action-if.ts b/src/panels/config/automation/action/types/ha-automation-action-if.ts index 0424b8cf79..8725e52dc0 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-if.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-if.ts @@ -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 {

    @@ -46,6 +49,7 @@ export class HaIfAction extends LitElement implements ActionElement {

    @@ -58,6 +62,7 @@ export class HaIfAction extends LitElement implements ActionElement { diff --git a/src/panels/config/automation/action/types/ha-automation-action-parallel.ts b/src/panels/config/automation/action/types/ha-automation-action-parallel.ts index 37dbfb429e..f4d56bbf91 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-parallel.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-parallel.ts @@ -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` diff --git a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts index 6b78e6c0cf..c7dc7c8eab 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts @@ -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 { diff --git a/src/panels/config/automation/condition/ha-automation-condition-editor.ts b/src/panels/config/automation/condition/ha-automation-condition-editor.ts index bcb3727209..3cd54473cb 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -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 {
    ${dynamicElement( `ha-automation-condition-${condition.condition}`, - { hass: this.hass, condition: condition } + { + hass: this.hass, + condition: condition, + reOrderMode: this.reOrderMode, + } )}
    `} diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index 8c2f1204b5..dcc93b8015 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -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 { )} - - - + + ${this.hideMenu + ? "" + : html` + + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.conditions.test" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.conditions.rename" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.duplicate" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.conditions.test" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.conditions.rename" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.duplicate" + )} + + -
  • +
  • - - ${this.hass.localize("ui.panel.config.automation.editor.edit_ui")} - ${!this._yamlMode - ? html`` - : ``} - + + ${this.hass.localize( + "ui.panel.config.automation.editor.edit_ui" + )} + ${!this._yamlMode + ? html`` + : ``} + - - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - ${this._yamlMode - ? html`` - : ``} - + + ${this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} + ${this._yamlMode + ? html`` + : ``} + -
  • +
  • - - ${this.condition.enabled === false - ? this.hass.localize( - "ui.panel.config.automation.editor.actions.enable" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.actions.disable" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - -
    + + ${this.condition.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + + +
    + `}
    diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 67233bbfe2..8815d15cb1 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -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(); + 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` - - ` - )} +
    + ${repeat( + this.conditions, + (condition) => this._getKey(condition), + (cond, idx) => html` + + ${this.reOrderMode + ? html` + + +
    + +
    + ` + : ""} +
    + ` + )} +
    { + (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; + } + `, + ]; } } diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts b/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts index 628c050663..0838feffc3 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts @@ -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} >
    `; } diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index 3e99317869..a3c7c36985 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -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` @@ -108,6 +114,13 @@ export class HaManualAutomationEditor extends LitElement { "ui.panel.config.automation.editor.triggers.header" )} +
    @@ -136,6 +150,13 @@ export class HaManualAutomationEditor extends LitElement { "ui.panel.config.automation.editor.conditions.header" )} + `; } diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index b3c32cdc92..2674664af9 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -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 { > ${capitalizeFirstLetter(describeTrigger(this.trigger, this.hass))} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.rename" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.duplicate" - )} - - + + ${this.hideMenu + ? "" + : html` + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.edit_id" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.rename" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.duplicate" + )} + + -
  • + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.edit_id" + )} + + - - ${this.hass.localize("ui.panel.config.automation.editor.edit_ui")} - ${!yamlMode - ? html`` - : ``} - +
  • - - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - ${yamlMode - ? html`` - : ``} - + + ${this.hass.localize( + "ui.panel.config.automation.editor.edit_ui" + )} + ${!yamlMode + ? html`` + : ``} + -
  • + + ${this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} + ${yamlMode + ? html`` + : ``} + - - ${this.trigger.enabled === false - ? this.hass.localize( - "ui.panel.config.automation.editor.actions.enable" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.actions.disable" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - -
    +
  • + + ${this.trigger.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + + +
    + `}
    (); + private _sortable?: SortableInstance; + protected render() { return html` - ${repeat( - this.triggers, - (trigger) => this._getKey(trigger), - (trg, idx) => html` - - ` - )} - - - - - ${this._processedTypes(this.hass.localize).map( - ([opt, label, icon]) => html` - - ${label} +
    + ${repeat( + this.triggers, + (trigger) => this._getKey(trigger), + (trg, idx) => html` + + ${this.reOrderMode + ? html` + + +
    + +
    + ` + : ""} +
    ` )} - +
    + + + + + ${this._processedTypes(this.hass.localize).map( + ([opt, label, icon]) => html` + + ${label} + ` + )} + +
    `; } 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; + } + `, + ]; } }