mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-04 23:27:20 +00:00
Compare commits
28 Commits
checkbox-i
...
improve-mu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fdf56a512 | ||
|
|
5ab123a08d | ||
|
|
b1662ece87 | ||
|
|
a5eacbd32d | ||
|
|
506d466c03 | ||
|
|
46735c72ed | ||
|
|
c43d41053b | ||
|
|
844d53a0ba | ||
|
|
1c8b78eae9 | ||
|
|
a918e878fa | ||
|
|
ebc354bf55 | ||
|
|
98a1f5ca3a | ||
|
|
48015ab312 | ||
|
|
09515b1937 | ||
|
|
5a40627676 | ||
|
|
cd34447603 | ||
|
|
803fabbf64 | ||
|
|
78c4dc48d0 | ||
|
|
147600ea43 | ||
|
|
2f91f0dd15 | ||
|
|
3fa330acfb | ||
|
|
e0a6b671ce | ||
|
|
d6edd150a8 | ||
|
|
2c00889921 | ||
|
|
0447d87f18 | ||
|
|
d7e18b0520 | ||
|
|
e7254b1587 | ||
|
|
8681a7d450 |
@@ -156,7 +156,9 @@ const createTestTranslation = () =>
|
||||
*/
|
||||
const createMasterTranslation = () =>
|
||||
gulp
|
||||
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
|
||||
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])], {
|
||||
allowEmpty: true,
|
||||
})
|
||||
.pipe(new CustomJSON(lokaliseTransform))
|
||||
.pipe(new MergeJSON("en"))
|
||||
.pipe(gulp.dest(workDir));
|
||||
|
||||
@@ -44,18 +44,24 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
|
||||
number_energy_price: null,
|
||||
},
|
||||
],
|
||||
power: [
|
||||
{ stat_rate: "sensor.power_grid" },
|
||||
{ stat_rate: "sensor.power_grid_return" },
|
||||
],
|
||||
cost_adjustment_day: 0,
|
||||
},
|
||||
{
|
||||
type: "solar",
|
||||
stat_energy_from: "sensor.solar_production",
|
||||
stat_rate: "sensor.power_solar",
|
||||
config_entry_solar_forecast: ["solar_forecast"],
|
||||
},
|
||||
/* {
|
||||
{
|
||||
type: "battery",
|
||||
stat_energy_from: "sensor.battery_output",
|
||||
stat_energy_to: "sensor.battery_input",
|
||||
}, */
|
||||
stat_rate: "sensor.power_battery",
|
||||
},
|
||||
{
|
||||
type: "gas",
|
||||
stat_energy_from: "sensor.energy_gas",
|
||||
@@ -63,28 +69,48 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
{
|
||||
type: "water",
|
||||
stat_energy_from: "sensor.energy_water",
|
||||
stat_cost: "sensor.energy_water_cost",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
],
|
||||
device_consumption: [
|
||||
{
|
||||
stat_consumption: "sensor.energy_car",
|
||||
stat_rate: "sensor.power_car",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_ac",
|
||||
stat_rate: "sensor.power_ac",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_washing_machine",
|
||||
stat_rate: "sensor.power_washing_machine",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_dryer",
|
||||
stat_rate: "sensor.power_dryer",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_heat_pump",
|
||||
stat_rate: "sensor.power_heat_pump",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_boiler",
|
||||
stat_rate: "sensor.power_boiler",
|
||||
},
|
||||
],
|
||||
device_consumption_water: [
|
||||
{
|
||||
stat_consumption: "sensor.water_kitchen",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.water_garden",
|
||||
},
|
||||
],
|
||||
device_consumption_water: [],
|
||||
})
|
||||
);
|
||||
hass.mockWS(
|
||||
|
||||
@@ -154,6 +154,38 @@ export const energyEntities = () =>
|
||||
unit_of_measurement: "EUR",
|
||||
},
|
||||
},
|
||||
"sensor.power_grid": {
|
||||
entity_id: "sensor.power_grid",
|
||||
state: "500",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_grid_return": {
|
||||
entity_id: "sensor.power_grid_return",
|
||||
state: "-100",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_solar": {
|
||||
entity_id: "sensor.power_solar",
|
||||
state: "200",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_battery": {
|
||||
entity_id: "sensor.power_battery",
|
||||
state: "100",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.energy_gas_cost": {
|
||||
entity_id: "sensor.energy_gas_cost",
|
||||
state: "2",
|
||||
@@ -171,6 +203,15 @@ export const energyEntities = () =>
|
||||
unit_of_measurement: "m³",
|
||||
},
|
||||
},
|
||||
"sensor.energy_water": {
|
||||
entity_id: "sensor.energy_water",
|
||||
state: "4000",
|
||||
attributes: {
|
||||
last_reset: "1970-01-01T00:00:00:00+00",
|
||||
friendly_name: "Water",
|
||||
unit_of_measurement: "L",
|
||||
},
|
||||
},
|
||||
"sensor.energy_car": {
|
||||
entity_id: "sensor.energy_car",
|
||||
state: "4",
|
||||
@@ -225,4 +266,58 @@ export const energyEntities = () =>
|
||||
unit_of_measurement: "kWh",
|
||||
},
|
||||
},
|
||||
"sensor.power_car": {
|
||||
entity_id: "sensor.power_car",
|
||||
state: "40",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
friendly_name: "Electric car",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_ac": {
|
||||
entity_id: "sensor.power_ac",
|
||||
state: "30",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
friendly_name: "Air conditioning",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_washing_machine": {
|
||||
entity_id: "sensor.power_washing_machine",
|
||||
state: "60",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
friendly_name: "Washing machine",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_dryer": {
|
||||
entity_id: "sensor.power_dryer",
|
||||
state: "55",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
friendly_name: "Dryer",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_heat_pump": {
|
||||
entity_id: "sensor.power_heat_pump",
|
||||
state: "60",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
friendly_name: "Heat pump",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_boiler": {
|
||||
entity_id: "sensor.power_boiler",
|
||||
state: "70",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
friendly_name: "Boiler",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,17 +17,15 @@ const generateMeanStatistics = (
|
||||
end: Date,
|
||||
// eslint-disable-next-line default-param-last
|
||||
period: "5minute" | "hour" | "day" | "month" = "hour",
|
||||
initValue: number,
|
||||
maxDiff: number
|
||||
): StatisticValue[] => {
|
||||
const statistics: StatisticValue[] = [];
|
||||
let currentDate = new Date(start);
|
||||
currentDate.setMinutes(0, 0, 0);
|
||||
let lastVal = initValue;
|
||||
const now = new Date();
|
||||
while (end > currentDate && currentDate < now) {
|
||||
const delta = Math.random() * maxDiff;
|
||||
const mean = lastVal + delta;
|
||||
const mean = delta;
|
||||
statistics.push({
|
||||
start: currentDate.getTime(),
|
||||
end: currentDate.getTime(),
|
||||
@@ -38,7 +36,6 @@ const generateMeanStatistics = (
|
||||
state: mean,
|
||||
sum: null,
|
||||
});
|
||||
lastVal = mean;
|
||||
currentDate =
|
||||
period === "day"
|
||||
? addDays(currentDate, 1)
|
||||
@@ -336,7 +333,6 @@ export const mockRecorder = (mockHass: MockHomeAssistant) => {
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
state,
|
||||
state * (state > 80 ? 0.05 : 0.1)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -381,10 +381,6 @@ export class DemoHaWaDialog extends LitElement {
|
||||
<td><code>--dialog-z-index</code></td>
|
||||
<td>Z-index for the dialog.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--dialog-surface-position</code></td>
|
||||
<td>CSS position of the dialog surface.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--dialog-surface-margin-top</code></td>
|
||||
<td>Top margin for the dialog surface.</td>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"@fullcalendar/list": "6.1.19",
|
||||
"@fullcalendar/luxon3": "6.1.19",
|
||||
"@fullcalendar/timegrid": "6.1.19",
|
||||
"@home-assistant/webawesome": "3.0.0-ha.0",
|
||||
"@home-assistant/webawesome": "3.0.0-ha.1",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@lit-labs/motion": "1.0.9",
|
||||
"@lit-labs/observers": "2.0.6",
|
||||
@@ -209,7 +209,7 @@
|
||||
"lodash.template": "4.5.0",
|
||||
"map-stream": "0.0.7",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.7.2",
|
||||
"prettier": "3.7.3",
|
||||
"rspack-manifest-plugin": "5.2.0",
|
||||
"serve": "14.2.5",
|
||||
"sinon": "21.0.0",
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20251029.0"
|
||||
version = "20251203.0"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*"]
|
||||
description = "The Home Assistant frontend"
|
||||
|
||||
@@ -373,6 +373,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
itemName: 3,
|
||||
},
|
||||
renderItem: this._renderItem,
|
||||
progressive: 0,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ export type DataTableColumnContainer<T = any> = Record<
|
||||
export interface DataTableSortColumnData {
|
||||
sortable?: boolean;
|
||||
filterable?: boolean;
|
||||
filterWeight?: number;
|
||||
filterKey?: string;
|
||||
valueColumn?: string;
|
||||
direction?: SortingDirection;
|
||||
|
||||
@@ -3,7 +3,10 @@ import Fuse from "fuse.js";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ipCompare, stringCompare } from "../../common/string/compare";
|
||||
import { stripDiacritics } from "../../common/string/strip-diacritics";
|
||||
import { HaFuse } from "../../resources/fuse";
|
||||
import {
|
||||
multiTermSearch,
|
||||
type FuseWeightedKey,
|
||||
} from "../../resources/fuseMultiTerm";
|
||||
import type {
|
||||
ClonedDataTableColumnData,
|
||||
DataTableRowData,
|
||||
@@ -11,22 +14,28 @@ import type {
|
||||
SortingDirection,
|
||||
} from "./ha-data-table";
|
||||
|
||||
const fuseIndex = memoizeOne(
|
||||
(data: DataTableRowData[], columns: SortableColumnContainer) => {
|
||||
const searchKeys = new Set<string>();
|
||||
const getSearchKeys = memoizeOne(
|
||||
(columns: SortableColumnContainer): FuseWeightedKey<DataTableRowData>[] => {
|
||||
const searchKeys: FuseWeightedKey<DataTableRowData>[] = [];
|
||||
Object.entries(columns).forEach(([key, column]) => {
|
||||
if (column.filterable) {
|
||||
searchKeys.add(
|
||||
column.filterKey
|
||||
searchKeys.push({
|
||||
name: column.filterKey
|
||||
? `${column.valueColumn || key}.${column.filterKey}`
|
||||
: key
|
||||
);
|
||||
: key,
|
||||
weight: column.filterWeight || 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
return Fuse.createIndex([...searchKeys], data);
|
||||
return searchKeys;
|
||||
}
|
||||
);
|
||||
|
||||
const fuseIndex = memoizeOne(
|
||||
(data: DataTableRowData[], keys: FuseWeightedKey<DataTableRowData>[]) =>
|
||||
Fuse.createIndex(keys, data)
|
||||
);
|
||||
|
||||
const filterData = (
|
||||
data: DataTableRowData[],
|
||||
columns: SortableColumnContainer,
|
||||
@@ -38,21 +47,13 @@ const filterData = (
|
||||
return data;
|
||||
}
|
||||
|
||||
const index = fuseIndex(data, columns);
|
||||
const keys = getSearchKeys(columns);
|
||||
|
||||
const fuse = new HaFuse(
|
||||
data,
|
||||
{ shouldSort: false, minMatchCharLength: 1 },
|
||||
index
|
||||
);
|
||||
const index = fuseIndex(data, keys);
|
||||
|
||||
const searchResults = fuse.multiTermsSearch(filter);
|
||||
|
||||
if (searchResults) {
|
||||
return searchResults.map((result) => result.item);
|
||||
}
|
||||
|
||||
return data;
|
||||
return multiTermSearch<DataTableRowData>(data, filter, keys, index, {
|
||||
threshold: 0.2, // reduce fuzzy matches in data tables
|
||||
});
|
||||
};
|
||||
|
||||
const sortData = (
|
||||
|
||||
@@ -9,6 +9,7 @@ import { computeDeviceName } from "../../common/entity/compute_device_name";
|
||||
import { getDeviceContext } from "../../common/entity/context/get_device_context";
|
||||
import { getConfigEntries, type ConfigEntry } from "../../data/config_entries";
|
||||
import {
|
||||
deviceComboBoxKeys,
|
||||
getDevices,
|
||||
type DevicePickerItem,
|
||||
type DeviceRegistryEntry,
|
||||
@@ -216,6 +217,7 @@ export class HaDevicePicker extends LitElement {
|
||||
.getItems=${this._getItems}
|
||||
.hideClearIcon=${this.hideClearIcon}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.searchKeys=${deviceComboBoxKeys}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity";
|
||||
import {
|
||||
entityComboBoxKeys,
|
||||
getEntities,
|
||||
type EntityComboBoxItem,
|
||||
} from "../../data/entity_registry";
|
||||
@@ -288,6 +289,7 @@ export class HaEntityPicker extends LitElement {
|
||||
.hideClearIcon=${this.hideClearIcon}
|
||||
.searchFn=${this._searchFn}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.searchKeys=${entityComboBoxKeys}
|
||||
@value-changed=${this._valueChanged}
|
||||
.addButtonLabel=${this.addButton
|
||||
? this.hass.localize("ui.components.entity.entity-picker.add")
|
||||
|
||||
@@ -38,9 +38,21 @@ type StatisticItemType = "entity" | "external" | "no_state";
|
||||
interface StatisticComboBoxItem extends PickerComboBoxItem {
|
||||
statistic_id?: string;
|
||||
stateObj?: HassEntity;
|
||||
domainName?: string;
|
||||
type?: StatisticItemType;
|
||||
}
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "label", weight: 10 },
|
||||
{ name: "search_labels.entityName", weight: 10 },
|
||||
{ name: "search_labels.friendlyName", weight: 9 },
|
||||
{ name: "search_labels.deviceName", weight: 8 },
|
||||
{ name: "search_labels.areaName", weight: 6 },
|
||||
{ name: "search_labels.domainName", weight: 4 },
|
||||
{ name: "statisticId", weight: 3 },
|
||||
{ name: "id", weight: 2 },
|
||||
];
|
||||
|
||||
@customElement("ha-statistic-picker")
|
||||
export class HaStatisticPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -233,7 +245,6 @@ export class HaStatisticPicker extends LitElement {
|
||||
),
|
||||
type,
|
||||
sorting_label: [sortingPrefix, label].join("_"),
|
||||
search_labels: [label, id],
|
||||
icon_path: mdiShape,
|
||||
});
|
||||
} else if (type === "external") {
|
||||
@@ -246,7 +257,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
secondary: domainName,
|
||||
type,
|
||||
sorting_label: [sortingPrefix, label].join("_"),
|
||||
search_labels: [label, domainName, id],
|
||||
search_labels: { label, domainName },
|
||||
icon_path: mdiChartLine,
|
||||
});
|
||||
}
|
||||
@@ -280,13 +291,12 @@ export class HaStatisticPicker extends LitElement {
|
||||
stateObj: stateObj,
|
||||
type: "entity",
|
||||
sorting_label: [sortingPrefix, deviceName, entityName].join("_"),
|
||||
search_labels: [
|
||||
entityName,
|
||||
deviceName,
|
||||
areaName,
|
||||
search_labels: {
|
||||
entityName: entityName || null,
|
||||
deviceName: deviceName || null,
|
||||
areaName: areaName || null,
|
||||
friendlyName,
|
||||
id,
|
||||
].filter(Boolean) as string[],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -361,13 +371,13 @@ export class HaStatisticPicker extends LitElement {
|
||||
stateObj: stateObj,
|
||||
type: "entity",
|
||||
sorting_label: [sortingPrefix, deviceName, entityName].join("_"),
|
||||
search_labels: [
|
||||
entityName,
|
||||
deviceName,
|
||||
areaName,
|
||||
search_labels: {
|
||||
entityName: entityName || null,
|
||||
deviceName: deviceName || null,
|
||||
areaName: areaName || null,
|
||||
friendlyName,
|
||||
statisticId,
|
||||
].filter(Boolean) as string[],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -394,7 +404,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
secondary: domainName,
|
||||
type: "external",
|
||||
sorting_label: [sortingPrefix, label].join("_"),
|
||||
search_labels: [label, domainName, statisticId],
|
||||
search_labels: { label, domainName, statisticId },
|
||||
icon_path: mdiChartLine,
|
||||
};
|
||||
}
|
||||
@@ -409,7 +419,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
secondary: this.hass.localize("ui.components.statistic-picker.no_state"),
|
||||
type: "no_state",
|
||||
sorting_label: [sortingPrefix, label].join("_"),
|
||||
search_labels: [label, statisticId],
|
||||
search_labels: { label, statisticId },
|
||||
icon_path: mdiShape,
|
||||
};
|
||||
}
|
||||
@@ -475,6 +485,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
.searchFn=${this._searchFn}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.helper=${this.helper}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { computeAreaName } from "../common/entity/compute_area_name";
|
||||
import { computeFloorName } from "../common/entity/compute_floor_name";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import {
|
||||
areaFloorComboBoxKeys,
|
||||
getAreasAndFloors,
|
||||
type AreaFloorValue,
|
||||
type FloorComboBoxItem,
|
||||
@@ -237,6 +238,7 @@ export class HaAreaFloorPicker extends LitElement {
|
||||
.getItems=${this._getItems}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
.searchKeys=${areaFloorComboBoxKeys}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -30,6 +30,12 @@ import "./ha-svg-icon";
|
||||
|
||||
const ADD_NEW_ID = "___ADD_NEW___";
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "areaName", weight: 10 },
|
||||
{ name: "aliases", weight: 8 },
|
||||
{ name: "floorName", weight: 6 },
|
||||
{ name: "id", weight: 3 },
|
||||
];
|
||||
@customElement("ha-area-picker")
|
||||
export class HaAreaPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -291,12 +297,12 @@ export class HaAreaPicker extends LitElement {
|
||||
icon: area.icon || undefined,
|
||||
icon_path: area.icon ? undefined : mdiTextureBox,
|
||||
sorting_label: areaName,
|
||||
search_labels: [
|
||||
areaName,
|
||||
floorName,
|
||||
area.area_id,
|
||||
...area.aliases,
|
||||
].filter((v): v is string => Boolean(v)),
|
||||
search_labels: {
|
||||
areaName: areaName || null,
|
||||
floorName: floorName || null,
|
||||
id: area.area_id,
|
||||
aliases: area.aliases.join(" "),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -379,6 +385,7 @@ export class HaAreaPicker extends LitElement {
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.addButtonLabel=${this.addButtonLabel}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { getAreaContext } from "../common/entity/context/get_area_context";
|
||||
import { areaCompare } from "../data/area_registry";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-expansion-panel";
|
||||
import "./ha-items-display-editor";
|
||||
@@ -37,11 +36,7 @@ export class HaAreasDisplayEditor extends LitElement {
|
||||
public showNavigationButton = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const compare = areaCompare(this.hass.areas);
|
||||
|
||||
const areas = Object.values(this.hass.areas).sort((areaA, areaB) =>
|
||||
compare(areaA.area_id, areaB.area_id)
|
||||
);
|
||||
const areas = Object.values(this.hass.areas);
|
||||
|
||||
const items: DisplayItem[] = areas.map((area) => {
|
||||
const { floor } = getAreaContext(area, this.hass.floors);
|
||||
|
||||
@@ -7,7 +7,6 @@ import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeFloorName } from "../common/entity/compute_floor_name";
|
||||
import { getAreaContext } from "../common/entity/context/get_area_context";
|
||||
import { areaCompare } from "../data/area_registry";
|
||||
import type { FloorRegistryEntry } from "../data/floor_registry";
|
||||
import { getFloors } from "../panels/lovelace/strategies/areas/helpers/areas-strategy-helper";
|
||||
import type { HomeAssistant } from "../types";
|
||||
@@ -131,11 +130,8 @@ export class HaAreasFloorsDisplayEditor extends LitElement {
|
||||
// update items if floors change
|
||||
_hassFloors: HomeAssistant["floors"]
|
||||
): Record<string, DisplayItem[]> => {
|
||||
const compare = areaCompare(hassAreas);
|
||||
const areas = Object.values(hassAreas);
|
||||
|
||||
const areas = Object.values(hassAreas).sort((areaA, areaB) =>
|
||||
compare(areaA.area_id, areaB.area_id)
|
||||
);
|
||||
const groupedItems: Record<string, DisplayItem[]> = areas.reduce(
|
||||
(acc, area) => {
|
||||
const { floor } = getAreaContext(area, this.hass.floors);
|
||||
|
||||
@@ -167,30 +167,33 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private async _labelSelected(ev: CustomEvent<SelectedDetail<Set<number>>>) {
|
||||
if (!ev.detail.index.size) {
|
||||
fireEvent(this, "data-table-filter-changed", {
|
||||
value: [],
|
||||
items: undefined,
|
||||
});
|
||||
this.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const value: string[] = [];
|
||||
const filteredLabels = this._filteredLabels(
|
||||
this._labels,
|
||||
this._filter,
|
||||
this.value
|
||||
);
|
||||
|
||||
const filteredLabelIds = new Set(filteredLabels.map((l) => l.label_id));
|
||||
|
||||
// Keep previously selected labels that are not in the current filtered view
|
||||
const preservedLabels = (this.value || []).filter(
|
||||
(id) => !filteredLabelIds.has(id)
|
||||
);
|
||||
|
||||
// Build the new selection from the filtered labels based on selected indices
|
||||
const newlySelectedLabels: string[] = [];
|
||||
for (const index of ev.detail.index) {
|
||||
const labelId = filteredLabels[index].label_id;
|
||||
value.push(labelId);
|
||||
const labelId = filteredLabels[index]?.label_id;
|
||||
if (labelId) {
|
||||
newlySelectedLabels.push(labelId);
|
||||
}
|
||||
}
|
||||
this.value = value;
|
||||
|
||||
const value = [...preservedLabels, ...newlySelectedLabels];
|
||||
this.value = value.length ? value : [];
|
||||
|
||||
fireEvent(this, "data-table-filter-changed", {
|
||||
value,
|
||||
value: value.length ? value : undefined,
|
||||
items: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ import "./ha-svg-icon";
|
||||
|
||||
const ADD_NEW_ID = "___ADD_NEW___";
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "floorName", weight: 10 },
|
||||
{ name: "aliases", weight: 8 },
|
||||
{ name: "floor_id", weight: 3 },
|
||||
];
|
||||
|
||||
interface FloorComboBoxItem extends PickerComboBoxItem {
|
||||
floor?: FloorRegistryEntry;
|
||||
}
|
||||
@@ -286,9 +292,11 @@ export class HaFloorPicker extends LitElement {
|
||||
primary: floorName,
|
||||
floor: floor,
|
||||
sorting_label: floor.level?.toString() || "zzzzz",
|
||||
search_labels: [floorName, floor.floor_id, ...floor.aliases].filter(
|
||||
(v): v is string => Boolean(v)
|
||||
),
|
||||
search_labels: {
|
||||
floorName,
|
||||
floor_id: floor.floor_id,
|
||||
aliases: floor.aliases.join(" "),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -393,6 +401,7 @@ export class HaFloorPicker extends LitElement {
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { tinykeys } from "tinykeys";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-bottom-sheet";
|
||||
import "./ha-button";
|
||||
@@ -64,6 +65,9 @@ export class HaGenericPicker extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public searchFn?: PickerComboBoxSearchFn<PickerComboBoxItem>;
|
||||
|
||||
@property({ attribute: false })
|
||||
public searchKeys?: FuseWeightedKey<PickerComboBoxItem>[];
|
||||
|
||||
@property({ attribute: false })
|
||||
public notFoundLabel?: string | ((search: string) => string);
|
||||
|
||||
@@ -229,6 +233,7 @@ export class HaGenericPicker extends LitElement {
|
||||
.sections=${this.sections}
|
||||
.sectionTitleFunction=${this.sectionTitleFunction}
|
||||
.selectedSection=${this.selectedSection}
|
||||
.searchKeys=${this.searchKeys}
|
||||
></ha-picker-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import type { LabelRegistryEntry } from "../data/label_registry";
|
||||
import {
|
||||
createLabelRegistryEntry,
|
||||
getLabels,
|
||||
labelComboBoxKeys,
|
||||
subscribeLabelRegistry,
|
||||
} from "../data/label_registry";
|
||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||
@@ -237,6 +238,7 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
||||
.getItems=${this._getItems}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.searchKeys=${labelComboBoxKeys}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<slot .slot=${this._slotNodes?.length ? "field" : undefined}></slot>
|
||||
|
||||
@@ -40,14 +40,12 @@ export const getLanguageOptions = (
|
||||
return {
|
||||
id: lang,
|
||||
primary,
|
||||
search_labels: [primary],
|
||||
};
|
||||
});
|
||||
} else if (locale) {
|
||||
options = languages.map((lang) => ({
|
||||
id: lang,
|
||||
primary: formatLanguageCode(lang, locale),
|
||||
search_labels: [formatLanguageCode(lang, locale)],
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,10 @@ import memoizeOne from "memoize-one";
|
||||
import { tinykeys } from "tinykeys";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { HaFuse } from "../resources/fuse";
|
||||
import {
|
||||
multiTermSortedSearch,
|
||||
type FuseWeightedKey,
|
||||
} from "../resources/fuseMultiTerm";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import { loadVirtualizer } from "../resources/virtualizer";
|
||||
import type { HomeAssistant } from "../types";
|
||||
@@ -25,11 +28,26 @@ import "./ha-icon";
|
||||
import "./ha-textfield";
|
||||
import type { HaTextField } from "./ha-textfield";
|
||||
|
||||
export const DEFAULT_SEARCH_KEYS: FuseWeightedKey<PickerComboBoxItem>[] = [
|
||||
{
|
||||
name: "primary",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "secondary",
|
||||
weight: 7,
|
||||
},
|
||||
{
|
||||
name: "id",
|
||||
weight: 3,
|
||||
},
|
||||
];
|
||||
|
||||
export interface PickerComboBoxItem {
|
||||
id: string;
|
||||
primary: string;
|
||||
secondary?: string;
|
||||
search_labels?: string[];
|
||||
search_labels?: Record<string, string | null>;
|
||||
sorting_label?: string;
|
||||
icon_path?: string;
|
||||
icon?: string;
|
||||
@@ -76,6 +94,9 @@ export class HaPickerComboBox extends LitElement {
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
public searchKeys?: FuseWeightedKey<PickerComboBoxItem>[];
|
||||
|
||||
@state() private _listScrolled = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
@@ -271,6 +292,9 @@ export class HaPickerComboBox extends LitElement {
|
||||
};
|
||||
|
||||
private _renderItem = (item: PickerComboBoxItem | string, index: number) => {
|
||||
if (!item) {
|
||||
return nothing;
|
||||
}
|
||||
if (item === "padding") {
|
||||
return html`<div class="bottom-padding"></div>`;
|
||||
}
|
||||
@@ -331,8 +355,11 @@ export class HaPickerComboBox extends LitElement {
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
};
|
||||
|
||||
private _fuseIndex = memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
Fuse.createIndex(["search_labels"], states)
|
||||
private _fuseIndex = memoizeOne(
|
||||
(
|
||||
states: PickerComboBoxItem[],
|
||||
searchKeys?: FuseWeightedKey<PickerComboBoxItem>[]
|
||||
) => Fuse.createIndex(searchKeys || DEFAULT_SEARCH_KEYS, states)
|
||||
);
|
||||
|
||||
private _filterChanged = (ev: Event) => {
|
||||
@@ -348,34 +375,26 @@ export class HaPickerComboBox extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = this._fuseIndex(this._allItems as PickerComboBoxItem[]);
|
||||
const fuse = new HaFuse(
|
||||
const index = this._fuseIndex(
|
||||
this._allItems as PickerComboBoxItem[],
|
||||
{
|
||||
shouldSort: false,
|
||||
minMatchCharLength: Math.min(searchString.length, 2),
|
||||
},
|
||||
index
|
||||
this.searchKeys
|
||||
);
|
||||
|
||||
const results = fuse.multiTermsSearch(searchString);
|
||||
let filteredItems = [...this._allItems];
|
||||
let filteredItems = multiTermSortedSearch<PickerComboBoxItem>(
|
||||
this._allItems as PickerComboBoxItem[],
|
||||
searchString,
|
||||
this.searchKeys || DEFAULT_SEARCH_KEYS,
|
||||
(item) => item.id,
|
||||
index
|
||||
) as (PickerComboBoxItem | string)[];
|
||||
|
||||
if (results) {
|
||||
const items: (PickerComboBoxItem | string)[] = results.map(
|
||||
(result) => result.item
|
||||
);
|
||||
|
||||
if (!items.length) {
|
||||
filteredItems.push(NO_ITEMS_AVAILABLE_ID);
|
||||
}
|
||||
|
||||
const additionalItems = this._getAdditionalItems();
|
||||
items.push(...additionalItems);
|
||||
|
||||
filteredItems = items;
|
||||
if (!filteredItems.length) {
|
||||
filteredItems.push(NO_ITEMS_AVAILABLE_ID);
|
||||
}
|
||||
|
||||
const additionalItems = this._getAdditionalItems();
|
||||
filteredItems.push(...additionalItems);
|
||||
|
||||
if (this.searchFn) {
|
||||
filteredItems = this.searchFn(
|
||||
searchString,
|
||||
@@ -582,7 +601,7 @@ export class HaPickerComboBox extends LitElement {
|
||||
}
|
||||
|
||||
private _keyFunction = (item: PickerComboBoxItem | string) =>
|
||||
typeof item === "string" ? item : item.id;
|
||||
typeof item === "string" ? item : item?.id;
|
||||
|
||||
static styles = [
|
||||
haStyleScrollbar,
|
||||
|
||||
@@ -21,6 +21,13 @@ interface ServiceComboBoxItem extends PickerComboBoxItem {
|
||||
service_id?: string;
|
||||
}
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "name", weight: 10 },
|
||||
{ name: "description", weight: 8 },
|
||||
{ name: "domainName", weight: 6 },
|
||||
{ name: "serviceId", weight: 3 },
|
||||
];
|
||||
|
||||
@customElement("ha-service-picker")
|
||||
class HaServicePicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -141,6 +148,7 @@ class HaServicePicker extends LitElement {
|
||||
this.hass.localize,
|
||||
this.hass.services
|
||||
)}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
@@ -194,9 +202,7 @@ class HaServicePicker extends LitElement {
|
||||
secondary: description,
|
||||
domain_name: domainName,
|
||||
service_id: serviceId,
|
||||
search_labels: [serviceId, domainName, name, description].filter(
|
||||
Boolean
|
||||
),
|
||||
search_labels: { serviceId, domainName, name, description },
|
||||
sorting_label: serviceId,
|
||||
});
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -15,17 +15,30 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import { isValidEntityId } from "../common/entity/valid_entity_id";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import {
|
||||
areaFloorComboBoxKeys,
|
||||
getAreasAndFloors,
|
||||
type AreaFloorValue,
|
||||
type FloorComboBoxItem,
|
||||
} from "../data/area_floor";
|
||||
import { getConfigEntries, type ConfigEntry } from "../data/config_entries";
|
||||
import { labelsContext } from "../data/context";
|
||||
import { getDevices, type DevicePickerItem } from "../data/device_registry";
|
||||
import {
|
||||
deviceComboBoxKeys,
|
||||
getDevices,
|
||||
type DevicePickerItem,
|
||||
} from "../data/device_registry";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../data/entity";
|
||||
import { getEntities, type EntityComboBoxItem } from "../data/entity_registry";
|
||||
import {
|
||||
entityComboBoxKeys,
|
||||
getEntities,
|
||||
type EntityComboBoxItem,
|
||||
} from "../data/entity_registry";
|
||||
import { domainToName } from "../data/integration";
|
||||
import { getLabels, type LabelRegistryEntry } from "../data/label_registry";
|
||||
import {
|
||||
getLabels,
|
||||
labelComboBoxKeys,
|
||||
type LabelRegistryEntry,
|
||||
} from "../data/label_registry";
|
||||
import {
|
||||
areaMeetsFilter,
|
||||
deviceMeetsFilter,
|
||||
@@ -37,7 +50,11 @@ import {
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { isHelperDomain } from "../panels/config/helpers/const";
|
||||
import { showHelperDetailDialog } from "../panels/config/helpers/show-dialog-helper-detail";
|
||||
import { HaFuse } from "../resources/fuse";
|
||||
import {
|
||||
multiTermSearch,
|
||||
multiTermSortedSearch,
|
||||
type FuseWeightedKey,
|
||||
} from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { brandsUrl } from "../util/brands-url";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
@@ -113,16 +130,16 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _fuseIndexes = {
|
||||
area: memoizeOne((states: FloorComboBoxItem[]) =>
|
||||
this._createFuseIndex(states)
|
||||
this._createFuseIndex(states, areaFloorComboBoxKeys)
|
||||
),
|
||||
entity: memoizeOne((states: EntityComboBoxItem[]) =>
|
||||
this._createFuseIndex(states)
|
||||
this._createFuseIndex(states, entityComboBoxKeys)
|
||||
),
|
||||
device: memoizeOne((states: DevicePickerItem[]) =>
|
||||
this._createFuseIndex(states)
|
||||
this._createFuseIndex(states, deviceComboBoxKeys)
|
||||
),
|
||||
label: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states)
|
||||
this._createFuseIndex(states, labelComboBoxKeys)
|
||||
),
|
||||
};
|
||||
|
||||
@@ -134,8 +151,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _createFuseIndex = (states) =>
|
||||
Fuse.createIndex(["search_labels"], states);
|
||||
private _createFuseIndex = (
|
||||
states,
|
||||
keys: FuseWeightedKey<PickerComboBoxItem>[]
|
||||
) => Fuse.createIndex(keys, states);
|
||||
|
||||
protected render() {
|
||||
if (this.addOnTop) {
|
||||
@@ -735,8 +754,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
"entity",
|
||||
entityItems,
|
||||
searchTerm,
|
||||
(item: EntityComboBoxItem) =>
|
||||
item.stateObj?.entity_id === searchTerm
|
||||
entityComboBoxKeys
|
||||
) as EntityComboBoxItem[];
|
||||
}
|
||||
|
||||
@@ -765,7 +783,12 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
|
||||
if (searchTerm) {
|
||||
deviceItems = this._filterGroup("device", deviceItems, searchTerm);
|
||||
deviceItems = this._filterGroup(
|
||||
"device",
|
||||
deviceItems,
|
||||
searchTerm,
|
||||
deviceComboBoxKeys
|
||||
);
|
||||
}
|
||||
|
||||
if (!filterType && deviceItems.length) {
|
||||
@@ -799,7 +822,9 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
areasAndFloors = this._filterGroup(
|
||||
"area",
|
||||
areasAndFloors,
|
||||
searchTerm
|
||||
searchTerm,
|
||||
areaFloorComboBoxKeys,
|
||||
false
|
||||
) as FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
@@ -844,7 +869,12 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
|
||||
if (searchTerm) {
|
||||
labels = this._filterGroup("label", labels, searchTerm);
|
||||
labels = this._filterGroup(
|
||||
"label",
|
||||
labels,
|
||||
searchTerm,
|
||||
labelComboBoxKeys
|
||||
);
|
||||
}
|
||||
|
||||
if (!filterType && labels.length) {
|
||||
@@ -863,40 +893,24 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
type: TargetType,
|
||||
items: (FloorComboBoxItem | PickerComboBoxItem | EntityComboBoxItem)[],
|
||||
searchTerm: string,
|
||||
checkExact?: (
|
||||
item: FloorComboBoxItem | PickerComboBoxItem | EntityComboBoxItem
|
||||
) => boolean
|
||||
weightedKeys: FuseWeightedKey<PickerComboBoxItem>[],
|
||||
sort = true
|
||||
) {
|
||||
const fuseIndex = this._fuseIndexes[type](items);
|
||||
const fuse = new HaFuse(
|
||||
items,
|
||||
{
|
||||
shouldSort: false,
|
||||
minMatchCharLength: Math.min(searchTerm.length, 2),
|
||||
},
|
||||
fuseIndex
|
||||
);
|
||||
|
||||
const results = fuse.multiTermsSearch(searchTerm);
|
||||
let filteredItems = items;
|
||||
if (results) {
|
||||
filteredItems = results.map((result) => result.item);
|
||||
if (sort) {
|
||||
return multiTermSortedSearch(
|
||||
items,
|
||||
searchTerm,
|
||||
weightedKeys,
|
||||
(item) => item.id,
|
||||
fuseIndex
|
||||
);
|
||||
}
|
||||
|
||||
if (!checkExact) {
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
// If there is exact match for entity id, put it first
|
||||
const index = filteredItems.findIndex((item) => checkExact(item));
|
||||
if (index === -1) {
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
const [exactMatch] = filteredItems.splice(index, 1);
|
||||
filteredItems.unshift(exactMatch);
|
||||
|
||||
return filteredItems;
|
||||
return multiTermSearch(items, searchTerm, weightedKeys, fuseIndex, {
|
||||
ignoreLocation: true,
|
||||
});
|
||||
}
|
||||
|
||||
private _getAdditionalItems = () => this._getCreateItems(this.createDomains);
|
||||
|
||||
@@ -49,7 +49,6 @@ export type DialogWidth = "small" | "medium" | "large" | "full";
|
||||
* @cssprop --ha-dialog-surface-background - Dialog background color.
|
||||
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface.
|
||||
* @cssprop --dialog-z-index - Z-index for the dialog.
|
||||
* @cssprop --dialog-surface-position - CSS position of the dialog surface.
|
||||
* @cssprop --dialog-surface-margin-top - Top margin for the dialog surface.
|
||||
*
|
||||
* @attr {boolean} open - Controls the dialog open state.
|
||||
@@ -244,7 +243,6 @@ export class HaWaDialog extends LitElement {
|
||||
calc(var(--safe-height) - var(--ha-space-20))
|
||||
);
|
||||
min-height: var(--ha-dialog-min-height);
|
||||
position: var(--dialog-surface-position, relative);
|
||||
margin-top: var(--dialog-surface-margin-top, auto);
|
||||
/* Used to offset the dialog from the safe areas when space is limited */
|
||||
transform: translate(
|
||||
|
||||
@@ -17,6 +17,12 @@ interface UserComboBoxItem extends PickerComboBoxItem {
|
||||
user?: User;
|
||||
}
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "primary", weight: 10 },
|
||||
{ name: "search_labels.username", weight: 6 },
|
||||
{ name: "id", weight: 3 },
|
||||
];
|
||||
|
||||
@customElement("ha-user-picker")
|
||||
class HaUserPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -109,9 +115,7 @@ class HaUserPicker extends LitElement {
|
||||
id: user.id,
|
||||
primary: user.name,
|
||||
domain_name: user.name,
|
||||
search_labels: [user.name, user.id, user.username].filter(
|
||||
Boolean
|
||||
) as string[],
|
||||
search_labels: { username: user.username },
|
||||
sorting_label: user.name,
|
||||
user,
|
||||
}));
|
||||
@@ -134,6 +138,7 @@ class HaUserPicker extends LitElement {
|
||||
.getItems=${this._getItems}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeFloorName } from "../common/entity/compute_floor_name";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { AreaRegistryEntry } from "./area_registry";
|
||||
import {
|
||||
@@ -26,7 +27,8 @@ export interface FloorNestedComboBoxItem extends PickerComboBoxItem {
|
||||
areas: FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
export interface UnassignedAreasFloorComboBoxItem extends PickerComboBoxItem {
|
||||
export interface UnassignedAreasFloorComboBoxItem {
|
||||
id: undefined;
|
||||
areas: FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
@@ -98,6 +100,29 @@ export const getAreasAndFloors = (
|
||||
excludeFloors
|
||||
) as FloorComboBoxItem[];
|
||||
|
||||
export const areaFloorComboBoxKeys: FuseWeightedKey<PickerComboBoxItem>[] = [
|
||||
{
|
||||
name: "search_labels.name",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "search_labels.aliases",
|
||||
weight: 8,
|
||||
},
|
||||
{
|
||||
name: "search_labels.floorName",
|
||||
weight: 6,
|
||||
},
|
||||
{
|
||||
name: "search_labels.relatedAreas",
|
||||
weight: 4,
|
||||
},
|
||||
{
|
||||
name: "search_labels.id",
|
||||
weight: 3,
|
||||
},
|
||||
];
|
||||
|
||||
const getAreasAndFloorsItems = (
|
||||
states: HomeAssistant["states"],
|
||||
haFloors: HomeAssistant["floors"],
|
||||
@@ -223,6 +248,7 @@ const getAreasAndFloorsItems = (
|
||||
}
|
||||
|
||||
let outputAreas = areas;
|
||||
let outputFloors = floors;
|
||||
|
||||
let areaIds: string[] | undefined;
|
||||
|
||||
@@ -254,9 +280,29 @@ const getAreasAndFloorsItems = (
|
||||
outputAreas = outputAreas.filter(
|
||||
(area) => !area.floor_id || !excludeFloors!.includes(area.floor_id)
|
||||
);
|
||||
|
||||
outputFloors = outputFloors.filter(
|
||||
(floor) => !excludeFloors!.includes(floor.floor_id)
|
||||
);
|
||||
}
|
||||
|
||||
const hierarchy = getAreasFloorHierarchy(floors, outputAreas);
|
||||
if (
|
||||
entityFilter ||
|
||||
deviceFilter ||
|
||||
includeDomains ||
|
||||
excludeDomains ||
|
||||
includeDeviceClasses
|
||||
) {
|
||||
// Ensure we only include floors that have areas with the filtered entities/devices
|
||||
const validFloorIds = new Set(
|
||||
outputAreas.map((area) => area.floor_id).filter((id) => id)
|
||||
);
|
||||
outputFloors = outputFloors.filter((floor) =>
|
||||
validFloorIds.has(floor.floor_id)
|
||||
);
|
||||
}
|
||||
|
||||
const hierarchy = getAreasFloorHierarchy(outputFloors, outputAreas);
|
||||
|
||||
const items: (
|
||||
| FloorComboBoxItem
|
||||
@@ -283,12 +329,12 @@ const getAreasAndFloorsItems = (
|
||||
primary: floorName,
|
||||
floor: floor,
|
||||
icon: floor.icon || undefined,
|
||||
search_labels: [
|
||||
floor.floor_id,
|
||||
floorName,
|
||||
...floor.aliases,
|
||||
...areaSearchLabels,
|
||||
],
|
||||
search_labels: {
|
||||
id: floor.floor_id,
|
||||
name: floorName || null,
|
||||
aliases: floor.aliases.join(", ") || null,
|
||||
relatedAreas: areaSearchLabels.join(" ") || null,
|
||||
},
|
||||
};
|
||||
|
||||
items.push(floorItem);
|
||||
@@ -301,11 +347,12 @@ const getAreasAndFloorsItems = (
|
||||
primary: areaName || area.area_id,
|
||||
area: area,
|
||||
icon: area.icon || undefined,
|
||||
search_labels: [
|
||||
area.area_id,
|
||||
...(areaName ? [areaName] : []),
|
||||
...area.aliases,
|
||||
],
|
||||
search_labels: {
|
||||
id: area.area_id,
|
||||
name: areaName || null,
|
||||
aliases: area.aliases.join(", ") || null,
|
||||
floorName: floorName || null,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -318,19 +365,24 @@ const getAreasAndFloorsItems = (
|
||||
|
||||
const unassignedAreaItems = hierarchy.areas.map((areaId) => {
|
||||
const area = haAreas[areaId];
|
||||
const areaName = computeAreaName(area) || area.area_id;
|
||||
const areaName = computeAreaName(area);
|
||||
return {
|
||||
id: formatId({ id: area.area_id, type: "area" }),
|
||||
type: "area" as const,
|
||||
primary: areaName,
|
||||
primary: areaName || area.area_id,
|
||||
area: area,
|
||||
icon: area.icon || undefined,
|
||||
search_labels: [area.area_id, areaName, ...area.aliases],
|
||||
search_labels: {
|
||||
id: area.area_id,
|
||||
name: areaName || null,
|
||||
aliases: area.aliases.join(", ") || null,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
if (nested && unassignedAreaItems.length) {
|
||||
items.push({
|
||||
id: undefined,
|
||||
areas: unassignedAreaItems,
|
||||
} as UnassignedAreasFloorComboBoxItem);
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { DeviceRegistryEntry } from "./device_registry";
|
||||
import type {
|
||||
@@ -105,22 +104,3 @@ export const getAreaDeviceLookup = (
|
||||
}
|
||||
return areaDeviceLookup;
|
||||
};
|
||||
|
||||
export const areaCompare =
|
||||
(entries?: HomeAssistant["areas"], order?: string[]) =>
|
||||
(a: string, b: string) => {
|
||||
const indexA = order ? order.indexOf(a) : -1;
|
||||
const indexB = order ? order.indexOf(b) : -1;
|
||||
if (indexA === -1 && indexB === -1) {
|
||||
const nameA = entries?.[a]?.name ?? a;
|
||||
const nameB = entries?.[b]?.name ?? b;
|
||||
return stringCompare(nameA, nameB);
|
||||
}
|
||||
if (indexA === -1) {
|
||||
return 1;
|
||||
}
|
||||
if (indexB === -1) {
|
||||
return -1;
|
||||
}
|
||||
return indexA - indexB;
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getDeviceContext } from "../common/entity/context/get_device_context";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { ConfigEntry } from "./config_entries";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
||||
@@ -181,6 +182,25 @@ export interface DevicePickerItem extends PickerComboBoxItem {
|
||||
domain_name?: string;
|
||||
}
|
||||
|
||||
export const deviceComboBoxKeys: FuseWeightedKey<DevicePickerItem>[] = [
|
||||
{
|
||||
name: "search_labels.deviceName",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "search_labels.areaName",
|
||||
weight: 8,
|
||||
},
|
||||
{
|
||||
name: "search_labels.domainName",
|
||||
weight: 4,
|
||||
},
|
||||
{
|
||||
name: "search_labels.domain",
|
||||
weight: 4,
|
||||
},
|
||||
];
|
||||
|
||||
export const getDevices = (
|
||||
hass: HomeAssistant,
|
||||
configEntryLookup: Record<string, ConfigEntry>,
|
||||
@@ -311,9 +331,12 @@ export const getDevices = (
|
||||
secondary: areaName,
|
||||
domain: configEntry?.domain,
|
||||
domain_name: domainName,
|
||||
search_labels: [deviceName, areaName, domain, domainName].filter(
|
||||
Boolean
|
||||
) as string[],
|
||||
search_labels: {
|
||||
deviceName,
|
||||
areaName: areaName || null,
|
||||
domain: domain || null,
|
||||
domainName: domainName || null,
|
||||
},
|
||||
sorting_label: deviceName || "zzz",
|
||||
};
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
||||
import { domainToName } from "./integration";
|
||||
@@ -335,6 +336,33 @@ export interface EntityComboBoxItem extends PickerComboBoxItem {
|
||||
stateObj?: HassEntity;
|
||||
}
|
||||
|
||||
export const entityComboBoxKeys: FuseWeightedKey<EntityComboBoxItem>[] = [
|
||||
{
|
||||
name: "search_labels.entityName",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "search_labels.friendlyName",
|
||||
weight: 8,
|
||||
},
|
||||
{
|
||||
name: "search_labels.deviceName",
|
||||
weight: 7,
|
||||
},
|
||||
{
|
||||
name: "search_labels.areaName",
|
||||
weight: 6,
|
||||
},
|
||||
{
|
||||
name: "search_labels.domainName",
|
||||
weight: 6,
|
||||
},
|
||||
{
|
||||
name: "search_labels.entityId",
|
||||
weight: 3,
|
||||
},
|
||||
];
|
||||
|
||||
export const getEntities = (
|
||||
hass: HomeAssistant,
|
||||
includeDomains?: string[],
|
||||
@@ -403,14 +431,14 @@ export const getEntities = (
|
||||
secondary: secondary,
|
||||
domain_name: domainName,
|
||||
sorting_label: [deviceName, entityName].filter(Boolean).join("_"),
|
||||
search_labels: [
|
||||
entityName,
|
||||
deviceName,
|
||||
areaName,
|
||||
domainName,
|
||||
friendlyName,
|
||||
entityId,
|
||||
].filter(Boolean) as string[],
|
||||
search_labels: {
|
||||
entityName: entityName || null,
|
||||
deviceName: deviceName || null,
|
||||
areaName: areaName || null,
|
||||
domainName: domainName || null,
|
||||
friendlyName: friendlyName || null,
|
||||
entityId: entityId,
|
||||
},
|
||||
stateObj: stateObj,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import { stringCompare } from "../common/string/compare";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import {
|
||||
getDeviceEntityDisplayLookup,
|
||||
@@ -100,6 +101,21 @@ export const deleteLabelRegistryEntry = (
|
||||
label_id: labelId,
|
||||
});
|
||||
|
||||
export const labelComboBoxKeys: FuseWeightedKey<PickerComboBoxItem>[] = [
|
||||
{
|
||||
name: "search_labels.name",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "search_labels.description",
|
||||
weight: 5,
|
||||
},
|
||||
{
|
||||
name: "search_labels.id",
|
||||
weight: 4,
|
||||
},
|
||||
];
|
||||
|
||||
export const getLabels = (
|
||||
hassStates: HomeAssistant["states"],
|
||||
hassAreas: HomeAssistant["areas"],
|
||||
@@ -273,9 +289,11 @@ export const getLabels = (
|
||||
icon: label.icon || undefined,
|
||||
icon_path: label.icon ? undefined : mdiLabel,
|
||||
sorting_label: label.name,
|
||||
search_labels: [label.name, label.label_id, label.description].filter(
|
||||
(v): v is string => Boolean(v)
|
||||
),
|
||||
search_labels: {
|
||||
name: label.name,
|
||||
description: label.description,
|
||||
id: label.label_id,
|
||||
},
|
||||
}));
|
||||
|
||||
return items;
|
||||
|
||||
@@ -18,6 +18,11 @@ export interface LabPreviewFeaturesResponse {
|
||||
features: LabPreviewFeature[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all lab features
|
||||
* @param hass - The Home Assistant instance
|
||||
* @returns A promise to fetch the lab features
|
||||
*/
|
||||
export const fetchLabFeatures = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<LabPreviewFeature[]> => {
|
||||
@@ -27,6 +32,15 @@ export const fetchLabFeatures = async (
|
||||
return response.features;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update a specific lab feature
|
||||
* @param hass - The Home Assistant instance
|
||||
* @param domain - The domain of the lab feature
|
||||
* @param preview_feature - The preview feature of the lab feature
|
||||
* @param enabled - Whether the lab feature is enabled
|
||||
* @param create_backup - Whether to create a backup of the lab feature
|
||||
* @returns A promise to update the lab feature
|
||||
*/
|
||||
export const labsUpdatePreviewFeature = (
|
||||
hass: HomeAssistant,
|
||||
domain: string,
|
||||
@@ -65,6 +79,12 @@ const subscribeLabUpdates = (
|
||||
"labs_updated"
|
||||
);
|
||||
|
||||
/**
|
||||
* Subscribe to a collection of lab features
|
||||
* @param conn - The connection to the Home Assistant instance
|
||||
* @param onChange - The function to call when the lab features change
|
||||
* @returns The unsubscribe function
|
||||
*/
|
||||
export const subscribeLabFeatures = (
|
||||
conn: Connection,
|
||||
onChange: (features: LabPreviewFeature[]) => void
|
||||
@@ -76,3 +96,27 @@ export const subscribeLabFeatures = (
|
||||
conn,
|
||||
onChange
|
||||
);
|
||||
|
||||
/**
|
||||
* Subscribe to a specific lab feature
|
||||
* @param conn - The connection to the Home Assistant instance
|
||||
* @param domain - The domain of the lab feature
|
||||
* @param previewFeature - The preview feature of the lab feature
|
||||
* @param onChange - The function to call when the lab feature changes
|
||||
* @returns The unsubscribe function
|
||||
*/
|
||||
export const subscribeLabFeature = (
|
||||
conn: Connection,
|
||||
domain: string,
|
||||
previewFeature: string,
|
||||
onChange: (enabled: boolean) => void
|
||||
) =>
|
||||
subscribeLabFeatures(conn, (features) => {
|
||||
const enabled =
|
||||
features.find(
|
||||
(feature) =>
|
||||
feature.domain === domain &&
|
||||
feature.preview_feature === previewFeature
|
||||
)?.enabled ?? false;
|
||||
onChange(enabled);
|
||||
});
|
||||
|
||||
@@ -82,6 +82,12 @@ export interface WeatherEntity extends HassEntityBase {
|
||||
attributes: WeatherEntityAttributes;
|
||||
}
|
||||
|
||||
export const WEATHER_TEMPERATURE_ATTRIBUTES = new Set<string>([
|
||||
"temperature",
|
||||
"apparent_temperature",
|
||||
"dew_point",
|
||||
]);
|
||||
|
||||
export const weatherSVGs = new Set<string>([
|
||||
"clear-night",
|
||||
"cloudy",
|
||||
@@ -256,9 +262,15 @@ export const getWeatherUnit = (
|
||||
export const getSecondaryWeatherAttribute = (
|
||||
hass: HomeAssistant,
|
||||
stateObj: WeatherEntity,
|
||||
forecast: ForecastAttribute[]
|
||||
forecast: ForecastAttribute[],
|
||||
temperatureFractionDigits?: number
|
||||
): TemplateResult | undefined => {
|
||||
const extrema = getWeatherExtrema(hass, stateObj, forecast);
|
||||
const extrema = getWeatherExtrema(
|
||||
hass,
|
||||
stateObj,
|
||||
forecast,
|
||||
temperatureFractionDigits
|
||||
);
|
||||
|
||||
if (extrema) {
|
||||
return extrema;
|
||||
@@ -298,7 +310,8 @@ export const getSecondaryWeatherAttribute = (
|
||||
const getWeatherExtrema = (
|
||||
hass: HomeAssistant,
|
||||
stateObj: WeatherEntity,
|
||||
forecast: ForecastAttribute[]
|
||||
forecast: ForecastAttribute[],
|
||||
temperatureFractionDigits?: number
|
||||
): TemplateResult | undefined => {
|
||||
if (!forecast?.length) {
|
||||
return undefined;
|
||||
@@ -313,13 +326,22 @@ const getWeatherExtrema = (
|
||||
break;
|
||||
}
|
||||
if (!tempHigh || fc.temperature > tempHigh) {
|
||||
tempHigh = fc.temperature;
|
||||
tempHigh =
|
||||
temperatureFractionDigits === undefined
|
||||
? fc.temperature
|
||||
: round(fc.temperature, temperatureFractionDigits);
|
||||
}
|
||||
if (!tempLow || (fc.templow && fc.templow < tempLow)) {
|
||||
tempLow = fc.templow;
|
||||
if (fc.templow !== undefined && (!tempLow || fc.templow < tempLow)) {
|
||||
tempLow =
|
||||
temperatureFractionDigits === undefined
|
||||
? fc.templow
|
||||
: round(fc.templow, temperatureFractionDigits);
|
||||
}
|
||||
if (!fc.templow && (!tempLow || fc.temperature < tempLow)) {
|
||||
tempLow = fc.temperature;
|
||||
tempLow =
|
||||
temperatureFractionDigits === undefined
|
||||
? fc.temperature
|
||||
: round(fc.temperature, temperatureFractionDigits);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ import { domainToName } from "../../data/integration";
|
||||
import { getPanelNameTranslationKey } from "../../data/panel";
|
||||
import type { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||
import { configSections } from "../../panels/config/ha-panel-config";
|
||||
import { HaFuse } from "../../resources/fuse";
|
||||
import { multiTermSortedSearch } from "../../resources/fuseMultiTerm";
|
||||
import {
|
||||
haStyleDialog,
|
||||
haStyleDialogFixedTop,
|
||||
@@ -58,7 +58,17 @@ import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||
import { showShortcutsDialog } from "../shortcuts/show-shortcuts-dialog";
|
||||
import { QuickBarMode, type QuickBarParams } from "./show-dialog-quick-bar";
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "primaryText", weight: 10 },
|
||||
{ name: "altText", weight: 8 },
|
||||
{ name: "friendlyName", weight: 8 },
|
||||
{ name: "area", weight: 6 },
|
||||
{ name: "translatedDomain", weight: 5 },
|
||||
{ name: "entityId", weight: 4 }, // for technical search
|
||||
];
|
||||
|
||||
interface QuickBarItem extends ScorableTextItem {
|
||||
id: string;
|
||||
primaryText: string;
|
||||
iconPath?: string;
|
||||
action(data?: any): void;
|
||||
@@ -593,6 +603,7 @@ export class QuickBar extends LitElement {
|
||||
const areaName = area ? computeAreaName(area) : undefined;
|
||||
|
||||
const deviceItem = {
|
||||
id: device.id,
|
||||
primaryText: deviceName,
|
||||
deviceId: device.id,
|
||||
area: areaName,
|
||||
@@ -666,6 +677,7 @@ export class QuickBar extends LitElement {
|
||||
);
|
||||
|
||||
const entityItem = {
|
||||
id: `entity-${entityId}`,
|
||||
primaryText: primary,
|
||||
altText: secondary,
|
||||
icon: html`
|
||||
@@ -767,8 +779,9 @@ export class QuickBar extends LitElement {
|
||||
),
|
||||
});
|
||||
|
||||
return commands.map((command) => ({
|
||||
return commands.map((command, index) => ({
|
||||
...command,
|
||||
id: `command_${index}_${command.primaryText}`,
|
||||
categoryKey: "reload",
|
||||
strings: [`${command.categoryText} ${command.primaryText}`],
|
||||
}));
|
||||
@@ -777,10 +790,11 @@ export class QuickBar extends LitElement {
|
||||
private _generateServerControlCommands(): CommandItem[] {
|
||||
const serverActions = ["restart", "stop"] as const;
|
||||
|
||||
return serverActions.map((action) => {
|
||||
return serverActions.map((action, index) => {
|
||||
const categoryKey: CommandItem["categoryKey"] = "server_control";
|
||||
|
||||
const item = {
|
||||
id: `server_control_${index}_${action}`,
|
||||
primaryText: this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.server_control.perform_action",
|
||||
{
|
||||
@@ -940,10 +954,11 @@ export class QuickBar extends LitElement {
|
||||
private _finalizeNavigationCommands(
|
||||
items: BaseNavigationCommand[]
|
||||
): CommandItem[] {
|
||||
return items.map((item) => {
|
||||
return items.map((item, index) => {
|
||||
const categoryKey: CommandItem["categoryKey"] = "navigation";
|
||||
|
||||
const navItem = {
|
||||
id: `navigation_${index}_${item.path}`,
|
||||
iconPath: mdiEarth,
|
||||
categoryText: this.hass.localize(
|
||||
`ui.dialogs.quick-bar.commands.types.${categoryKey}`
|
||||
@@ -961,28 +976,20 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
|
||||
private _fuseIndex = memoizeOne((items: QuickBarItem[]) =>
|
||||
Fuse.createIndex(
|
||||
[
|
||||
"primaryText",
|
||||
"altText",
|
||||
"friendlyName",
|
||||
"translatedDomain",
|
||||
"entityId", // for technical search
|
||||
],
|
||||
items
|
||||
)
|
||||
Fuse.createIndex(SEARCH_KEYS, items)
|
||||
);
|
||||
|
||||
private _filterItems = memoizeOne(
|
||||
(items: QuickBarItem[], filter: string): QuickBarItem[] => {
|
||||
const index = this._fuseIndex(items);
|
||||
const fuse = new HaFuse(items, {}, index);
|
||||
|
||||
const results = fuse.multiTermsSearch(filter.trim());
|
||||
if (!results || !results.length) {
|
||||
return items;
|
||||
}
|
||||
return results.map((result) => result.item);
|
||||
return multiTermSortedSearch(
|
||||
items,
|
||||
filter,
|
||||
SEARCH_KEYS,
|
||||
(item) => item.id,
|
||||
index
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ class DialogAreasFloorsOrder extends LitElement {
|
||||
? html`<div class="floor-header">
|
||||
<span class="floor-name">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.dialog.unassigned_areas"
|
||||
"ui.panel.config.areas.dialog.other_areas"
|
||||
)}
|
||||
</span>
|
||||
</div>`
|
||||
|
||||
@@ -266,7 +266,7 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
<div class="header">
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.picker.unassigned_areas"
|
||||
"ui.panel.config.areas.picker.other_areas"
|
||||
)}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAlertCircleCheck,
|
||||
@@ -32,11 +33,11 @@ import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import "../../../../components/ha-service-icon";
|
||||
import "../../../../components/ha-tooltip";
|
||||
import {
|
||||
@@ -288,15 +289,12 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
</ha-tooltip>`
|
||||
: nothing}
|
||||
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
<ha-dropdown
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -304,30 +302,24 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-md-menu-item .clickAction=${this._runAction}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
|
||||
<ha-dropdown-item value="run">
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize("ui.panel.config.automation.editor.actions.run")
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._renameAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
</ha-dropdown-item>
|
||||
<wa-divider></wa-divider>
|
||||
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
|
||||
@@ -336,13 +328,10 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -351,7 +340,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -362,13 +350,10 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span>C</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -377,7 +362,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -388,51 +372,48 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span>X</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
<ha-dropdown-item
|
||||
value="move_up"
|
||||
.disabled=${this.disabled || !!this.first}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_up"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="move_down"
|
||||
.disabled=${this.disabled || !!this.last}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
<ha-dropdown-item
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!this._uiModeAvailable || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-dropdown-item value="disable" .disabled=${this.disabled}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${this.action.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
@@ -443,15 +424,15 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
`ui.panel.config.automation.editor.actions.${this.action.enabled === false ? "enable" : "disable"}`
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
class="warning"
|
||||
.clickAction=${this._onDelete}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="delete"
|
||||
variant="danger"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
|
||||
@@ -463,7 +444,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -478,8 +458,8 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`${this._warnings
|
||||
@@ -890,6 +870,47 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
this._automationRowElement?.focus();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "run":
|
||||
this._runAction();
|
||||
break;
|
||||
case "rename":
|
||||
this._renameAction();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicateAction();
|
||||
break;
|
||||
case "copy":
|
||||
this._copyAction();
|
||||
break;
|
||||
case "cut":
|
||||
this._cutAction();
|
||||
break;
|
||||
case "move_up":
|
||||
this._moveUp();
|
||||
break;
|
||||
case "move_down":
|
||||
this._moveDown();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode(ev.target as HTMLElement);
|
||||
break;
|
||||
case "disable":
|
||||
this._onDisable();
|
||||
break;
|
||||
case "delete":
|
||||
this._onDelete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [rowStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ import {
|
||||
fetchIntegrationManifests,
|
||||
} from "../../../data/integration";
|
||||
import type { LabelRegistryEntry } from "../../../data/label_registry";
|
||||
import { subscribeLabFeatures } from "../../../data/labs";
|
||||
import { subscribeLabFeature } from "../../../data/labs";
|
||||
import {
|
||||
TARGET_SEPARATOR,
|
||||
getConditionsForTarget,
|
||||
@@ -281,15 +281,12 @@ class DialogAddAutomationElement
|
||||
this._fetchManifests();
|
||||
this._calculateUsedDomains();
|
||||
|
||||
this._unsubscribeLabFeatures = subscribeLabFeatures(
|
||||
this._unsubscribeLabFeatures = subscribeLabFeature(
|
||||
this.hass.connection,
|
||||
(features) => {
|
||||
this._newTriggersAndConditions =
|
||||
features.find(
|
||||
(feature) =>
|
||||
feature.domain === "automation" &&
|
||||
feature.preview_feature === "new_triggers_conditions"
|
||||
)?.enabled ?? false;
|
||||
"automation",
|
||||
"new_triggers_conditions",
|
||||
(enabled) => {
|
||||
this._newTriggersAndConditions = enabled;
|
||||
this._tab = this._newTriggersAndConditions ? "targets" : "groups";
|
||||
}
|
||||
);
|
||||
@@ -686,6 +683,7 @@ class DialogAddAutomationElement
|
||||
<ha-automation-add-items
|
||||
.hass=${this.hass}
|
||||
.items=${this._getItems()}
|
||||
.scrollable=${!this._narrow}
|
||||
.error=${this._tab === "targets" && this._loadItemsError
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.load_target_items_failed"
|
||||
@@ -2146,7 +2144,7 @@ class DialogAddAutomationElement
|
||||
min-height: 160px;
|
||||
}
|
||||
.content.column ha-automation-add-from-target {
|
||||
overflow: hidden;
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
ha-wa-dialog ha-automation-add-items {
|
||||
|
||||
@@ -911,6 +911,10 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
const services: Record<string, Level3Entries> = {};
|
||||
|
||||
unassignedDevices.forEach(({ id: deviceId, entry_type }) => {
|
||||
const device = this.devices[deviceId];
|
||||
if (!device || device.disabled_by) {
|
||||
return;
|
||||
}
|
||||
const deviceEntry = {
|
||||
open: false,
|
||||
entities:
|
||||
@@ -1012,6 +1016,10 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
const devices: Record<string, Level3Entries> = {};
|
||||
|
||||
referenced_devices.forEach(({ id: deviceId }) => {
|
||||
const device = this.devices[deviceId];
|
||||
if (!device || device.disabled_by) {
|
||||
return;
|
||||
}
|
||||
devices[deviceId] = {
|
||||
open: false,
|
||||
entities:
|
||||
|
||||
@@ -60,6 +60,8 @@ export class HaAutomationAddItems extends LitElement {
|
||||
@property({ type: Boolean, attribute: "tooltip-description" })
|
||||
public tooltipDescription = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) scrollable = false;
|
||||
|
||||
@state() private _itemsScrolled = false;
|
||||
|
||||
@query(".items")
|
||||
@@ -260,11 +262,12 @@ export class HaAutomationAddItems extends LitElement {
|
||||
:host {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
:host([scrollable]) .items {
|
||||
overflow: auto;
|
||||
}
|
||||
.items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
}
|
||||
.items.blank {
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { LitVirtualizer } from "@lit-labs/virtualizer";
|
||||
import { consume } from "@lit/context";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import { mdiPlus, mdiTextureBox } from "@mdi/js";
|
||||
import type { IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import { css, html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import {
|
||||
@@ -34,6 +33,7 @@ import "../../../../components/ha-section-title";
|
||||
import "../../../../components/ha-tree-indicator";
|
||||
import { ACTION_BUILDING_BLOCKS_GROUP } from "../../../../data/action";
|
||||
import {
|
||||
areaFloorComboBoxKeys,
|
||||
getAreasAndFloors,
|
||||
type AreaFloorValue,
|
||||
type FloorComboBoxItem,
|
||||
@@ -42,23 +42,30 @@ import { CONDITION_BUILDING_BLOCKS_GROUP } from "../../../../data/condition";
|
||||
import type { ConfigEntry } from "../../../../data/config_entries";
|
||||
import { labelsContext } from "../../../../data/context";
|
||||
import {
|
||||
deviceComboBoxKeys,
|
||||
getDevices,
|
||||
type DevicePickerItem,
|
||||
} from "../../../../data/device_registry";
|
||||
import {
|
||||
entityComboBoxKeys,
|
||||
getEntities,
|
||||
type EntityComboBoxItem,
|
||||
} from "../../../../data/entity_registry";
|
||||
import type { DomainManifestLookup } from "../../../../data/integration";
|
||||
import {
|
||||
getLabels,
|
||||
labelComboBoxKeys,
|
||||
type LabelRegistryEntry,
|
||||
} from "../../../../data/label_registry";
|
||||
import {
|
||||
getTargetComboBoxItemType,
|
||||
TARGET_SEPARATOR,
|
||||
} from "../../../../data/target";
|
||||
import { HaFuse } from "../../../../resources/fuse";
|
||||
import {
|
||||
multiTermSearch,
|
||||
multiTermSortedSearch,
|
||||
type FuseWeightedKey,
|
||||
} from "../../../../resources/fuseMultiTerm";
|
||||
import { loadVirtualizer } from "../../../../resources/virtualizer";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type {
|
||||
@@ -76,6 +83,17 @@ const TARGET_SEARCH_SECTIONS = [
|
||||
"label",
|
||||
] as const;
|
||||
|
||||
export const ITEM_SEARCH_KEYS: FuseWeightedKey<PickerComboBoxItem>[] = [
|
||||
{
|
||||
name: "name",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
weight: 7,
|
||||
},
|
||||
];
|
||||
|
||||
type SearchSection = "item" | "block" | "entity" | "device" | "area" | "label";
|
||||
|
||||
@customElement("ha-automation-add-search")
|
||||
@@ -434,27 +452,29 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
private _keyFunction = (item: PickerComboBoxItem | string) =>
|
||||
typeof item === "string" ? item : item.id;
|
||||
|
||||
private _createFuseIndex = (states) =>
|
||||
Fuse.createIndex(["search_labels"], states);
|
||||
private _createFuseIndex = (
|
||||
states,
|
||||
keys: FuseWeightedKey<PickerComboBoxItem>[]
|
||||
) => Fuse.createIndex(keys, states);
|
||||
|
||||
private _fuseIndexes = {
|
||||
area: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states)
|
||||
this._createFuseIndex(states, areaFloorComboBoxKeys)
|
||||
),
|
||||
entity: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states)
|
||||
this._createFuseIndex(states, entityComboBoxKeys)
|
||||
),
|
||||
device: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states)
|
||||
this._createFuseIndex(states, deviceComboBoxKeys)
|
||||
),
|
||||
label: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states)
|
||||
this._createFuseIndex(states, labelComboBoxKeys)
|
||||
),
|
||||
item: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states)
|
||||
this._createFuseIndex(states, ITEM_SEARCH_KEYS)
|
||||
),
|
||||
block: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states)
|
||||
this._createFuseIndex(states, ITEM_SEARCH_KEYS)
|
||||
),
|
||||
};
|
||||
|
||||
@@ -478,11 +498,12 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
if (!selectedSection || selectedSection === "item") {
|
||||
let items = this._convertItemsToComboBoxItems(automationItems, type);
|
||||
if (searchTerm) {
|
||||
items = this._filterGroup("item", items, searchTerm, {
|
||||
ignoreLocation: true,
|
||||
includeScore: true,
|
||||
minMatchCharLength: Math.min(2, this.filter.length),
|
||||
}) as AutomationItemComboBoxItem[];
|
||||
items = this._filterGroup(
|
||||
"item",
|
||||
items,
|
||||
searchTerm,
|
||||
ITEM_SEARCH_KEYS
|
||||
) as AutomationItemComboBoxItem[];
|
||||
}
|
||||
|
||||
if (!selectedSection && items.length) {
|
||||
@@ -514,11 +535,12 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
);
|
||||
|
||||
if (searchTerm) {
|
||||
blocks = this._filterGroup("block", blocks, searchTerm, {
|
||||
ignoreLocation: true,
|
||||
includeScore: true,
|
||||
minMatchCharLength: Math.min(2, this.filter.length),
|
||||
}) as AutomationItemComboBoxItem[];
|
||||
blocks = this._filterGroup(
|
||||
"block",
|
||||
blocks,
|
||||
searchTerm,
|
||||
ITEM_SEARCH_KEYS
|
||||
) as AutomationItemComboBoxItem[];
|
||||
}
|
||||
|
||||
if (!selectedSection && blocks.length) {
|
||||
@@ -550,9 +572,7 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
"entity",
|
||||
entityItems,
|
||||
searchTerm,
|
||||
undefined,
|
||||
(item: EntityComboBoxItem) =>
|
||||
item.stateObj?.entity_id === searchTerm
|
||||
entityComboBoxKeys
|
||||
) as EntityComboBoxItem[];
|
||||
}
|
||||
|
||||
@@ -581,7 +601,12 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
);
|
||||
|
||||
if (searchTerm) {
|
||||
deviceItems = this._filterGroup("device", deviceItems, searchTerm);
|
||||
deviceItems = this._filterGroup(
|
||||
"device",
|
||||
deviceItems,
|
||||
searchTerm,
|
||||
deviceComboBoxKeys
|
||||
);
|
||||
}
|
||||
|
||||
if (!selectedSection && deviceItems.length) {
|
||||
@@ -617,7 +642,9 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
areasAndFloors = this._filterGroup(
|
||||
"area",
|
||||
areasAndFloors,
|
||||
searchTerm
|
||||
searchTerm,
|
||||
areaFloorComboBoxKeys,
|
||||
false
|
||||
) as FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
@@ -664,7 +691,12 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
);
|
||||
|
||||
if (searchTerm) {
|
||||
labels = this._filterGroup("label", labels, searchTerm);
|
||||
labels = this._filterGroup(
|
||||
"label",
|
||||
labels,
|
||||
searchTerm,
|
||||
labelComboBoxKeys
|
||||
);
|
||||
}
|
||||
|
||||
if (!selectedSection && labels.length) {
|
||||
@@ -691,50 +723,28 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
| AutomationItemComboBoxItem
|
||||
)[],
|
||||
searchTerm: string,
|
||||
fuseOptions?: IFuseOptions<any>,
|
||||
checkExact?: (
|
||||
item:
|
||||
| FloorComboBoxItem
|
||||
| PickerComboBoxItem
|
||||
| EntityComboBoxItem
|
||||
| AutomationItemComboBoxItem
|
||||
) => boolean
|
||||
searchKeys: FuseWeightedKey<any>[],
|
||||
sort = true
|
||||
) {
|
||||
const fuseIndex = this._fuseIndexes[type](items);
|
||||
const fuse = new HaFuse<
|
||||
| FloorComboBoxItem
|
||||
| PickerComboBoxItem
|
||||
| EntityComboBoxItem
|
||||
| AutomationItemComboBoxItem
|
||||
>(
|
||||
|
||||
if (sort) {
|
||||
return multiTermSortedSearch<PickerComboBoxItem>(
|
||||
items,
|
||||
searchTerm,
|
||||
searchKeys,
|
||||
(item) => item.id,
|
||||
fuseIndex
|
||||
);
|
||||
}
|
||||
|
||||
return multiTermSearch<PickerComboBoxItem>(
|
||||
items,
|
||||
fuseOptions || {
|
||||
shouldSort: false,
|
||||
minMatchCharLength: Math.min(searchTerm.length, 2),
|
||||
},
|
||||
fuseIndex
|
||||
searchTerm,
|
||||
searchKeys,
|
||||
fuseIndex,
|
||||
{ ignoreLocation: true }
|
||||
);
|
||||
|
||||
const results = fuse.multiTermsSearch(searchTerm);
|
||||
let filteredItems = items;
|
||||
if (results) {
|
||||
filteredItems = results.map((result) => result.item);
|
||||
}
|
||||
|
||||
if (!checkExact) {
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
// If there is exact match for entity id, put it first
|
||||
const index = filteredItems.findIndex((item) => checkExact(item));
|
||||
if (index === -1) {
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
const [exactMatch] = filteredItems.splice(index, 1);
|
||||
filteredItems.unshift(exactMatch);
|
||||
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
private _toggleSection(ev: Event) {
|
||||
@@ -787,7 +797,11 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
iconPath,
|
||||
renderedIcon: icon,
|
||||
type,
|
||||
search_labels: [key, name, description],
|
||||
search_labels: {
|
||||
key,
|
||||
name,
|
||||
description,
|
||||
},
|
||||
}));
|
||||
|
||||
private _focusSearchList = () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
@@ -33,11 +34,11 @@ import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-condition-icon";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import type {
|
||||
AutomationClipboard,
|
||||
Condition,
|
||||
@@ -194,15 +195,12 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
<ha-dropdown
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -211,34 +209,28 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
>
|
||||
</ha-icon-button>
|
||||
|
||||
<ha-md-menu-item .clickAction=${this._testCondition}>
|
||||
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
|
||||
<ha-dropdown-item value="test">
|
||||
<ha-svg-icon slot="icon" .path=${mdiFlask}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.test"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._renameCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.rename"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
@@ -246,13 +238,10 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon
|
||||
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon
|
||||
>${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -261,7 +250,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -272,13 +260,10 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span>C</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon
|
||||
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon
|
||||
>${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -287,7 +272,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -298,48 +282,45 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span>X</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
.disabled=${this.disabled || this.first}
|
||||
<ha-dropdown-item
|
||||
value="move_up"
|
||||
.disabled=${this.disabled || !!this.first}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_up"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
.disabled=${this.disabled || this.last}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="move_down"
|
||||
.disabled=${this.disabled || !!this.last}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-md-menu-item .clickAction=${this._toggleYamlMode}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-dropdown-item value="toggle_yaml_mode">
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-dropdown-item value="disable" .disabled=${this.disabled}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${this.condition.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
@@ -350,15 +331,15 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
`ui.panel.config.automation.editor.actions.${this.condition.enabled === false ? "enable" : "disable"}`
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
class="warning"
|
||||
.clickAction=${this._onDelete}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
variant="danger"
|
||||
value="delete"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
@@ -369,7 +350,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -384,8 +364,8 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
${!this.optionsInSidebar
|
||||
? html`${this._warnings
|
||||
? html`<ha-automation-editor-warning
|
||||
@@ -837,6 +817,47 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
this._automationRowElement?.focus();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "test":
|
||||
this._testCondition();
|
||||
break;
|
||||
case "rename":
|
||||
this._renameCondition();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicateCondition();
|
||||
break;
|
||||
case "copy":
|
||||
this._copyCondition();
|
||||
break;
|
||||
case "cut":
|
||||
this._cutCondition();
|
||||
break;
|
||||
case "move_up":
|
||||
this._moveUp();
|
||||
break;
|
||||
case "move_down":
|
||||
this._moveDown();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode(ev.target as HTMLElement);
|
||||
break;
|
||||
case "disable":
|
||||
this._onDisable();
|
||||
break;
|
||||
case "delete":
|
||||
this._onDelete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
rowStyles,
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
CONDITION_BUILDING_BLOCKS,
|
||||
subscribeConditions,
|
||||
} from "../../../../data/condition";
|
||||
import { subscribeLabFeatures } from "../../../../data/labs";
|
||||
import { subscribeLabFeature } from "../../../../data/labs";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
@@ -90,14 +90,14 @@ export default class HaAutomationCondition extends SubscribeMixin(LitElement) {
|
||||
|
||||
protected hassSubscribe() {
|
||||
return [
|
||||
subscribeLabFeatures(this.hass!.connection, (features) => {
|
||||
this._newTriggersAndConditions =
|
||||
features.find(
|
||||
(feature) =>
|
||||
feature.domain === "automation" &&
|
||||
feature.preview_feature === "new_triggers_conditions"
|
||||
)?.enabled ?? false;
|
||||
}),
|
||||
subscribeLabFeature(
|
||||
this.hass!.connection,
|
||||
"automation",
|
||||
"new_triggers_conditions",
|
||||
(enabled) => {
|
||||
this._newTriggersAndConditions = enabled;
|
||||
}
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
@@ -25,18 +26,20 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
|
||||
import { transform } from "../../../common/decorators/transform";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { goBack, navigate } from "../../../common/navigate";
|
||||
import { promiseTimeout } from "../../../common/util/promise-timeout";
|
||||
import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-fade-in";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-spinner";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
@@ -74,7 +77,6 @@ import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||
import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin";
|
||||
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { Entries, HomeAssistant, Route } from "../../../types";
|
||||
import { isMac } from "../../../util/is_mac";
|
||||
@@ -86,10 +88,10 @@ import {
|
||||
type EntityRegistryUpdate,
|
||||
showAutomationSaveDialog,
|
||||
} from "./automation-save-dialog/show-dialog-automation-save";
|
||||
import { showAutomationSaveTimeoutDialog } from "./automation-save-timeout-dialog/show-dialog-automation-save-timeout";
|
||||
import "./blueprint-automation-editor";
|
||||
import "./manual-automation-editor";
|
||||
import type { HaManualAutomationEditor } from "./manual-automation-editor";
|
||||
import { showAutomationSaveTimeoutDialog } from "./automation-save-timeout-dialog/show-dialog-automation-save-timeout";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -292,7 +294,10 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
</ha-button>
|
||||
`
|
||||
: ""}
|
||||
<ha-button-menu slot="toolbar-icon">
|
||||
<ha-dropdown
|
||||
slot="toolbar-icon"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
@@ -300,99 +305,73 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
></ha-icon-button>
|
||||
|
||||
${this._mode === "gui" && this.narrow
|
||||
? html`<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._undo}
|
||||
? html`<ha-dropdown-item
|
||||
value="undo"
|
||||
.disabled=${!this._undoRedoController.canUndo}
|
||||
>
|
||||
${this.hass.localize("ui.common.undo")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiUndo}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._redo}
|
||||
<ha-svg-icon slot="icon" .path=${mdiUndo}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="redo"
|
||||
.disabled=${!this._undoRedoController.canRedo}
|
||||
>
|
||||
${this.hass.localize("ui.common.redo")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRedo}></ha-svg-icon>
|
||||
</ha-list-item>`
|
||||
<ha-svg-icon slot="icon" .path=${mdiRedo}></ha-svg-icon>
|
||||
</ha-dropdown-item>`
|
||||
: nothing}
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._showInfo}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="info">
|
||||
${this.hass.localize("ui.panel.config.automation.editor.show_info")}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiInformationOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._showSettings}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="settings">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.show_settings"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiCog}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiCog}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._editCategory}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="category">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.scene.picker.${this._registryEntry?.categories?.automation ? "edit_category" : "assign_category"}`
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiTag}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiTag}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._runActions}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="run">
|
||||
${this.hass.localize("ui.panel.config.automation.editor.run")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${stateObj && this.narrow
|
||||
? html`<a
|
||||
href="/config/automation/trace/${encodeURIComponent(
|
||||
this._config.id!
|
||||
)}"
|
||||
>
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.show_trace"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiTransitConnection}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
? html`<ha-dropdown-item value="trace">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.show_trace"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiTransitConnection}
|
||||
></ha-svg-icon>
|
||||
</ha-dropdown-item>`
|
||||
: nothing}
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._promptAutomationAlias}
|
||||
<ha-dropdown-item
|
||||
value="rename"
|
||||
.disabled=${this._readOnly ||
|
||||
!this.automationId ||
|
||||
this._mode === "yaml"}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.rename")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
${!useBlueprint
|
||||
? html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
<ha-dropdown-item
|
||||
@click=${this._promptAutomationMode}
|
||||
.disabled=${this._readOnly || this._mode === "yaml"}
|
||||
>
|
||||
@@ -400,18 +379,17 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
"ui.panel.config.automation.editor.change_mode"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiDebugStepOver}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-list-item
|
||||
.disabled=${this._blueprintConfig ||
|
||||
<ha-dropdown-item
|
||||
.disabled=${!!this._blueprintConfig ||
|
||||
(!this._readOnly && !this.automationId)}
|
||||
graphic="icon"
|
||||
@click=${this._duplicate}
|
||||
value="duplicate"
|
||||
>
|
||||
${this.hass.localize(
|
||||
this._readOnly
|
||||
@@ -419,74 +397,60 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
: "ui.panel.config.automation.editor.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${useBlueprint
|
||||
? html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._takeControl}
|
||||
<ha-dropdown-item
|
||||
value="take_control"
|
||||
.disabled=${this._readOnly}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.take_control"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiFileEdit}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiFileEdit}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._mode === "gui"
|
||||
? this._switchYamlMode
|
||||
: this._switchUiMode}
|
||||
>
|
||||
<ha-dropdown-item value="toggle_yaml_mode">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${this._mode === "gui" ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<li divider role="separator"></li>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._toggle}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="disable">
|
||||
${stateObj?.state === "off"
|
||||
? this.hass.localize("ui.panel.config.automation.editor.enable")
|
||||
: this.hass.localize("ui.panel.config.automation.editor.disable")}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${stateObj?.state === "off"
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
<ha-dropdown-item
|
||||
.disabled=${!this.automationId}
|
||||
class=${classMap({ warning: Boolean(this.automationId) })}
|
||||
graphic="icon"
|
||||
@click=${this._deleteConfirm}
|
||||
.variant=${this.automationId ? "danger" : "default"}
|
||||
value="delete"
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.picker.delete")}
|
||||
<ha-svg-icon
|
||||
class=${classMap({ warning: Boolean(this.automationId) })}
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
>
|
||||
</ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
<div
|
||||
class=${this._mode === "yaml" ? "yaml-mode" : ""}
|
||||
@subscribe-automation-config=${this._subscribeAutomationConfig}
|
||||
@@ -1249,6 +1213,63 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
this._undoRedoController.redo();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "undo":
|
||||
this._undo();
|
||||
break;
|
||||
case "redo":
|
||||
this._redo();
|
||||
break;
|
||||
case "info":
|
||||
this._showInfo();
|
||||
break;
|
||||
case "settings":
|
||||
this._showSettings();
|
||||
break;
|
||||
case "category":
|
||||
this._editCategory();
|
||||
break;
|
||||
case "run":
|
||||
this._runActions();
|
||||
break;
|
||||
case "rename":
|
||||
this._promptAutomationAlias();
|
||||
break;
|
||||
case "change_mode":
|
||||
this._promptAutomationMode();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicate();
|
||||
break;
|
||||
case "take_control":
|
||||
this._takeControl();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
if (this._mode === "gui") {
|
||||
this._switchYamlMode();
|
||||
break;
|
||||
}
|
||||
this._switchUiMode();
|
||||
break;
|
||||
case "disable":
|
||||
this._toggle();
|
||||
break;
|
||||
case "delete":
|
||||
this._deleteConfirm();
|
||||
break;
|
||||
case "trace":
|
||||
this._showTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -1301,13 +1322,6 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
margin-inline-end: 8px;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
ha-button-menu a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiDotsVertical,
|
||||
mdiDownload,
|
||||
@@ -15,11 +16,13 @@ import { repeat } from "lit/directives/repeat";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/trace/ha-trace-blueprint-config";
|
||||
import "../../../components/trace/ha-trace-config";
|
||||
import "../../../components/trace/ha-trace-logbook";
|
||||
@@ -104,9 +107,7 @@ export class HaAutomationTrace extends LitElement {
|
||||
appearance="plain"
|
||||
size="small"
|
||||
class="trace-link"
|
||||
href="/config/automation/edit/${encodeURIComponent(
|
||||
stateObj.attributes.id
|
||||
)}"
|
||||
@click=${this._navigateToAutomation}
|
||||
slot="toolbar-icon"
|
||||
>
|
||||
${this.hass.localize(
|
||||
@@ -114,65 +115,50 @@ export class HaAutomationTrace extends LitElement {
|
||||
)}
|
||||
</ha-button>
|
||||
`
|
||||
: ""}
|
||||
<ha-button-menu slot="toolbar-icon">
|
||||
: nothing}
|
||||
<ha-dropdown
|
||||
slot="toolbar-icon"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._showInfo}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="show_info">
|
||||
${this.hass.localize("ui.panel.config.automation.editor.show_info")}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiInformationOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${stateObj?.attributes.id && this.narrow
|
||||
? html`
|
||||
<a
|
||||
class="trace-link"
|
||||
href="/config/automation/edit/${encodeURIComponent(
|
||||
stateObj.attributes.id
|
||||
)}"
|
||||
>
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.trace.edit_automation"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiPencil}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
<ha-dropdown-item value="edit_automation">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.trace.edit_automation"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
|
||||
<li divider role="separator"></li>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-list-item graphic="icon" @click=${this._refreshTraces}>
|
||||
<ha-dropdown-item value="refresh">
|
||||
${this.hass.localize("ui.panel.config.automation.trace.refresh")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRefresh}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRefresh}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!this._trace}
|
||||
@click=${this._downloadTrace}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!this._trace} value="download_trace">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.trace.download_trace"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiDownload}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
|
||||
<div class="toolbar">
|
||||
${this._traces && this._traces.length > 0
|
||||
@@ -520,6 +506,37 @@ export class HaAutomationTrace extends LitElement {
|
||||
fireEvent(this, "hass-more-info", { entityId: this._entityId });
|
||||
}
|
||||
|
||||
private _navigateToAutomation() {
|
||||
if (this._entityId && this.hass.states[this._entityId]) {
|
||||
navigate(
|
||||
`/config/automation/edit/${encodeURIComponent(this.hass.states[this._entityId].attributes.id)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "show_info":
|
||||
this._showInfo();
|
||||
break;
|
||||
case "refresh":
|
||||
this._refreshTraces();
|
||||
break;
|
||||
case "download_trace":
|
||||
this._downloadTrace();
|
||||
break;
|
||||
case "edit_automation":
|
||||
this._navigateToAutomation();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
|
||||
@@ -20,10 +20,11 @@ import { capitalizeFirstLetter } from "../../../../common/string/capitalize-firs
|
||||
import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type {
|
||||
Condition,
|
||||
@@ -36,6 +37,7 @@ import type { Action, Option } from "../../../../data/script";
|
||||
import { showPromptDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
import "../action/ha-automation-action";
|
||||
import type HaAutomationAction from "../action/ha-automation-action";
|
||||
import "../condition/ha-automation-condition";
|
||||
@@ -46,7 +48,6 @@ import {
|
||||
overflowStyles,
|
||||
rowStyles,
|
||||
} from "../styles";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
|
||||
@customElement("ha-automation-option-row")
|
||||
export default class HaAutomationOptionRow extends LitElement {
|
||||
@@ -155,15 +156,12 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
|
||||
${this.option
|
||||
? html`
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
<ha-dropdown
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -171,24 +169,18 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-md-menu-item
|
||||
@click=${this._renameOption}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
@click=${this._duplicateOption}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
|
||||
@@ -197,45 +189,42 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
<ha-dropdown-item
|
||||
value="move_up"
|
||||
.disabled=${this.disabled || !!this.first}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_up"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiArrowUp}
|
||||
></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="move_down"
|
||||
.disabled=${this.disabled || !!this.last}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiArrowDown}
|
||||
></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
></ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-md-menu-item
|
||||
@click=${this._removeOption}
|
||||
class="warning"
|
||||
<ha-dropdown-item
|
||||
value="delete"
|
||||
variant="danger"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
@@ -246,7 +235,6 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -261,8 +249,8 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
`
|
||||
: nothing}
|
||||
${!this.optionsInSidebar ? this._renderContent() : nothing}
|
||||
@@ -361,6 +349,32 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
fireEvent(this, "move-down");
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this._renameOption();
|
||||
break;
|
||||
case "delete":
|
||||
this._removeOption();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicateOption();
|
||||
break;
|
||||
case "move_up":
|
||||
this._moveUp();
|
||||
break;
|
||||
case "move_down":
|
||||
this._moveDown();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _removeOption = () => {
|
||||
if (this.option) {
|
||||
fireEvent(this, "value-changed", {
|
||||
@@ -513,9 +527,6 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
overflowStyles,
|
||||
indentStyle,
|
||||
css`
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
h4 {
|
||||
color: var(--ha-color-text-secondary);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiContentCopy,
|
||||
@@ -16,8 +17,8 @@ import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import { ACTION_BUILDING_BLOCKS } from "../../../../data/action";
|
||||
import type { ActionSidebarConfig } from "../../../../data/automation";
|
||||
import { domainToName } from "../../../../data/integration";
|
||||
@@ -116,6 +117,7 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle"
|
||||
@@ -126,38 +128,35 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
: ""}</span
|
||||
>
|
||||
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.run}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
|
||||
<ha-dropdown-item slot="menu-items" value="run">
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize("ui.panel.config.automation.editor.actions.run")}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
value="rename"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
</ha-dropdown-item>
|
||||
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.duplicate}
|
||||
value="duplicate"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -166,9 +165,9 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item slot="menu-items" value="copy">
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -178,7 +177,6 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -190,13 +188,13 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.cut}
|
||||
value="cut"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -206,7 +204,6 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -218,32 +215,29 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
</ha-dropdown-item>
|
||||
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.disable}
|
||||
value="disable"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -252,14 +246,14 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
value="delete"
|
||||
variant="danger"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -269,7 +263,6 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -285,7 +278,7 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
${description && !this.yamlMode
|
||||
? html`<div class="description">${description}</div>`
|
||||
: keyed(
|
||||
@@ -341,6 +334,41 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this.config.rename();
|
||||
break;
|
||||
case "run":
|
||||
this.config.run();
|
||||
break;
|
||||
case "duplicate":
|
||||
this.config.duplicate();
|
||||
break;
|
||||
case "copy":
|
||||
this.config.copy();
|
||||
break;
|
||||
case "cut":
|
||||
this.config.cut();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "disable":
|
||||
this.config.disable();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [sidebarEditorStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,16 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { preventDefaultStopPropagation } from "../../../../common/dom/prevent_default_stop_propagation";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import { ScrollableFadeMixin } from "../../../../mixins/scrollable-fade-mixin";
|
||||
import { haStyleScrollbar } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../ha-automation-editor-warning";
|
||||
import { ScrollableFadeMixin } from "../../../../mixins/scrollable-fade-mixin";
|
||||
|
||||
export interface SidebarOverflowMenuEntry {
|
||||
clickAction: () => void;
|
||||
@@ -36,6 +36,10 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
|
||||
|
||||
@property({ attribute: false }) public warnings?: string[];
|
||||
|
||||
@property({ attribute: false }) public handleDropdownSelect!: (
|
||||
ev: CustomEvent
|
||||
) => void;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@query(".card-content") private _contentElement!: HTMLDivElement;
|
||||
@@ -63,14 +67,10 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
|
||||
<slot slot="title" name="title"></slot>
|
||||
<slot slot="subtitle" name="subtitle"></slot>
|
||||
<slot name="overflow-menu" slot="actionItems">
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
@click=${this._openOverflowMenu}
|
||||
<ha-dropdown
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
.positioning=${this.narrow ? "absolute" : "fixed"}
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -78,7 +78,7 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<slot name="menu-items"></slot>
|
||||
</ha-md-button-menu>
|
||||
</ha-dropdown>
|
||||
</slot>
|
||||
</ha-dialog-header>
|
||||
${this.warnings
|
||||
@@ -100,11 +100,6 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
|
||||
fireEvent(this, "close-sidebar");
|
||||
}
|
||||
|
||||
private _openOverflowMenu(ev: MouseEvent) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
...super.styles,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiContentCopy,
|
||||
@@ -16,9 +17,11 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import type {
|
||||
LegacyCondition,
|
||||
ConditionSidebarConfig,
|
||||
LegacyCondition,
|
||||
} from "../../../../data/automation";
|
||||
import { testCondition } from "../../../../data/automation";
|
||||
import {
|
||||
@@ -117,6 +120,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle"
|
||||
@@ -124,42 +128,38 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
? ` (${this.hass.localize("ui.panel.config.automation.editor.actions.disabled")})`
|
||||
: ""}</span
|
||||
>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this._testCondition}>
|
||||
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
|
||||
<ha-dropdown-item slot="menu-items" value="test">
|
||||
<ha-svg-icon slot="icon" .path=${mdiFlask}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.test"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
value="rename"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.duplicate}
|
||||
value="duplicate"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -168,10 +168,10 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<ha-dropdown-item slot="menu-items" value="copy">
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -181,7 +181,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -193,14 +192,14 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.cut}
|
||||
value="cut"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -210,7 +209,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -222,32 +220,29 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
</ha-dropdown-item>
|
||||
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.disable}
|
||||
value="disable"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -256,14 +251,14 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
value="delete"
|
||||
variant="danger"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -273,7 +268,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -289,7 +283,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
${description && !this.yamlMode
|
||||
? html`<div class="description">${description}</div>`
|
||||
: keyed(
|
||||
@@ -419,6 +413,41 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this.config.rename();
|
||||
break;
|
||||
case "test":
|
||||
this._testCondition();
|
||||
break;
|
||||
case "duplicate":
|
||||
this.config.duplicate();
|
||||
break;
|
||||
case "copy":
|
||||
this.config.copy();
|
||||
break;
|
||||
case "cut":
|
||||
this.config.cut();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "disable":
|
||||
this.config.disable();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [
|
||||
sidebarEditorStyles,
|
||||
overflowStyles,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiDelete,
|
||||
@@ -6,8 +7,9 @@ import {
|
||||
} from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type { OptionSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@@ -50,33 +52,34 @@ export default class HaAutomationSidebarOption extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.isWide=${this.isWide}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
${this.config.defaultOption
|
||||
? html`<span slot="overflow-menu"></span>`
|
||||
: html`
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
value="rename"
|
||||
.disabled=${!!disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
@click=${this.config.duplicate}
|
||||
value="duplicate"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -85,19 +88,15 @@ export default class HaAutomationSidebarOption extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
</ha-dropdown-item>
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
value="delete"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
variant="danger"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
|
||||
@@ -123,13 +122,33 @@ export default class HaAutomationSidebarOption extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
`}
|
||||
|
||||
<div class="description">${description}</div>
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this.config.rename();
|
||||
break;
|
||||
case "duplicate":
|
||||
this.config.duplicate();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [sidebarEditorStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
@@ -62,29 +64,30 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
value="delete"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
variant="danger"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -94,7 +97,6 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -110,7 +112,7 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-script-field-selector-editor
|
||||
@@ -160,6 +162,23 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = sidebarEditorStyles;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
@@ -56,28 +58,29 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
value="delete"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
variant="danger"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -87,7 +90,6 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -103,7 +105,7 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-script-field-editor
|
||||
@@ -154,6 +156,23 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [sidebarEditorStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiContentCopy,
|
||||
@@ -15,6 +16,8 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import type {
|
||||
LegacyTrigger,
|
||||
TriggerSidebarConfig,
|
||||
@@ -99,6 +102,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle"
|
||||
@@ -106,60 +110,56 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
? ` (${this.hass.localize("ui.panel.config.automation.editor.actions.disabled")})`
|
||||
: ""}</span
|
||||
>
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
value="rename"
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${!this.yamlMode &&
|
||||
!("id" in this.config.config) &&
|
||||
!this._requestShowId
|
||||
? html`<ha-md-menu-item
|
||||
? html`<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._showTriggerId}
|
||||
value="show_id"
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiIdentifier}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiIdentifier}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.edit_id"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>`
|
||||
</ha-dropdown-item>`
|
||||
: nothing}
|
||||
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.duplicate}
|
||||
value="duplicate"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<ha-dropdown-item slot="menu-items" value="copy">
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -169,7 +169,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -181,14 +180,14 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.cut}
|
||||
value="cut"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -198,7 +197,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -210,32 +208,28 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
</ha-dropdown-item>
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.disable}
|
||||
value="disable"
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -244,14 +238,14 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
value="delete"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
variant="danger"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -261,7 +255,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -277,7 +270,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-automation-trigger-editor
|
||||
@@ -335,6 +328,41 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
this._requestShowId = true;
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this.config.rename();
|
||||
break;
|
||||
case "show_id":
|
||||
this._showTriggerId();
|
||||
break;
|
||||
case "duplicate":
|
||||
this.config.duplicate();
|
||||
break;
|
||||
case "copy":
|
||||
this.config.copy();
|
||||
break;
|
||||
case "cut":
|
||||
this.config.cut();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "disable":
|
||||
this.config.disable();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [sidebarEditorStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
@@ -34,11 +35,11 @@ import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { TRIGGER_ICONS } from "../../../../components/ha-trigger-icon";
|
||||
import type {
|
||||
@@ -208,41 +209,35 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
<ha-dropdown
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._renameTrigger}
|
||||
<ha-dropdown-item
|
||||
value="rename"
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
|
||||
@@ -251,13 +246,10 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -266,7 +258,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -277,13 +268,10 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span>C</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -292,7 +280,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -303,51 +290,51 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span>X</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
<ha-dropdown-item
|
||||
value="move_up"
|
||||
.disabled=${this.disabled || !!this.first}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_up"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="move_down"
|
||||
.disabled=${this.disabled || !!this.last}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
<ha-dropdown-item
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!supported || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!yamlMode ? "yaml" : "ui"}`
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
<ha-dropdown-item
|
||||
value="disable"
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${"enabled" in this.trigger && this.trigger.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
@@ -358,15 +345,15 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
`ui.panel.config.automation.editor.actions.${"enabled" in this.trigger && this.trigger.enabled === false ? "enable" : "disable"}`
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDelete}
|
||||
class="warning"
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="delete"
|
||||
variant="danger"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
@@ -377,7 +364,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -392,8 +378,8 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
${!this.optionsInSidebar
|
||||
? html`${this._warnings
|
||||
? html`<ha-automation-editor-warning
|
||||
@@ -804,6 +790,44 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
this._automationRowElement?.focus();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this._renameTrigger();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicateTrigger();
|
||||
break;
|
||||
case "copy":
|
||||
this._copyTrigger();
|
||||
break;
|
||||
case "cut":
|
||||
this._cutTrigger();
|
||||
break;
|
||||
case "move_up":
|
||||
this._moveUp();
|
||||
break;
|
||||
case "move_down":
|
||||
this._moveDown();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode(ev.target as HTMLElement);
|
||||
break;
|
||||
case "disable":
|
||||
this._onDisable();
|
||||
break;
|
||||
case "delete":
|
||||
this._onDelete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
rowStyles,
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
type Trigger,
|
||||
type TriggerList,
|
||||
} from "../../../../data/automation";
|
||||
import { subscribeLabFeatures } from "../../../../data/labs";
|
||||
import { subscribeLabFeature } from "../../../../data/labs";
|
||||
import type { TriggerDescriptions } from "../../../../data/trigger";
|
||||
import { isTriggerList, subscribeTriggers } from "../../../../data/trigger";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
@@ -85,14 +85,14 @@ export default class HaAutomationTrigger extends SubscribeMixin(LitElement) {
|
||||
|
||||
protected hassSubscribe() {
|
||||
return [
|
||||
subscribeLabFeatures(this.hass!.connection, (features) => {
|
||||
this._newTriggersAndConditions =
|
||||
features.find(
|
||||
(feature) =>
|
||||
feature.domain === "automation" &&
|
||||
feature.preview_feature === "new_triggers_conditions"
|
||||
)?.enabled ?? false;
|
||||
}),
|
||||
subscribeLabFeature(
|
||||
this.hass!.connection,
|
||||
"automation",
|
||||
"new_triggers_conditions",
|
||||
(enabled) => {
|
||||
this._newTriggersAndConditions = enabled;
|
||||
}
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -120,9 +120,6 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) {
|
||||
icon: category.icon || undefined,
|
||||
icon_path: category.icon ? undefined : mdiTag,
|
||||
sorting_label: category.name,
|
||||
search_labels: [category.name, category.category_id].filter(
|
||||
(v): v is string => Boolean(v)
|
||||
),
|
||||
}));
|
||||
|
||||
return items;
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import {
|
||||
mdiCheckboxBlankOutline,
|
||||
mdiCheckboxMarked,
|
||||
mdiDotsVertical,
|
||||
mdiLocationEnter,
|
||||
mdiLocationExit,
|
||||
mdiRefresh,
|
||||
} from "@mdi/js";
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical, mdiRefresh } from "@mdi/js";
|
||||
import type { HassEntities } from "home-assistant-js-websocket";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-bar";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-check-list-item";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-metric";
|
||||
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
||||
import type {
|
||||
@@ -35,9 +33,6 @@ import "../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../dashboard/ha-config-updates";
|
||||
import { showJoinBetaDialog } from "./updates/show-dialog-join-beta";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
|
||||
@customElement("ha-config-section-updates")
|
||||
class HaConfigSectionUpdates extends LitElement {
|
||||
@@ -78,44 +73,35 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
.path=${mdiRefresh}
|
||||
@click=${this._checkUpdates}
|
||||
></ha-icon-button>
|
||||
<ha-dropdown @wa-select=${this._handleOverflowAction}>
|
||||
<ha-button-menu multi>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-dropdown-item value="show_skipped">
|
||||
<ha-svg-icon
|
||||
.path=${this._showSkipped
|
||||
? mdiCheckboxMarked
|
||||
: mdiCheckboxBlankOutline}
|
||||
slot="icon"
|
||||
></ha-svg-icon>
|
||||
<ha-check-list-item
|
||||
left
|
||||
@request-selected=${this._toggleSkipped}
|
||||
.selected=${this._showSkipped}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.updates.show_skipped")}
|
||||
</ha-dropdown-item>
|
||||
</ha-check-list-item>
|
||||
${this._supervisorInfo
|
||||
? html`
|
||||
<wa-divider></wa-divider>
|
||||
<ha-dropdown-item
|
||||
value="toggle_beta"
|
||||
<li divider role="separator"></li>
|
||||
<ha-list-item
|
||||
@request-selected=${this._toggleBeta}
|
||||
.disabled=${this._supervisorInfo.channel === "dev"}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${this._supervisorInfo.channel === "stable"
|
||||
? mdiLocationEnter
|
||||
: mdiLocationExit}
|
||||
slot="icon"
|
||||
></ha-svg-icon>
|
||||
${this._supervisorInfo.channel === "stable"
|
||||
? this.hass.localize("ui.panel.config.updates.join_beta")
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.updates.leave_beta"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-list-item>
|
||||
`
|
||||
: ""}
|
||||
</ha-dropdown>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
<div class="content">
|
||||
<ha-card outlined>
|
||||
@@ -147,19 +133,27 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
this._supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
}
|
||||
|
||||
private async _handleOverflowAction(
|
||||
ev: CustomEvent<{ item: { value: string } }>
|
||||
private _toggleSkipped(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (ev.detail.source !== "property") {
|
||||
return;
|
||||
}
|
||||
|
||||
this._showSkipped = !this._showSkipped;
|
||||
}
|
||||
|
||||
private async _toggleBeta(
|
||||
ev: CustomEvent<RequestSelectedDetail>
|
||||
): Promise<void> {
|
||||
if (ev.detail.item.value === "toggle_beta") {
|
||||
if (this._supervisorInfo!.channel === "stable") {
|
||||
showJoinBetaDialog(this, {
|
||||
join: async () => this._setChannel("beta"),
|
||||
});
|
||||
} else {
|
||||
this._setChannel("stable");
|
||||
}
|
||||
} else if (ev.detail.item.value === "show_skipped") {
|
||||
this._showSkipped = !this._showSkipped;
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._supervisorInfo!.channel === "stable") {
|
||||
showJoinBetaDialog(this, {
|
||||
join: async () => this._setChannel("beta"),
|
||||
});
|
||||
} else {
|
||||
this._setChannel("stable");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ export class MatterAddDevice extends HTMLElement {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
connectedCallback() {
|
||||
showMatterAddDeviceDialog(this);
|
||||
navigate(`/config/devices`, {
|
||||
navigate("/config/devices/dashboard", {
|
||||
replace: true,
|
||||
});
|
||||
showMatterAddDeviceDialog(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
@@ -23,18 +24,19 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
|
||||
import { transform } from "../../../common/decorators/transform";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { goBack, navigate } from "../../../common/navigate";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { promiseTimeout } from "../../../common/util/promise-timeout";
|
||||
import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-fab";
|
||||
|
||||
import { transform } from "../../../common/decorators/transform";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import { substituteBlueprint } from "../../../data/blueprint";
|
||||
@@ -65,7 +67,6 @@ import "../../../layouts/hass-subpage";
|
||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||
import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { Entries, HomeAssistant, Route } from "../../../types";
|
||||
import { isMac } from "../../../util/is_mac";
|
||||
@@ -73,11 +74,11 @@ import { showToast } from "../../../util/toast";
|
||||
import { showAutomationModeDialog } from "../automation/automation-mode-dialog/show-dialog-automation-mode";
|
||||
import type { EntityRegistryUpdate } from "../automation/automation-save-dialog/show-dialog-automation-save";
|
||||
import { showAutomationSaveDialog } from "../automation/automation-save-dialog/show-dialog-automation-save";
|
||||
import { showAutomationSaveTimeoutDialog } from "../automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout";
|
||||
import { showAssignCategoryDialog } from "../category/show-dialog-assign-category";
|
||||
import "./blueprint-script-editor";
|
||||
import "./manual-script-editor";
|
||||
import type { HaManualScriptEditor } from "./manual-script-editor";
|
||||
import { showAutomationSaveTimeoutDialog } from "../automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout";
|
||||
|
||||
@customElement("ha-script-editor")
|
||||
export class HaScriptEditor extends SubscribeMixin(
|
||||
@@ -241,7 +242,10 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
</ha-button>
|
||||
`
|
||||
: ""}
|
||||
<ha-button-menu slot="toolbar-icon">
|
||||
<ha-dropdown
|
||||
slot="toolbar-icon"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
@@ -249,133 +253,107 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
></ha-icon-button>
|
||||
|
||||
${this._mode === "gui" && this.narrow
|
||||
? html`<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._undo}
|
||||
? html`<ha-dropdown-item
|
||||
value="undo"
|
||||
.disabled=${!this._undoRedoController.canUndo}
|
||||
>
|
||||
${this.hass.localize("ui.common.undo")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiUndo}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._redo}
|
||||
<ha-svg-icon slot="icon" .path=${mdiUndo}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="redo"
|
||||
.disabled=${!this._undoRedoController.canRedo}
|
||||
>
|
||||
${this.hass.localize("ui.common.redo")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRedo}></ha-svg-icon>
|
||||
</ha-list-item>`
|
||||
<ha-svg-icon slot="icon" .path=${mdiRedo}></ha-svg-icon>
|
||||
</ha-dropdown-item>`
|
||||
: nothing}
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!this.scriptId}
|
||||
@click=${this._showInfo}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!this.scriptId} value="info">
|
||||
${this.hass.localize("ui.panel.config.script.editor.show_info")}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiInformationOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._showSettings}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="settings">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.show_settings"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiCog}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiCog}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._editCategory}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="category">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.scene.picker.${this._registryEntry?.categories?.script ? "edit_category" : "assign_category"}`
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiTag}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiTag}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!this.scriptId}
|
||||
@click=${this._runScript}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!this.scriptId} value="run">
|
||||
${this.hass.localize("ui.panel.config.script.picker.run_script")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${this.scriptId && this.narrow
|
||||
? html`
|
||||
<a href="/config/script/trace/${this.scriptId}">
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.show_trace"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiTransitConnection}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`
|
||||
? html`<ha-dropdown-item value="trace">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.show_trace"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiTransitConnection}
|
||||
></ha-svg-icon>
|
||||
</ha-dropdown-item>`
|
||||
: nothing}
|
||||
${!useBlueprint && !("fields" in this._config)
|
||||
? html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
<ha-dropdown-item
|
||||
.disabled=${this._readOnly || this._mode === "yaml"}
|
||||
@click=${this._addFields}
|
||||
value="add_fields"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.field.add_fields"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiFormTextbox}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._promptScriptAlias}
|
||||
<ha-dropdown-item
|
||||
value="rename"
|
||||
.disabled=${!this.scriptId ||
|
||||
this._readOnly ||
|
||||
this._mode === "yaml"}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.script.editor.rename")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
${!useBlueprint
|
||||
? html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._promptScriptMode}
|
||||
<ha-dropdown-item
|
||||
value="change_mode"
|
||||
.disabled=${this._readOnly || this._mode === "yaml"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.change_mode"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiDebugStepOver}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-list-item
|
||||
.disabled=${this._blueprintConfig ||
|
||||
<ha-dropdown-item
|
||||
.disabled=${!!this._blueprintConfig ||
|
||||
(!this._readOnly && !this.scriptId)}
|
||||
graphic="icon"
|
||||
@click=${this._duplicate}
|
||||
value="duplicate"
|
||||
>
|
||||
${this.hass.localize(
|
||||
this._readOnly
|
||||
@@ -383,58 +361,48 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
: "ui.panel.config.script.editor.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${useBlueprint
|
||||
? html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._takeControl}
|
||||
<ha-dropdown-item
|
||||
value="take_control"
|
||||
.disabled=${this._readOnly}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.take_control"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiFileEdit}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiFileEdit}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._mode === "gui"
|
||||
? this._switchYamlMode
|
||||
: this._switchUiMode}
|
||||
>
|
||||
<ha-dropdown-item value="toggle_yaml_mode">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${this._mode === "gui" ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<li divider role="separator"></li>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-list-item
|
||||
<ha-dropdown-item
|
||||
.disabled=${this._readOnly || !this.scriptId}
|
||||
class=${classMap({ warning: Boolean(this.scriptId) })}
|
||||
graphic="icon"
|
||||
@click=${this._deleteConfirm}
|
||||
value="delete"
|
||||
.variant=${this.scriptId ? "danger" : "default"}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.script.picker.delete")}
|
||||
<ha-svg-icon
|
||||
class=${classMap({ warning: Boolean(this.scriptId) })}
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
>
|
||||
</ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
<div class=${this._mode === "yaml" ? "yaml-mode" : ""}>
|
||||
${this._mode === "gui"
|
||||
? html`
|
||||
@@ -688,9 +656,7 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
private async _runScript(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
private async _runScript() {
|
||||
if (hasScriptFields(this.hass, this._entityId!)) {
|
||||
showMoreInfoDialog(this, {
|
||||
entityId: this._entityId!,
|
||||
@@ -1155,6 +1121,63 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
this._undoRedoController.redo();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "undo":
|
||||
this._undo();
|
||||
break;
|
||||
case "redo":
|
||||
this._redo();
|
||||
break;
|
||||
case "info":
|
||||
this._showInfo();
|
||||
break;
|
||||
case "settings":
|
||||
this._showSettings();
|
||||
break;
|
||||
case "category":
|
||||
this._editCategory();
|
||||
break;
|
||||
case "run":
|
||||
this._runScript();
|
||||
break;
|
||||
case "add_fields":
|
||||
this._addFields();
|
||||
break;
|
||||
case "rename":
|
||||
this._promptScriptAlias();
|
||||
break;
|
||||
case "change_mode":
|
||||
this._promptScriptMode();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicate();
|
||||
break;
|
||||
case "take_control":
|
||||
this._takeControl();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
if (this._mode === "gui") {
|
||||
this._switchYamlMode();
|
||||
break;
|
||||
}
|
||||
this._switchUiMode();
|
||||
break;
|
||||
case "delete":
|
||||
this._deleteConfirm();
|
||||
break;
|
||||
case "trace":
|
||||
this._showTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -1245,9 +1268,6 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
ha-fab.dirty {
|
||||
bottom: calc(16px + var(--safe-area-inset-bottom, 0px));
|
||||
}
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
margin: 16px 0;
|
||||
@@ -1261,10 +1281,6 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
.header a {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-button-menu a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-tooltip ha-svg-icon {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
@@ -15,18 +15,19 @@ import type { LocalizeKeys } from "../../../common/translations/localize";
|
||||
import "../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../components/ha-automation-row";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-md-button-menu";
|
||||
import "../../../components/ha-md-menu-item";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../data/automation";
|
||||
import type { Field } from "../../../data/script";
|
||||
import { SELECTOR_SELECTOR_BUILDING_BLOCKS } from "../../../data/selector/selector_selector";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { isMac } from "../../../util/is_mac";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { indentStyle, overflowStyles } from "../automation/styles";
|
||||
import "./ha-script-field-selector-editor";
|
||||
import type HaScriptFieldSelectorEditor from "./ha-script-field-selector-editor";
|
||||
import { showToast } from "../../../util/toast";
|
||||
|
||||
@customElement("ha-script-field-row")
|
||||
export default class HaScriptFieldRow extends LitElement {
|
||||
@@ -79,36 +80,33 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
.highlight=${this.highlight}
|
||||
@delete-row=${this._onDelete}
|
||||
>
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
<ha-dropdown
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-md-menu-item .clickAction=${this._toggleYamlMode}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-dropdown-item value="toggle_yaml_mode">
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDelete}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="delete"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
variant="danger"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -118,7 +116,6 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -134,8 +131,8 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
|
||||
<h3 slot="header">${this.key}</h3>
|
||||
|
||||
@@ -170,27 +167,21 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
"ui.panel.config.script.editor.field.selector"
|
||||
)}
|
||||
</h3>
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
<ha-dropdown
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
selector-row
|
||||
>
|
||||
<ha-dropdown-item value="toggle_yaml_mode" selector-row>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlaylistEdit}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -201,16 +192,13 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
class="shortcut-placeholder ${isMac ? "mac" : ""}"
|
||||
></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDelete}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="delete"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
variant="danger"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -220,7 +208,6 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -236,8 +223,8 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</ha-automation-row>
|
||||
</ha-card>
|
||||
${typeof this.field.selector === "object" &&
|
||||
@@ -420,6 +407,23 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
this._selectorRowElement?.focus();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode(ev.target as HTMLElement);
|
||||
break;
|
||||
case "delete":
|
||||
this._onDelete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -477,9 +481,6 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
.selected_menu_item {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
.selector-row {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 16px;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiDotsVertical,
|
||||
mdiDownload,
|
||||
@@ -8,17 +9,19 @@ import {
|
||||
mdiRefresh,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-button-menu";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/trace/ha-trace-blueprint-config";
|
||||
import "../../../components/trace/ha-trace-config";
|
||||
import "../../../components/trace/ha-trace-logbook";
|
||||
@@ -104,7 +107,7 @@ export class HaScriptTrace extends LitElement {
|
||||
? html`
|
||||
<ha-button
|
||||
class="trace-link"
|
||||
href="/config/script/edit/${this.scriptId}"
|
||||
@click=${this._navigateToScript}
|
||||
slot="toolbar-icon"
|
||||
appearance="plain"
|
||||
>
|
||||
@@ -113,64 +116,49 @@ export class HaScriptTrace extends LitElement {
|
||||
)}
|
||||
</ha-button>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
|
||||
<ha-button-menu slot="toolbar-icon">
|
||||
<ha-dropdown
|
||||
slot="toolbar-icon"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._showInfo}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="show_info">
|
||||
${this.hass.localize("ui.panel.config.script.editor.show_info")}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiInformationOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${this.narrow && this.scriptId
|
||||
? html`
|
||||
<a
|
||||
class="trace-link"
|
||||
href="/config/script/edit/${this.scriptId}"
|
||||
>
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.trace.edit_script"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiPencil}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
? html`<ha-dropdown-item value="edit_script">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.trace.edit_script"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
|
||||
</ha-dropdown-item> `
|
||||
: nothing}
|
||||
|
||||
<li divider role="separator"></li>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-list-item graphic="icon" @click=${this._refreshTraces}>
|
||||
<ha-dropdown-item value="refresh">
|
||||
${this.hass.localize("ui.panel.config.automation.trace.refresh")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRefresh}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRefresh}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!this._trace}
|
||||
@click=${this._downloadTrace}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!this._trace} value="download_trace">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.trace.download_trace"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiDownload}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
|
||||
<div class="toolbar">
|
||||
${this._traces && this._traces.length > 0
|
||||
@@ -530,6 +518,35 @@ export class HaScriptTrace extends LitElement {
|
||||
fireEvent(this, "hass-more-info", { entityId: this._entityId });
|
||||
}
|
||||
|
||||
private _navigateToScript() {
|
||||
if (this.scriptId) {
|
||||
navigate(`/config/script/edit/${this.scriptId}`);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "show_info":
|
||||
this._showInfo();
|
||||
break;
|
||||
case "refresh":
|
||||
this._refreshTraces();
|
||||
break;
|
||||
case "download_trace":
|
||||
this._downloadTrace();
|
||||
break;
|
||||
case "edit_script":
|
||||
this._navigateToScript();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
|
||||
@@ -215,57 +215,62 @@ export class AssistPipelineDebug extends LitElement {
|
||||
? html`
|
||||
<div class="messages">
|
||||
${messages.map((content) =>
|
||||
content.role === "system" || content.role === "tool_result"
|
||||
? html`
|
||||
<ha-expansion-panel
|
||||
class="content-expansion ${content.role}"
|
||||
>
|
||||
<div slot="header">
|
||||
${content.role === "system"
|
||||
? "System"
|
||||
: `Result for ${content.tool_name}`}
|
||||
</div>
|
||||
${content.role === "system"
|
||||
? html`<pre>${content.content}</pre>`
|
||||
: html`
|
||||
<ha-yaml-editor
|
||||
read-only
|
||||
auto-update
|
||||
.value=${content}
|
||||
></ha-yaml-editor>
|
||||
`}
|
||||
</ha-expansion-panel>
|
||||
`
|
||||
: html`
|
||||
${content.content
|
||||
? html`
|
||||
<div class=${`message ${content.role}`}>
|
||||
${content.content}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${content.role === "assistant" &&
|
||||
content.tool_calls?.length
|
||||
? html`
|
||||
<ha-expansion-panel
|
||||
class="content-expansion assistant"
|
||||
>
|
||||
<span slot="header">
|
||||
Call
|
||||
${content.tool_calls.length === 1
|
||||
? content.tool_calls[0].tool_name
|
||||
: `${content.tool_calls.length} tools`}
|
||||
</span>
|
||||
content.role === "system"
|
||||
? content.content
|
||||
? html`
|
||||
<ha-expansion-panel
|
||||
class="content-expansion ${content.role}"
|
||||
>
|
||||
<div slot="header">System</div>
|
||||
<pre>${content.content}</pre>
|
||||
</ha-expansion-panel>
|
||||
`
|
||||
: nothing
|
||||
: content.role === "tool_result"
|
||||
? html`
|
||||
<ha-expansion-panel
|
||||
class="content-expansion ${content.role}"
|
||||
>
|
||||
<div slot="header">
|
||||
Result for ${content.tool_name}
|
||||
</div>
|
||||
<ha-yaml-editor
|
||||
read-only
|
||||
auto-update
|
||||
.value=${content}
|
||||
></ha-yaml-editor>
|
||||
</ha-expansion-panel>
|
||||
`
|
||||
: html`
|
||||
${content.content
|
||||
? html`
|
||||
<div class=${`message ${content.role}`}>
|
||||
${content.content}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${content.role === "assistant" &&
|
||||
content.tool_calls?.length
|
||||
? html`
|
||||
<ha-expansion-panel
|
||||
class="content-expansion assistant"
|
||||
>
|
||||
<span slot="header">
|
||||
Call
|
||||
${content.tool_calls.length === 1
|
||||
? content.tool_calls[0].tool_name
|
||||
: `${content.tool_calls.length} tools`}
|
||||
</span>
|
||||
|
||||
<ha-yaml-editor
|
||||
read-only
|
||||
auto-update
|
||||
.value=${content.tool_calls}
|
||||
></ha-yaml-editor>
|
||||
</ha-expansion-panel>
|
||||
`
|
||||
: nothing}
|
||||
`
|
||||
<ha-yaml-editor
|
||||
read-only
|
||||
auto-update
|
||||
.value=${content.tool_calls}
|
||||
></ha-yaml-editor>
|
||||
</ha-expansion-panel>
|
||||
`
|
||||
: nothing}
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<div style="clear:both"></div>
|
||||
|
||||
@@ -268,8 +268,10 @@ class PanelEnergy extends LitElement {
|
||||
(source) => source.type === "gas"
|
||||
);
|
||||
|
||||
const hasDeviceConsumption = this._prefs.device_consumption.length > 0;
|
||||
|
||||
const views: LovelaceViewConfig[] = [];
|
||||
if (hasEnergy) {
|
||||
if (hasEnergy || hasDeviceConsumption) {
|
||||
views.push(ENERGY_VIEW);
|
||||
}
|
||||
if (hasGas) {
|
||||
|
||||
@@ -8,14 +8,6 @@ import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strat
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
|
||||
|
||||
const sourceHasCost = (source: Record<string, any>): boolean =>
|
||||
Boolean(
|
||||
source.stat_cost ||
|
||||
source.stat_compensation ||
|
||||
source.entity_energy_price ||
|
||||
source.number_energy_price
|
||||
);
|
||||
|
||||
@customElement("energy-overview-view-strategy")
|
||||
export class EnergyOverviewViewStrategy extends ReactiveElement {
|
||||
static async generate(
|
||||
@@ -68,13 +60,6 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
|
||||
(source.type === "battery" && source.stat_rate) ||
|
||||
(source.type === "grid" && source.power?.length)
|
||||
);
|
||||
const hasCost = prefs.energy_sources.some(
|
||||
(source) =>
|
||||
sourceHasCost(source) ||
|
||||
(source.type === "grid" &&
|
||||
(source.flow_from?.some(sourceHasCost) ||
|
||||
source.flow_to?.some(sourceHasCost)))
|
||||
);
|
||||
|
||||
const overviewSection: LovelaceSectionConfig = {
|
||||
type: "grid",
|
||||
@@ -95,7 +80,7 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
|
||||
}
|
||||
view.sections!.push(overviewSection);
|
||||
|
||||
if (hasCost) {
|
||||
if (prefs.energy_sources.length) {
|
||||
view.sections!.push({
|
||||
type: "grid",
|
||||
cards: [
|
||||
|
||||
@@ -56,6 +56,19 @@ export function getSuggestedPeriod(
|
||||
return dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour";
|
||||
}
|
||||
|
||||
function createYAxisLabelFormatter(locale: FrontendLocaleData) {
|
||||
let previousValue: number | undefined;
|
||||
|
||||
return (value: number): string => {
|
||||
const maximumFractionDigits = Math.max(
|
||||
1,
|
||||
-Math.floor(Math.log10(Math.abs(value - (previousValue ?? value) || 1)))
|
||||
);
|
||||
previousValue = value;
|
||||
return formatNumber(value, locale, { maximumFractionDigits });
|
||||
};
|
||||
}
|
||||
|
||||
export function getCommonOptions(
|
||||
start: Date,
|
||||
end: Date,
|
||||
@@ -86,7 +99,7 @@ export function getCommonOptions(
|
||||
align: "left",
|
||||
},
|
||||
axisLabel: {
|
||||
formatter: (value: number) => formatNumber(Math.abs(value), locale),
|
||||
formatter: createYAxisLabelFormatter(locale),
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { List } from "@material/mwc-list/mwc-list";
|
||||
import type { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import {
|
||||
mdiClock,
|
||||
mdiDelete,
|
||||
@@ -18,15 +17,16 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-check-list-item";
|
||||
import "../../../components/ha-checkbox";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-markdown-element";
|
||||
import "../../../components/ha-relative-time";
|
||||
import "../../../components/ha-select";
|
||||
@@ -378,28 +378,29 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
${this._todoListSupportsFeature(
|
||||
TodoListEntityFeature.DELETE_TODO_ITEM
|
||||
)
|
||||
? html`<ha-button-menu
|
||||
@closed=${stopPropagation}
|
||||
fixed
|
||||
@action=${this._handleCompletedMenuAction}
|
||||
? html`<ha-dropdown
|
||||
@wa-select=${this._handleCompletedMenuSelect}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-list-item graphic="icon" class="warning">
|
||||
<ha-dropdown-item
|
||||
value="clear"
|
||||
variant="danger"
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.cards.todo-list.clear_items"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiDeleteSweep}
|
||||
.disabled=${unavailable}
|
||||
>
|
||||
</ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>`
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>`
|
||||
: nothing}
|
||||
</div>`
|
||||
: nothing}
|
||||
@@ -413,33 +414,27 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderMenu(config: TodoListCardConfig, unavailable: boolean) {
|
||||
private _renderMenu(config: TodoListCardConfig, _unavailable: boolean) {
|
||||
return (!config.display_order ||
|
||||
config.display_order === TodoSortMode.NONE) &&
|
||||
this._todoListSupportsFeature(TodoListEntityFeature.MOVE_TODO_ITEM)
|
||||
? html`<ha-button-menu
|
||||
@closed=${stopPropagation}
|
||||
fixed
|
||||
@action=${this._handlePrimaryMenuAction}
|
||||
? html`<ha-dropdown
|
||||
@wa-select=${this._handlePrimaryMenuSelect}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-list-item graphic="icon">
|
||||
<ha-dropdown-item value="reorder">
|
||||
${this.hass!.localize(
|
||||
this._reordering
|
||||
? "ui.panel.lovelace.cards.todo-list.exit_reorder_items"
|
||||
: "ui.panel.lovelace.cards.todo-list.reorder_items"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiSort}
|
||||
.disabled=${unavailable}
|
||||
>
|
||||
</ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>`
|
||||
<ha-svg-icon slot="icon" .path=${mdiSort}> </ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
@@ -641,11 +636,11 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
}
|
||||
|
||||
private _handleCompletedMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._clearCompletedItems();
|
||||
break;
|
||||
private _handleCompletedMenuSelect(
|
||||
ev: CustomEvent<{ item: HaDropdownItem }>
|
||||
) {
|
||||
if (ev.detail?.item?.value === "clear") {
|
||||
this._clearCompletedItems();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,11 +699,9 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
}
|
||||
|
||||
private _handlePrimaryMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._toggleReorder();
|
||||
break;
|
||||
private _handlePrimaryMenuSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
if (ev.detail?.item?.value === "reorder") {
|
||||
this._toggleReorder();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,10 @@ import {
|
||||
subscribeForecast,
|
||||
weatherAttrIcons,
|
||||
weatherSVGStyles,
|
||||
WEATHER_TEMPERATURE_ATTRIBUTES,
|
||||
} from "../../../data/weather";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { round } from "../../../common/number/round";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { computeLovelaceEntityName } from "../common/entity/compute-lovelace-entity-name";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
@@ -266,6 +268,20 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
this._config.name
|
||||
);
|
||||
|
||||
const temperatureFractionDigits = this._config.round_temperature
|
||||
? 0
|
||||
: undefined;
|
||||
|
||||
const isSecondaryInfoAttributeTemperature =
|
||||
this._config?.secondary_info_attribute &&
|
||||
WEATHER_TEMPERATURE_ATTRIBUTES.has(this._config.secondary_info_attribute);
|
||||
|
||||
const isSecondaryInfoNumber =
|
||||
this._config.secondary_info_attribute &&
|
||||
!Number.isNaN(
|
||||
+stateObj.attributes[this._config.secondary_info_attribute]
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
class=${classMap({
|
||||
@@ -312,7 +328,11 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
? html`
|
||||
${formatNumber(
|
||||
stateObj.attributes.temperature,
|
||||
this.hass.locale
|
||||
this.hass.locale,
|
||||
{
|
||||
maximumFractionDigits:
|
||||
temperatureFractionDigits,
|
||||
}
|
||||
)} <span
|
||||
>${getWeatherUnit(
|
||||
this.hass.config,
|
||||
@@ -350,14 +370,26 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
: html`
|
||||
${this.hass.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
this._config.secondary_info_attribute
|
||||
this._config.secondary_info_attribute,
|
||||
temperatureFractionDigits === 0 &&
|
||||
isSecondaryInfoNumber &&
|
||||
isSecondaryInfoAttributeTemperature
|
||||
? round(
|
||||
stateObj.attributes[
|
||||
this._config
|
||||
.secondary_info_attribute
|
||||
],
|
||||
temperatureFractionDigits
|
||||
)
|
||||
: undefined
|
||||
)}
|
||||
`}
|
||||
`
|
||||
: getSecondaryWeatherAttribute(
|
||||
this.hass,
|
||||
stateObj,
|
||||
forecast!
|
||||
forecast!,
|
||||
temperatureFractionDigits
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -425,7 +457,11 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
${this._showValue(item.temperature)
|
||||
? html`${formatNumber(
|
||||
item.temperature,
|
||||
this.hass!.locale
|
||||
this.hass!.locale,
|
||||
{
|
||||
maximumFractionDigits:
|
||||
temperatureFractionDigits,
|
||||
}
|
||||
)}°`
|
||||
: "—"}
|
||||
</div>
|
||||
@@ -433,7 +469,11 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
${this._showValue(item.templow)
|
||||
? html`${formatNumber(
|
||||
item.templow!,
|
||||
this.hass!.locale
|
||||
this.hass!.locale,
|
||||
{
|
||||
maximumFractionDigits:
|
||||
temperatureFractionDigits,
|
||||
}
|
||||
)}°`
|
||||
: hourly
|
||||
? ""
|
||||
|
||||
@@ -597,6 +597,7 @@ export interface WeatherForecastCardConfig extends LovelaceCardConfig {
|
||||
forecast_type?: ForecastType;
|
||||
forecast_slots?: number;
|
||||
secondary_info_attribute?: keyof TranslationDict["ui"]["card"]["weather"]["attributes"];
|
||||
round_temperature?: boolean;
|
||||
theme?: string;
|
||||
tap_action?: ActionConfig;
|
||||
hold_action?: ActionConfig;
|
||||
|
||||
@@ -21,8 +21,8 @@ export const computeLovelaceEntityName = (
|
||||
if (!config) {
|
||||
return stateObj ? computeStateName(stateObj) : "";
|
||||
}
|
||||
if (typeof config === "string") {
|
||||
return config;
|
||||
if (typeof config !== "object") {
|
||||
return String(config);
|
||||
}
|
||||
if (stateObj) {
|
||||
return hass.formatEntityName(stateObj, config);
|
||||
|
||||
@@ -5,10 +5,9 @@ import { computeStateDomain } from "../../../common/entity/compute_state_domain"
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { splitByGroups } from "../../../common/entity/split_by_groups";
|
||||
import { stripPrefixFromEntityName } from "../../../common/entity/strip_prefix_from_entity_name";
|
||||
import { stringCompare } from "../../../common/string/compare";
|
||||
import { orderCompare, stringCompare } from "../../../common/string/compare";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import type { AreasDisplayValue } from "../../../components/ha-areas-display-editor";
|
||||
import { areaCompare } from "../../../data/area_registry";
|
||||
import type {
|
||||
EnergyPreferences,
|
||||
GridSourceTypeEnergyPreference,
|
||||
@@ -572,13 +571,21 @@ export const generateDefaultViewConfig = (
|
||||
|
||||
const areaCards: LovelaceCardConfig[] = [];
|
||||
|
||||
const sortedAreas = Object.keys(splittedByAreaDevice.areasWithEntities).sort(
|
||||
areaCompare(areaEntries, areasPrefs?.order)
|
||||
);
|
||||
const areaIds = Object.keys(areaEntries);
|
||||
|
||||
for (const areaId of sortedAreas) {
|
||||
if (areasPrefs?.order) {
|
||||
const areaOrder = areasPrefs.order;
|
||||
areaIds.sort(orderCompare(areaOrder));
|
||||
}
|
||||
|
||||
for (const areaId of areaIds) {
|
||||
// Skip areas with no entities
|
||||
if (!(areaId in splittedByAreaDevice.areasWithEntities)) {
|
||||
continue;
|
||||
}
|
||||
const areaEntities = splittedByAreaDevice.areasWithEntities[areaId];
|
||||
const area = areaEntries[areaId];
|
||||
|
||||
areaCards.push(
|
||||
...computeCards(
|
||||
hass,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
@@ -12,9 +13,10 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
@@ -41,9 +43,6 @@ export class HuiCardEditMode extends LitElement {
|
||||
@property({ type: Boolean, attribute: "no-move" })
|
||||
public noMove = false;
|
||||
|
||||
@state()
|
||||
public _menuOpened = false;
|
||||
|
||||
@state()
|
||||
public _hover = false;
|
||||
|
||||
@@ -91,8 +90,7 @@ export class HuiCardEditMode extends LitElement {
|
||||
};
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const showOverlay =
|
||||
(this._hover || this._menuOpened || this._focused) && !this.hiddenOverlay;
|
||||
const showOverlay = (this._hover || this._focused) && !this.hiddenOverlay;
|
||||
|
||||
return html`
|
||||
<div class="card-wrapper" inert><slot></slot></div>
|
||||
@@ -115,107 +113,71 @@ export class HuiCardEditMode extends LitElement {
|
||||
<ha-svg-icon .path=${mdiPencil}> </ha-svg-icon>
|
||||
</div>
|
||||
`}
|
||||
<ha-button-menu
|
||||
<ha-dropdown
|
||||
class="more"
|
||||
corner="BOTTOM_END"
|
||||
menu-corner="END"
|
||||
.path=${[this.path!]}
|
||||
@action=${this._handleAction}
|
||||
@opened=${this._handleOpened}
|
||||
@closed=${this._handleClosed}
|
||||
placement="bottom-end"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
|
||||
</ha-icon-button>
|
||||
${this.noEdit
|
||||
? nothing
|
||||
: html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._handleAction}
|
||||
.action=${"edit"}
|
||||
>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPencil}></ha-svg-icon>
|
||||
<ha-dropdown-item value="edit">
|
||||
<ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.edit"
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
`}
|
||||
${this.noDuplicate
|
||||
? nothing
|
||||
: html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._handleAction}
|
||||
.action=${"duplicate"}
|
||||
>
|
||||
<ha-dropdown-item value="duplicate">
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.duplicate"
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
`}
|
||||
${this.noMove
|
||||
? nothing
|
||||
: html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._handleAction}
|
||||
.action=${"copy"}
|
||||
>
|
||||
<ha-dropdown-item value="copy">
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiContentCopy}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.copy"
|
||||
)}
|
||||
</ha-list-item>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._handleAction}
|
||||
.action=${"cut"}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiContentCut}
|
||||
></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item value="cut">
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.cut"
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
`}
|
||||
${this.noDuplicate && this.noEdit && this.noMove
|
||||
? nothing
|
||||
: html`<li divider role="separator"></li>`}
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
class="warning"
|
||||
@click=${this._handleAction}
|
||||
.action=${"delete"}
|
||||
>
|
||||
: html`<wa-divider></wa-divider>`}
|
||||
<ha-dropdown-item value="delete" variant="danger">
|
||||
${this.hass.localize("ui.panel.lovelace.editor.edit_card.delete")}
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleOpened() {
|
||||
this._menuOpened = true;
|
||||
}
|
||||
|
||||
private _handleClosed() {
|
||||
this._menuOpened = false;
|
||||
}
|
||||
|
||||
private _handleOverlayClick(ev): void {
|
||||
if (ev.defaultPrevented) {
|
||||
return;
|
||||
@@ -228,8 +190,14 @@ export class HuiCardEditMode extends LitElement {
|
||||
this._editCard();
|
||||
}
|
||||
|
||||
private _handleAction(ev) {
|
||||
switch (ev.currentTarget.action) {
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "edit":
|
||||
this._editCard();
|
||||
break;
|
||||
@@ -330,14 +298,12 @@ export class HuiCardEditMode extends LitElement {
|
||||
background: var(--secondary-background-color);
|
||||
--mdc-icon-size: 20px;
|
||||
}
|
||||
.more {
|
||||
.more ha-icon-button {
|
||||
position: absolute;
|
||||
right: -6px;
|
||||
top: -6px;
|
||||
inset-inline-end: -6px;
|
||||
inset-inline-start: initial;
|
||||
}
|
||||
.more ha-icon-button {
|
||||
cursor: pointer;
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
background: var(--secondary-background-color);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
@@ -15,10 +15,11 @@ import { customElement, property, queryAssignedElements } from "lit/decorators";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list-item";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import { saveConfig } from "../../../data/lovelace/config/types";
|
||||
import { isStrategyView } from "../../../data/lovelace/config/view";
|
||||
@@ -132,7 +133,10 @@ export class HuiCardOptions extends LitElement {
|
||||
></ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
<ha-button-menu @action=${this._handleAction}>
|
||||
<ha-dropdown
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass!.localize(
|
||||
@@ -140,52 +144,46 @@ export class HuiCardOptions extends LitElement {
|
||||
)}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-list-item graphic="icon">
|
||||
<ha-dropdown-item value="move">
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiFileMoveOutline}
|
||||
></ha-svg-icon>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.move"
|
||||
)}
|
||||
</ha-list-item>
|
||||
<ha-list-item graphic="icon">
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item value="duplicate">
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.duplicate"
|
||||
)}
|
||||
</ha-list-item>
|
||||
<ha-list-item graphic="icon">
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiContentCopy}
|
||||
></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item value="copy">
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.copy"
|
||||
)}
|
||||
</ha-list-item>
|
||||
<ha-list-item graphic="icon">
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiContentCut}
|
||||
></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item value="cut">
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
${this.hass!.localize("ui.panel.lovelace.editor.edit_card.cut")}
|
||||
</ha-list-item>
|
||||
<li divider role="separator"></li>
|
||||
<ha-list-item class="warning" graphic="icon">
|
||||
</ha-dropdown-item>
|
||||
<wa-divider></wa-divider>
|
||||
<ha-dropdown-item value="delete" variant="danger">
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.delete"
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -244,30 +242,31 @@ export class HuiCardOptions extends LitElement {
|
||||
ha-icon-button.move-arrow[disabled] {
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
|
||||
ha-list-item {
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "move":
|
||||
this._moveCard();
|
||||
break;
|
||||
case 1:
|
||||
case "duplicate":
|
||||
this._duplicateCard();
|
||||
break;
|
||||
case 2:
|
||||
case "copy":
|
||||
this._copyCard();
|
||||
break;
|
||||
case 3:
|
||||
case "cut":
|
||||
this._cutCard();
|
||||
break;
|
||||
case 4:
|
||||
case "delete":
|
||||
this._deleteCard();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ const cardConfigStruct = assign(
|
||||
forecast_type: optional(string()),
|
||||
forecast_slots: optional(number()),
|
||||
secondary_info_attribute: optional(string()),
|
||||
round_temperature: optional(boolean()),
|
||||
tap_action: optional(actionConfigStruct),
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
@@ -156,6 +157,10 @@ export class HuiWeatherForecastCardEditor
|
||||
},
|
||||
context: { entity: "entity" },
|
||||
},
|
||||
{
|
||||
name: "round_temperature",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
|
||||
@@ -5,7 +5,6 @@ import { generateEntityFilter } from "../../../../../common/entity/entity_filter
|
||||
import { stripPrefixFromEntityName } from "../../../../../common/entity/strip_prefix_from_entity_name";
|
||||
import { orderCompare } from "../../../../../common/string/compare";
|
||||
import type { AreaRegistryEntry } from "../../../../../data/area_registry";
|
||||
import { areaCompare } from "../../../../../data/area_registry";
|
||||
import type { FloorRegistryEntry } from "../../../../../data/floor_registry";
|
||||
import type { LovelaceCardConfig } from "../../../../../data/lovelace/config/card";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
@@ -287,7 +286,11 @@ export const getAreas = (
|
||||
? areas.filter((area) => !hiddenAreas!.includes(area.area_id))
|
||||
: areas.concat();
|
||||
|
||||
const compare = areaCompare(entries, areasOrder);
|
||||
if (!areasOrder) {
|
||||
return filteredAreas;
|
||||
}
|
||||
|
||||
const compare = orderCompare(areasOrder);
|
||||
|
||||
const sortedAreas = filteredAreas.sort((areaA, areaB) =>
|
||||
compare(areaA.area_id, areaB.area_id)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||
import {
|
||||
mdiChevronDown,
|
||||
@@ -23,6 +24,10 @@ import {
|
||||
extractSearchParam,
|
||||
} from "../../common/url/search-params";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-button-menu";
|
||||
import "../../components/ha-dropdown";
|
||||
import "../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../components/ha-dropdown-item";
|
||||
import "../../components/ha-fab";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-list";
|
||||
@@ -222,47 +227,41 @@ class PanelTodo extends LitElement {
|
||||
${this.hass.localize("ui.panel.todo.create_list")}
|
||||
</ha-list-item>`
|
||||
: nothing}
|
||||
<ha-button-menu slot="actionItems">
|
||||
<ha-dropdown
|
||||
slot="actionItems"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${""}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
${this._conversation(this.hass.config.components)
|
||||
? html`<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._showMoreInfoDialog}
|
||||
.disabled=${!this._entityId}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiInformationOutline} slot="graphic">
|
||||
? html`<ha-dropdown-item value="info" .disabled=${!this._entityId}>
|
||||
<ha-svg-icon .path=${mdiInformationOutline} slot="icon">
|
||||
</ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.todo.information")}
|
||||
</ha-list-item>`
|
||||
</ha-dropdown-item>`
|
||||
: nothing}
|
||||
<li divider role="separator"></li>
|
||||
<ha-list-item graphic="icon" @click=${this._showVoiceCommandDialog}>
|
||||
<ha-svg-icon .path=${mdiCommentProcessingOutline} slot="graphic">
|
||||
<wa-divider></wa-divider>
|
||||
<ha-dropdown-item value="assist">
|
||||
<ha-svg-icon .path=${mdiCommentProcessingOutline} slot="icon">
|
||||
</ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.todo.assist")}
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
${entityRegistryEntry?.platform === "local_todo"
|
||||
? html` <li divider role="separator"></li>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._deleteList}
|
||||
class="warning"
|
||||
? html` <wa-divider></wa-divider>
|
||||
<ha-dropdown-item
|
||||
value="delete"
|
||||
variant="danger"
|
||||
.disabled=${!this._entityId}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${mdiDelete}
|
||||
slot="graphic"
|
||||
class="warning"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDelete} slot="icon" class="warning">
|
||||
</ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.todo.delete_list")}
|
||||
</ha-list-item>`
|
||||
</ha-dropdown-item>`
|
||||
: nothing}
|
||||
</ha-button-menu>
|
||||
</ha-dropdown>
|
||||
<div id="columns">
|
||||
<div class="column">
|
||||
${this._entityId
|
||||
@@ -363,6 +362,26 @@ class PanelTodo extends LitElement {
|
||||
showTodoItemEditDialog(this, { entity: this._entityId! });
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "info":
|
||||
this._showMoreInfoDialog();
|
||||
break;
|
||||
case "assist":
|
||||
this._showVoiceCommandDialog();
|
||||
break;
|
||||
case "delete":
|
||||
this._deleteList();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import Fuse, {
|
||||
type Expression,
|
||||
type FuseIndex,
|
||||
type FuseResult,
|
||||
type FuseSearchOptions,
|
||||
type IFuseOptions,
|
||||
} from "fuse.js";
|
||||
|
||||
export interface FuseKey {
|
||||
getFn: null;
|
||||
id: string;
|
||||
path: string[];
|
||||
src: string;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
const DEFAULT_OPTIONS: IFuseOptions<any> = {
|
||||
ignoreDiacritics: true,
|
||||
isCaseSensitive: false,
|
||||
threshold: 0.3,
|
||||
minMatchCharLength: 2,
|
||||
};
|
||||
|
||||
export class HaFuse<T> extends Fuse<T> {
|
||||
public constructor(
|
||||
list: readonly T[],
|
||||
options?: IFuseOptions<T>,
|
||||
index?: FuseIndex<T>
|
||||
) {
|
||||
const mergedOptions = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...options,
|
||||
};
|
||||
super(list, mergedOptions, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a multi-term search across the indexed data.
|
||||
* Splits the search string into individual terms and performs an AND operation between terms,
|
||||
* where each term is searched across all indexed keys with an OR operation. words with less than
|
||||
* 2 characters are ignored. If no valid terms are found, the search will return null.
|
||||
*
|
||||
* @param search - The search string to split into terms. Terms are space-separated.
|
||||
* @param options - Optional Fuse.js search options to customize the search behavior.
|
||||
* @typeParam R - The type of the result items. Defaults to T (the type of the indexed items).
|
||||
* @returns An array of FuseResult objects containing matched items and their matching information.
|
||||
* If no valid terms are found (after filtering by minimum length), returns all items with empty matches.
|
||||
*/
|
||||
public multiTermsSearch(
|
||||
search: string,
|
||||
options?: FuseSearchOptions
|
||||
): FuseResult<T>[] | null {
|
||||
const terms = search.toLowerCase().split(" ");
|
||||
|
||||
// @ts-expect-error options is not part of the Fuse type
|
||||
const { minMatchCharLength } = this.options as IFuseOptions<T>;
|
||||
|
||||
const filteredTerms = minMatchCharLength
|
||||
? terms.filter((term) => term.length >= minMatchCharLength)
|
||||
: terms;
|
||||
|
||||
if (filteredTerms.length === 0) {
|
||||
// If no valid terms are found, return null to indicate no search was performed
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = this.getIndex().toJSON();
|
||||
const keys = index.keys as unknown as FuseKey[]; // Fuse type for key is not correct
|
||||
|
||||
const expression: Expression = {
|
||||
$and: filteredTerms.map((term) => ({
|
||||
$or: keys.map((key) => ({ $path: key.path, $val: term })),
|
||||
})),
|
||||
};
|
||||
|
||||
return this.search(expression, options);
|
||||
}
|
||||
}
|
||||
242
src/resources/fuseMultiTerm.ts
Normal file
242
src/resources/fuseMultiTerm.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
import type { Expression, FuseIndex, FuseResult, IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
|
||||
export interface FuseWeightedKey<T> {
|
||||
getFn?: (obj: T) => string;
|
||||
name: string | string[];
|
||||
weight?: number;
|
||||
}
|
||||
|
||||
const DEFAULT_OPTIONS: IFuseOptions<any> = {
|
||||
ignoreDiacritics: true,
|
||||
isCaseSensitive: false,
|
||||
threshold: 0.3,
|
||||
minMatchCharLength: 2,
|
||||
};
|
||||
|
||||
const DEFAULT_MIN_CHAR_LENGTH = 2;
|
||||
|
||||
/**
|
||||
* Searches for a term within a collection of items using Fuse.js fuzzy search.
|
||||
* @param items - The array of items to search through.
|
||||
* @param search - The search term to look for.
|
||||
* @param fuseIndex - Optional pre-built Fuse index for improved performance.
|
||||
* @param options - Optional Fuse.js configuration options to override defaults.
|
||||
* @returns An array of search results matching the search term.
|
||||
*/
|
||||
function searchTerm<T>(
|
||||
items: T[],
|
||||
search: string | Expression,
|
||||
fuseIndex?: FuseIndex<T>,
|
||||
options?: IFuseOptions<T>,
|
||||
minMatchCharLength?: number
|
||||
) {
|
||||
const fuse = new Fuse<T>(
|
||||
items,
|
||||
{
|
||||
...DEFAULT_OPTIONS,
|
||||
minMatchCharLength:
|
||||
minMatchCharLength ??
|
||||
(typeof search === "string" && search.length < DEFAULT_MIN_CHAR_LENGTH
|
||||
? search.length
|
||||
: DEFAULT_MIN_CHAR_LENGTH),
|
||||
...(options || {}),
|
||||
},
|
||||
fuseIndex
|
||||
);
|
||||
|
||||
return fuse.search(search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a multi-term search across an array of items using Fuse.js.
|
||||
* All search terms must match for an item to be included in the results.
|
||||
* Result is NOT sorted by relevance.
|
||||
*
|
||||
* @template T - The type of items being searched
|
||||
* @param items - The array of items to search through
|
||||
* @param search - The search string containing one or more space-separated terms
|
||||
* @param searchKeys - An array of weighted keys defining which properties to search
|
||||
* @param fuseIndex - Optional pre-built Fuse index for improved performance
|
||||
* @param options - Optional Fuse.js configuration options
|
||||
* @returns An array of items that match all search terms
|
||||
*/
|
||||
export function multiTermSearch<T>(
|
||||
items: T[],
|
||||
search: string,
|
||||
searchKeys: FuseWeightedKey<T>[],
|
||||
fuseIndex?: FuseIndex<T>,
|
||||
options: IFuseOptions<T> = {}
|
||||
): T[] {
|
||||
const terms = search
|
||||
.toLowerCase()
|
||||
.split(" ")
|
||||
.filter((t) => t.trim());
|
||||
|
||||
if (!terms.length) {
|
||||
return items;
|
||||
}
|
||||
|
||||
// be sure that all terms are used in the search
|
||||
// just use DEFAULT_MIN_CHAR_LENGTH if the terms are at least that long
|
||||
let minLength = DEFAULT_MIN_CHAR_LENGTH;
|
||||
terms.forEach((term) => {
|
||||
if (term.length < minLength) {
|
||||
minLength = term.length;
|
||||
}
|
||||
});
|
||||
|
||||
const expression: Expression = {
|
||||
$and: terms.map((term) => ({
|
||||
$or: searchKeys.map((key) => ({
|
||||
$path: typeof key.name === "string" ? key.name : key.name.join("."),
|
||||
$val: term,
|
||||
})),
|
||||
})),
|
||||
};
|
||||
|
||||
return searchTerm<T>(
|
||||
items,
|
||||
expression,
|
||||
fuseIndex,
|
||||
{
|
||||
...options,
|
||||
shouldSort: false,
|
||||
},
|
||||
minLength
|
||||
).map((r) => r.item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a multi-term search across items using Fuse.js, returning results sorted by relevance.
|
||||
*
|
||||
* This function splits the search string into individual terms and searches for each term
|
||||
* independently. Results are aggregated and scored based on:
|
||||
* - Number of terms matched (items must match ALL terms to be included)
|
||||
* - Fuse.js match score for each term
|
||||
* - Weight of the matched keys
|
||||
*
|
||||
* @template T - The type of items being searched
|
||||
* @param items - The array of items to search through
|
||||
* @param search - The search string, which will be split by spaces into multiple terms
|
||||
* @param searchKeys - Array of weighted keys configuration for Fuse.js search
|
||||
* @param getItemId - Function to extract a unique identifier from each item
|
||||
* @param fuseIndex - Optional but highly recommended! Pre-built Fuse.js index for improved performance
|
||||
* @param options - Optional Fuse.js options to customize search behavior
|
||||
* @returns An array of items that match all search terms, sorted by relevance score (highest first).
|
||||
* Returns all items if search is empty, or empty array if not all terms have matches.
|
||||
*/
|
||||
export function multiTermSortedSearch<T>(
|
||||
items: T[],
|
||||
search: string,
|
||||
searchKeys: FuseWeightedKey<T>[],
|
||||
getItemId: (item: T) => string,
|
||||
fuseIndex?: FuseIndex<T>,
|
||||
options: IFuseOptions<T> = {}
|
||||
) {
|
||||
const terms = search
|
||||
.toLowerCase()
|
||||
.split(" ")
|
||||
.filter((t) => t.trim());
|
||||
|
||||
if (!terms.length) {
|
||||
return items;
|
||||
}
|
||||
|
||||
if (terms.length === 1) {
|
||||
return searchTerm<T>(items, terms[0], fuseIndex, options).map(
|
||||
(r) => r.item
|
||||
);
|
||||
}
|
||||
|
||||
const searchResults: Record<
|
||||
string,
|
||||
{ item: T; hits: number; score: number }
|
||||
> = {};
|
||||
|
||||
let termHits = 0;
|
||||
|
||||
terms.forEach((term) => {
|
||||
if (!term.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const termResults = searchTerm<T>(items, term, fuseIndex, {
|
||||
...options,
|
||||
shouldSort: false,
|
||||
includeScore: true,
|
||||
includeMatches: true,
|
||||
});
|
||||
|
||||
if (termResults.length) {
|
||||
termHits++;
|
||||
termResults.forEach((r) => {
|
||||
const itemId = getItemId(r.item);
|
||||
if (!searchResults[itemId]) {
|
||||
searchResults[itemId] = {
|
||||
item: r.item,
|
||||
hits: 0,
|
||||
score: 0,
|
||||
};
|
||||
}
|
||||
|
||||
searchResults[itemId].hits += 1;
|
||||
|
||||
const weight = _getMatchedKeyHighestWeight<T>(r, searchKeys);
|
||||
|
||||
const score = r.score ? 1 - r.score : 0;
|
||||
const weightedScore = score * weight;
|
||||
|
||||
searchResults[itemId].score += weightedScore;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// just return smth if for the full terms combination are results
|
||||
if (termHits !== terms.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Filter to only items that matched all terms
|
||||
const results = Object.values(searchResults).filter(
|
||||
({ hits }) => hits === terms.length
|
||||
);
|
||||
|
||||
// Sort by score descending
|
||||
results.sort((a, b) => b.score - a.score);
|
||||
|
||||
return results.map(({ item }) => item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the highest weight among all matched keys in a Fuse.js search result.
|
||||
*
|
||||
* @typeParam T - The type of items being searched
|
||||
* @param result - A single Fuse.js search result containing match information
|
||||
* @param searchKeys - Array of weighted search keys configured for the Fuse instance
|
||||
* @returns The highest weight value among matched keys, or 1 if no matches exist or no weights are defined
|
||||
*/
|
||||
function _getMatchedKeyHighestWeight<T>(
|
||||
result: FuseResult<T>,
|
||||
searchKeys: FuseWeightedKey<T>[]
|
||||
): number {
|
||||
if (!result.matches || result.matches.length === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Find the highest weighted key that matched
|
||||
let maxWeight = 1;
|
||||
for (const match of result.matches) {
|
||||
const keyConfig = searchKeys.find((k) => {
|
||||
if (typeof k.name === "string") {
|
||||
return k.name === match.key;
|
||||
}
|
||||
return k.name.join(".") === match.key;
|
||||
});
|
||||
if (keyConfig && keyConfig.weight && keyConfig.weight > maxWeight) {
|
||||
maxWeight = keyConfig.weight;
|
||||
}
|
||||
}
|
||||
|
||||
return maxWeight;
|
||||
}
|
||||
@@ -304,6 +304,7 @@
|
||||
},
|
||||
"weather": {
|
||||
"attributes": {
|
||||
"dew_point": "Dew point",
|
||||
"air_pressure": "Air pressure",
|
||||
"humidity": "Humidity",
|
||||
"temperature": "Temperature",
|
||||
@@ -830,7 +831,6 @@
|
||||
"add_new": "Add new area…",
|
||||
"no_areas": "No areas available",
|
||||
"no_match": "No areas found for {term}",
|
||||
"unassigned_areas": "Unassigned areas",
|
||||
"failed_create_area": "Failed to create area."
|
||||
},
|
||||
"floor-picker": {
|
||||
@@ -2463,7 +2463,7 @@
|
||||
"introduction2": "To place devices in an area, use the link below to navigate to the integrations page and then click on a configured integration to get to the device cards.",
|
||||
"integrations_page": "Integrations page",
|
||||
"no_areas": "Looks like you have no areas yet!",
|
||||
"unassigned_areas": "Unassigned areas",
|
||||
"other_areas": "Other areas",
|
||||
"create_area": "Create area",
|
||||
"create_floor": "Create floor",
|
||||
"floor": {
|
||||
@@ -2479,7 +2479,7 @@
|
||||
},
|
||||
"dialog": {
|
||||
"reorder_title": "Reorder floors and areas",
|
||||
"unassigned_areas": "Unassigned areas",
|
||||
"other_areas": "Other areas",
|
||||
"reorder_failed": "Failed to save order",
|
||||
"empty_floor": "No areas on this floor",
|
||||
"empty_unassigned": "All your areas are assigned to floors"
|
||||
@@ -8026,6 +8026,7 @@
|
||||
"suggested_cards": "Suggested cards",
|
||||
"other_cards": "Other cards",
|
||||
"custom_cards": "Custom cards",
|
||||
"round_temperature": "Round temperature",
|
||||
"features": "Features",
|
||||
"actions": "Actions",
|
||||
"content": "Content"
|
||||
|
||||
20
yarn.lock
20
yarn.lock
@@ -1940,9 +1940,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@home-assistant/webawesome@npm:3.0.0-ha.0":
|
||||
version: 3.0.0-ha.0
|
||||
resolution: "@home-assistant/webawesome@npm:3.0.0-ha.0"
|
||||
"@home-assistant/webawesome@npm:3.0.0-ha.1":
|
||||
version: 3.0.0-ha.1
|
||||
resolution: "@home-assistant/webawesome@npm:3.0.0-ha.1"
|
||||
dependencies:
|
||||
"@ctrl/tinycolor": "npm:4.1.0"
|
||||
"@floating-ui/dom": "npm:^1.6.13"
|
||||
@@ -1953,7 +1953,7 @@ __metadata:
|
||||
lit: "npm:^3.2.1"
|
||||
nanoid: "npm:^5.1.5"
|
||||
qr-creator: "npm:^1.0.0"
|
||||
checksum: 10/2034d498d5b26bb0573ebc2c9aadd144604bb48c04becbae0c67b16857d8e5d6562626e795974362c3fc41e9b593a9005595d8b5ff434b1569b2d724af13043b
|
||||
checksum: 10/281f16c2c6c28d95a381de6fca05948a9c67d8184f20844d64ce33dc2caf9e6761d2cf8337b97e7487a71be011ab04f2a021b20b823a20e3c049cc68205de86a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9213,7 +9213,7 @@ __metadata:
|
||||
"@fullcalendar/list": "npm:6.1.19"
|
||||
"@fullcalendar/luxon3": "npm:6.1.19"
|
||||
"@fullcalendar/timegrid": "npm:6.1.19"
|
||||
"@home-assistant/webawesome": "npm:3.0.0-ha.0"
|
||||
"@home-assistant/webawesome": "npm:3.0.0-ha.1"
|
||||
"@lezer/highlight": "npm:1.2.3"
|
||||
"@lit-labs/motion": "npm:1.0.9"
|
||||
"@lit-labs/observers": "npm:2.0.6"
|
||||
@@ -9343,7 +9343,7 @@ __metadata:
|
||||
node-vibrant: "npm:4.0.3"
|
||||
object-hash: "npm:3.0.0"
|
||||
pinst: "npm:3.0.0"
|
||||
prettier: "npm:3.7.2"
|
||||
prettier: "npm:3.7.3"
|
||||
punycode: "npm:2.3.1"
|
||||
qr-scanner: "npm:1.4.2"
|
||||
qrcode: "npm:1.5.4"
|
||||
@@ -12131,12 +12131,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prettier@npm:3.7.2":
|
||||
version: 3.7.2
|
||||
resolution: "prettier@npm:3.7.2"
|
||||
"prettier@npm:3.7.3":
|
||||
version: 3.7.3
|
||||
resolution: "prettier@npm:3.7.3"
|
||||
bin:
|
||||
prettier: bin/prettier.cjs
|
||||
checksum: 10/2d51d3318f2eb80d3339dc884a4425ce7c326722c05be13bc7712403c2b0ddf8b07696776210cb8015e2ede6a737704423749218b2eda27647c92693eda01672
|
||||
checksum: 10/f83ca7e3c69717c1e2f7a95af2d9700eea520c235dfc0f94727a550a4bf0f14eda956a9b2682991b4f1da4605247b3cd3c5547bf2ada4a3ca718bc9ca230040a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user