Automation nested drag and drop (#19338)
* Add nested drag and drop for actions * Add nested drag and drop for triggers, conditions and options * Update src/panels/config/automation/action/types/ha-automation-action-choose.ts Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Move object at the root level * Add support for choose option * Fix undefined container (e.g else action) * Add common nested array move function * Move item at root level for manual automation * Fix array move * Don't fallback on body * migrate blueprint and script * Add drag and drop to service control * Use context for reorder mode * Rename reorder mode functions * Fix hide menu props * Fix drag and drop for choose action --------- Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
4046534fa8
commit
7398c6ab3e
|
@ -0,0 +1,53 @@
|
|||
import { ItemPath } from "../../types";
|
||||
|
||||
function findNestedItem(
|
||||
obj: any,
|
||||
path: ItemPath,
|
||||
createNonExistingPath?: boolean
|
||||
): any {
|
||||
return path.reduce((ac, p, index, array) => {
|
||||
if (ac === undefined) return undefined;
|
||||
if (!ac[p] && createNonExistingPath) {
|
||||
const nextP = array[index + 1];
|
||||
// Create object or array depending on next path
|
||||
if (nextP === undefined || typeof nextP === "number") {
|
||||
ac[p] = [];
|
||||
} else {
|
||||
ac[p] = {};
|
||||
}
|
||||
}
|
||||
return ac[p];
|
||||
}, obj);
|
||||
}
|
||||
|
||||
export function nestedArrayMove<T>(
|
||||
obj: T | T[],
|
||||
oldIndex: number,
|
||||
newIndex: number,
|
||||
oldPath?: ItemPath,
|
||||
newPath?: ItemPath
|
||||
): T | T[] {
|
||||
const newObj = Array.isArray(obj) ? [...obj] : { ...obj };
|
||||
const from = oldPath ? findNestedItem(newObj, oldPath) : newObj;
|
||||
const to = newPath ? findNestedItem(newObj, newPath, true) : newObj;
|
||||
|
||||
if (!Array.isArray(from) || !Array.isArray(to)) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
const item = from.splice(oldIndex, 1)[0];
|
||||
to.splice(newIndex, 0, item);
|
||||
|
||||
return newObj;
|
||||
}
|
||||
|
||||
export function arrayMove<T = any>(
|
||||
array: T[],
|
||||
oldIndex: number,
|
||||
newIndex: number
|
||||
): T[] {
|
||||
const newArray = [...array];
|
||||
const [item] = newArray.splice(oldIndex, 1);
|
||||
newArray.splice(newIndex, 0, item);
|
||||
return newArray;
|
||||
}
|
|
@ -24,8 +24,7 @@ export class HaActionSelector extends LitElement {
|
|||
.disabled=${this.disabled}
|
||||
.actions=${this.value || []}
|
||||
.hass=${this.hass}
|
||||
.nested=${this.selector.action?.nested}
|
||||
.reOrderMode=${this.selector.action?.reorder_mode}
|
||||
.path=${this.selector.action?.path}
|
||||
></ha-automation-action>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -24,8 +24,7 @@ export class HaConditionSelector extends LitElement {
|
|||
.disabled=${this.disabled}
|
||||
.conditions=${this.value || []}
|
||||
.hass=${this.hass}
|
||||
.nested=${this.selector.condition?.nested}
|
||||
.reOrderMode=${this.selector.condition?.reorder_mode}
|
||||
.path=${this.selector.condition?.path}
|
||||
></ha-automation-condition>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -24,8 +24,7 @@ export class HaTriggerSelector extends LitElement {
|
|||
.disabled=${this.disabled}
|
||||
.triggers=${this.value || []}
|
||||
.hass=${this.hass}
|
||||
.nested=${this.selector.trigger?.nested}
|
||||
.reOrderMode=${this.selector.trigger?.reorder_mode}
|
||||
.path=${this.selector.trigger?.path}
|
||||
></ha-automation-trigger>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,8 @@ import "./ha-service-picker";
|
|||
import "./ha-settings-row";
|
||||
import "./ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "./ha-yaml-editor";
|
||||
import { nestedArrayMove } from "../common/util/array-move";
|
||||
import { ReorderModeMixin } from "../state/reorder-mode-mixin";
|
||||
|
||||
const attributeFilter = (values: any[], attribute: any) => {
|
||||
if (typeof attribute === "object") {
|
||||
|
@ -75,7 +77,7 @@ interface ExtHassService extends Omit<HassService, "fields"> {
|
|||
}
|
||||
|
||||
@customElement("ha-service-control")
|
||||
export class HaServiceControl extends LitElement {
|
||||
export class HaServiceControl extends ReorderModeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public value?: {
|
||||
|
@ -439,6 +441,7 @@ export class HaServiceControl extends LitElement {
|
|||
allow-custom-entity
|
||||
></ha-entity-picker>`
|
||||
: ""}
|
||||
${this._renderReorderModeAlert()}
|
||||
${shouldRenderServiceDataYaml
|
||||
? html`<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
|
@ -449,7 +452,23 @@ export class HaServiceControl extends LitElement {
|
|||
@value-changed=${this._dataChanged}
|
||||
></ha-yaml-editor>`
|
||||
: filteredFields?.map((dataField) => {
|
||||
const selector = dataField?.selector ?? { text: undefined };
|
||||
const type = Object.keys(selector)[0];
|
||||
const enhancedSelector = [
|
||||
"action",
|
||||
"condition",
|
||||
"trigger",
|
||||
].includes(type)
|
||||
? {
|
||||
[type]: {
|
||||
...selector[type],
|
||||
path: [dataField.key],
|
||||
},
|
||||
}
|
||||
: selector;
|
||||
|
||||
const showOptional = showOptionalToggle(dataField);
|
||||
|
||||
return dataField.selector &&
|
||||
(!dataField.advanced ||
|
||||
this.showAdvanced ||
|
||||
|
@ -488,7 +507,7 @@ export class HaServiceControl extends LitElement {
|
|||
(!this._value?.data ||
|
||||
this._value.data[dataField.key] === undefined))}
|
||||
.hass=${this.hass}
|
||||
.selector=${dataField.selector}
|
||||
.selector=${enhancedSelector}
|
||||
.key=${dataField.key}
|
||||
@value-changed=${this._serviceDataChanged}
|
||||
.value=${this._value?.data
|
||||
|
@ -496,12 +515,41 @@ export class HaServiceControl extends LitElement {
|
|||
: undefined}
|
||||
.placeholder=${dataField.default}
|
||||
.localizeValue=${this._localizeValueCallback}
|
||||
@item-moved=${this._itemMoved}
|
||||
></ha-selector>
|
||||
</ha-settings-row>`
|
||||
: "";
|
||||
})}`;
|
||||
}
|
||||
|
||||
private _renderReorderModeAlert() {
|
||||
if (!this._reorderMode.active) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-alert
|
||||
class="re-order"
|
||||
alert-type="info"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.title"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.description_all"
|
||||
)}
|
||||
<ha-button slot="action" @click=${this._exitReOrderMode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.exit"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _exitReOrderMode() {
|
||||
this._reorderMode.exit();
|
||||
}
|
||||
|
||||
private _localizeValueCallback = (key: string) => {
|
||||
if (!this._value?.service) {
|
||||
return "";
|
||||
|
@ -697,6 +745,22 @@ export class HaServiceControl extends LitElement {
|
|||
});
|
||||
}
|
||||
|
||||
private _itemMoved(ev) {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
||||
|
||||
const data = this.value?.data ?? {};
|
||||
|
||||
const newData = nestedArrayMove(data, oldIndex, newIndex, oldPath, newPath);
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.value,
|
||||
data: newData,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _dataChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
if (!ev.detail.isValid) {
|
||||
|
|
|
@ -4,12 +4,15 @@ import { customElement, property } from "lit/decorators";
|
|||
import type { SortableEvent } from "sortablejs";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { SortableInstance } from "../resources/sortable";
|
||||
import { ItemPath } from "../types";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"item-moved": {
|
||||
oldIndex: number;
|
||||
newIndex: number;
|
||||
oldPath?: ItemPath;
|
||||
newPath?: ItemPath;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +24,9 @@ export class HaSortable extends LitElement {
|
|||
@property({ type: Boolean })
|
||||
public disabled = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public path?: ItemPath;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-style" })
|
||||
public noStyle: boolean = false;
|
||||
|
||||
|
@ -30,6 +36,9 @@ export class HaSortable extends LitElement {
|
|||
@property({ type: String, attribute: "handle-selector" })
|
||||
public handleSelector?: string;
|
||||
|
||||
@property({ type: String, attribute: "group" })
|
||||
public group?: string;
|
||||
|
||||
protected updated(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has("disabled")) {
|
||||
if (this.disabled) {
|
||||
|
@ -100,6 +109,7 @@ export class HaSortable extends LitElement {
|
|||
|
||||
const options: SortableInstance.Options = {
|
||||
animation: 150,
|
||||
swapThreshold: 0.75,
|
||||
onChoose: this._handleChoose,
|
||||
onEnd: this._handleEnd,
|
||||
};
|
||||
|
@ -110,27 +120,41 @@ export class HaSortable extends LitElement {
|
|||
if (this.handleSelector) {
|
||||
options.handle = this.handleSelector;
|
||||
}
|
||||
if (this.draggableSelector) {
|
||||
options.draggable = this.draggableSelector;
|
||||
}
|
||||
if (this.group) {
|
||||
options.group = this.group;
|
||||
}
|
||||
|
||||
this._sortable = new Sortable(container, options);
|
||||
}
|
||||
|
||||
private _handleEnd = (evt: SortableEvent) => {
|
||||
private _handleEnd = async (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;
|
||||
}
|
||||
// if item was not moved, ignore
|
||||
|
||||
const oldIndex = evt.oldIndex;
|
||||
const oldPath = (evt.from.parentElement as HaSortable).path;
|
||||
const newIndex = evt.newIndex;
|
||||
const newPath = (evt.to.parentElement as HaSortable).path;
|
||||
|
||||
if (
|
||||
evt.oldIndex === undefined ||
|
||||
evt.newIndex === undefined ||
|
||||
evt.oldIndex === evt.newIndex
|
||||
oldIndex === undefined ||
|
||||
newIndex === undefined ||
|
||||
(oldIndex === newIndex && oldPath?.join(".") === newPath?.join("."))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fireEvent(this, "item-moved", {
|
||||
oldIndex: evt.oldIndex!,
|
||||
newIndex: evt.newIndex!,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
oldPath,
|
||||
newPath,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ensureArray } from "../common/array/ensure-array";
|
|||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import { UiAction } from "../panels/lovelace/components/hui-action-editor";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { HomeAssistant, ItemPath } from "../types";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
getDeviceIntegrationLookup,
|
||||
|
@ -59,8 +59,7 @@ export type Selector =
|
|||
|
||||
export interface ActionSelector {
|
||||
action: {
|
||||
reorder_mode?: boolean;
|
||||
nested?: boolean;
|
||||
path?: ItemPath;
|
||||
} | null;
|
||||
}
|
||||
|
||||
|
@ -113,8 +112,7 @@ export interface ColorTempSelector {
|
|||
|
||||
export interface ConditionSelector {
|
||||
condition: {
|
||||
reorder_mode?: boolean;
|
||||
nested?: boolean;
|
||||
path?: ItemPath;
|
||||
} | null;
|
||||
}
|
||||
|
||||
|
@ -392,8 +390,7 @@ export interface TimeSelector {
|
|||
|
||||
export interface TriggerSelector {
|
||||
trigger: {
|
||||
reorder_mode?: boolean;
|
||||
nested?: boolean;
|
||||
path?: ItemPath;
|
||||
} | null;
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,11 @@ import {
|
|||
showPromptDialog,
|
||||
} from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
ReorderMode,
|
||||
reorderModeContext,
|
||||
} from "../../../../state/reorder-mode-mixin";
|
||||
import type { HomeAssistant, ItemPath } from "../../../../types";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
import "./types/ha-automation-action-activate_scene";
|
||||
import "./types/ha-automation-action-choose";
|
||||
|
@ -129,7 +133,7 @@ export default class HaAutomationActionRow extends LitElement {
|
|||
|
||||
@property({ type: Boolean }) public hideMenu = false;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
@property() public path?: ItemPath;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
|
@ -143,6 +147,10 @@ export default class HaAutomationActionRow extends LitElement {
|
|||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entityReg!: EntityRegistryEntry[];
|
||||
|
||||
@state()
|
||||
@consume({ context: reorderModeContext, subscribe: true })
|
||||
private _reorderMode?: ReorderMode;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@state() private _uiModeAvailable = true;
|
||||
|
@ -176,9 +184,13 @@ export default class HaAutomationActionRow extends LitElement {
|
|||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.action) return nothing;
|
||||
|
||||
const type = getType(this.action);
|
||||
const yamlMode = this._yamlMode;
|
||||
|
||||
const noReorderModeAvailable = this._reorderMode === undefined;
|
||||
|
||||
return html`
|
||||
<ha-card outlined>
|
||||
${this.action.enabled === false
|
||||
|
@ -247,7 +259,12 @@ export default class HaAutomationActionRow extends LitElement {
|
|||
.path=${mdiRenameBox}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
.disabled=${this.disabled}
|
||||
class=${classMap({ hidden: noReorderModeAvailable })}
|
||||
?aria-hidden=${noReorderModeAvailable}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.re_order"
|
||||
)}
|
||||
|
@ -405,8 +422,8 @@ export default class HaAutomationActionRow extends LitElement {
|
|||
hass: this.hass,
|
||||
action: this.action,
|
||||
narrow: this.narrow,
|
||||
reOrderMode: this.reOrderMode,
|
||||
disabled: this.disabled,
|
||||
path: this.path,
|
||||
})}
|
||||
</div>
|
||||
`}
|
||||
|
@ -435,7 +452,7 @@ export default class HaAutomationActionRow extends LitElement {
|
|||
await this._renameAction();
|
||||
break;
|
||||
case 2:
|
||||
fireEvent(this, "re-order");
|
||||
this._reorderMode?.enter();
|
||||
break;
|
||||
case 3:
|
||||
fireEvent(this, "duplicate");
|
||||
|
@ -640,6 +657,9 @@ export default class HaAutomationActionRow extends LitElement {
|
|||
mwc-list-item[disabled] {
|
||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||
}
|
||||
mwc-list-item.hidden {
|
||||
display: none;
|
||||
}
|
||||
.warning ul {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
import { consume } from "@lit-labs/context";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { nestedArrayMove } from "../../../../common/util/array-move";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-sortable";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { getService, isService } from "../../../../data/action";
|
||||
import type { AutomationClipboard } from "../../../../data/automation";
|
||||
import { Action } from "../../../../data/script";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
ReorderMode,
|
||||
reorderModeContext,
|
||||
} from "../../../../state/reorder-mode-mixin";
|
||||
import { HomeAssistant, ItemPath } from "../../../../types";
|
||||
import {
|
||||
PASTE_VALUE,
|
||||
showAddAutomationElementDialog,
|
||||
|
@ -27,11 +33,13 @@ export default class HaAutomationAction extends LitElement {
|
|||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public nested = false;
|
||||
@property() public path?: ItemPath;
|
||||
|
||||
@property() public actions!: Action[];
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
@state()
|
||||
@consume({ context: reorderModeContext, subscribe: true })
|
||||
private _reorderMode?: ReorderMode;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
|
@ -45,31 +53,18 @@ export default class HaAutomationAction extends LitElement {
|
|||
|
||||
private _actionKeys = new WeakMap<Action, string>();
|
||||
|
||||
private get nested() {
|
||||
return this.path !== undefined;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${this.reOrderMode && !this.nested
|
||||
? html`
|
||||
<ha-alert
|
||||
alert-type="info"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.title"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.description_actions"
|
||||
)}
|
||||
<ha-button slot="action" @click=${this._exitReOrderMode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.exit"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: null}
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this.reOrderMode}
|
||||
.disabled=${!this._reorderMode?.active}
|
||||
@item-moved=${this._actionMoved}
|
||||
group="actions"
|
||||
.path=${this.path}
|
||||
>
|
||||
<div class="actions">
|
||||
${repeat(
|
||||
|
@ -77,18 +72,17 @@ export default class HaAutomationAction extends LitElement {
|
|||
(action) => this._getKey(action),
|
||||
(action, idx) => html`
|
||||
<ha-automation-action-row
|
||||
.path=${[...(this.path ?? []), idx]}
|
||||
.index=${idx}
|
||||
.action=${action}
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
.hideMenu=${this.reOrderMode}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.hideMenu=${Boolean(this._reorderMode?.active)}
|
||||
@duplicate=${this._duplicateAction}
|
||||
@value-changed=${this._actionChanged}
|
||||
@re-order=${this._enterReOrderMode}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
${this.reOrderMode
|
||||
${this._reorderMode?.active
|
||||
? html`
|
||||
<ha-icon-button
|
||||
.index=${idx}
|
||||
|
@ -199,16 +193,6 @@ export default class HaAutomationAction extends LitElement {
|
|||
fireEvent(this, "value-changed", { value: actions });
|
||||
};
|
||||
|
||||
private async _enterReOrderMode(ev: CustomEvent) {
|
||||
if (this.nested) return;
|
||||
ev.stopPropagation();
|
||||
this.reOrderMode = true;
|
||||
}
|
||||
|
||||
private async _exitReOrderMode() {
|
||||
this.reOrderMode = false;
|
||||
}
|
||||
|
||||
private _getKey(action: Action) {
|
||||
if (!this._actionKeys.has(action)) {
|
||||
this._actionKeys.set(action, Math.random().toString());
|
||||
|
@ -229,17 +213,28 @@ export default class HaAutomationAction extends LitElement {
|
|||
this._move(index, newIndex);
|
||||
}
|
||||
|
||||
private _move(index: number, newIndex: number) {
|
||||
const actions = this.actions.concat();
|
||||
const action = actions.splice(index, 1)[0];
|
||||
actions.splice(newIndex, 0, action);
|
||||
private _move(
|
||||
oldIndex: number,
|
||||
newIndex: number,
|
||||
oldPath?: ItemPath,
|
||||
newPath?: ItemPath
|
||||
) {
|
||||
const actions = nestedArrayMove(
|
||||
this.actions,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
oldPath,
|
||||
newPath
|
||||
);
|
||||
|
||||
fireEvent(this, "value-changed", { value: actions });
|
||||
}
|
||||
|
||||
private _actionMoved(ev: CustomEvent): void {
|
||||
if (this.nested) return;
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
this._move(oldIndex, newIndex);
|
||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
||||
this._move(oldIndex, newIndex, oldPath, newPath);
|
||||
}
|
||||
|
||||
private _actionChanged(ev: CustomEvent) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
import deepClone from "deep-clone-simple";
|
||||
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
|
@ -36,7 +37,11 @@ import {
|
|||
showPromptDialog,
|
||||
} from "../../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import {
|
||||
ReorderMode,
|
||||
reorderModeContext,
|
||||
} from "../../../../../state/reorder-mode-mixin";
|
||||
import { HomeAssistant, ItemPath } from "../../../../../types";
|
||||
import { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
const preventDefault = (ev) => ev.preventDefault();
|
||||
|
@ -47,9 +52,9 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property() public action!: ChooseAction;
|
||||
@property({ attribute: false }) public path?: ItemPath;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
@property() public action!: ChooseAction;
|
||||
|
||||
@state() private _showDefault = false;
|
||||
|
||||
|
@ -59,6 +64,10 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entityReg!: EntityRegistryEntry[];
|
||||
|
||||
@state()
|
||||
@consume({ context: reorderModeContext, subscribe: true })
|
||||
private _reorderMode?: ReorderMode;
|
||||
|
||||
private _expandLast = false;
|
||||
|
||||
public static get defaultConfig() {
|
||||
|
@ -95,11 +104,14 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||
protected render() {
|
||||
const action = this.action;
|
||||
|
||||
const noReorderModeAvailable = this._reorderMode === undefined;
|
||||
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this.reOrderMode}
|
||||
@item-moved=${this._optionMoved}
|
||||
.disabled=${!this._reorderMode?.active}
|
||||
group="choose-options"
|
||||
.path=${[...(this.path ?? []), "choose"]}
|
||||
>
|
||||
<div class="options">
|
||||
${repeat(
|
||||
|
@ -123,7 +135,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||
? ""
|
||||
: this._getDescription(option))}
|
||||
</h3>
|
||||
${this.reOrderMode
|
||||
${this._reorderMode?.active
|
||||
? html`
|
||||
<ha-icon-button
|
||||
.index=${idx}
|
||||
|
@ -178,6 +190,10 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||
<mwc-list-item
|
||||
graphic="icon"
|
||||
.disabled=${this.disabled}
|
||||
class=${classMap({
|
||||
hidden: noReorderModeAvailable,
|
||||
})}
|
||||
?aria-hidden=${noReorderModeAvailable}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.re_order"
|
||||
|
@ -224,11 +240,15 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||
)}:
|
||||
</h4>
|
||||
<ha-automation-condition
|
||||
nested
|
||||
.path=${[
|
||||
...(this.path ?? []),
|
||||
"choose",
|
||||
idx,
|
||||
"conditions",
|
||||
]}
|
||||
.conditions=${ensureArray<string | Condition>(
|
||||
option.conditions
|
||||
)}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.disabled=${this.disabled}
|
||||
.hass=${this.hass}
|
||||
.idx=${idx}
|
||||
|
@ -240,9 +260,13 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||
)}:
|
||||
</h4>
|
||||
<ha-automation-action
|
||||
nested
|
||||
.path=${[
|
||||
...(this.path ?? []),
|
||||
"choose",
|
||||
idx,
|
||||
"sequence",
|
||||
]}
|
||||
.actions=${ensureArray(option.sequence) || []}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.disabled=${this.disabled}
|
||||
.hass=${this.hass}
|
||||
.idx=${idx}
|
||||
|
@ -274,9 +298,8 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||
)}:
|
||||
</h2>
|
||||
<ha-automation-action
|
||||
nested
|
||||
.path=${[...(this.path ?? []), "choose", "default"]}
|
||||
.actions=${ensureArray(action.default) || []}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._defaultChanged}
|
||||
.hass=${this.hass}
|
||||
|
@ -302,7 +325,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||
await this._renameAction(ev);
|
||||
break;
|
||||
case 1:
|
||||
fireEvent(this, "re-order");
|
||||
this._reorderMode?.enter();
|
||||
break;
|
||||
case 2:
|
||||
this._duplicateOption(ev);
|
||||
|
@ -435,12 +458,6 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||
});
|
||||
}
|
||||
|
||||
private _optionMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
this._move(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
private _removeOption(ev: CustomEvent) {
|
||||
const index = (ev.target as any).idx;
|
||||
showConfirmationDialog(this, {
|
||||
|
@ -495,6 +512,12 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||
--expansion-panel-summary-padding: 0 0 0 8px;
|
||||
--expansion-panel-content-padding: 0;
|
||||
}
|
||||
mwc-list-item[disabled] {
|
||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||
}
|
||||
mwc-list-item.hidden {
|
||||
display: none;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: inherit;
|
||||
|
|
|
@ -4,7 +4,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
|
|||
import "../../../../../components/ha-textfield";
|
||||
import { Action, IfAction } from "../../../../../data/script";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { HomeAssistant, ItemPath } from "../../../../../types";
|
||||
import type { Condition } from "../../../../lovelace/common/validate-condition";
|
||||
import "../ha-automation-action";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
|
@ -15,9 +15,9 @@ export class HaIfAction extends LitElement implements ActionElement {
|
|||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ attribute: false }) public action!: IfAction;
|
||||
@property({ attribute: false }) public path?: ItemPath;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
@property({ attribute: false }) public action!: IfAction;
|
||||
|
||||
@state() private _showElse = false;
|
||||
|
||||
|
@ -38,9 +38,8 @@ export class HaIfAction extends LitElement implements ActionElement {
|
|||
)}*:
|
||||
</h3>
|
||||
<ha-automation-condition
|
||||
nested
|
||||
.path=${[...(this.path ?? []), "if"]}
|
||||
.conditions=${action.if}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._ifChanged}
|
||||
.hass=${this.hass}
|
||||
|
@ -52,9 +51,8 @@ export class HaIfAction extends LitElement implements ActionElement {
|
|||
)}*:
|
||||
</h3>
|
||||
<ha-automation-action
|
||||
nested
|
||||
.path=${[...(this.path ?? []), "then"]}
|
||||
.actions=${action.then}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._thenChanged}
|
||||
.hass=${this.hass}
|
||||
|
@ -67,9 +65,8 @@ export class HaIfAction extends LitElement implements ActionElement {
|
|||
)}:
|
||||
</h3>
|
||||
<ha-automation-action
|
||||
nested
|
||||
.path=${[...(this.path ?? []), "else"]}
|
||||
.actions=${action.else || []}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._elseChanged}
|
||||
.hass=${this.hass}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
|
|||
import "../../../../../components/ha-textfield";
|
||||
import { Action, ParallelAction } from "../../../../../data/script";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { HomeAssistant, ItemPath } from "../../../../../types";
|
||||
import "../ha-automation-action";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
|
@ -14,9 +14,9 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
|||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ attribute: false }) public action!: ParallelAction;
|
||||
@property({ attribute: false }) public path?: ItemPath;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
@property({ attribute: false }) public action!: ParallelAction;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return {
|
||||
|
@ -29,9 +29,8 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
|||
|
||||
return html`
|
||||
<ha-automation-action
|
||||
nested
|
||||
.path=${[...(this.path ?? []), "parallel"]}
|
||||
.actions=${action.parallel}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._actionsChanged}
|
||||
.hass=${this.hass}
|
||||
|
|
|
@ -5,14 +5,17 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
|
|||
import "../../../../../components/ha-textfield";
|
||||
import { RepeatAction } from "../../../../../data/script";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { HomeAssistant, ItemPath } from "../../../../../types";
|
||||
import "../ha-automation-action";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
import { isTemplate } from "../../../../../common/string/has-template";
|
||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||
import type {
|
||||
HaFormSchema,
|
||||
SchemaUnion,
|
||||
} from "../../../../../components/ha-form/types";
|
||||
|
||||
const OPTIONS = ["count", "while", "until", "for_each"] as const;
|
||||
|
||||
|
@ -26,7 +29,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||
|
||||
@property({ attribute: false }) public action!: RepeatAction;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
@property() public path?: ItemPath;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { repeat: { count: 2, sequence: [] } };
|
||||
|
@ -36,8 +39,8 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||
(
|
||||
localize: LocalizeFunc,
|
||||
type: string,
|
||||
reOrderMode: boolean,
|
||||
template: boolean
|
||||
template: boolean,
|
||||
path?: ItemPath
|
||||
) =>
|
||||
[
|
||||
{
|
||||
|
@ -60,20 +63,22 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||
name: "count",
|
||||
required: true,
|
||||
selector: template
|
||||
? ({ template: {} } as const)
|
||||
: ({ number: { mode: "box", min: 1 } } as const),
|
||||
? { template: {} }
|
||||
: { number: { mode: "box", min: 1 } },
|
||||
},
|
||||
] as const)
|
||||
] as const satisfies readonly HaFormSchema[])
|
||||
: []),
|
||||
...(type === "until" || type === "while"
|
||||
? ([
|
||||
{
|
||||
name: type,
|
||||
selector: {
|
||||
condition: { nested: true, reorder_mode: reOrderMode },
|
||||
condition: {
|
||||
path: [...(path ?? []), "repeat", type],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const)
|
||||
] as const satisfies readonly HaFormSchema[])
|
||||
: []),
|
||||
...(type === "for_each"
|
||||
? ([
|
||||
|
@ -82,13 +87,17 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||
required: true,
|
||||
selector: { object: {} },
|
||||
},
|
||||
] as const)
|
||||
] as const satisfies readonly HaFormSchema[])
|
||||
: []),
|
||||
{
|
||||
name: "sequence",
|
||||
selector: { action: { nested: true, reorder_mode: reOrderMode } },
|
||||
selector: {
|
||||
action: {
|
||||
path: [...(path ?? []), "repeat", "sequence"],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const
|
||||
] as const satisfies readonly HaFormSchema[]
|
||||
);
|
||||
|
||||
protected render() {
|
||||
|
@ -97,11 +106,12 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||
const schema = this._schema(
|
||||
this.hass.localize,
|
||||
type ?? "count",
|
||||
this.reOrderMode,
|
||||
"count" in action && typeof action.count === "string"
|
||||
? isTemplate(action.count)
|
||||
: false
|
||||
: false,
|
||||
this.path
|
||||
);
|
||||
|
||||
const data = { ...action, type };
|
||||
return html`<ha-form
|
||||
.hass=${this.hass}
|
||||
|
|
|
@ -8,7 +8,7 @@ import "../../../../../components/ha-duration-input";
|
|||
import "../../../../../components/ha-formfield";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import { WaitForTriggerAction } from "../../../../../data/script";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { HomeAssistant, ItemPath } from "../../../../../types";
|
||||
import "../../trigger/ha-automation-trigger";
|
||||
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||
|
||||
|
@ -23,7 +23,7 @@ export class HaWaitForTriggerAction
|
|||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
@property({ attribute: false }) public path?: ItemPath;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { wait_for_trigger: [] };
|
||||
|
@ -55,12 +55,11 @@ export class HaWaitForTriggerAction
|
|||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-automation-trigger
|
||||
nested
|
||||
.path=${[...(this.path ?? []), "wait_for_trigger"]}
|
||||
.triggers=${ensureArray(this.action.wait_for_trigger)}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.name=${"wait_for_trigger"}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-automation-trigger>
|
||||
`;
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import "@material/mwc-button/mwc-button";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { nestedArrayMove } from "../../../common/util/array-move";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-blueprint-picker";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-markdown";
|
||||
import "../../../components/ha-selector/ha-selector";
|
||||
import "../../../components/ha-settings-row";
|
||||
import "../../../components/ha-alert";
|
||||
import { BlueprintAutomationConfig } from "../../../data/automation";
|
||||
import {
|
||||
BlueprintOrError,
|
||||
|
@ -17,11 +18,12 @@ import {
|
|||
fetchBlueprints,
|
||||
} from "../../../data/blueprint";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { ReorderModeMixin } from "../../../state/reorder-mode-mixin";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
|
||||
@customElement("blueprint-automation-editor")
|
||||
export class HaBlueprintAutomationEditor extends LitElement {
|
||||
export class HaBlueprintAutomationEditor extends ReorderModeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public isWide = false;
|
||||
|
@ -76,6 +78,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
|||
${this.config.description
|
||||
? html`<p class="description">${this.config.description}</p>`
|
||||
: ""}
|
||||
${this._renderReorderModeAlert()}
|
||||
<ha-card
|
||||
outlined
|
||||
class="blueprint"
|
||||
|
@ -119,8 +122,23 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
|||
${blueprint?.metadata?.input &&
|
||||
Object.keys(blueprint.metadata.input).length
|
||||
? Object.entries(blueprint.metadata.input).map(
|
||||
([key, value]) =>
|
||||
html`<ha-settings-row .narrow=${this.narrow}>
|
||||
([key, value]) => {
|
||||
const selector = value?.selector ?? { text: undefined };
|
||||
const type = Object.keys(selector)[0];
|
||||
const enhancedSelector = [
|
||||
"action",
|
||||
"condition",
|
||||
"trigger",
|
||||
].includes(type)
|
||||
? {
|
||||
[type]: {
|
||||
...selector[type],
|
||||
path: [key],
|
||||
},
|
||||
}
|
||||
: selector;
|
||||
|
||||
return html`<ha-settings-row .narrow=${this.narrow}>
|
||||
<span slot="heading">${value?.name || key}</span>
|
||||
<ha-markdown
|
||||
slot="description"
|
||||
|
@ -130,7 +148,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
|||
></ha-markdown>
|
||||
${html`<ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${value?.selector ?? { text: undefined }}
|
||||
.selector=${enhancedSelector}
|
||||
.key=${key}
|
||||
.disabled=${this.disabled}
|
||||
.required=${value?.default === undefined}
|
||||
|
@ -140,8 +158,10 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
|||
? this.config.use_blueprint.input[key]
|
||||
: value?.default}
|
||||
@value-changed=${this._inputChanged}
|
||||
@item-moved=${this._itemMoved}
|
||||
></ha-selector>`}
|
||||
</ha-settings-row>`
|
||||
</ha-settings-row>`;
|
||||
}
|
||||
)
|
||||
: html`<p class="padding">
|
||||
${this.hass.localize(
|
||||
|
@ -153,6 +173,34 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
|||
`;
|
||||
}
|
||||
|
||||
private _renderReorderModeAlert() {
|
||||
if (!this._reorderMode.active) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-alert
|
||||
class="re-order"
|
||||
alert-type="info"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.title"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.description_all"
|
||||
)}
|
||||
<ha-button slot="action" @click=${this._exitReOrderMode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.exit"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _exitReOrderMode() {
|
||||
this._reorderMode.exit();
|
||||
}
|
||||
|
||||
private async _getBlueprints() {
|
||||
this._blueprints = await fetchBlueprints(this.hass, "automation");
|
||||
}
|
||||
|
@ -197,6 +245,29 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
|||
});
|
||||
}
|
||||
|
||||
private _itemMoved(ev) {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
||||
|
||||
const input = nestedArrayMove(
|
||||
this.config.use_blueprint.input,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
oldPath,
|
||||
newPath
|
||||
);
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config,
|
||||
use_blueprint: {
|
||||
...this.config.use_blueprint,
|
||||
input,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async _enable(): Promise<void> {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return;
|
||||
|
@ -259,6 +330,10 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
|||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
ha-alert.re-order {
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
overflow: hidden;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import "../../../../components/ha-yaml-editor";
|
|||
import type { Condition } from "../../../../data/automation";
|
||||
import { expandConditionWithShorthand } from "../../../../data/automation";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { HomeAssistant, ItemPath } from "../../../../types";
|
||||
import "./types/ha-automation-condition-and";
|
||||
import "./types/ha-automation-condition-device";
|
||||
import "./types/ha-automation-condition-not";
|
||||
|
@ -30,7 +30,7 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||
|
||||
@property({ type: Boolean }) public yamlMode = false;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
@property() public path?: ItemPath;
|
||||
|
||||
private _processedCondition = memoizeOne((condition) =>
|
||||
expandConditionWithShorthand(condition)
|
||||
|
@ -67,8 +67,8 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||
{
|
||||
hass: this.hass,
|
||||
condition: condition,
|
||||
reOrderMode: this.reOrderMode,
|
||||
disabled: this.disabled,
|
||||
path: this.path,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -39,8 +39,12 @@ import {
|
|||
showPromptDialog,
|
||||
} from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { HomeAssistant, ItemPath } from "../../../../types";
|
||||
import "./ha-automation-condition-editor";
|
||||
import {
|
||||
ReorderMode,
|
||||
reorderModeContext,
|
||||
} from "../../../../state/reorder-mode-mixin";
|
||||
|
||||
export interface ConditionElement extends LitElement {
|
||||
condition: Condition;
|
||||
|
@ -81,10 +85,10 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||
|
||||
@property({ type: Boolean }) public hideMenu = false;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property() public path?: ItemPath;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: false,
|
||||
|
@ -105,10 +109,17 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entityReg!: EntityRegistryEntry[];
|
||||
|
||||
@state()
|
||||
@consume({ context: reorderModeContext, subscribe: true })
|
||||
private _reorderMode?: ReorderMode;
|
||||
|
||||
protected render() {
|
||||
if (!this.condition) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const noReorderModeAvailable = this._reorderMode === undefined;
|
||||
|
||||
return html`
|
||||
<ha-card outlined>
|
||||
${this.condition.enabled === false
|
||||
|
@ -163,7 +174,12 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
|
||||
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
.disabled=${this.disabled}
|
||||
class=${classMap({ hidden: noReorderModeAvailable })}
|
||||
?aria-hidden=${noReorderModeAvailable}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.re_order"
|
||||
)}
|
||||
|
@ -297,7 +313,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||
.disabled=${this.disabled}
|
||||
.hass=${this.hass}
|
||||
.condition=${this.condition}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.path=${this.path}
|
||||
></ha-automation-condition-editor>
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
|
@ -344,7 +360,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||
await this._renameCondition();
|
||||
break;
|
||||
case 2:
|
||||
fireEvent(this, "re-order");
|
||||
this._reorderMode?.enter();
|
||||
break;
|
||||
case 3:
|
||||
fireEvent(this, "duplicate");
|
||||
|
@ -547,6 +563,9 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||
mwc-list-item[disabled] {
|
||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||
}
|
||||
mwc-list-item.hidden {
|
||||
display: none;
|
||||
}
|
||||
.testing {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { consume } from "@lit-labs/context";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import {
|
||||
|
@ -8,10 +9,11 @@ import {
|
|||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { nestedArrayMove } from "../../../../common/util/array-move";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-sortable";
|
||||
|
@ -20,7 +22,11 @@ import type {
|
|||
AutomationClipboard,
|
||||
Condition,
|
||||
} from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
ReorderMode,
|
||||
reorderModeContext,
|
||||
} from "../../../../state/reorder-mode-mixin";
|
||||
import type { HomeAssistant, ItemPath } from "../../../../types";
|
||||
import {
|
||||
PASTE_VALUE,
|
||||
showAddAutomationElementDialog,
|
||||
|
@ -36,9 +42,11 @@ export default class HaAutomationCondition extends LitElement {
|
|||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public nested = false;
|
||||
@property() public path?: ItemPath;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
@state()
|
||||
@consume({ context: reorderModeContext, subscribe: true })
|
||||
private _reorderMode?: ReorderMode;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
|
@ -89,35 +97,21 @@ export default class HaAutomationCondition extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
private get nested() {
|
||||
return this.path !== undefined;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!Array.isArray(this.conditions)) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
${this.reOrderMode && !this.nested
|
||||
? html`
|
||||
<ha-alert
|
||||
alert-type="info"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.title"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.description_conditions"
|
||||
)}
|
||||
<ha-button slot="action" @click=${this._exitReOrderMode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.exit"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: null}
|
||||
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this.reOrderMode}
|
||||
.disabled=${!this._reorderMode?.active}
|
||||
@item-moved=${this._conditionMoved}
|
||||
group="conditions"
|
||||
.path=${this.path}
|
||||
>
|
||||
<div class="conditions">
|
||||
${repeat(
|
||||
|
@ -125,19 +119,18 @@ export default class HaAutomationCondition extends LitElement {
|
|||
(condition) => this._getKey(condition),
|
||||
(cond, idx) => html`
|
||||
<ha-automation-condition-row
|
||||
.path=${[...(this.path ?? []), idx]}
|
||||
.index=${idx}
|
||||
.totalConditions=${this.conditions.length}
|
||||
.condition=${cond}
|
||||
.hideMenu=${this.reOrderMode}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.hideMenu=${Boolean(this._reorderMode?.active)}
|
||||
.disabled=${this.disabled}
|
||||
@duplicate=${this._duplicateCondition}
|
||||
@move-condition=${this._move}
|
||||
@value-changed=${this._conditionChanged}
|
||||
@re-order=${this._enterReOrderMode}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
${this.reOrderMode
|
||||
${this._reorderMode?.active
|
||||
? html`
|
||||
<ha-icon-button
|
||||
.index=${idx}
|
||||
|
@ -232,16 +225,6 @@ export default class HaAutomationCondition extends LitElement {
|
|||
fireEvent(this, "value-changed", { value: conditions });
|
||||
};
|
||||
|
||||
private async _enterReOrderMode(ev: CustomEvent) {
|
||||
if (this.nested) return;
|
||||
ev.stopPropagation();
|
||||
this.reOrderMode = true;
|
||||
}
|
||||
|
||||
private async _exitReOrderMode() {
|
||||
this.reOrderMode = false;
|
||||
}
|
||||
|
||||
private _getKey(condition: Condition) {
|
||||
if (!this._conditionKeys.has(condition)) {
|
||||
this._conditionKeys.set(condition, Math.random().toString());
|
||||
|
@ -262,17 +245,28 @@ export default class HaAutomationCondition extends LitElement {
|
|||
this._move(index, newIndex);
|
||||
}
|
||||
|
||||
private _move(index: number, newIndex: number) {
|
||||
const conditions = this.conditions.concat();
|
||||
const condition = conditions.splice(index, 1)[0];
|
||||
conditions.splice(newIndex, 0, condition);
|
||||
private _move(
|
||||
oldIndex: number,
|
||||
newIndex: number,
|
||||
oldPath?: ItemPath,
|
||||
newPath?: ItemPath
|
||||
) {
|
||||
const conditions = nestedArrayMove(
|
||||
this.conditions,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
oldPath,
|
||||
newPath
|
||||
);
|
||||
|
||||
fireEvent(this, "value-changed", { value: conditions });
|
||||
}
|
||||
|
||||
private _conditionMoved(ev: CustomEvent): void {
|
||||
if (this.nested) return;
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
this._move(oldIndex, newIndex);
|
||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
||||
this._move(oldIndex, newIndex, oldPath, newPath);
|
||||
}
|
||||
|
||||
private _conditionChanged(ev: CustomEvent) {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { html, LitElement } from "lit";
|
|||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import type { LogicalCondition } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { HomeAssistant, ItemPath } from "../../../../../types";
|
||||
import "../ha-automation-condition";
|
||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||
|
||||
|
@ -14,7 +14,7 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
|
|||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
@property({ attribute: false }) public path?: ItemPath;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return {
|
||||
|
@ -25,12 +25,11 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
|
|||
protected render() {
|
||||
return html`
|
||||
<ha-automation-condition
|
||||
nested
|
||||
.path=${[...(this.path ?? []), "conditions"]}
|
||||
.conditions=${this.condition.conditions || []}
|
||||
@value-changed=${this._valueChanged}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
></ha-automation-condition>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
|||
import { customElement, property } from "lit/decorators";
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { nestedArrayMove } from "../../../common/util/array-move";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-markdown";
|
||||
|
@ -15,6 +16,7 @@ import {
|
|||
} from "../../../data/automation";
|
||||
import { Action } from "../../../data/script";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { ReorderModeMixin } from "../../../state/reorder-mode-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import "./action/ha-automation-action";
|
||||
|
@ -22,7 +24,7 @@ import "./condition/ha-automation-condition";
|
|||
import "./trigger/ha-automation-trigger";
|
||||
|
||||
@customElement("manual-automation-editor")
|
||||
export class HaManualAutomationEditor extends LitElement {
|
||||
export class HaManualAutomationEditor extends ReorderModeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
@ -44,7 +46,7 @@ export class HaManualAutomationEditor extends LitElement {
|
|||
${this.hass.localize("ui.panel.config.automation.editor.migrate")}
|
||||
</mwc-button>
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
: nothing}
|
||||
${this.stateObj?.state === "off"
|
||||
? html`
|
||||
<ha-alert alert-type="info">
|
||||
|
@ -92,12 +94,15 @@ export class HaManualAutomationEditor extends LitElement {
|
|||
)}
|
||||
</p>`
|
||||
: nothing}
|
||||
${this._renderReorderModeAlert("triggers")}
|
||||
|
||||
<ha-automation-trigger
|
||||
role="region"
|
||||
aria-labelledby="triggers-heading"
|
||||
.triggers=${this.config.trigger}
|
||||
.path=${["trigger"]}
|
||||
@value-changed=${this._triggerChanged}
|
||||
@item-moved=${this._itemMoved}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
></ha-automation-trigger>
|
||||
|
@ -132,12 +137,15 @@ export class HaManualAutomationEditor extends LitElement {
|
|||
)}
|
||||
</p>`
|
||||
: nothing}
|
||||
${this._renderReorderModeAlert("conditions")}
|
||||
|
||||
<ha-automation-condition
|
||||
role="region"
|
||||
aria-labelledby="conditions-heading"
|
||||
.conditions=${this.config.condition || []}
|
||||
.path=${["condition"]}
|
||||
@value-changed=${this._conditionChanged}
|
||||
@item-moved=${this._itemMoved}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
></ha-automation-condition>
|
||||
|
@ -170,12 +178,15 @@ export class HaManualAutomationEditor extends LitElement {
|
|||
)}
|
||||
</p>`
|
||||
: nothing}
|
||||
${this._renderReorderModeAlert("actions")}
|
||||
|
||||
<ha-automation-action
|
||||
role="region"
|
||||
aria-labelledby="actions-heading"
|
||||
.actions=${this.config.action}
|
||||
.path=${["action"]}
|
||||
@value-changed=${this._actionChanged}
|
||||
@item-moved=${this._itemMoved}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
|
@ -183,6 +194,34 @@ export class HaManualAutomationEditor extends LitElement {
|
|||
`;
|
||||
}
|
||||
|
||||
private _renderReorderModeAlert(type: "conditions" | "actions" | "triggers") {
|
||||
if (!this._reorderMode.active) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-alert
|
||||
class="re-order"
|
||||
alert-type="info"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.title"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.re_order_mode.description_${type}`
|
||||
)}
|
||||
<ha-button slot="action" @click=${this._exitReOrderMode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.exit"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _exitReOrderMode() {
|
||||
this._reorderMode.exit();
|
||||
}
|
||||
|
||||
private _triggerChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
|
@ -207,6 +246,21 @@ export class HaManualAutomationEditor extends LitElement {
|
|||
});
|
||||
}
|
||||
|
||||
private _itemMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
||||
const updatedConfig = nestedArrayMove(
|
||||
this.config,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
oldPath,
|
||||
newPath
|
||||
);
|
||||
fireEvent(this, "value-changed", {
|
||||
value: updatedConfig,
|
||||
});
|
||||
}
|
||||
|
||||
private async _enable(): Promise<void> {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return;
|
||||
|
@ -258,6 +312,12 @@ export class HaManualAutomationEditor extends LitElement {
|
|||
font-weight: normal;
|
||||
line-height: 0;
|
||||
}
|
||||
ha-alert.re-order {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
overflow: hidden;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -15,7 +15,14 @@ import {
|
|||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
|
@ -44,7 +51,7 @@ import {
|
|||
showPromptDialog,
|
||||
} from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { HomeAssistant, ItemPath } from "../../../../types";
|
||||
import "./types/ha-automation-trigger-calendar";
|
||||
import "./types/ha-automation-trigger-device";
|
||||
import "./types/ha-automation-trigger-event";
|
||||
|
@ -62,6 +69,10 @@ import "./types/ha-automation-trigger-time";
|
|||
import "./types/ha-automation-trigger-time_pattern";
|
||||
import "./types/ha-automation-trigger-webhook";
|
||||
import "./types/ha-automation-trigger-zone";
|
||||
import {
|
||||
ReorderMode,
|
||||
reorderModeContext,
|
||||
} from "../../../../state/reorder-mode-mixin";
|
||||
|
||||
export interface TriggerElement extends LitElement {
|
||||
trigger: Trigger;
|
||||
|
@ -101,6 +112,8 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property() public path?: ItemPath;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
@ -125,9 +138,17 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entityReg!: EntityRegistryEntry[];
|
||||
|
||||
@state()
|
||||
@consume({ context: reorderModeContext, subscribe: true })
|
||||
private _reorderMode?: ReorderMode;
|
||||
|
||||
private _triggerUnsub?: Promise<UnsubscribeFunc>;
|
||||
|
||||
protected render() {
|
||||
if (!this.trigger) return nothing;
|
||||
|
||||
const noReorderModeAvailable = this._reorderMode === undefined;
|
||||
|
||||
const supported =
|
||||
customElements.get(`ha-automation-trigger-${this.trigger.platform}`) !==
|
||||
undefined;
|
||||
|
@ -181,7 +202,12 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
|
||||
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
.disabled=${this.disabled}
|
||||
class=${classMap({ hidden: noReorderModeAvailable })}
|
||||
?aria-hidden=${noReorderModeAvailable}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.re_order"
|
||||
)}
|
||||
|
@ -357,6 +383,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||
hass: this.hass,
|
||||
trigger: this.trigger,
|
||||
disabled: this.disabled,
|
||||
path: this.path,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
|
@ -470,7 +497,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||
await this._renameTrigger();
|
||||
break;
|
||||
case 1:
|
||||
fireEvent(this, "re-order");
|
||||
this._reorderMode?.enter();
|
||||
break;
|
||||
case 2:
|
||||
this._requestShowId = true;
|
||||
|
@ -702,6 +729,9 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||
mwc-list-item[disabled] {
|
||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||
}
|
||||
mwc-list-item.hidden {
|
||||
display: none;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
import { consume } from "@lit-labs/context";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { nestedArrayMove } from "../../../../common/util/array-move";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-sortable";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { AutomationClipboard, Trigger } from "../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
ReorderMode,
|
||||
reorderModeContext,
|
||||
} from "../../../../state/reorder-mode-mixin";
|
||||
import { HomeAssistant, ItemPath } from "../../../../types";
|
||||
import {
|
||||
PASTE_VALUE,
|
||||
showAddAutomationElementDialog,
|
||||
|
@ -26,9 +32,11 @@ export default class HaAutomationTrigger extends LitElement {
|
|||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public nested = false;
|
||||
@property() public path?: ItemPath;
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
@state()
|
||||
@consume({ context: reorderModeContext, subscribe: true })
|
||||
private _reorderMode?: ReorderMode;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
|
@ -42,31 +50,18 @@ export default class HaAutomationTrigger extends LitElement {
|
|||
|
||||
private _triggerKeys = new WeakMap<Trigger, string>();
|
||||
|
||||
private get nested() {
|
||||
return this.path !== undefined;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${this.reOrderMode && !this.nested
|
||||
? html`
|
||||
<ha-alert
|
||||
alert-type="info"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.title"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.description_triggers"
|
||||
)}
|
||||
<ha-button slot="action" @click=${this._exitReOrderMode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.exit"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: null}
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this.reOrderMode}
|
||||
.disabled=${!this._reorderMode?.active}
|
||||
@item-moved=${this._triggerMoved}
|
||||
group="triggers"
|
||||
.path=${this.path}
|
||||
>
|
||||
<div class="triggers">
|
||||
${repeat(
|
||||
|
@ -74,16 +69,16 @@ export default class HaAutomationTrigger extends LitElement {
|
|||
(trigger) => this._getKey(trigger),
|
||||
(trg, idx) => html`
|
||||
<ha-automation-trigger-row
|
||||
.path=${[...(this.path ?? []), idx]}
|
||||
.index=${idx}
|
||||
.trigger=${trg}
|
||||
.hideMenu=${this.reOrderMode}
|
||||
.hideMenu=${Boolean(this._reorderMode?.active)}
|
||||
@duplicate=${this._duplicateTrigger}
|
||||
@value-changed=${this._triggerChanged}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
@re-order=${this._enterReOrderMode}
|
||||
>
|
||||
${this.reOrderMode
|
||||
${this._reorderMode?.active
|
||||
? html`
|
||||
<ha-icon-button
|
||||
.index=${idx}
|
||||
|
@ -173,16 +168,6 @@ export default class HaAutomationTrigger extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
private async _enterReOrderMode(ev: CustomEvent) {
|
||||
if (this.nested) return;
|
||||
ev.stopPropagation();
|
||||
this.reOrderMode = true;
|
||||
}
|
||||
|
||||
private async _exitReOrderMode() {
|
||||
this.reOrderMode = false;
|
||||
}
|
||||
|
||||
private _getKey(action: Trigger) {
|
||||
if (!this._triggerKeys.has(action)) {
|
||||
this._triggerKeys.set(action, Math.random().toString());
|
||||
|
@ -203,17 +188,28 @@ export default class HaAutomationTrigger extends LitElement {
|
|||
this._move(index, newIndex);
|
||||
}
|
||||
|
||||
private _move(index: number, newIndex: number) {
|
||||
const triggers = this.triggers.concat();
|
||||
const trigger = triggers.splice(index, 1)[0];
|
||||
triggers.splice(newIndex, 0, trigger);
|
||||
private _move(
|
||||
oldIndex: number,
|
||||
newIndex: number,
|
||||
oldPath?: ItemPath,
|
||||
newPath?: ItemPath
|
||||
) {
|
||||
const triggers = nestedArrayMove(
|
||||
this.triggers,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
oldPath,
|
||||
newPath
|
||||
);
|
||||
|
||||
fireEvent(this, "value-changed", { value: triggers });
|
||||
}
|
||||
|
||||
private _triggerMoved(ev: CustomEvent): void {
|
||||
if (this.nested) return;
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
this._move(oldIndex, newIndex);
|
||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
||||
this._move(oldIndex, newIndex, oldPath, newPath);
|
||||
}
|
||||
|
||||
private _triggerChanged(ev: CustomEvent) {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { nestedArrayMove } from "../../../common/util/array-move";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-blueprint-picker";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-markdown";
|
||||
import "../../../components/ha-selector/ha-selector";
|
||||
import "../../../components/ha-settings-row";
|
||||
|
||||
import {
|
||||
BlueprintOrError,
|
||||
Blueprints,
|
||||
|
@ -17,9 +18,10 @@ import { BlueprintScriptConfig } from "../../../data/script";
|
|||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { ReorderModeMixin } from "../../../state/reorder-mode-mixin";
|
||||
|
||||
@customElement("blueprint-script-editor")
|
||||
export class HaBlueprintScriptEditor extends LitElement {
|
||||
export class HaBlueprintScriptEditor extends ReorderModeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
@ -55,6 +57,7 @@ export class HaBlueprintScriptEditor extends LitElement {
|
|||
</mwc-button>
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
${this._renderReorderModeAlert()}
|
||||
<ha-card
|
||||
outlined
|
||||
class="blueprint"
|
||||
|
@ -82,7 +85,6 @@ export class HaBlueprintScriptEditor extends LitElement {
|
|||
)
|
||||
: html`<ha-circular-progress indeterminate></ha-circular-progress>`}
|
||||
</div>
|
||||
|
||||
${this.config.use_blueprint.path
|
||||
? blueprint && "error" in blueprint
|
||||
? html`<p class="warning padding">
|
||||
|
@ -98,8 +100,23 @@ export class HaBlueprintScriptEditor extends LitElement {
|
|||
${blueprint?.metadata?.input &&
|
||||
Object.keys(blueprint.metadata.input).length
|
||||
? Object.entries(blueprint.metadata.input).map(
|
||||
([key, value]) =>
|
||||
html`<ha-settings-row .narrow=${this.narrow}>
|
||||
([key, value]) => {
|
||||
const selector = value?.selector ?? { text: undefined };
|
||||
const type = Object.keys(selector)[0];
|
||||
const enhancedSelector = [
|
||||
"action",
|
||||
"condition",
|
||||
"trigger",
|
||||
].includes(type)
|
||||
? {
|
||||
[type]: {
|
||||
...selector[type],
|
||||
path: [key],
|
||||
},
|
||||
}
|
||||
: selector;
|
||||
|
||||
return html`<ha-settings-row .narrow=${this.narrow}>
|
||||
<span slot="heading">${value?.name || key}</span>
|
||||
<ha-markdown
|
||||
slot="description"
|
||||
|
@ -109,7 +126,7 @@ export class HaBlueprintScriptEditor extends LitElement {
|
|||
></ha-markdown>
|
||||
${html`<ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${value?.selector ?? { text: undefined }}
|
||||
.selector=${enhancedSelector}
|
||||
.key=${key}
|
||||
.disabled=${this.disabled}
|
||||
.required=${value?.default === undefined}
|
||||
|
@ -119,8 +136,10 @@ export class HaBlueprintScriptEditor extends LitElement {
|
|||
? this.config.use_blueprint.input[key]
|
||||
: value?.default}
|
||||
@value-changed=${this._inputChanged}
|
||||
@item-moved=${this._itemMoved}
|
||||
></ha-selector>`}
|
||||
</ha-settings-row>`
|
||||
</ha-settings-row>`;
|
||||
}
|
||||
)
|
||||
: html`<p class="padding">
|
||||
${this.hass.localize(
|
||||
|
@ -132,6 +151,34 @@ export class HaBlueprintScriptEditor extends LitElement {
|
|||
`;
|
||||
}
|
||||
|
||||
private _renderReorderModeAlert() {
|
||||
if (!this._reorderMode.active) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-alert
|
||||
class="re-order"
|
||||
alert-type="info"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.title"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.description_all"
|
||||
)}
|
||||
<ha-button slot="action" @click=${this._exitReOrderMode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.exit"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _exitReOrderMode() {
|
||||
this._reorderMode.exit();
|
||||
}
|
||||
|
||||
private async _getBlueprints() {
|
||||
this._blueprints = await fetchBlueprints(this.hass, "script");
|
||||
}
|
||||
|
@ -176,6 +223,29 @@ export class HaBlueprintScriptEditor extends LitElement {
|
|||
});
|
||||
}
|
||||
|
||||
private _itemMoved(ev) {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
||||
|
||||
const input = nestedArrayMove(
|
||||
this.config.use_blueprint.input,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
oldPath,
|
||||
newPath
|
||||
);
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config,
|
||||
use_blueprint: {
|
||||
...this.config.use_blueprint,
|
||||
input,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _duplicate() {
|
||||
fireEvent(this, "duplicate");
|
||||
}
|
||||
|
@ -229,6 +299,10 @@ export class HaBlueprintScriptEditor extends LitElement {
|
|||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
ha-alert.re-order {
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
overflow: hidden;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -3,10 +3,12 @@ import { mdiHelpCircle } from "@mdi/js";
|
|||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { nestedArrayMove } from "../../../common/util/array-move";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { Action, Fields, ScriptConfig } from "../../../data/script";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { ReorderModeMixin } from "../../../state/reorder-mode-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import "../automation/action/ha-automation-action";
|
||||
|
@ -14,7 +16,7 @@ import "./ha-script-fields";
|
|||
import type HaScriptFields from "./ha-script-fields";
|
||||
|
||||
@customElement("manual-script-editor")
|
||||
export class HaManualScriptEditor extends LitElement {
|
||||
export class HaManualScriptEditor extends ReorderModeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
@ -118,11 +120,15 @@ export class HaManualScriptEditor extends LitElement {
|
|||
</a>
|
||||
</div>
|
||||
|
||||
${this._renderReorderModeAlert()}
|
||||
|
||||
<ha-automation-action
|
||||
role="region"
|
||||
aria-labelledby="sequence-heading"
|
||||
.actions=${this.config.sequence}
|
||||
.path=${["sequence"]}
|
||||
@value-changed=${this._sequenceChanged}
|
||||
@item-moved=${this._itemMoved}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
|
@ -130,6 +136,34 @@ export class HaManualScriptEditor extends LitElement {
|
|||
`;
|
||||
}
|
||||
|
||||
private _renderReorderModeAlert() {
|
||||
if (!this._reorderMode.active) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-alert
|
||||
class="re-order"
|
||||
alert-type="info"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.title"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.description_all"
|
||||
)}
|
||||
<ha-button slot="action" @click=${this._exitReOrderMode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.exit"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _exitReOrderMode() {
|
||||
this._reorderMode.exit();
|
||||
}
|
||||
|
||||
private _fieldsChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
|
@ -144,6 +178,21 @@ export class HaManualScriptEditor extends LitElement {
|
|||
});
|
||||
}
|
||||
|
||||
private _itemMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
||||
const updatedConfig = nestedArrayMove(
|
||||
this.config,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
oldPath,
|
||||
newPath
|
||||
);
|
||||
fireEvent(this, "value-changed", {
|
||||
value: updatedConfig,
|
||||
});
|
||||
}
|
||||
|
||||
private _duplicate() {
|
||||
fireEvent(this, "duplicate");
|
||||
}
|
||||
|
@ -179,6 +228,12 @@ export class HaManualScriptEditor extends LitElement {
|
|||
.header a {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-alert.re-order {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
overflow: hidden;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import { ContextProvider, createContext } from "@lit-labs/context";
|
||||
import { LitElement } from "lit";
|
||||
import { Constructor } from "../types";
|
||||
|
||||
export type ReorderMode = {
|
||||
active: boolean;
|
||||
enter: () => void;
|
||||
exit: () => void;
|
||||
};
|
||||
export const reorderModeContext = createContext<ReorderMode>("reorder-mode");
|
||||
|
||||
export const ReorderModeMixin = <T extends Constructor<LitElement>>(
|
||||
superClass: T
|
||||
) =>
|
||||
class extends superClass {
|
||||
private _reorderModeProvider = new ContextProvider(this, {
|
||||
context: reorderModeContext,
|
||||
initialValue: {
|
||||
active: false,
|
||||
enter: () => {
|
||||
this._reorderModeProvider.setValue({
|
||||
...this._reorderModeProvider.value,
|
||||
active: true,
|
||||
});
|
||||
this.requestUpdate("_reorderMode");
|
||||
},
|
||||
exit: () => {
|
||||
this._reorderModeProvider.setValue({
|
||||
...this._reorderModeProvider.value,
|
||||
active: false,
|
||||
});
|
||||
this.requestUpdate("_reorderMode");
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
get _reorderMode() {
|
||||
return this._reorderModeProvider.value;
|
||||
}
|
||||
};
|
|
@ -2444,6 +2444,7 @@
|
|||
"description_triggers": "You are in re-order mode, you can re-order your triggers.",
|
||||
"description_conditions": "You are in re-order mode, you can re-order your conditions.",
|
||||
"description_actions": "You are in re-order mode, you can re-order your actions.",
|
||||
"description_all": "You are in re-order mode, you can re-order your triggers, conditions and actions.",
|
||||
"exit": "Exit"
|
||||
},
|
||||
"description": {
|
||||
|
|
|
@ -294,3 +294,5 @@ export type AsyncReturnType<T extends (...args: any) => any> = T extends (
|
|||
: never;
|
||||
|
||||
export type Entries<T> = [keyof T, T[keyof T]][];
|
||||
|
||||
export type ItemPath = (number | string)[];
|
||||
|
|
Loading…
Reference in New Issue