mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-22 16:56:35 +00:00
Handle automation and dashboard drag and drop at the element level (#22300)
* Handle drag and drop at action, condition, trigger level * Clean item path * Clean item path * Fix selectors * Clean selector config * Remove enhancedSelector * Add option row component * Fix DnD inside option sequence or condition * Add comments * Remove item path logic from the dashboard too * Fix floor/area drag and drop * Avoid UI jump in area dashboard * Remove unused import * Add comment
This commit is contained in:
parent
51f89b00c1
commit
bc11c0b3ac
@ -1,66 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateNestedItem(obj: any, path: ItemPath): any {
|
|
||||||
const lastKey = path.pop()!;
|
|
||||||
const parent = findNestedItem(obj, path);
|
|
||||||
parent[lastKey] = Array.isArray(parent[lastKey])
|
|
||||||
? [...parent[lastKey]]
|
|
||||||
: [parent[lastKey]];
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function nestedArrayMove<A>(
|
|
||||||
obj: A,
|
|
||||||
oldIndex: number,
|
|
||||||
newIndex: number,
|
|
||||||
oldPath?: ItemPath,
|
|
||||||
newPath?: ItemPath
|
|
||||||
): A {
|
|
||||||
let newObj = (Array.isArray(obj) ? [...obj] : { ...obj }) as A;
|
|
||||||
|
|
||||||
if (oldPath) {
|
|
||||||
newObj = updateNestedItem(newObj, [...oldPath]);
|
|
||||||
}
|
|
||||||
if (newPath) {
|
|
||||||
newObj = updateNestedItem(newObj, [...newPath]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const from = oldPath ? findNestedItem(newObj, oldPath) : newObj;
|
|
||||||
const to = newPath ? findNestedItem(newObj, newPath, true) : newObj;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
@ -32,7 +32,6 @@ export class HaActionSelector extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.actions=${this._actions(this.value)}
|
.actions=${this._actions(this.value)}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.path=${this.selector.action?.path}
|
|
||||||
></ha-automation-action>
|
></ha-automation-action>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ export class HaConditionSelector extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.conditions=${this.value || []}
|
.conditions=${this.value || []}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.path=${this.selector.condition?.path}
|
|
||||||
></ha-automation-condition>
|
></ha-automation-condition>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@ export class HaTriggerSelector extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.triggers=${this._triggers(this.value)}
|
.triggers=${this._triggers(this.value)}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.path=${this.selector.trigger?.path}
|
|
||||||
></ha-automation-trigger>
|
></ha-automation-trigger>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import { fireEvent } from "../common/dom/fire_event";
|
|||||||
import { computeDomain } from "../common/entity/compute_domain";
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
import { computeObjectId } from "../common/entity/compute_object_id";
|
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||||
import { supportsFeature } from "../common/entity/supports-feature";
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
import { nestedArrayMove } from "../common/util/array-move";
|
|
||||||
import {
|
import {
|
||||||
fetchIntegrationManifest,
|
fetchIntegrationManifest,
|
||||||
IntegrationManifest,
|
IntegrationManifest,
|
||||||
@ -597,15 +596,6 @@ export class HaServiceControl extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selector = dataField?.selector ?? { text: undefined };
|
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);
|
const showOptional = showOptionalToggle(dataField);
|
||||||
|
|
||||||
@ -646,7 +636,7 @@ export class HaServiceControl extends LitElement {
|
|||||||
(!this._value?.data ||
|
(!this._value?.data ||
|
||||||
this._value.data[dataField.key] === undefined))}
|
this._value.data[dataField.key] === undefined))}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.selector=${enhancedSelector}
|
.selector=${selector}
|
||||||
.key=${dataField.key}
|
.key=${dataField.key}
|
||||||
@value-changed=${this._serviceDataChanged}
|
@value-changed=${this._serviceDataChanged}
|
||||||
.value=${this._value?.data
|
.value=${this._value?.data
|
||||||
@ -654,7 +644,6 @@ export class HaServiceControl extends LitElement {
|
|||||||
: undefined}
|
: undefined}
|
||||||
.placeholder=${dataField.default}
|
.placeholder=${dataField.default}
|
||||||
.localizeValue=${this._localizeValueCallback}
|
.localizeValue=${this._localizeValueCallback}
|
||||||
@item-moved=${this._itemMoved}
|
|
||||||
></ha-selector>
|
></ha-selector>
|
||||||
</ha-settings-row>`
|
</ha-settings-row>`
|
||||||
: "";
|
: "";
|
||||||
@ -856,22 +845,6 @@ 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) {
|
private _dataChanged(ev: CustomEvent) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
if (!ev.detail.isValid) {
|
if (!ev.detail.isValid) {
|
||||||
|
@ -4,15 +4,19 @@ import { customElement, property } from "lit/decorators";
|
|||||||
import type { SortableEvent } from "sortablejs";
|
import type { SortableEvent } from "sortablejs";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import type { SortableInstance } from "../resources/sortable";
|
import type { SortableInstance } from "../resources/sortable";
|
||||||
import { ItemPath } from "../types";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
"item-moved": {
|
"item-moved": {
|
||||||
oldIndex: number;
|
oldIndex: number;
|
||||||
newIndex: number;
|
newIndex: number;
|
||||||
oldPath?: ItemPath;
|
};
|
||||||
newPath?: ItemPath;
|
"item-added": {
|
||||||
|
index: number;
|
||||||
|
data: any;
|
||||||
|
};
|
||||||
|
"item-removed": {
|
||||||
|
index: number;
|
||||||
};
|
};
|
||||||
"drag-start": undefined;
|
"drag-start": undefined;
|
||||||
"drag-end": undefined;
|
"drag-end": undefined;
|
||||||
@ -21,7 +25,7 @@ declare global {
|
|||||||
|
|
||||||
export type HaSortableOptions = Omit<
|
export type HaSortableOptions = Omit<
|
||||||
SortableInstance.SortableOptions,
|
SortableInstance.SortableOptions,
|
||||||
"onStart" | "onChoose" | "onEnd"
|
"onStart" | "onChoose" | "onEnd" | "onUpdate" | "onAdd" | "onRemove"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@customElement("ha-sortable")
|
@customElement("ha-sortable")
|
||||||
@ -31,9 +35,6 @@ export class HaSortable extends LitElement {
|
|||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
public disabled = false;
|
public disabled = false;
|
||||||
|
|
||||||
@property({ type: Array })
|
|
||||||
public path?: ItemPath;
|
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "no-style" })
|
@property({ type: Boolean, attribute: "no-style" })
|
||||||
public noStyle: boolean = false;
|
public noStyle: boolean = false;
|
||||||
|
|
||||||
@ -138,6 +139,9 @@ export class HaSortable extends LitElement {
|
|||||||
onChoose: this._handleChoose,
|
onChoose: this._handleChoose,
|
||||||
onStart: this._handleStart,
|
onStart: this._handleStart,
|
||||||
onEnd: this._handleEnd,
|
onEnd: this._handleEnd,
|
||||||
|
onUpdate: this._handleUpdate,
|
||||||
|
onAdd: this._handleAdd,
|
||||||
|
onRemove: this._handleRemove,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.draggableSelector) {
|
if (this.draggableSelector) {
|
||||||
@ -159,33 +163,31 @@ export class HaSortable extends LitElement {
|
|||||||
this._sortable = new Sortable(container, options);
|
this._sortable = new Sortable(container, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleEnd = async (evt: SortableEvent) => {
|
private _handleUpdate = (evt) => {
|
||||||
|
fireEvent(this, "item-moved", {
|
||||||
|
newIndex: evt.newIndex,
|
||||||
|
oldIndex: evt.oldIndex,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _handleAdd = (evt) => {
|
||||||
|
fireEvent(this, "item-added", {
|
||||||
|
index: evt.newIndex,
|
||||||
|
data: evt.item.sortableData,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _handleRemove = (evt) => {
|
||||||
|
fireEvent(this, "item-removed", { index: evt.oldIndex });
|
||||||
|
};
|
||||||
|
|
||||||
|
private _handleEnd = async (evt) => {
|
||||||
fireEvent(this, "drag-end");
|
fireEvent(this, "drag-end");
|
||||||
// put back in original location
|
// put back in original location
|
||||||
if (this.rollback && (evt.item as any).placeholder) {
|
if (this.rollback && (evt.item as any).placeholder) {
|
||||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||||
delete (evt.item as any).placeholder;
|
delete (evt.item as any).placeholder;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (
|
|
||||||
oldIndex === undefined ||
|
|
||||||
newIndex === undefined ||
|
|
||||||
(oldIndex === newIndex && oldPath?.join(".") === newPath?.join("."))
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fireEvent(this, "item-moved", {
|
|
||||||
oldIndex,
|
|
||||||
newIndex,
|
|
||||||
oldPath,
|
|
||||||
newPath,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private _handleStart = () => {
|
private _handleStart = () => {
|
||||||
|
@ -33,7 +33,7 @@ import { LabelRegistryEntry } from "../../data/label_registry";
|
|||||||
import { LogbookEntry } from "../../data/logbook";
|
import { LogbookEntry } from "../../data/logbook";
|
||||||
import {
|
import {
|
||||||
ChooseAction,
|
ChooseAction,
|
||||||
ChooseActionChoice,
|
Option,
|
||||||
IfAction,
|
IfAction,
|
||||||
ParallelAction,
|
ParallelAction,
|
||||||
RepeatAction,
|
RepeatAction,
|
||||||
@ -413,7 +413,7 @@ class ActionRenderer {
|
|||||||
: undefined;
|
: undefined;
|
||||||
const choiceConfig = this._getDataFromPath(
|
const choiceConfig = this._getDataFromPath(
|
||||||
`${this.keys[index]}/choose/${chooseTrace.result.choice}`
|
`${this.keys[index]}/choose/${chooseTrace.result.choice}`
|
||||||
) as ChooseActionChoice | undefined;
|
) as Option | undefined;
|
||||||
const choiceName = choiceConfig
|
const choiceName = choiceConfig
|
||||||
? `${
|
? `${
|
||||||
choiceConfig.alias ||
|
choiceConfig.alias ||
|
||||||
|
@ -224,13 +224,14 @@ export interface ForEachRepeat extends BaseRepeat {
|
|||||||
for_each: string | any[];
|
for_each: string | any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChooseActionChoice extends BaseAction {
|
export interface Option {
|
||||||
|
alias?: string;
|
||||||
conditions: string | Condition[];
|
conditions: string | Condition[];
|
||||||
sequence: Action | Action[];
|
sequence: Action | Action[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChooseAction extends BaseAction {
|
export interface ChooseAction extends BaseAction {
|
||||||
choose: ChooseActionChoice | ChooseActionChoice[] | null;
|
choose: Option | Option[] | null;
|
||||||
default?: Action | Action[];
|
default?: Action | Action[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { supportsFeature } from "../common/entity/supports-feature";
|
|||||||
import type { CropOptions } from "../dialogs/image-cropper-dialog/show-image-cropper-dialog";
|
import type { CropOptions } from "../dialogs/image-cropper-dialog/show-image-cropper-dialog";
|
||||||
import { isHelperDomain } from "../panels/config/helpers/const";
|
import { isHelperDomain } from "../panels/config/helpers/const";
|
||||||
import { UiAction } from "../panels/lovelace/components/hui-action-editor";
|
import { UiAction } from "../panels/lovelace/components/hui-action-editor";
|
||||||
import { HomeAssistant, ItemPath } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import {
|
import {
|
||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
getDeviceIntegrationLookup,
|
getDeviceIntegrationLookup,
|
||||||
@ -68,9 +68,8 @@ export type Selector =
|
|||||||
| UiStateContentSelector;
|
| UiStateContentSelector;
|
||||||
|
|
||||||
export interface ActionSelector {
|
export interface ActionSelector {
|
||||||
action: {
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
path?: ItemPath;
|
action: {} | null;
|
||||||
} | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddonSelector {
|
export interface AddonSelector {
|
||||||
@ -121,9 +120,8 @@ export interface ColorTempSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ConditionSelector {
|
export interface ConditionSelector {
|
||||||
condition: {
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
path?: ItemPath;
|
condition: {} | null;
|
||||||
} | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConversationAgentSelector {
|
export interface ConversationAgentSelector {
|
||||||
@ -432,9 +430,8 @@ export interface TimeSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TriggerSelector {
|
export interface TriggerSelector {
|
||||||
trigger: {
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
path?: ItemPath;
|
trigger: {} | null;
|
||||||
} | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TTSSelector {
|
export interface TTSSelector {
|
||||||
|
@ -9,12 +9,13 @@ import {
|
|||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
LitElement,
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
css,
|
css,
|
||||||
html,
|
html,
|
||||||
nothing,
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { formatListWithAnds } from "../../../common/string/format-list";
|
import { formatListWithAnds } from "../../../common/string/format-list";
|
||||||
@ -49,7 +50,7 @@ import {
|
|||||||
} from "./show-dialog-area-registry-detail";
|
} from "./show-dialog-area-registry-detail";
|
||||||
import { showFloorRegistryDetailDialog } from "./show-dialog-floor-registry-detail";
|
import { showFloorRegistryDetailDialog } from "./show-dialog-floor-registry-detail";
|
||||||
|
|
||||||
const UNASSIGNED_PATH = ["__unassigned__"];
|
const UNASSIGNED_FLOOR = "__unassigned__";
|
||||||
|
|
||||||
const SORT_OPTIONS = { sort: false, delay: 500, delayOnTouchOnly: true };
|
const SORT_OPTIONS = { sort: false, delay: 500, delayOnTouchOnly: true };
|
||||||
|
|
||||||
@ -63,9 +64,11 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public route!: Route;
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
|
@state() private _areas: AreaRegistryEntry[] = [];
|
||||||
|
|
||||||
private _processAreas = memoizeOne(
|
private _processAreas = memoizeOne(
|
||||||
(
|
(
|
||||||
areas: HomeAssistant["areas"],
|
areas: AreaRegistryEntry[],
|
||||||
devices: HomeAssistant["devices"],
|
devices: HomeAssistant["devices"],
|
||||||
entities: HomeAssistant["entities"],
|
entities: HomeAssistant["entities"],
|
||||||
floors: HomeAssistant["floors"]
|
floors: HomeAssistant["floors"]
|
||||||
@ -99,8 +102,8 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const floorAreaLookup = getFloorAreaLookup(Object.values(areas));
|
const floorAreaLookup = getFloorAreaLookup(areas);
|
||||||
const unassisgnedAreas = Object.values(areas).filter(
|
const unassignedAreas = areas.filter(
|
||||||
(area) => !area.floor_id || !floorAreaLookup[area.floor_id]
|
(area) => !area.floor_id || !floorAreaLookup[area.floor_id]
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
@ -108,11 +111,21 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
...floor,
|
...floor,
|
||||||
areas: (floorAreaLookup[floor.floor_id] || []).map(processArea),
|
areas: (floorAreaLookup[floor.floor_id] || []).map(processArea),
|
||||||
})),
|
})),
|
||||||
unassisgnedAreas: unassisgnedAreas.map(processArea),
|
unassignedAreas: unassignedAreas.map(processArea),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
if (changedProperties.has("hass")) {
|
||||||
|
const oldHass = changedProperties.get("hass");
|
||||||
|
if (this.hass.areas !== oldHass?.areas) {
|
||||||
|
this._areas = Object.values(this.hass.areas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const areasAndFloors =
|
const areasAndFloors =
|
||||||
!this.hass.areas ||
|
!this.hass.areas ||
|
||||||
@ -121,7 +134,7 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
!this.hass.floors
|
!this.hass.floors
|
||||||
? undefined
|
? undefined
|
||||||
: this._processAreas(
|
: this._processAreas(
|
||||||
this.hass.areas,
|
this._areas,
|
||||||
this.hass.devices,
|
this.hass.devices,
|
||||||
this.hass.entities,
|
this.hass.entities,
|
||||||
this.hass.floors
|
this.hass.floors
|
||||||
@ -183,10 +196,10 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
<ha-sortable
|
<ha-sortable
|
||||||
handle-selector="a"
|
handle-selector="a"
|
||||||
draggable-selector="a"
|
draggable-selector="a"
|
||||||
@item-moved=${this._areaMoved}
|
@item-added=${this._areaAdded}
|
||||||
group="floor"
|
group="floor"
|
||||||
.options=${SORT_OPTIONS}
|
.options=${SORT_OPTIONS}
|
||||||
.path=${[floor.floor_id]}
|
.floor=${floor.floor_id}
|
||||||
>
|
>
|
||||||
<div class="areas">
|
<div class="areas">
|
||||||
${floor.areas.map((area) => this._renderArea(area))}
|
${floor.areas.map((area) => this._renderArea(area))}
|
||||||
@ -194,7 +207,7 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
</ha-sortable>
|
</ha-sortable>
|
||||||
</div>`
|
</div>`
|
||||||
)}
|
)}
|
||||||
${areasAndFloors?.unassisgnedAreas.length
|
${areasAndFloors?.unassignedAreas.length
|
||||||
? html`<div class="floor">
|
? html`<div class="floor">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h2>
|
<h2>
|
||||||
@ -206,13 +219,13 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
<ha-sortable
|
<ha-sortable
|
||||||
handle-selector="a"
|
handle-selector="a"
|
||||||
draggable-selector="a"
|
draggable-selector="a"
|
||||||
@item-moved=${this._areaMoved}
|
@item-added=${this._areaAdded}
|
||||||
group="floor"
|
group="floor"
|
||||||
.options=${SORT_OPTIONS}
|
.options=${SORT_OPTIONS}
|
||||||
.path=${UNASSIGNED_PATH}
|
.floor=${UNASSIGNED_FLOOR}
|
||||||
>
|
>
|
||||||
<div class="areas">
|
<div class="areas">
|
||||||
${areasAndFloors?.unassisgnedAreas.map((area) =>
|
${areasAndFloors?.unassignedAreas.map((area) =>
|
||||||
this._renderArea(area)
|
this._renderArea(area)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -246,7 +259,10 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _renderArea(area) {
|
private _renderArea(area) {
|
||||||
return html`<a href=${`/config/areas/area/${area.area_id}`}>
|
return html`<a
|
||||||
|
href=${`/config/areas/area/${area.area_id}`}
|
||||||
|
.sortableData=${area}
|
||||||
|
>
|
||||||
<ha-card outlined>
|
<ha-card outlined>
|
||||||
<div
|
<div
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
@ -309,26 +325,23 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _areaMoved(ev) {
|
private async _areaAdded(ev) {
|
||||||
const areasAndFloors = this._processAreas(
|
ev.stopPropagation();
|
||||||
this.hass.areas,
|
const { floor } = ev.currentTarget;
|
||||||
this.hass.devices,
|
|
||||||
this.hass.entities,
|
const newFloorId = floor === UNASSIGNED_FLOOR ? null : floor;
|
||||||
this.hass.floors
|
|
||||||
);
|
const { data: area } = ev.detail;
|
||||||
let area: AreaRegistryEntry;
|
|
||||||
if (ev.detail.oldPath === UNASSIGNED_PATH) {
|
this._areas = this._areas.map<AreaRegistryEntry>((a) => {
|
||||||
area = areasAndFloors.unassisgnedAreas[ev.detail.oldIndex];
|
if (a.area_id === area.area_id) {
|
||||||
} else {
|
return { ...a, floor_id: newFloorId };
|
||||||
const oldFloor = areasAndFloors.floors!.find(
|
}
|
||||||
(floor) => floor.floor_id === ev.detail.oldPath[0]
|
return a;
|
||||||
);
|
});
|
||||||
area = oldFloor!.areas[ev.detail.oldIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
await updateAreaRegistryEntry(this.hass, area.area_id, {
|
await updateAreaRegistryEntry(this.hass, area.area_id, {
|
||||||
floor_id:
|
floor_id: newFloorId,
|
||||||
ev.detail.newPath === UNASSIGNED_PATH ? null : ev.detail.newPath[0],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ import {
|
|||||||
showPromptDialog,
|
showPromptDialog,
|
||||||
} from "../../../../dialogs/generic/show-dialog-box";
|
} from "../../../../dialogs/generic/show-dialog-box";
|
||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant, ItemPath } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { showToast } from "../../../../util/toast";
|
import { showToast } from "../../../../util/toast";
|
||||||
import "./types/ha-automation-action-activate_scene";
|
import "./types/ha-automation-action-activate_scene";
|
||||||
import "./types/ha-automation-action-choose";
|
import "./types/ha-automation-action-choose";
|
||||||
@ -137,8 +137,6 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Array }) public path?: ItemPath;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public first?: boolean;
|
@property({ type: Boolean }) public first?: boolean;
|
||||||
|
|
||||||
@property({ type: Boolean }) public last?: boolean;
|
@property({ type: Boolean }) public last?: boolean;
|
||||||
@ -432,7 +430,6 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
action: this.action,
|
action: this.action,
|
||||||
narrow: this.narrow,
|
narrow: this.narrow,
|
||||||
disabled: this.disabled,
|
disabled: this.disabled,
|
||||||
path: this.path,
|
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
@ -13,14 +13,14 @@ import { repeat } from "lit/directives/repeat";
|
|||||||
import { storage } from "../../../../common/decorators/storage";
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
||||||
import { nestedArrayMove } from "../../../../common/util/array-move";
|
import { nextRender } from "../../../../common/util/render-status";
|
||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
import "../../../../components/ha-sortable";
|
import "../../../../components/ha-sortable";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import { getService, isService } from "../../../../data/action";
|
import { getService, isService } from "../../../../data/action";
|
||||||
import type { AutomationClipboard } from "../../../../data/automation";
|
import type { AutomationClipboard } from "../../../../data/automation";
|
||||||
import { Action } from "../../../../data/script";
|
import { Action } from "../../../../data/script";
|
||||||
import { HomeAssistant, ItemPath } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import {
|
import {
|
||||||
PASTE_VALUE,
|
PASTE_VALUE,
|
||||||
showAddAutomationElementDialog,
|
showAddAutomationElementDialog,
|
||||||
@ -36,8 +36,6 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Array }) public path?: ItemPath;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public actions!: Action[];
|
@property({ attribute: false }) public actions!: Action[];
|
||||||
|
|
||||||
@state() private _showReorder: boolean = false;
|
@state() private _showReorder: boolean = false;
|
||||||
@ -69,20 +67,17 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
this._unsubMql = undefined;
|
this._unsubMql = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get nested() {
|
|
||||||
return this.path !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<ha-sortable
|
<ha-sortable
|
||||||
handle-selector=".handle"
|
handle-selector=".handle"
|
||||||
draggable-selector="ha-automation-action-row"
|
draggable-selector="ha-automation-action-row"
|
||||||
.disabled=${!this._showReorder || this.disabled}
|
.disabled=${!this._showReorder || this.disabled}
|
||||||
@item-moved=${this._actionMoved}
|
|
||||||
group="actions"
|
group="actions"
|
||||||
.path=${this.path}
|
|
||||||
invert-swap
|
invert-swap
|
||||||
|
@item-moved=${this._actionMoved}
|
||||||
|
@item-added=${this._actionAdded}
|
||||||
|
@item-removed=${this._actionRemoved}
|
||||||
>
|
>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
${repeat(
|
${repeat(
|
||||||
@ -90,7 +85,7 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
(action) => this._getKey(action),
|
(action) => this._getKey(action),
|
||||||
(action, idx) => html`
|
(action, idx) => html`
|
||||||
<ha-automation-action-row
|
<ha-automation-action-row
|
||||||
.path=${[...(this.path ?? []), idx]}
|
.sortableData=${action}
|
||||||
.index=${idx}
|
.index=${idx}
|
||||||
.first=${idx === 0}
|
.first=${idx === 0}
|
||||||
.last=${idx === this.actions.length - 1}
|
.last=${idx === this.actions.length - 1}
|
||||||
@ -225,28 +220,44 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
this._move(index, newIndex);
|
this._move(index, newIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _move(
|
private _move(oldIndex: number, newIndex: number) {
|
||||||
oldIndex: number,
|
const actions = this.actions.concat();
|
||||||
newIndex: number,
|
const item = actions.splice(oldIndex, 1)[0];
|
||||||
oldPath?: ItemPath,
|
actions.splice(newIndex, 0, item);
|
||||||
newPath?: ItemPath
|
this.actions = actions;
|
||||||
) {
|
|
||||||
const actions = nestedArrayMove(
|
|
||||||
this.actions,
|
|
||||||
oldIndex,
|
|
||||||
newIndex,
|
|
||||||
oldPath,
|
|
||||||
newPath
|
|
||||||
);
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", { value: actions });
|
fireEvent(this, "value-changed", { value: actions });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _actionMoved(ev: CustomEvent): void {
|
private _actionMoved(ev: CustomEvent): void {
|
||||||
if (this.nested) return;
|
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
const { oldIndex, newIndex } = ev.detail;
|
||||||
this._move(oldIndex, newIndex, oldPath, newPath);
|
this._move(oldIndex, newIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _actionAdded(ev: CustomEvent): Promise<void> {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const { index, data } = ev.detail;
|
||||||
|
const actions = [
|
||||||
|
...this.actions.slice(0, index),
|
||||||
|
data,
|
||||||
|
...this.actions.slice(index),
|
||||||
|
];
|
||||||
|
// Add action locally to avoid UI jump
|
||||||
|
this.actions = actions;
|
||||||
|
await nextRender();
|
||||||
|
fireEvent(this, "value-changed", { value: this.actions });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _actionRemoved(ev: CustomEvent): Promise<void> {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const { index } = ev.detail;
|
||||||
|
const action = this.actions[index];
|
||||||
|
// Remove action locally to avoid UI jump
|
||||||
|
this.actions = this.actions.filter((a) => a !== action);
|
||||||
|
await nextRender();
|
||||||
|
// Ensure action is removed even after update
|
||||||
|
const actions = this.actions.filter((a) => a !== action);
|
||||||
|
fireEvent(this, "value-changed", { value: actions });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _actionChanged(ev: CustomEvent) {
|
private _actionChanged(ev: CustomEvent) {
|
||||||
|
@ -1,299 +1,40 @@
|
|||||||
import { consume } from "@lit-labs/context";
|
import { CSSResultGroup, LitElement, css, html } from "lit";
|
||||||
import type { ActionDetail } from "@material/mwc-list";
|
|
||||||
import {
|
|
||||||
mdiArrowDown,
|
|
||||||
mdiArrowUp,
|
|
||||||
mdiContentDuplicate,
|
|
||||||
mdiDelete,
|
|
||||||
mdiDotsVertical,
|
|
||||||
mdiDrag,
|
|
||||||
mdiPlus,
|
|
||||||
mdiRenameBox,
|
|
||||||
} from "@mdi/js";
|
|
||||||
import deepClone from "deep-clone-simple";
|
|
||||||
import {
|
|
||||||
CSSResultGroup,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
css,
|
|
||||||
html,
|
|
||||||
nothing,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { repeat } from "lit/directives/repeat";
|
|
||||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
|
||||||
import { listenMediaQuery } from "../../../../../common/dom/media_query";
|
|
||||||
import { capitalizeFirstLetter } from "../../../../../common/string/capitalize-first-letter";
|
|
||||||
import "../../../../../components/ha-button";
|
import "../../../../../components/ha-button";
|
||||||
import "../../../../../components/ha-button-menu";
|
import { Action, ChooseAction, Option } from "../../../../../data/script";
|
||||||
import "../../../../../components/ha-icon-button";
|
|
||||||
import "../../../../../components/ha-sortable";
|
|
||||||
import { Condition } from "../../../../../data/automation";
|
|
||||||
import { describeCondition } from "../../../../../data/automation_i18n";
|
|
||||||
import { fullEntitiesContext } from "../../../../../data/context";
|
|
||||||
import { EntityRegistryEntry } from "../../../../../data/entity_registry";
|
|
||||||
import {
|
|
||||||
Action,
|
|
||||||
ChooseAction,
|
|
||||||
ChooseActionChoice,
|
|
||||||
} from "../../../../../data/script";
|
|
||||||
import {
|
|
||||||
showConfirmationDialog,
|
|
||||||
showPromptDialog,
|
|
||||||
} from "../../../../../dialogs/generic/show-dialog-box";
|
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import { HomeAssistant, ItemPath } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import "../../option/ha-automation-option";
|
||||||
import { ActionElement } from "../ha-automation-action-row";
|
import { ActionElement } from "../ha-automation-action-row";
|
||||||
|
|
||||||
const preventDefault = (ev) => ev.preventDefault();
|
|
||||||
|
|
||||||
@customElement("ha-automation-action-choose")
|
@customElement("ha-automation-action-choose")
|
||||||
export class HaChooseAction extends LitElement implements ActionElement {
|
export class HaChooseAction extends LitElement implements ActionElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ attribute: false }) public path?: ItemPath;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public action!: ChooseAction;
|
@property({ attribute: false }) public action!: ChooseAction;
|
||||||
|
|
||||||
@state() private _showDefault = false;
|
@state() private _showDefault = false;
|
||||||
|
|
||||||
@state() private _expandedStates: boolean[] = [];
|
|
||||||
|
|
||||||
@state()
|
|
||||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
|
||||||
_entityReg!: EntityRegistryEntry[];
|
|
||||||
|
|
||||||
@state() private _showReorder: boolean = false;
|
|
||||||
|
|
||||||
private _expandLast = false;
|
|
||||||
|
|
||||||
private _unsubMql?: () => void;
|
|
||||||
|
|
||||||
public connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
|
|
||||||
this._showReorder = matches;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public disconnectedCallback() {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
this._unsubMql?.();
|
|
||||||
this._unsubMql = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get defaultConfig(): ChooseAction {
|
public static get defaultConfig(): ChooseAction {
|
||||||
return { choose: [{ conditions: [], sequence: [] }] };
|
return { choose: [{ conditions: [], sequence: [] }] };
|
||||||
}
|
}
|
||||||
|
|
||||||
private _expandedChanged(ev) {
|
|
||||||
this._expandedStates = this._expandedStates.concat();
|
|
||||||
this._expandedStates[ev.target!.index] = ev.detail.expanded;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getDescription(option) {
|
|
||||||
const conditions = ensureArray(option.conditions);
|
|
||||||
if (!conditions || conditions.length === 0) {
|
|
||||||
return this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.choose.no_conditions"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let str = "";
|
|
||||||
if (typeof conditions[0] === "string") {
|
|
||||||
str += conditions[0];
|
|
||||||
} else {
|
|
||||||
str += describeCondition(conditions[0], this.hass, this._entityReg);
|
|
||||||
}
|
|
||||||
if (conditions.length > 1) {
|
|
||||||
str += this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.choose.option_description_additional",
|
|
||||||
{ numberOfAdditionalConditions: conditions.length - 1 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const action = this.action;
|
const action = this.action;
|
||||||
|
|
||||||
|
const options = action.choose ? ensureArray(action.choose) : [];
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-sortable
|
<ha-automation-option
|
||||||
handle-selector=".handle"
|
.options=${options}
|
||||||
draggable-selector=".option"
|
.disabled=${this.disabled}
|
||||||
.disabled=${!this._showReorder || this.disabled}
|
@value-changed=${this._optionsChanged}
|
||||||
group="choose-options"
|
.hass=${this.hass}
|
||||||
.path=${[...(this.path ?? []), "choose"]}
|
></ha-automation-option>
|
||||||
invert-swap
|
|
||||||
>
|
|
||||||
<div class="options">
|
|
||||||
${repeat(
|
|
||||||
action.choose ? ensureArray(action.choose) : [],
|
|
||||||
(option) => option,
|
|
||||||
(option, idx) => html`
|
|
||||||
<div class="option">
|
|
||||||
<ha-card>
|
|
||||||
<ha-expansion-panel
|
|
||||||
.index=${idx}
|
|
||||||
leftChevron
|
|
||||||
@expanded-changed=${this._expandedChanged}
|
|
||||||
>
|
|
||||||
<h3 slot="header">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.choose.option",
|
|
||||||
{ number: idx + 1 }
|
|
||||||
)}:
|
|
||||||
${option.alias ||
|
|
||||||
(this._expandedStates[idx]
|
|
||||||
? ""
|
|
||||||
: this._getDescription(option))}
|
|
||||||
</h3>
|
|
||||||
${this._showReorder && !this.disabled
|
|
||||||
? html`
|
|
||||||
<div class="handle" slot="icons">
|
|
||||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
|
|
||||||
<ha-button-menu
|
|
||||||
slot="icons"
|
|
||||||
.idx=${idx}
|
|
||||||
@action=${this._handleAction}
|
|
||||||
@click=${preventDefault}
|
|
||||||
@closed=${stopPropagation}
|
|
||||||
fixed
|
|
||||||
>
|
|
||||||
<ha-icon-button
|
|
||||||
slot="trigger"
|
|
||||||
.label=${this.hass.localize("ui.common.menu")}
|
|
||||||
.path=${mdiDotsVertical}
|
|
||||||
></ha-icon-button>
|
|
||||||
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.rename"
|
|
||||||
)}
|
|
||||||
<ha-svg-icon
|
|
||||||
slot="graphic"
|
|
||||||
.path=${mdiRenameBox}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</mwc-list-item>
|
|
||||||
|
|
||||||
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.duplicate"
|
|
||||||
)}
|
|
||||||
<ha-svg-icon
|
|
||||||
slot="graphic"
|
|
||||||
.path=${mdiContentDuplicate}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</mwc-list-item>
|
|
||||||
|
|
||||||
<mwc-list-item
|
|
||||||
graphic="icon"
|
|
||||||
.disabled=${this.disabled || idx === 0}
|
|
||||||
>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.move_up"
|
|
||||||
)}
|
|
||||||
<ha-svg-icon
|
|
||||||
slot="graphic"
|
|
||||||
.path=${mdiArrowUp}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</mwc-list-item>
|
|
||||||
|
|
||||||
<mwc-list-item
|
|
||||||
graphic="icon"
|
|
||||||
.disabled=${this.disabled ||
|
|
||||||
idx === ensureArray(this.action.choose).length - 1}
|
|
||||||
>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.move_down"
|
|
||||||
)}
|
|
||||||
<ha-svg-icon
|
|
||||||
slot="graphic"
|
|
||||||
.path=${mdiArrowDown}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</mwc-list-item>
|
|
||||||
|
|
||||||
<mwc-list-item
|
|
||||||
class="warning"
|
|
||||||
graphic="icon"
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
|
|
||||||
)}
|
|
||||||
<ha-svg-icon
|
|
||||||
class="warning"
|
|
||||||
slot="graphic"
|
|
||||||
.path=${mdiDelete}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</mwc-list-item>
|
|
||||||
</ha-button-menu>
|
|
||||||
|
|
||||||
<div class="card-content">
|
|
||||||
<h4>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.choose.conditions"
|
|
||||||
)}:
|
|
||||||
</h4>
|
|
||||||
<ha-automation-condition
|
|
||||||
.path=${[
|
|
||||||
...(this.path ?? []),
|
|
||||||
"choose",
|
|
||||||
idx,
|
|
||||||
"conditions",
|
|
||||||
]}
|
|
||||||
.conditions=${ensureArray<string | Condition>(
|
|
||||||
option.conditions
|
|
||||||
)}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
.hass=${this.hass}
|
|
||||||
.idx=${idx}
|
|
||||||
@value-changed=${this._conditionChanged}
|
|
||||||
></ha-automation-condition>
|
|
||||||
<h4>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.choose.sequence"
|
|
||||||
)}:
|
|
||||||
</h4>
|
|
||||||
<ha-automation-action
|
|
||||||
.path=${[
|
|
||||||
...(this.path ?? []),
|
|
||||||
"choose",
|
|
||||||
idx,
|
|
||||||
"sequence",
|
|
||||||
]}
|
|
||||||
.actions=${ensureArray(option.sequence) || []}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
.hass=${this.hass}
|
|
||||||
.idx=${idx}
|
|
||||||
@value-changed=${this._actionChanged}
|
|
||||||
></ha-automation-action>
|
|
||||||
</div>
|
|
||||||
</ha-expansion-panel>
|
|
||||||
</ha-card>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
<div class="buttons">
|
|
||||||
<ha-button
|
|
||||||
outlined
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.choose.add_option"
|
|
||||||
)}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
@click=${this._addOption}
|
|
||||||
>
|
|
||||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
|
||||||
</ha-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ha-sortable>
|
|
||||||
|
|
||||||
${this._showDefault || action.default
|
${this._showDefault || action.default
|
||||||
? html`
|
? html`
|
||||||
@ -303,190 +44,39 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||||||
)}:
|
)}:
|
||||||
</h2>
|
</h2>
|
||||||
<ha-automation-action
|
<ha-automation-action
|
||||||
.path=${[...(this.path ?? []), "default"]}
|
|
||||||
.actions=${ensureArray(action.default) || []}
|
.actions=${ensureArray(action.default) || []}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._defaultChanged}
|
@value-changed=${this._defaultChanged}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
></ha-automation-action>
|
></ha-automation-action>
|
||||||
`
|
`
|
||||||
: html`<div class="link-button-row">
|
: html`
|
||||||
<button
|
<div class="link-button-row">
|
||||||
class="link"
|
<button
|
||||||
@click=${this._addDefault}
|
class="link"
|
||||||
.disabled=${this.disabled}
|
@click=${this._addDefault}
|
||||||
>
|
.disabled=${this.disabled}
|
||||||
${this.hass.localize(
|
>
|
||||||
"ui.panel.config.automation.editor.actions.type.choose.add_default"
|
${this.hass.localize(
|
||||||
)}
|
"ui.panel.config.automation.editor.actions.type.choose.add_default"
|
||||||
</button>
|
)}
|
||||||
</div>`}
|
</button>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleAction(ev: CustomEvent<ActionDetail>) {
|
|
||||||
switch (ev.detail.index) {
|
|
||||||
case 0:
|
|
||||||
await this._renameAction(ev);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
this._duplicateOption(ev);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
this._moveUp(ev);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
this._moveDown(ev);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
this._removeOption(ev);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _renameAction(ev: CustomEvent<ActionDetail>): Promise<void> {
|
|
||||||
const index = (ev.target as any).idx;
|
|
||||||
const choose = this.action.choose
|
|
||||||
? [...ensureArray(this.action.choose)]
|
|
||||||
: [];
|
|
||||||
const choice = choose[index];
|
|
||||||
const alias = await showPromptDialog(this, {
|
|
||||||
title: this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.choose.change_alias"
|
|
||||||
),
|
|
||||||
inputLabel: this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.choose.alias"
|
|
||||||
),
|
|
||||||
inputType: "string",
|
|
||||||
placeholder: capitalizeFirstLetter(this._getDescription(choice)),
|
|
||||||
defaultValue: choice.alias,
|
|
||||||
confirmText: this.hass.localize("ui.common.submit"),
|
|
||||||
});
|
|
||||||
if (alias !== null) {
|
|
||||||
if (alias === "") {
|
|
||||||
delete choose[index].alias;
|
|
||||||
} else {
|
|
||||||
choose[index].alias = alias;
|
|
||||||
}
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: { ...this.action, choose },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _duplicateOption(ev) {
|
|
||||||
const index = (ev.target as any).idx;
|
|
||||||
this._createOption(deepClone(ensureArray(this.action.choose)[index]));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected firstUpdated() {
|
|
||||||
ensureArray(this.action.choose).forEach(() =>
|
|
||||||
this._expandedStates.push(false)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
|
||||||
super.updated(changedProps);
|
|
||||||
|
|
||||||
if (this._expandLast) {
|
|
||||||
const nodes = this.shadowRoot!.querySelectorAll("ha-expansion-panel");
|
|
||||||
nodes[nodes.length - 1].expanded = true;
|
|
||||||
this._expandLast = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addDefault() {
|
private _addDefault() {
|
||||||
this._showDefault = true;
|
this._showDefault = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _conditionChanged(ev: CustomEvent) {
|
private _optionsChanged(ev: CustomEvent) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const value = ev.detail.value as Condition[];
|
const value = ev.detail.value as Option[];
|
||||||
const index = (ev.target as any).idx;
|
|
||||||
const choose = this.action.choose
|
|
||||||
? [...ensureArray(this.action.choose)]
|
|
||||||
: [];
|
|
||||||
choose[index].conditions = value;
|
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: { ...this.action, choose },
|
value: {
|
||||||
});
|
...this.action,
|
||||||
}
|
choose: value,
|
||||||
|
|
||||||
private _actionChanged(ev: CustomEvent) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
const value = ev.detail.value as Action[];
|
|
||||||
const index = (ev.target as any).idx;
|
|
||||||
const choose = this.action.choose
|
|
||||||
? [...ensureArray(this.action.choose)]
|
|
||||||
: [];
|
|
||||||
choose[index].sequence = value;
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: { ...this.action, choose },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addOption() {
|
|
||||||
this._createOption({ conditions: [], sequence: [] });
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createOption(opt: ChooseActionChoice) {
|
|
||||||
const choose = this.action.choose
|
|
||||||
? [...ensureArray(this.action.choose)]
|
|
||||||
: [];
|
|
||||||
choose.push(opt);
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: { ...this.action, choose },
|
|
||||||
});
|
|
||||||
this._expandLast = true;
|
|
||||||
this._expandedStates[choose.length - 1] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _moveUp(ev) {
|
|
||||||
const index = (ev.target as any).idx;
|
|
||||||
const newIndex = index - 1;
|
|
||||||
this._move(index, newIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _moveDown(ev) {
|
|
||||||
const index = (ev.target as any).idx;
|
|
||||||
const newIndex = index + 1;
|
|
||||||
this._move(index, newIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _move(index: number, newIndex: number) {
|
|
||||||
const options = ensureArray(this.action.choose)!.concat();
|
|
||||||
const item = options.splice(index, 1)[0];
|
|
||||||
options.splice(newIndex, 0, item);
|
|
||||||
|
|
||||||
const expanded = this._expandedStates.splice(index, 1)[0];
|
|
||||||
this._expandedStates.splice(newIndex, 0, expanded);
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: { ...this.action, choose: options },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _removeOption(ev: CustomEvent) {
|
|
||||||
const index = (ev.target as any).idx;
|
|
||||||
showConfirmationDialog(this, {
|
|
||||||
title: this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.choose.delete_confirm_title"
|
|
||||||
),
|
|
||||||
text: this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.delete_confirm_text"
|
|
||||||
),
|
|
||||||
dismissText: this.hass.localize("ui.common.cancel"),
|
|
||||||
confirmText: this.hass.localize("ui.common.delete"),
|
|
||||||
destructive: true,
|
|
||||||
confirm: () => {
|
|
||||||
const choose = this.action.choose
|
|
||||||
? [...ensureArray(this.action.choose)]
|
|
||||||
: [];
|
|
||||||
choose.splice(index, 1);
|
|
||||||
this._expandedStates.splice(index, 1);
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: { ...this.action, choose },
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -509,68 +99,9 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
css`
|
css`
|
||||||
.options {
|
|
||||||
padding: 16px;
|
|
||||||
margin: -16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
.sortable-ghost {
|
|
||||||
background: none;
|
|
||||||
border-radius: var(--ha-card-border-radius, 12px);
|
|
||||||
}
|
|
||||||
.sortable-drag {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
.add-card mwc-button {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
ha-expansion-panel {
|
|
||||||
--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;
|
|
||||||
font-weight: inherit;
|
|
||||||
}
|
|
||||||
ha-icon-button {
|
|
||||||
inset-inline-start: initial;
|
|
||||||
inset-inline-end: 0;
|
|
||||||
direction: var(--direction);
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
.link-button-row {
|
.link-button-row {
|
||||||
padding: 14px 14px 0 14px;
|
padding: 14px 14px 0 14px;
|
||||||
}
|
}
|
||||||
.card-content {
|
|
||||||
padding: 0 16px 16px 16px;
|
|
||||||
}
|
|
||||||
.handle {
|
|
||||||
padding: 12px;
|
|
||||||
cursor: move; /* fallback if grab cursor is unsupported */
|
|
||||||
cursor: grab;
|
|
||||||
}
|
|
||||||
.handle ha-svg-icon {
|
|
||||||
pointer-events: none;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
order: 1;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
|
|||||||
import "../../../../../components/ha-textfield";
|
import "../../../../../components/ha-textfield";
|
||||||
import { Action, IfAction } from "../../../../../data/script";
|
import { Action, IfAction } from "../../../../../data/script";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant, ItemPath } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import type { Condition } from "../../../../lovelace/common/validate-condition";
|
import type { Condition } from "../../../../lovelace/common/validate-condition";
|
||||||
import "../ha-automation-action";
|
import "../ha-automation-action";
|
||||||
import type { ActionElement } from "../ha-automation-action-row";
|
import type { ActionElement } from "../ha-automation-action-row";
|
||||||
@ -15,8 +15,6 @@ export class HaIfAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ attribute: false }) public path?: ItemPath;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public action!: IfAction;
|
@property({ attribute: false }) public action!: IfAction;
|
||||||
|
|
||||||
@state() private _showElse = false;
|
@state() private _showElse = false;
|
||||||
@ -38,7 +36,6 @@ export class HaIfAction extends LitElement implements ActionElement {
|
|||||||
)}*:
|
)}*:
|
||||||
</h3>
|
</h3>
|
||||||
<ha-automation-condition
|
<ha-automation-condition
|
||||||
.path=${[...(this.path ?? []), "if"]}
|
|
||||||
.conditions=${action.if}
|
.conditions=${action.if}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._ifChanged}
|
@value-changed=${this._ifChanged}
|
||||||
@ -51,7 +48,6 @@ export class HaIfAction extends LitElement implements ActionElement {
|
|||||||
)}*:
|
)}*:
|
||||||
</h3>
|
</h3>
|
||||||
<ha-automation-action
|
<ha-automation-action
|
||||||
.path=${[...(this.path ?? []), "then"]}
|
|
||||||
.actions=${action.then}
|
.actions=${action.then}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._thenChanged}
|
@value-changed=${this._thenChanged}
|
||||||
@ -65,7 +61,6 @@ export class HaIfAction extends LitElement implements ActionElement {
|
|||||||
)}:
|
)}:
|
||||||
</h3>
|
</h3>
|
||||||
<ha-automation-action
|
<ha-automation-action
|
||||||
.path=${[...(this.path ?? []), "else"]}
|
|
||||||
.actions=${action.else || []}
|
.actions=${action.else || []}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._elseChanged}
|
@value-changed=${this._elseChanged}
|
||||||
|
@ -4,7 +4,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
|
|||||||
import "../../../../../components/ha-textfield";
|
import "../../../../../components/ha-textfield";
|
||||||
import { Action, ParallelAction } from "../../../../../data/script";
|
import { Action, ParallelAction } from "../../../../../data/script";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant, ItemPath } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../ha-automation-action";
|
import "../ha-automation-action";
|
||||||
import type { ActionElement } from "../ha-automation-action-row";
|
import type { ActionElement } from "../ha-automation-action-row";
|
||||||
|
|
||||||
@ -14,8 +14,6 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ attribute: false }) public path?: ItemPath;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public action!: ParallelAction;
|
@property({ attribute: false }) public action!: ParallelAction;
|
||||||
|
|
||||||
public static get defaultConfig(): ParallelAction {
|
public static get defaultConfig(): ParallelAction {
|
||||||
@ -29,7 +27,6 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-automation-action
|
<ha-automation-action
|
||||||
.path=${[...(this.path ?? []), "parallel"]}
|
|
||||||
.actions=${action.parallel}
|
.actions=${action.parallel}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._actionsChanged}
|
@value-changed=${this._actionsChanged}
|
||||||
|
@ -5,7 +5,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
|
|||||||
import "../../../../../components/ha-textfield";
|
import "../../../../../components/ha-textfield";
|
||||||
import { RepeatAction } from "../../../../../data/script";
|
import { RepeatAction } from "../../../../../data/script";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant, ItemPath } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../ha-automation-action";
|
import "../ha-automation-action";
|
||||||
import type { ActionElement } from "../ha-automation-action-row";
|
import type { ActionElement } from "../ha-automation-action-row";
|
||||||
|
|
||||||
@ -29,19 +29,12 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public action!: RepeatAction;
|
@property({ attribute: false }) public action!: RepeatAction;
|
||||||
|
|
||||||
@property({ type: Array }) public path?: ItemPath;
|
|
||||||
|
|
||||||
public static get defaultConfig(): RepeatAction {
|
public static get defaultConfig(): RepeatAction {
|
||||||
return { repeat: { count: 2, sequence: [] } };
|
return { repeat: { count: 2, sequence: [] } };
|
||||||
}
|
}
|
||||||
|
|
||||||
private _schema = memoizeOne(
|
private _schema = memoizeOne(
|
||||||
(
|
(localize: LocalizeFunc, type: string, template: boolean) =>
|
||||||
localize: LocalizeFunc,
|
|
||||||
type: string,
|
|
||||||
template: boolean,
|
|
||||||
path?: ItemPath
|
|
||||||
) =>
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: "type",
|
name: "type",
|
||||||
@ -73,9 +66,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||||||
{
|
{
|
||||||
name: type,
|
name: type,
|
||||||
selector: {
|
selector: {
|
||||||
condition: {
|
condition: {},
|
||||||
path: [...(path ?? []), "repeat", type],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
] as const satisfies readonly HaFormSchema[])
|
] as const satisfies readonly HaFormSchema[])
|
||||||
@ -92,9 +83,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||||||
{
|
{
|
||||||
name: "sequence",
|
name: "sequence",
|
||||||
selector: {
|
selector: {
|
||||||
action: {
|
action: {},
|
||||||
path: [...(path ?? []), "repeat", "sequence"],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
] as const satisfies readonly HaFormSchema[]
|
] as const satisfies readonly HaFormSchema[]
|
||||||
@ -108,8 +97,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||||||
type ?? "count",
|
type ?? "count",
|
||||||
"count" in action && typeof action.count === "string"
|
"count" in action && typeof action.count === "string"
|
||||||
? isTemplate(action.count)
|
? isTemplate(action.count)
|
||||||
: false,
|
: false
|
||||||
this.path
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = { ...action, type };
|
const data = { ...action, type };
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { CSSResultGroup, html, LitElement } from "lit";
|
import { CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import "../../../../../components/ha-textfield";
|
import "../../../../../components/ha-textfield";
|
||||||
import { Action, SequenceAction } from "../../../../../data/script";
|
import { Action, SequenceAction } from "../../../../../data/script";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant, ItemPath } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../ha-automation-action";
|
import "../ha-automation-action";
|
||||||
import type { ActionElement } from "../ha-automation-action-row";
|
import type { ActionElement } from "../ha-automation-action-row";
|
||||||
|
|
||||||
@ -15,8 +14,6 @@ export class HaSequenceAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ attribute: false }) public path?: ItemPath;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public action!: SequenceAction;
|
@property({ attribute: false }) public action!: SequenceAction;
|
||||||
|
|
||||||
public static get defaultConfig(): SequenceAction {
|
public static get defaultConfig(): SequenceAction {
|
||||||
@ -25,17 +22,11 @@ export class HaSequenceAction extends LitElement implements ActionElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getMemoizedPath = memoizeOne((path: ItemPath | undefined) => [
|
|
||||||
...(path ?? []),
|
|
||||||
"sequence",
|
|
||||||
]);
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const { action } = this;
|
const { action } = this;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-automation-action
|
<ha-automation-action
|
||||||
.path=${this._getMemoizedPath(this.path)}
|
|
||||||
.actions=${action.sequence}
|
.actions=${action.sequence}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._actionsChanged}
|
@value-changed=${this._actionsChanged}
|
||||||
|
@ -8,7 +8,7 @@ import "../../../../../components/ha-duration-input";
|
|||||||
import "../../../../../components/ha-formfield";
|
import "../../../../../components/ha-formfield";
|
||||||
import "../../../../../components/ha-textfield";
|
import "../../../../../components/ha-textfield";
|
||||||
import { WaitForTriggerAction } from "../../../../../data/script";
|
import { WaitForTriggerAction } from "../../../../../data/script";
|
||||||
import { HomeAssistant, ItemPath } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import "../../trigger/ha-automation-trigger";
|
import "../../trigger/ha-automation-trigger";
|
||||||
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||||
|
|
||||||
@ -23,8 +23,6 @@ export class HaWaitForTriggerAction
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ attribute: false }) public path?: ItemPath;
|
|
||||||
|
|
||||||
public static get defaultConfig(): WaitForTriggerAction {
|
public static get defaultConfig(): WaitForTriggerAction {
|
||||||
return { wait_for_trigger: [] };
|
return { wait_for_trigger: [] };
|
||||||
}
|
}
|
||||||
@ -55,7 +53,6 @@ export class HaWaitForTriggerAction
|
|||||||
></ha-switch>
|
></ha-switch>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
<ha-automation-trigger
|
<ha-automation-trigger
|
||||||
.path=${[...(this.path ?? []), "wait_for_trigger"]}
|
|
||||||
.triggers=${ensureArray(this.action.wait_for_trigger)}
|
.triggers=${ensureArray(this.action.wait_for_trigger)}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
@ -7,7 +7,7 @@ import "../../../../components/ha-yaml-editor";
|
|||||||
import type { Condition } from "../../../../data/automation";
|
import type { Condition } from "../../../../data/automation";
|
||||||
import { expandConditionWithShorthand } from "../../../../data/automation";
|
import { expandConditionWithShorthand } from "../../../../data/automation";
|
||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant, ItemPath } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import "./types/ha-automation-condition-and";
|
import "./types/ha-automation-condition-and";
|
||||||
import "./types/ha-automation-condition-device";
|
import "./types/ha-automation-condition-device";
|
||||||
import "./types/ha-automation-condition-not";
|
import "./types/ha-automation-condition-not";
|
||||||
@ -30,8 +30,6 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public yamlMode = false;
|
@property({ type: Boolean }) public yamlMode = false;
|
||||||
|
|
||||||
@property({ type: Array }) public path?: ItemPath;
|
|
||||||
|
|
||||||
private _processedCondition = memoizeOne((condition) =>
|
private _processedCondition = memoizeOne((condition) =>
|
||||||
expandConditionWithShorthand(condition)
|
expandConditionWithShorthand(condition)
|
||||||
);
|
);
|
||||||
@ -68,7 +66,6 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||||||
hass: this.hass,
|
hass: this.hass,
|
||||||
condition: condition,
|
condition: condition,
|
||||||
disabled: this.disabled,
|
disabled: this.disabled,
|
||||||
path: this.path,
|
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,7 +41,7 @@ import {
|
|||||||
showPromptDialog,
|
showPromptDialog,
|
||||||
} from "../../../../dialogs/generic/show-dialog-box";
|
} from "../../../../dialogs/generic/show-dialog-box";
|
||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../../resources/styles";
|
||||||
import { HomeAssistant, ItemPath } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import "./ha-automation-condition-editor";
|
import "./ha-automation-condition-editor";
|
||||||
|
|
||||||
export interface ConditionElement extends LitElement {
|
export interface ConditionElement extends LitElement {
|
||||||
@ -83,8 +83,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Array }) public path?: ItemPath;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public first?: boolean;
|
@property({ type: Boolean }) public first?: boolean;
|
||||||
|
|
||||||
@property({ type: Boolean }) public last?: boolean;
|
@property({ type: Boolean }) public last?: boolean;
|
||||||
@ -302,7 +300,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.condition=${this.condition}
|
.condition=${this.condition}
|
||||||
.path=${this.path}
|
|
||||||
></ha-automation-condition-editor>
|
></ha-automation-condition-editor>
|
||||||
</div>
|
</div>
|
||||||
</ha-expansion-panel>
|
</ha-expansion-panel>
|
||||||
|
@ -13,7 +13,7 @@ import { repeat } from "lit/directives/repeat";
|
|||||||
import { storage } from "../../../../common/decorators/storage";
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
||||||
import { nestedArrayMove } from "../../../../common/util/array-move";
|
import { nextRender } from "../../../../common/util/render-status";
|
||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
import "../../../../components/ha-button-menu";
|
import "../../../../components/ha-button-menu";
|
||||||
import "../../../../components/ha-sortable";
|
import "../../../../components/ha-sortable";
|
||||||
@ -22,7 +22,7 @@ import type {
|
|||||||
AutomationClipboard,
|
AutomationClipboard,
|
||||||
Condition,
|
Condition,
|
||||||
} from "../../../../data/automation";
|
} from "../../../../data/automation";
|
||||||
import type { HomeAssistant, ItemPath } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import {
|
import {
|
||||||
PASTE_VALUE,
|
PASTE_VALUE,
|
||||||
showAddAutomationElementDialog,
|
showAddAutomationElementDialog,
|
||||||
@ -38,8 +38,6 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Array }) public path?: ItemPath;
|
|
||||||
|
|
||||||
@state() private _showReorder: boolean = false;
|
@state() private _showReorder: boolean = false;
|
||||||
|
|
||||||
@storage({
|
@storage({
|
||||||
@ -115,10 +113,6 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private get nested() {
|
|
||||||
return this.path !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!Array.isArray(this.conditions)) {
|
if (!Array.isArray(this.conditions)) {
|
||||||
return nothing;
|
return nothing;
|
||||||
@ -128,10 +122,11 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
handle-selector=".handle"
|
handle-selector=".handle"
|
||||||
draggable-selector="ha-automation-condition-row"
|
draggable-selector="ha-automation-condition-row"
|
||||||
.disabled=${!this._showReorder || this.disabled}
|
.disabled=${!this._showReorder || this.disabled}
|
||||||
@item-moved=${this._conditionMoved}
|
|
||||||
group="conditions"
|
group="conditions"
|
||||||
.path=${this.path}
|
|
||||||
invert-swap
|
invert-swap
|
||||||
|
@item-moved=${this._conditionMoved}
|
||||||
|
@item-added=${this._conditionAdded}
|
||||||
|
@item-removed=${this._conditionRemoved}
|
||||||
>
|
>
|
||||||
<div class="conditions">
|
<div class="conditions">
|
||||||
${repeat(
|
${repeat(
|
||||||
@ -139,7 +134,7 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
(condition) => this._getKey(condition),
|
(condition) => this._getKey(condition),
|
||||||
(cond, idx) => html`
|
(cond, idx) => html`
|
||||||
<ha-automation-condition-row
|
<ha-automation-condition-row
|
||||||
.path=${[...(this.path ?? []), idx]}
|
.sortableData=${cond}
|
||||||
.index=${idx}
|
.index=${idx}
|
||||||
.first=${idx === 0}
|
.first=${idx === 0}
|
||||||
.last=${idx === this.conditions.length - 1}
|
.last=${idx === this.conditions.length - 1}
|
||||||
@ -248,28 +243,44 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
this._move(index, newIndex);
|
this._move(index, newIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _move(
|
private _move(oldIndex: number, newIndex: number) {
|
||||||
oldIndex: number,
|
const conditions = this.conditions.concat();
|
||||||
newIndex: number,
|
const item = conditions.splice(oldIndex, 1)[0];
|
||||||
oldPath?: ItemPath,
|
conditions.splice(newIndex, 0, item);
|
||||||
newPath?: ItemPath
|
this.conditions = conditions;
|
||||||
) {
|
|
||||||
const conditions = nestedArrayMove(
|
|
||||||
this.conditions,
|
|
||||||
oldIndex,
|
|
||||||
newIndex,
|
|
||||||
oldPath,
|
|
||||||
newPath
|
|
||||||
);
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", { value: conditions });
|
fireEvent(this, "value-changed", { value: conditions });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _conditionMoved(ev: CustomEvent): void {
|
private _conditionMoved(ev: CustomEvent): void {
|
||||||
if (this.nested) return;
|
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
const { oldIndex, newIndex } = ev.detail;
|
||||||
this._move(oldIndex, newIndex, oldPath, newPath);
|
this._move(oldIndex, newIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _conditionAdded(ev: CustomEvent): Promise<void> {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const { index, data } = ev.detail;
|
||||||
|
const conditions = [
|
||||||
|
...this.conditions.slice(0, index),
|
||||||
|
data,
|
||||||
|
...this.conditions.slice(index),
|
||||||
|
];
|
||||||
|
// Add condition locally to avoid UI jump
|
||||||
|
this.conditions = conditions;
|
||||||
|
await nextRender();
|
||||||
|
fireEvent(this, "value-changed", { value: this.conditions });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _conditionRemoved(ev: CustomEvent): Promise<void> {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const { index } = ev.detail;
|
||||||
|
const condition = this.conditions[index];
|
||||||
|
// Remove condition locally to avoid UI jump
|
||||||
|
this.conditions = this.conditions.filter((c) => c !== condition);
|
||||||
|
await nextRender();
|
||||||
|
// Ensure condition is removed even after update
|
||||||
|
const conditions = this.conditions.filter((c) => c !== condition);
|
||||||
|
fireEvent(this, "value-changed", { value: conditions });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _conditionChanged(ev: CustomEvent) {
|
private _conditionChanged(ev: CustomEvent) {
|
||||||
|
@ -2,7 +2,7 @@ import { html, LitElement } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import type { LogicalCondition } from "../../../../../data/automation";
|
import type { LogicalCondition } from "../../../../../data/automation";
|
||||||
import type { HomeAssistant, ItemPath } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../ha-automation-condition";
|
import "../ha-automation-condition";
|
||||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||||
|
|
||||||
@ -17,12 +17,9 @@ export abstract class HaLogicalCondition
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ attribute: false }) public path?: ItemPath;
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<ha-automation-condition
|
<ha-automation-condition
|
||||||
.path=${[...(this.path ?? []), "conditions"]}
|
|
||||||
.conditions=${this.condition.conditions || []}
|
.conditions=${this.condition.conditions || []}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
@ -12,7 +12,6 @@ import {
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { ensureArray } from "../../../common/array/ensure-array";
|
import { ensureArray } from "../../../common/array/ensure-array";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { nestedArrayMove } from "../../../common/util/array-move";
|
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-markdown";
|
import "../../../components/ha-markdown";
|
||||||
@ -132,7 +131,6 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
.triggers=${this.config.triggers || []}
|
.triggers=${this.config.triggers || []}
|
||||||
.path=${["triggers"]}
|
.path=${["triggers"]}
|
||||||
@value-changed=${this._triggerChanged}
|
@value-changed=${this._triggerChanged}
|
||||||
@item-moved=${this._itemMoved}
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
></ha-automation-trigger>
|
></ha-automation-trigger>
|
||||||
@ -174,7 +172,6 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
.conditions=${this.config.conditions || []}
|
.conditions=${this.config.conditions || []}
|
||||||
.path=${["conditions"]}
|
.path=${["conditions"]}
|
||||||
@value-changed=${this._conditionChanged}
|
@value-changed=${this._conditionChanged}
|
||||||
@item-moved=${this._itemMoved}
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
></ha-automation-condition>
|
></ha-automation-condition>
|
||||||
@ -214,7 +211,6 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
.actions=${this.config.actions || []}
|
.actions=${this.config.actions || []}
|
||||||
.path=${["actions"]}
|
.path=${["actions"]}
|
||||||
@value-changed=${this._actionChanged}
|
@value-changed=${this._actionChanged}
|
||||||
@item-moved=${this._itemMoved}
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@ -246,21 +242,6 @@ 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> {
|
private async _enable(): Promise<void> {
|
||||||
if (!this.hass || !this.stateObj) {
|
if (!this.hass || !this.stateObj) {
|
||||||
return;
|
return;
|
||||||
|
335
src/panels/config/automation/option/ha-automation-option-row.ts
Normal file
335
src/panels/config/automation/option/ha-automation-option-row.ts
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
import { consume } from "@lit-labs/context";
|
||||||
|
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
import {
|
||||||
|
mdiArrowDown,
|
||||||
|
mdiArrowUp,
|
||||||
|
mdiContentDuplicate,
|
||||||
|
mdiDelete,
|
||||||
|
mdiDotsVertical,
|
||||||
|
mdiRenameBox,
|
||||||
|
} from "@mdi/js";
|
||||||
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { ensureArray } from "../../../../common/array/ensure-array";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { preventDefault } from "../../../../common/dom/prevent_default";
|
||||||
|
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||||
|
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||||
|
import "../../../../components/ha-button-menu";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import "../../../../components/ha-expansion-panel";
|
||||||
|
import "../../../../components/ha-icon-button";
|
||||||
|
import { Condition } from "../../../../data/automation";
|
||||||
|
import { describeCondition } from "../../../../data/automation_i18n";
|
||||||
|
import { fullEntitiesContext } from "../../../../data/context";
|
||||||
|
import { EntityRegistryEntry } from "../../../../data/entity_registry";
|
||||||
|
import { Action, Option } from "../../../../data/script";
|
||||||
|
import {
|
||||||
|
showConfirmationDialog,
|
||||||
|
showPromptDialog,
|
||||||
|
} from "../../../../dialogs/generic/show-dialog-box";
|
||||||
|
import { haStyle } from "../../../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
|
||||||
|
@customElement("ha-automation-option-row")
|
||||||
|
export default class HaAutomationOptionRow extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public option!: Option;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Number }) public index!: number;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public first = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public last = false;
|
||||||
|
|
||||||
|
@state() private _expanded = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||||
|
_entityReg!: EntityRegistryEntry[];
|
||||||
|
|
||||||
|
private _expandedChanged(ev) {
|
||||||
|
if (ev.currentTarget.id !== "option") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._expanded = ev.detail.expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getDescription() {
|
||||||
|
const conditions = ensureArray<Condition | string>(this.option.conditions);
|
||||||
|
if (!conditions || conditions.length === 0) {
|
||||||
|
return this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.no_conditions"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let str = "";
|
||||||
|
if (typeof conditions[0] === "string") {
|
||||||
|
str += conditions[0];
|
||||||
|
} else {
|
||||||
|
str += describeCondition(conditions[0], this.hass, this._entityReg);
|
||||||
|
}
|
||||||
|
if (conditions.length > 1) {
|
||||||
|
str += this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.option_description_additional",
|
||||||
|
{ numberOfAdditionalConditions: conditions.length - 1 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this.option) return nothing;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-card outlined>
|
||||||
|
<ha-expansion-panel
|
||||||
|
leftChevron
|
||||||
|
@expanded-changed=${this._expandedChanged}
|
||||||
|
id="option"
|
||||||
|
>
|
||||||
|
<h3 slot="header">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.option",
|
||||||
|
{ number: this.index + 1 }
|
||||||
|
)}:
|
||||||
|
${this.option.alias ||
|
||||||
|
(this._expanded ? "" : this._getDescription())}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<slot name="icons" slot="icons"></slot>
|
||||||
|
|
||||||
|
<ha-button-menu
|
||||||
|
slot="icons"
|
||||||
|
@action=${this._handleAction}
|
||||||
|
@click=${preventDefault}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
fixed
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
slot="trigger"
|
||||||
|
.label=${this.hass.localize("ui.common.menu")}
|
||||||
|
.path=${mdiDotsVertical}
|
||||||
|
></ha-icon-button>
|
||||||
|
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.rename"
|
||||||
|
)}
|
||||||
|
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
|
||||||
|
</mwc-list-item>
|
||||||
|
|
||||||
|
<mwc-list-item graphic="icon" .disabled=${this.disabled}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.duplicate"
|
||||||
|
)}
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="graphic"
|
||||||
|
.path=${mdiContentDuplicate}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</mwc-list-item>
|
||||||
|
|
||||||
|
<mwc-list-item
|
||||||
|
graphic="icon"
|
||||||
|
.disabled=${this.disabled || this.first}
|
||||||
|
>
|
||||||
|
${this.hass.localize("ui.panel.config.automation.editor.move_up")}
|
||||||
|
<ha-svg-icon slot="graphic" .path=${mdiArrowUp}></ha-svg-icon>
|
||||||
|
</mwc-list-item>
|
||||||
|
|
||||||
|
<mwc-list-item
|
||||||
|
graphic="icon"
|
||||||
|
.disabled=${this.disabled || this.last}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.move_down"
|
||||||
|
)}
|
||||||
|
<ha-svg-icon slot="graphic" .path=${mdiArrowDown}></ha-svg-icon>
|
||||||
|
</mwc-list-item>
|
||||||
|
|
||||||
|
<mwc-list-item
|
||||||
|
class="warning"
|
||||||
|
graphic="icon"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
|
||||||
|
)}
|
||||||
|
<ha-svg-icon
|
||||||
|
class="warning"
|
||||||
|
slot="graphic"
|
||||||
|
.path=${mdiDelete}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</mwc-list-item>
|
||||||
|
</ha-button-menu>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<h4>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.conditions"
|
||||||
|
)}:
|
||||||
|
</h4>
|
||||||
|
<ha-automation-condition
|
||||||
|
.conditions=${ensureArray<string | Condition>(
|
||||||
|
this.option.conditions
|
||||||
|
)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.hass=${this.hass}
|
||||||
|
@value-changed=${this._conditionChanged}
|
||||||
|
></ha-automation-condition>
|
||||||
|
<h4>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.sequence"
|
||||||
|
)}:
|
||||||
|
</h4>
|
||||||
|
<ha-automation-action
|
||||||
|
.actions=${ensureArray(this.option.sequence) || []}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.hass=${this.hass}
|
||||||
|
@value-changed=${this._actionChanged}
|
||||||
|
></ha-automation-action>
|
||||||
|
</div>
|
||||||
|
</ha-expansion-panel>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||||
|
switch (ev.detail.index) {
|
||||||
|
case 0:
|
||||||
|
await this._renameOption();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
fireEvent(this, "duplicate");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
fireEvent(this, "move-up");
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
fireEvent(this, "move-down");
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this._removeOption();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _removeOption() {
|
||||||
|
showConfirmationDialog(this, {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.delete_confirm_title"
|
||||||
|
),
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.delete_confirm_text"
|
||||||
|
),
|
||||||
|
dismissText: this.hass.localize("ui.common.cancel"),
|
||||||
|
confirmText: this.hass.localize("ui.common.delete"),
|
||||||
|
destructive: true,
|
||||||
|
confirm: () =>
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: null,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _renameOption(): Promise<void> {
|
||||||
|
const alias = await showPromptDialog(this, {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.change_alias"
|
||||||
|
),
|
||||||
|
inputLabel: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.alias"
|
||||||
|
),
|
||||||
|
inputType: "string",
|
||||||
|
placeholder: capitalizeFirstLetter(this._getDescription()),
|
||||||
|
defaultValue: this.option.alias,
|
||||||
|
confirmText: this.hass.localize("ui.common.submit"),
|
||||||
|
});
|
||||||
|
if (alias !== null) {
|
||||||
|
const value = { ...this.option };
|
||||||
|
if (alias === "") {
|
||||||
|
delete value.alias;
|
||||||
|
} else {
|
||||||
|
value.alias = alias;
|
||||||
|
}
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _conditionChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const conditions = ev.detail.value as Condition[];
|
||||||
|
const value = { ...this.option, conditions: conditions };
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _actionChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const actions = ev.detail.value as Action[];
|
||||||
|
const value = { ...this.option, sequence: actions };
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public expand() {
|
||||||
|
this.updateComplete.then(() => {
|
||||||
|
this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
ha-button-menu,
|
||||||
|
ha-icon-button {
|
||||||
|
--mdc-theme-text-primary-on-background: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
ha-expansion-panel {
|
||||||
|
--expansion-panel-summary-padding: 0 0 0 8px;
|
||||||
|
--expansion-panel-content-padding: 0;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
.card-content {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
li[role="separator"] {
|
||||||
|
border-bottom-color: var(--divider-color);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-option-row": HaAutomationOptionRow;
|
||||||
|
}
|
||||||
|
}
|
290
src/panels/config/automation/option/ha-automation-option.ts
Normal file
290
src/panels/config/automation/option/ha-automation-option.ts
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
import { mdiDrag, mdiPlus } from "@mdi/js";
|
||||||
|
import deepClone from "deep-clone-simple";
|
||||||
|
import {
|
||||||
|
CSSResultGroup,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
|
} from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
||||||
|
import { nextRender } from "../../../../common/util/render-status";
|
||||||
|
import "../../../../components/ha-button";
|
||||||
|
import "../../../../components/ha-sortable";
|
||||||
|
import "../../../../components/ha-svg-icon";
|
||||||
|
import type { AutomationClipboard } from "../../../../data/automation";
|
||||||
|
import { Option } from "../../../../data/script";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import "./ha-automation-option-row";
|
||||||
|
import type HaAutomationOptionRow from "./ha-automation-option-row";
|
||||||
|
|
||||||
|
@customElement("ha-automation-option")
|
||||||
|
export default class HaAutomationOption extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public options!: Option[];
|
||||||
|
|
||||||
|
@state() private _showReorder: boolean = false;
|
||||||
|
|
||||||
|
@storage({
|
||||||
|
key: "automationClipboard",
|
||||||
|
state: true,
|
||||||
|
subscribe: true,
|
||||||
|
storage: "sessionStorage",
|
||||||
|
})
|
||||||
|
public _clipboard?: AutomationClipboard;
|
||||||
|
|
||||||
|
private _focusLastOptionOnChange = false;
|
||||||
|
|
||||||
|
private _optionsKeys = new WeakMap<Option, string>();
|
||||||
|
|
||||||
|
private _unsubMql?: () => void;
|
||||||
|
|
||||||
|
public connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
|
||||||
|
this._showReorder = matches;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this._unsubMql?.();
|
||||||
|
this._unsubMql = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<ha-sortable
|
||||||
|
handle-selector=".handle"
|
||||||
|
draggable-selector="ha-automation-option-row"
|
||||||
|
.disabled=${!this._showReorder || this.disabled}
|
||||||
|
group="options"
|
||||||
|
invert-swap
|
||||||
|
@item-moved=${this._optionMoved}
|
||||||
|
@item-added=${this._optionAdded}
|
||||||
|
@item-removed=${this._optionRemoved}
|
||||||
|
>
|
||||||
|
<div class="options">
|
||||||
|
${repeat(
|
||||||
|
this.options,
|
||||||
|
(option) => this._getKey(option),
|
||||||
|
(option, idx) => html`
|
||||||
|
<ha-automation-option-row
|
||||||
|
.sortableData=${option}
|
||||||
|
.index=${idx}
|
||||||
|
.first=${idx === 0}
|
||||||
|
.last=${idx === this.options.length - 1}
|
||||||
|
.option=${option}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@duplicate=${this._duplicateOption}
|
||||||
|
@move-down=${this._moveDown}
|
||||||
|
@move-up=${this._moveUp}
|
||||||
|
@value-changed=${this._optionChanged}
|
||||||
|
.hass=${this.hass}
|
||||||
|
>
|
||||||
|
${this._showReorder && !this.disabled
|
||||||
|
? html`
|
||||||
|
<div class="handle" slot="icons">
|
||||||
|
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</ha-automation-option-row>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
<div class="buttons">
|
||||||
|
<ha-button
|
||||||
|
outlined
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.add_option"
|
||||||
|
)}
|
||||||
|
@click=${this._addOption}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||||
|
</ha-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ha-sortable>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
super.updated(changedProps);
|
||||||
|
|
||||||
|
if (changedProps.has("options") && this._focusLastOptionOnChange) {
|
||||||
|
this._focusLastOptionOnChange = false;
|
||||||
|
|
||||||
|
const row = this.shadowRoot!.querySelector<HaAutomationOptionRow>(
|
||||||
|
"ha-automation-option-row:last-of-type"
|
||||||
|
)!;
|
||||||
|
row.updateComplete.then(() => {
|
||||||
|
row.expand();
|
||||||
|
row.scrollIntoView();
|
||||||
|
row.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public expandAll() {
|
||||||
|
const rows = this.shadowRoot!.querySelectorAll<HaAutomationOptionRow>(
|
||||||
|
"ha-automation-option-row"
|
||||||
|
)!;
|
||||||
|
rows.forEach((row) => {
|
||||||
|
row.expand();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addOption = () => {
|
||||||
|
const options = this.options.concat({ conditions: [], sequence: [] });
|
||||||
|
this._focusLastOptionOnChange = true;
|
||||||
|
fireEvent(this, "value-changed", { value: options });
|
||||||
|
};
|
||||||
|
|
||||||
|
private _getKey(option: Option) {
|
||||||
|
if (!this._optionsKeys.has(option)) {
|
||||||
|
this._optionsKeys.set(option, Math.random().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._optionsKeys.get(option)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _moveUp(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const index = (ev.target as any).index;
|
||||||
|
const newIndex = index - 1;
|
||||||
|
this._move(index, newIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _moveDown(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const index = (ev.target as any).index;
|
||||||
|
const newIndex = index + 1;
|
||||||
|
this._move(index, newIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _move(oldIndex: number, newIndex: number) {
|
||||||
|
const options = this.options.concat();
|
||||||
|
const item = options.splice(oldIndex, 1)[0];
|
||||||
|
options.splice(newIndex, 0, item);
|
||||||
|
this.options = options;
|
||||||
|
fireEvent(this, "value-changed", { value: options });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _optionMoved(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const { oldIndex, newIndex } = ev.detail;
|
||||||
|
this._move(oldIndex, newIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _optionAdded(ev: CustomEvent): Promise<void> {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const { index, data } = ev.detail;
|
||||||
|
const options = [
|
||||||
|
...this.options.slice(0, index),
|
||||||
|
data,
|
||||||
|
...this.options.slice(index),
|
||||||
|
];
|
||||||
|
// Add option locally to avoid UI jump
|
||||||
|
this.options = options;
|
||||||
|
await nextRender();
|
||||||
|
fireEvent(this, "value-changed", { value: this.options });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _optionRemoved(ev: CustomEvent): Promise<void> {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const { index } = ev.detail;
|
||||||
|
const option = this.options[index];
|
||||||
|
// Remove option locally to avoid UI jump
|
||||||
|
this.options = this.options.filter((o) => o !== option);
|
||||||
|
await nextRender();
|
||||||
|
// Ensure option is removed even after update
|
||||||
|
const options = this.options.filter((o) => o !== option);
|
||||||
|
fireEvent(this, "value-changed", { value: options });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _optionChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const options = [...this.options];
|
||||||
|
const newValue = ev.detail.value;
|
||||||
|
const index = (ev.target as any).index;
|
||||||
|
|
||||||
|
if (newValue === null) {
|
||||||
|
options.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
// Store key on new value.
|
||||||
|
const key = this._getKey(options[index]);
|
||||||
|
this._optionsKeys.set(newValue, key);
|
||||||
|
|
||||||
|
options[index] = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", { value: options });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _duplicateOption(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const index = (ev.target as any).index;
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: this.options.concat(deepClone(this.options[index])),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
.options {
|
||||||
|
padding: 16px;
|
||||||
|
margin: -16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.sortable-ghost {
|
||||||
|
background: none;
|
||||||
|
border-radius: var(--ha-card-border-radius, 12px);
|
||||||
|
}
|
||||||
|
.sortable-drag {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
ha-automation-option-row {
|
||||||
|
display: block;
|
||||||
|
scroll-margin-top: 48px;
|
||||||
|
}
|
||||||
|
ha-svg-icon {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
.handle {
|
||||||
|
padding: 12px;
|
||||||
|
cursor: move; /* fallback if grab cursor is unsupported */
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
.handle ha-svg-icon {
|
||||||
|
pointer-events: none;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-option": HaAutomationOption;
|
||||||
|
}
|
||||||
|
}
|
@ -58,7 +58,7 @@ import {
|
|||||||
showPromptDialog,
|
showPromptDialog,
|
||||||
} from "../../../../dialogs/generic/show-dialog-box";
|
} from "../../../../dialogs/generic/show-dialog-box";
|
||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant, ItemPath } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import "./types/ha-automation-trigger-calendar";
|
import "./types/ha-automation-trigger-calendar";
|
||||||
import "./types/ha-automation-trigger-conversation";
|
import "./types/ha-automation-trigger-conversation";
|
||||||
import "./types/ha-automation-trigger-device";
|
import "./types/ha-automation-trigger-device";
|
||||||
@ -112,8 +112,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Array }) public path?: ItemPath;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public first?: boolean;
|
@property({ type: Boolean }) public first?: boolean;
|
||||||
|
|
||||||
@property({ type: Boolean }) public last?: boolean;
|
@property({ type: Boolean }) public last?: boolean;
|
||||||
@ -383,7 +381,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
hass: this.hass,
|
hass: this.hass,
|
||||||
trigger: this.trigger,
|
trigger: this.trigger,
|
||||||
disabled: this.disabled,
|
disabled: this.disabled,
|
||||||
path: this.path,
|
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
@ -13,7 +13,7 @@ import { repeat } from "lit/directives/repeat";
|
|||||||
import { storage } from "../../../../common/decorators/storage";
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
||||||
import { nestedArrayMove } from "../../../../common/util/array-move";
|
import { nextRender } from "../../../../common/util/render-status";
|
||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
import "../../../../components/ha-button-menu";
|
import "../../../../components/ha-button-menu";
|
||||||
import "../../../../components/ha-sortable";
|
import "../../../../components/ha-sortable";
|
||||||
@ -23,14 +23,14 @@ import {
|
|||||||
Trigger,
|
Trigger,
|
||||||
TriggerList,
|
TriggerList,
|
||||||
} from "../../../../data/automation";
|
} from "../../../../data/automation";
|
||||||
import { HomeAssistant, ItemPath } from "../../../../types";
|
import { isTriggerList } from "../../../../data/trigger";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
import {
|
import {
|
||||||
PASTE_VALUE,
|
PASTE_VALUE,
|
||||||
showAddAutomationElementDialog,
|
showAddAutomationElementDialog,
|
||||||
} from "../show-add-automation-element-dialog";
|
} from "../show-add-automation-element-dialog";
|
||||||
import "./ha-automation-trigger-row";
|
import "./ha-automation-trigger-row";
|
||||||
import type HaAutomationTriggerRow from "./ha-automation-trigger-row";
|
import type HaAutomationTriggerRow from "./ha-automation-trigger-row";
|
||||||
import { isTriggerList } from "../../../../data/trigger";
|
|
||||||
|
|
||||||
@customElement("ha-automation-trigger")
|
@customElement("ha-automation-trigger")
|
||||||
export default class HaAutomationTrigger extends LitElement {
|
export default class HaAutomationTrigger extends LitElement {
|
||||||
@ -40,8 +40,6 @@ export default class HaAutomationTrigger extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Array }) public path?: ItemPath;
|
|
||||||
|
|
||||||
@state() private _showReorder: boolean = false;
|
@state() private _showReorder: boolean = false;
|
||||||
|
|
||||||
@storage({
|
@storage({
|
||||||
@ -71,20 +69,17 @@ export default class HaAutomationTrigger extends LitElement {
|
|||||||
this._unsubMql = undefined;
|
this._unsubMql = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get nested() {
|
|
||||||
return this.path !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<ha-sortable
|
<ha-sortable
|
||||||
handle-selector=".handle"
|
handle-selector=".handle"
|
||||||
draggable-selector="ha-automation-trigger-row"
|
draggable-selector="ha-automation-trigger-row"
|
||||||
.disabled=${!this._showReorder || this.disabled}
|
.disabled=${!this._showReorder || this.disabled}
|
||||||
@item-moved=${this._triggerMoved}
|
|
||||||
group="triggers"
|
group="triggers"
|
||||||
.path=${this.path}
|
|
||||||
invert-swap
|
invert-swap
|
||||||
|
@item-moved=${this._triggerMoved}
|
||||||
|
@item-added=${this._triggerAdded}
|
||||||
|
@item-removed=${this._triggerRemoved}
|
||||||
>
|
>
|
||||||
<div class="triggers">
|
<div class="triggers">
|
||||||
${repeat(
|
${repeat(
|
||||||
@ -92,7 +87,7 @@ export default class HaAutomationTrigger extends LitElement {
|
|||||||
(trigger) => this._getKey(trigger),
|
(trigger) => this._getKey(trigger),
|
||||||
(trg, idx) => html`
|
(trg, idx) => html`
|
||||||
<ha-automation-trigger-row
|
<ha-automation-trigger-row
|
||||||
.path=${[...(this.path ?? []), idx]}
|
.sortableData=${trg}
|
||||||
.index=${idx}
|
.index=${idx}
|
||||||
.first=${idx === 0}
|
.first=${idx === 0}
|
||||||
.last=${idx === this.triggers.length - 1}
|
.last=${idx === this.triggers.length - 1}
|
||||||
@ -210,28 +205,44 @@ export default class HaAutomationTrigger extends LitElement {
|
|||||||
this._move(index, newIndex);
|
this._move(index, newIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _move(
|
private _move(oldIndex: number, newIndex: number) {
|
||||||
oldIndex: number,
|
const triggers = this.triggers.concat();
|
||||||
newIndex: number,
|
const item = triggers.splice(oldIndex, 1)[0];
|
||||||
oldPath?: ItemPath,
|
triggers.splice(newIndex, 0, item);
|
||||||
newPath?: ItemPath
|
this.triggers = triggers;
|
||||||
) {
|
|
||||||
const triggers = nestedArrayMove(
|
|
||||||
this.triggers,
|
|
||||||
oldIndex,
|
|
||||||
newIndex,
|
|
||||||
oldPath,
|
|
||||||
newPath
|
|
||||||
);
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", { value: triggers });
|
fireEvent(this, "value-changed", { value: triggers });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _triggerMoved(ev: CustomEvent): void {
|
private _triggerMoved(ev: CustomEvent): void {
|
||||||
if (this.nested) return;
|
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
const { oldIndex, newIndex } = ev.detail;
|
||||||
this._move(oldIndex, newIndex, oldPath, newPath);
|
this._move(oldIndex, newIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _triggerAdded(ev: CustomEvent): Promise<void> {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const { index, data } = ev.detail;
|
||||||
|
const triggers = [
|
||||||
|
...this.triggers.slice(0, index),
|
||||||
|
data,
|
||||||
|
...this.triggers.slice(index),
|
||||||
|
];
|
||||||
|
// Add trigger locally to avoid UI jump
|
||||||
|
this.triggers = triggers;
|
||||||
|
await nextRender();
|
||||||
|
fireEvent(this, "value-changed", { value: this.triggers });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _triggerRemoved(ev: CustomEvent): Promise<void> {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const { index } = ev.detail;
|
||||||
|
const trigger = this.triggers[index];
|
||||||
|
// Remove trigger locally to avoid UI jump
|
||||||
|
this.triggers = this.triggers.filter((t) => t !== trigger);
|
||||||
|
await nextRender();
|
||||||
|
// Ensure trigger is removed even after update
|
||||||
|
const triggers = this.triggers.filter((t) => t !== trigger);
|
||||||
|
fireEvent(this, "value-changed", { value: triggers });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _triggerChanged(ev: CustomEvent) {
|
private _triggerChanged(ev: CustomEvent) {
|
||||||
|
@ -2,7 +2,7 @@ import { css, html, LitElement } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||||
import type { TriggerList } from "../../../../../data/automation";
|
import type { TriggerList } from "../../../../../data/automation";
|
||||||
import type { HomeAssistant, ItemPath } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../ha-automation-trigger";
|
import "../ha-automation-trigger";
|
||||||
import {
|
import {
|
||||||
handleChangeEvent,
|
handleChangeEvent,
|
||||||
@ -15,8 +15,6 @@ export class HaTriggerList extends LitElement implements TriggerElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public trigger!: TriggerList;
|
@property({ attribute: false }) public trigger!: TriggerList;
|
||||||
|
|
||||||
@property({ attribute: false }) public path?: ItemPath;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
public static get defaultConfig(): TriggerList {
|
public static get defaultConfig(): TriggerList {
|
||||||
@ -30,7 +28,6 @@ export class HaTriggerList extends LitElement implements TriggerElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-automation-trigger
|
<ha-automation-trigger
|
||||||
.path=${[...(this.path ?? []), "triggers"]}
|
|
||||||
.triggers=${triggers}
|
.triggers=${triggers}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
@ -2,7 +2,6 @@ import "@material/mwc-button/mwc-button";
|
|||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { nestedArrayMove } from "../../../common/util/array-move";
|
|
||||||
import "../../../components/ha-blueprint-picker";
|
import "../../../components/ha-blueprint-picker";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-circular-progress";
|
import "../../../components/ha-circular-progress";
|
||||||
@ -158,15 +157,6 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
|
|||||||
border: boolean
|
border: boolean
|
||||||
) {
|
) {
|
||||||
const selector = value?.selector ?? { text: undefined };
|
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
|
return html`<ha-settings-row
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
class=${border ? "border" : ""}
|
class=${border ? "border" : ""}
|
||||||
@ -180,7 +170,7 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
|
|||||||
></ha-markdown>
|
></ha-markdown>
|
||||||
${html`<ha-selector
|
${html`<ha-selector
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.selector=${enhancedSelector}
|
.selector=${selector}
|
||||||
.key=${key}
|
.key=${key}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${value?.default === undefined}
|
.required=${value?.default === undefined}
|
||||||
@ -190,7 +180,6 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
|
|||||||
? this._config.use_blueprint.input[key]
|
? this._config.use_blueprint.input[key]
|
||||||
: value?.default}
|
: value?.default}
|
||||||
@value-changed=${this._inputChanged}
|
@value-changed=${this._inputChanged}
|
||||||
@item-moved=${this._itemMoved}
|
|
||||||
></ha-selector>`}
|
></ha-selector>`}
|
||||||
</ha-settings-row>`;
|
</ha-settings-row>`;
|
||||||
}
|
}
|
||||||
@ -237,29 +226,6 @@ export abstract class HaBlueprintGenericEditor 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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@ -15,7 +15,6 @@ import {
|
|||||||
extractSearchParam,
|
extractSearchParam,
|
||||||
removeSearchParam,
|
removeSearchParam,
|
||||||
} from "../../../common/url/search-params";
|
} from "../../../common/url/search-params";
|
||||||
import { nestedArrayMove } from "../../../common/util/array-move";
|
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-markdown";
|
import "../../../components/ha-markdown";
|
||||||
@ -163,7 +162,6 @@ export class HaManualScriptEditor extends LitElement {
|
|||||||
.actions=${this.config.sequence || []}
|
.actions=${this.config.sequence || []}
|
||||||
.path=${["sequence"]}
|
.path=${["sequence"]}
|
||||||
@value-changed=${this._sequenceChanged}
|
@value-changed=${this._sequenceChanged}
|
||||||
@item-moved=${this._itemMoved}
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@ -185,21 +183,6 @@ 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@ -83,11 +83,11 @@ export class HuiViewBadges extends LitElement {
|
|||||||
|
|
||||||
private _badgeMoved(ev) {
|
private _badgeMoved(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
const { oldIndex, newIndex } = ev.detail;
|
||||||
const newConfig = moveBadge(
|
const newConfig = moveBadge(
|
||||||
this.lovelace!.config,
|
this.lovelace!.config,
|
||||||
[...oldPath, oldIndex] as [number, number, number],
|
[this.viewIndex!, oldIndex],
|
||||||
[...newPath, newIndex] as [number, number, number]
|
[this.viewIndex!, newIndex]
|
||||||
);
|
);
|
||||||
this.lovelace!.saveConfig(newConfig);
|
this.lovelace!.saveConfig(newConfig);
|
||||||
}
|
}
|
||||||
@ -121,7 +121,6 @@ export class HuiViewBadges extends LitElement {
|
|||||||
@drag-end=${this._dragEnd}
|
@drag-end=${this._dragEnd}
|
||||||
group="badge"
|
group="badge"
|
||||||
draggable-selector="[data-sortable]"
|
draggable-selector="[data-sortable]"
|
||||||
.path=${[this.viewIndex]}
|
|
||||||
.rollback=${false}
|
.rollback=${false}
|
||||||
.options=${BADGE_SORTABLE_OPTIONS}
|
.options=${BADGE_SORTABLE_OPTIONS}
|
||||||
invert-swap
|
invert-swap
|
||||||
|
@ -4,8 +4,8 @@ import { property, state } from "lit/decorators";
|
|||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import "../../../components/ha-ripple";
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import "../../../components/ha-ripple";
|
||||||
import type { HaSortableOptions } from "../../../components/ha-sortable";
|
import type { HaSortableOptions } from "../../../components/ha-sortable";
|
||||||
import { LovelaceSectionElement } from "../../../data/lovelace";
|
import { LovelaceSectionElement } from "../../../data/lovelace";
|
||||||
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||||
@ -16,6 +16,7 @@ import { HuiCard } from "../cards/hui-card";
|
|||||||
import { computeCardGridSize } from "../common/compute-card-grid-size";
|
import { computeCardGridSize } from "../common/compute-card-grid-size";
|
||||||
import "../components/hui-card-edit-mode";
|
import "../components/hui-card-edit-mode";
|
||||||
import { moveCard } from "../editor/config-util";
|
import { moveCard } from "../editor/config-util";
|
||||||
|
import { LovelaceCardPath } from "../editor/lovelace-path";
|
||||||
import type { Lovelace } from "../types";
|
import type { Lovelace } from "../types";
|
||||||
|
|
||||||
const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
|
const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
|
||||||
@ -68,14 +69,15 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
|||||||
return html`
|
return html`
|
||||||
<ha-sortable
|
<ha-sortable
|
||||||
.disabled=${!editMode}
|
.disabled=${!editMode}
|
||||||
@item-moved=${this._cardMoved}
|
|
||||||
@drag-start=${this._dragStart}
|
@drag-start=${this._dragStart}
|
||||||
@drag-end=${this._dragEnd}
|
@drag-end=${this._dragEnd}
|
||||||
group="card"
|
group="card"
|
||||||
draggable-selector=".card"
|
draggable-selector=".card"
|
||||||
.path=${[this.viewIndex, this.index]}
|
|
||||||
.rollback=${false}
|
.rollback=${false}
|
||||||
.options=${CARD_SORTABLE_OPTIONS}
|
.options=${CARD_SORTABLE_OPTIONS}
|
||||||
|
@item-moved=${this._cardMoved}
|
||||||
|
@item-added=${this._cardAdded}
|
||||||
|
@item-removed=${this._cardRemoved}
|
||||||
invert-swap
|
invert-swap
|
||||||
>
|
>
|
||||||
<div class="container ${classMap({ "edit-mode": editMode })}">
|
<div class="container ${classMap({ "edit-mode": editMode })}">
|
||||||
@ -89,6 +91,11 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
|||||||
|
|
||||||
const { rows, columns } = computeCardGridSize(layoutOptions);
|
const { rows, columns } = computeCardGridSize(layoutOptions);
|
||||||
|
|
||||||
|
const cardPath: LovelaceCardPath = [
|
||||||
|
this.viewIndex!,
|
||||||
|
this.index!,
|
||||||
|
idx,
|
||||||
|
];
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
@ -100,13 +107,14 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
|||||||
"fit-rows": typeof layoutOptions?.grid_rows === "number",
|
"fit-rows": typeof layoutOptions?.grid_rows === "number",
|
||||||
"full-width": columns === "full",
|
"full-width": columns === "full",
|
||||||
})}"
|
})}"
|
||||||
|
.sortableData=${cardPath}
|
||||||
>
|
>
|
||||||
${editMode
|
${editMode
|
||||||
? html`
|
? html`
|
||||||
<hui-card-edit-mode
|
<hui-card-edit-mode
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.lovelace=${this.lovelace}
|
.lovelace=${this.lovelace!}
|
||||||
.path=${[this.viewIndex, this.index, idx]}
|
.path=${cardPath}
|
||||||
.hiddenOverlay=${this._dragging}
|
.hiddenOverlay=${this._dragging}
|
||||||
>
|
>
|
||||||
${card}
|
${card}
|
||||||
@ -141,15 +149,28 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
|||||||
|
|
||||||
private _cardMoved(ev) {
|
private _cardMoved(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
const { oldIndex, newIndex } = ev.detail;
|
||||||
const newConfig = moveCard(
|
const newConfig = moveCard(
|
||||||
this.lovelace!.config,
|
this.lovelace!.config,
|
||||||
[...oldPath, oldIndex] as [number, number, number],
|
[this.viewIndex!, this.index!, oldIndex],
|
||||||
[...newPath, newIndex] as [number, number, number]
|
[this.viewIndex!, this.index!, newIndex]
|
||||||
);
|
);
|
||||||
this.lovelace!.saveConfig(newConfig);
|
this.lovelace!.saveConfig(newConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _cardAdded(ev) {
|
||||||
|
const { index, data } = ev.detail;
|
||||||
|
const oldPath = data as LovelaceCardPath;
|
||||||
|
const newPath = [this.viewIndex!, this.index!, index] as LovelaceCardPath;
|
||||||
|
const newConfig = moveCard(this.lovelace!.config, oldPath, newPath);
|
||||||
|
this.lovelace!.saveConfig(newConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _cardRemoved(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
// Do nothing, it's handle by the "card-added" event from the new parent.
|
||||||
|
}
|
||||||
|
|
||||||
private _dragStart() {
|
private _dragStart() {
|
||||||
this._dragging = true;
|
this._dragging = true;
|
||||||
}
|
}
|
||||||
|
@ -175,8 +175,6 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
|||||||
|
|
||||||
const rowSpan = sectionConfig?.row_span || 1;
|
const rowSpan = sectionConfig?.row_span || 1;
|
||||||
|
|
||||||
(section as any).itemPath = [idx];
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="section"
|
class="section"
|
||||||
|
@ -307,5 +307,3 @@ export type AsyncReturnType<T extends (...args: any) => any> = T extends (
|
|||||||
: never;
|
: never;
|
||||||
|
|
||||||
export type Entries<T> = [keyof T, T[keyof T]][];
|
export type Entries<T> = [keyof T, T[keyof T]][];
|
||||||
|
|
||||||
export type ItemPath = (number | string)[];
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user