mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-21 07:27:24 +00:00
Compare commits
1 Commits
20251203.3
...
number-ent
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d215064c6e |
File diff suppressed because one or more lines are too long
@@ -6,4 +6,4 @@ enableGlobalCache: false
|
|||||||
|
|
||||||
nodeLinker: node-modules
|
nodeLinker: node-modules
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-4.12.0.cjs
|
yarnPath: .yarn/releases/yarn-4.11.0.cjs
|
||||||
|
|||||||
@@ -156,9 +156,7 @@ const createTestTranslation = () =>
|
|||||||
*/
|
*/
|
||||||
const createMasterTranslation = () =>
|
const createMasterTranslation = () =>
|
||||||
gulp
|
gulp
|
||||||
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])], {
|
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
|
||||||
allowEmpty: true,
|
|
||||||
})
|
|
||||||
.pipe(new CustomJSON(lokaliseTransform))
|
.pipe(new CustomJSON(lokaliseTransform))
|
||||||
.pipe(new MergeJSON("en"))
|
.pipe(new MergeJSON("en"))
|
||||||
.pipe(gulp.dest(workDir));
|
.pipe(gulp.dest(workDir));
|
||||||
|
|||||||
@@ -44,24 +44,18 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
|
|||||||
number_energy_price: null,
|
number_energy_price: null,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
power: [
|
|
||||||
{ stat_rate: "sensor.power_grid" },
|
|
||||||
{ stat_rate: "sensor.power_grid_return" },
|
|
||||||
],
|
|
||||||
cost_adjustment_day: 0,
|
cost_adjustment_day: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "solar",
|
type: "solar",
|
||||||
stat_energy_from: "sensor.solar_production",
|
stat_energy_from: "sensor.solar_production",
|
||||||
stat_rate: "sensor.power_solar",
|
|
||||||
config_entry_solar_forecast: ["solar_forecast"],
|
config_entry_solar_forecast: ["solar_forecast"],
|
||||||
},
|
},
|
||||||
{
|
/* {
|
||||||
type: "battery",
|
type: "battery",
|
||||||
stat_energy_from: "sensor.battery_output",
|
stat_energy_from: "sensor.battery_output",
|
||||||
stat_energy_to: "sensor.battery_input",
|
stat_energy_to: "sensor.battery_input",
|
||||||
stat_rate: "sensor.power_battery",
|
}, */
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: "gas",
|
type: "gas",
|
||||||
stat_energy_from: "sensor.energy_gas",
|
stat_energy_from: "sensor.energy_gas",
|
||||||
@@ -69,48 +63,28 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
|
|||||||
entity_energy_price: null,
|
entity_energy_price: null,
|
||||||
number_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: [
|
device_consumption: [
|
||||||
{
|
{
|
||||||
stat_consumption: "sensor.energy_car",
|
stat_consumption: "sensor.energy_car",
|
||||||
stat_rate: "sensor.power_car",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stat_consumption: "sensor.energy_ac",
|
stat_consumption: "sensor.energy_ac",
|
||||||
stat_rate: "sensor.power_ac",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stat_consumption: "sensor.energy_washing_machine",
|
stat_consumption: "sensor.energy_washing_machine",
|
||||||
stat_rate: "sensor.power_washing_machine",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stat_consumption: "sensor.energy_dryer",
|
stat_consumption: "sensor.energy_dryer",
|
||||||
stat_rate: "sensor.power_dryer",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stat_consumption: "sensor.energy_heat_pump",
|
stat_consumption: "sensor.energy_heat_pump",
|
||||||
stat_rate: "sensor.power_heat_pump",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stat_consumption: "sensor.energy_boiler",
|
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(
|
hass.mockWS(
|
||||||
|
|||||||
@@ -154,38 +154,6 @@ export const energyEntities = () =>
|
|||||||
unit_of_measurement: "EUR",
|
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": {
|
"sensor.energy_gas_cost": {
|
||||||
entity_id: "sensor.energy_gas_cost",
|
entity_id: "sensor.energy_gas_cost",
|
||||||
state: "2",
|
state: "2",
|
||||||
@@ -203,15 +171,6 @@ export const energyEntities = () =>
|
|||||||
unit_of_measurement: "m³",
|
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": {
|
"sensor.energy_car": {
|
||||||
entity_id: "sensor.energy_car",
|
entity_id: "sensor.energy_car",
|
||||||
state: "4",
|
state: "4",
|
||||||
@@ -266,58 +225,4 @@ export const energyEntities = () =>
|
|||||||
unit_of_measurement: "kWh",
|
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,15 +17,17 @@ const generateMeanStatistics = (
|
|||||||
end: Date,
|
end: Date,
|
||||||
// eslint-disable-next-line default-param-last
|
// eslint-disable-next-line default-param-last
|
||||||
period: "5minute" | "hour" | "day" | "month" = "hour",
|
period: "5minute" | "hour" | "day" | "month" = "hour",
|
||||||
|
initValue: number,
|
||||||
maxDiff: number
|
maxDiff: number
|
||||||
): StatisticValue[] => {
|
): StatisticValue[] => {
|
||||||
const statistics: StatisticValue[] = [];
|
const statistics: StatisticValue[] = [];
|
||||||
let currentDate = new Date(start);
|
let currentDate = new Date(start);
|
||||||
currentDate.setMinutes(0, 0, 0);
|
currentDate.setMinutes(0, 0, 0);
|
||||||
|
let lastVal = initValue;
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
while (end > currentDate && currentDate < now) {
|
while (end > currentDate && currentDate < now) {
|
||||||
const delta = Math.random() * maxDiff;
|
const delta = Math.random() * maxDiff;
|
||||||
const mean = delta;
|
const mean = lastVal + delta;
|
||||||
statistics.push({
|
statistics.push({
|
||||||
start: currentDate.getTime(),
|
start: currentDate.getTime(),
|
||||||
end: currentDate.getTime(),
|
end: currentDate.getTime(),
|
||||||
@@ -36,6 +38,7 @@ const generateMeanStatistics = (
|
|||||||
state: mean,
|
state: mean,
|
||||||
sum: null,
|
sum: null,
|
||||||
});
|
});
|
||||||
|
lastVal = mean;
|
||||||
currentDate =
|
currentDate =
|
||||||
period === "day"
|
period === "day"
|
||||||
? addDays(currentDate, 1)
|
? addDays(currentDate, 1)
|
||||||
@@ -333,6 +336,7 @@ export const mockRecorder = (mockHass: MockHomeAssistant) => {
|
|||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
period,
|
period,
|
||||||
|
state,
|
||||||
state * (state > 80 ? 0.05 : 0.1)
|
state * (state > 80 ? 0.05 : 0.1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { HaEventTrigger } from "../../../../src/panels/config/automation/trigger
|
|||||||
import { HaGeolocationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location";
|
import { HaGeolocationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location";
|
||||||
import { HaHassTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant";
|
import { HaHassTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant";
|
||||||
import { HaTriggerList } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-list";
|
import { HaTriggerList } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-list";
|
||||||
|
import { HaMQTTTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt";
|
||||||
import { HaNumericStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state";
|
import { HaNumericStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state";
|
||||||
import { HaPersistentNotificationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-persistent_notification";
|
import { HaPersistentNotificationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-persistent_notification";
|
||||||
import { HaStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-state";
|
import { HaStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-state";
|
||||||
@@ -37,6 +38,11 @@ const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
|
|||||||
triggers: [{ ...HaStateTrigger.defaultConfig }],
|
triggers: [{ ...HaStateTrigger.defaultConfig }],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "MQTT",
|
||||||
|
triggers: [{ ...HaMQTTTrigger.defaultConfig }],
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "GeoLocation",
|
name: "GeoLocation",
|
||||||
triggers: [{ ...HaGeolocationTrigger.defaultConfig }],
|
triggers: [{ ...HaGeolocationTrigger.defaultConfig }],
|
||||||
|
|||||||
@@ -381,6 +381,10 @@ export class DemoHaWaDialog extends LitElement {
|
|||||||
<td><code>--dialog-z-index</code></td>
|
<td><code>--dialog-z-index</code></td>
|
||||||
<td>Z-index for the dialog.</td>
|
<td>Z-index for the dialog.</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>--dialog-surface-position</code></td>
|
||||||
|
<td>CSS position of the dialog surface.</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>--dialog-surface-margin-top</code></td>
|
<td><code>--dialog-surface-margin-top</code></td>
|
||||||
<td>Top margin for the dialog surface.</td>
|
<td>Top margin for the dialog surface.</td>
|
||||||
|
|||||||
44
package.json
44
package.json
@@ -28,13 +28,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.28.4",
|
"@babel/runtime": "7.28.4",
|
||||||
"@braintree/sanitize-url": "7.1.1",
|
"@braintree/sanitize-url": "7.1.1",
|
||||||
"@codemirror/autocomplete": "6.20.0",
|
"@codemirror/autocomplete": "6.19.1",
|
||||||
"@codemirror/commands": "6.10.0",
|
"@codemirror/commands": "6.10.0",
|
||||||
"@codemirror/language": "6.11.3",
|
"@codemirror/language": "6.11.3",
|
||||||
"@codemirror/legacy-modes": "6.5.2",
|
"@codemirror/legacy-modes": "6.5.2",
|
||||||
"@codemirror/search": "6.5.11",
|
"@codemirror/search": "6.5.11",
|
||||||
"@codemirror/state": "6.5.2",
|
"@codemirror/state": "6.5.2",
|
||||||
"@codemirror/view": "6.38.8",
|
"@codemirror/view": "6.38.6",
|
||||||
"@date-fns/tz": "1.4.1",
|
"@date-fns/tz": "1.4.1",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.18.2",
|
"@formatjs/intl-datetimeformat": "6.18.2",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"@fullcalendar/list": "6.1.19",
|
"@fullcalendar/list": "6.1.19",
|
||||||
"@fullcalendar/luxon3": "6.1.19",
|
"@fullcalendar/luxon3": "6.1.19",
|
||||||
"@fullcalendar/timegrid": "6.1.19",
|
"@fullcalendar/timegrid": "6.1.19",
|
||||||
"@home-assistant/webawesome": "3.0.0-ha.0",
|
"@home-assistant/webawesome": "3.0.0",
|
||||||
"@lezer/highlight": "1.2.3",
|
"@lezer/highlight": "1.2.3",
|
||||||
"@lit-labs/motion": "1.0.9",
|
"@lit-labs/motion": "1.0.9",
|
||||||
"@lit-labs/observers": "2.0.6",
|
"@lit-labs/observers": "2.0.6",
|
||||||
@@ -96,10 +96,10 @@
|
|||||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||||
"app-datepicker": "5.1.1",
|
"app-datepicker": "5.1.1",
|
||||||
"barcode-detector": "3.0.8",
|
"barcode-detector": "3.0.6",
|
||||||
"color-name": "2.1.0",
|
"color-name": "2.0.2",
|
||||||
"comlink": "4.4.2",
|
"comlink": "4.4.2",
|
||||||
"core-js": "3.47.0",
|
"core-js": "3.46.0",
|
||||||
"cropperjs": "1.6.2",
|
"cropperjs": "1.6.2",
|
||||||
"culori": "4.0.2",
|
"culori": "4.0.2",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
"fuse.js": "7.1.0",
|
"fuse.js": "7.1.0",
|
||||||
"google-timezones-json": "1.2.0",
|
"google-timezones-json": "1.2.0",
|
||||||
"gulp-zopfli-green": "6.0.2",
|
"gulp-zopfli-green": "6.0.2",
|
||||||
"hls.js": "1.6.15",
|
"hls.js": "1.6.14",
|
||||||
"home-assistant-js-websocket": "9.6.0",
|
"home-assistant-js-websocket": "9.6.0",
|
||||||
"idb-keyval": "6.2.2",
|
"idb-keyval": "6.2.2",
|
||||||
"intl-messageformat": "10.7.18",
|
"intl-messageformat": "10.7.18",
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
"lit": "3.3.1",
|
"lit": "3.3.1",
|
||||||
"lit-html": "3.3.1",
|
"lit-html": "3.3.1",
|
||||||
"luxon": "3.7.2",
|
"luxon": "3.7.2",
|
||||||
"marked": "17.0.1",
|
"marked": "17.0.0",
|
||||||
"memoize-one": "6.0.0",
|
"memoize-one": "6.0.0",
|
||||||
"node-vibrant": "4.0.3",
|
"node-vibrant": "4.0.3",
|
||||||
"object-hash": "3.0.0",
|
"object-hash": "3.0.0",
|
||||||
@@ -139,12 +139,12 @@
|
|||||||
"vue": "2.7.16",
|
"vue": "2.7.16",
|
||||||
"vue2-daterange-picker": "0.6.8",
|
"vue2-daterange-picker": "0.6.8",
|
||||||
"weekstart": "2.0.0",
|
"weekstart": "2.0.0",
|
||||||
"workbox-cacheable-response": "7.4.0",
|
"workbox-cacheable-response": "7.3.0",
|
||||||
"workbox-core": "7.4.0",
|
"workbox-core": "7.3.0",
|
||||||
"workbox-expiration": "7.4.0",
|
"workbox-expiration": "7.3.0",
|
||||||
"workbox-precaching": "7.4.0",
|
"workbox-precaching": "7.3.0",
|
||||||
"workbox-routing": "7.4.0",
|
"workbox-routing": "7.3.0",
|
||||||
"workbox-strategies": "7.4.0",
|
"workbox-strategies": "7.3.0",
|
||||||
"xss": "1.0.15"
|
"xss": "1.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -153,7 +153,7 @@
|
|||||||
"@babel/plugin-transform-runtime": "7.28.5",
|
"@babel/plugin-transform-runtime": "7.28.5",
|
||||||
"@babel/preset-env": "7.28.5",
|
"@babel/preset-env": "7.28.5",
|
||||||
"@bundle-stats/plugin-webpack-filter": "4.21.6",
|
"@bundle-stats/plugin-webpack-filter": "4.21.6",
|
||||||
"@lokalise/node-api": "15.4.0",
|
"@lokalise/node-api": "15.3.1",
|
||||||
"@octokit/auth-oauth-device": "8.0.3",
|
"@octokit/auth-oauth-device": "8.0.3",
|
||||||
"@octokit/plugin-retry": "8.0.3",
|
"@octokit/plugin-retry": "8.0.3",
|
||||||
"@octokit/rest": "22.0.1",
|
"@octokit/rest": "22.0.1",
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
"@types/tar": "6.1.13",
|
"@types/tar": "6.1.13",
|
||||||
"@types/ua-parser-js": "0.7.39",
|
"@types/ua-parser-js": "0.7.39",
|
||||||
"@types/webspeechapi": "0.0.29",
|
"@types/webspeechapi": "0.0.29",
|
||||||
"@vitest/coverage-v8": "4.0.13",
|
"@vitest/coverage-v8": "4.0.8",
|
||||||
"babel-loader": "10.0.0",
|
"babel-loader": "10.0.0",
|
||||||
"babel-plugin-template-html-minifier": "4.1.0",
|
"babel-plugin-template-html-minifier": "4.1.0",
|
||||||
"browserslist-useragent-regexp": "4.1.3",
|
"browserslist-useragent-regexp": "4.1.3",
|
||||||
@@ -201,25 +201,25 @@
|
|||||||
"gulp-rename": "2.1.0",
|
"gulp-rename": "2.1.0",
|
||||||
"html-minifier-terser": "7.2.0",
|
"html-minifier-terser": "7.2.0",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"jsdom": "27.2.0",
|
"jsdom": "27.1.0",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lint-staged": "16.2.7",
|
"lint-staged": "16.2.6",
|
||||||
"lit-analyzer": "2.0.3",
|
"lit-analyzer": "2.0.3",
|
||||||
"lodash.merge": "4.6.2",
|
"lodash.merge": "4.6.2",
|
||||||
"lodash.template": "4.5.0",
|
"lodash.template": "4.5.0",
|
||||||
"map-stream": "0.0.7",
|
"map-stream": "0.0.7",
|
||||||
"pinst": "3.0.0",
|
"pinst": "3.0.0",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"rspack-manifest-plugin": "5.2.0",
|
"rspack-manifest-plugin": "5.1.0",
|
||||||
"serve": "14.2.5",
|
"serve": "14.2.5",
|
||||||
"sinon": "21.0.0",
|
"sinon": "21.0.0",
|
||||||
"tar": "7.5.2",
|
"tar": "7.5.2",
|
||||||
"terser-webpack-plugin": "5.3.14",
|
"terser-webpack-plugin": "5.3.14",
|
||||||
"ts-lit-plugin": "2.0.2",
|
"ts-lit-plugin": "2.0.2",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"typescript-eslint": "8.47.0",
|
"typescript-eslint": "8.46.3",
|
||||||
"vite-tsconfig-paths": "5.1.4",
|
"vite-tsconfig-paths": "5.1.4",
|
||||||
"vitest": "4.0.13",
|
"vitest": "4.0.8",
|
||||||
"webpack-stats-plugin": "1.1.3",
|
"webpack-stats-plugin": "1.1.3",
|
||||||
"webpackbar": "7.0.0",
|
"webpackbar": "7.0.0",
|
||||||
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
|
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
|
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
|
||||||
"glob@^10.2.2": "^10.5.0"
|
"glob@^10.2.2": "^10.5.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.12.0",
|
"packageManager": "yarn@4.11.0",
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "22.21.1"
|
"node": "22.21.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20251203.3"
|
version = "20251029.0"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
license-files = ["LICENSE*"]
|
license-files = ["LICENSE*"]
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { AuthData } from "home-assistant-js-websocket";
|
import type { AuthData } from "home-assistant-js-websocket";
|
||||||
import { extractSearchParam } from "../url/search-params";
|
import { extractSearchParam } from "../url/search-params";
|
||||||
import { hassUrl } from "../../data/auth";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -31,11 +30,7 @@ export function askWrite() {
|
|||||||
export function saveTokens(tokens: AuthData | null) {
|
export function saveTokens(tokens: AuthData | null) {
|
||||||
tokenCache.tokens = tokens;
|
tokenCache.tokens = tokens;
|
||||||
|
|
||||||
if (
|
if (!tokenCache.writeEnabled && extractSearchParam("storeToken") === "true") {
|
||||||
!tokenCache.writeEnabled &&
|
|
||||||
(extractSearchParam("storeToken") === "true" ||
|
|
||||||
hassUrl !== `${location.protocol}//${location.host}`)
|
|
||||||
) {
|
|
||||||
tokenCache.writeEnabled = true;
|
tokenCache.writeEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,64 @@
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { theme2hex } from "./convert-color";
|
import { theme2hex } from "./convert-color";
|
||||||
|
|
||||||
// Total number of colors defined in CSS variables (--color-1 through --color-54)
|
export const COLORS = [
|
||||||
export const COLORS_COUNT = 54;
|
"#4269d0",
|
||||||
|
"#f4bd4a",
|
||||||
|
"#ff725c",
|
||||||
|
"#6cc5b0",
|
||||||
|
"#a463f2",
|
||||||
|
"#ff8ab7",
|
||||||
|
"#9c6b4e",
|
||||||
|
"#97bbf5",
|
||||||
|
"#01ab63",
|
||||||
|
"#094bad",
|
||||||
|
"#c99000",
|
||||||
|
"#d84f3e",
|
||||||
|
"#49a28f",
|
||||||
|
"#048732",
|
||||||
|
"#d96895",
|
||||||
|
"#8043ce",
|
||||||
|
"#7599d1",
|
||||||
|
"#7a4c31",
|
||||||
|
"#6989f4",
|
||||||
|
"#ffd444",
|
||||||
|
"#ff957c",
|
||||||
|
"#8fe9d3",
|
||||||
|
"#62cc71",
|
||||||
|
"#ffadda",
|
||||||
|
"#c884ff",
|
||||||
|
"#badeff",
|
||||||
|
"#bf8b6d",
|
||||||
|
"#927acc",
|
||||||
|
"#97ee3f",
|
||||||
|
"#bf3947",
|
||||||
|
"#9f5b00",
|
||||||
|
"#f48758",
|
||||||
|
"#8caed6",
|
||||||
|
"#f2b94f",
|
||||||
|
"#eff26e",
|
||||||
|
"#e43872",
|
||||||
|
"#d9b100",
|
||||||
|
"#9d7a00",
|
||||||
|
"#698cff",
|
||||||
|
"#00d27e",
|
||||||
|
"#d06800",
|
||||||
|
"#009f82",
|
||||||
|
"#c49200",
|
||||||
|
"#cbe8ff",
|
||||||
|
"#fecddf",
|
||||||
|
"#c27eb6",
|
||||||
|
"#8cd2ce",
|
||||||
|
"#c4b8d9",
|
||||||
|
"#f883b0",
|
||||||
|
"#a49100",
|
||||||
|
"#f48800",
|
||||||
|
"#27d0df",
|
||||||
|
"#a04a9b",
|
||||||
|
];
|
||||||
|
|
||||||
export function getColorByIndex(
|
export function getColorByIndex(index: number) {
|
||||||
index: number,
|
return COLORS[index % COLORS.length];
|
||||||
style: CSSStyleDeclaration
|
|
||||||
): string {
|
|
||||||
// Wrap around using modulo to support unlimited indices
|
|
||||||
const colorIndex = (index % COLORS_COUNT) + 1;
|
|
||||||
return style.getPropertyValue(`--color-${colorIndex}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGraphColorByIndex(
|
export function getGraphColorByIndex(
|
||||||
@@ -20,19 +68,15 @@ export function getGraphColorByIndex(
|
|||||||
// The CSS vars for the colors use range 1..n, so we need to adjust the index from the internal 0..n color index range.
|
// The CSS vars for the colors use range 1..n, so we need to adjust the index from the internal 0..n color index range.
|
||||||
const themeColor =
|
const themeColor =
|
||||||
style.getPropertyValue(`--graph-color-${index + 1}`) ||
|
style.getPropertyValue(`--graph-color-${index + 1}`) ||
|
||||||
getColorByIndex(index, style);
|
getColorByIndex(index);
|
||||||
return theme2hex(themeColor);
|
return theme2hex(themeColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAllGraphColors = memoizeOne(
|
export const getAllGraphColors = memoizeOne(
|
||||||
(style: CSSStyleDeclaration) =>
|
(style: CSSStyleDeclaration) =>
|
||||||
Array.from({ length: COLORS_COUNT }, (_, index) =>
|
COLORS.map((_color, index) => getGraphColorByIndex(index, style)),
|
||||||
getGraphColorByIndex(index, style)
|
|
||||||
),
|
|
||||||
(newArgs: [CSSStyleDeclaration], lastArgs: [CSSStyleDeclaration]) =>
|
(newArgs: [CSSStyleDeclaration], lastArgs: [CSSStyleDeclaration]) =>
|
||||||
// this is not ideal, but we need to memoize the colors
|
// this is not ideal, but we need to memoize the colors
|
||||||
newArgs[0].getPropertyValue("--graph-color-1") ===
|
newArgs[0].getPropertyValue("--graph-color-1") ===
|
||||||
lastArgs[0].getPropertyValue("--graph-color-1") &&
|
lastArgs[0].getPropertyValue("--graph-color-1")
|
||||||
newArgs[0].getPropertyValue("--color-1") ===
|
|
||||||
lastArgs[0].getPropertyValue("--color-1")
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -593,7 +593,6 @@ export class HaChartBase extends LitElement {
|
|||||||
}
|
}
|
||||||
const options = {
|
const options = {
|
||||||
animation: !this._reducedMotion,
|
animation: !this._reducedMotion,
|
||||||
animationDuration: 500,
|
|
||||||
darkMode: this._themes.darkMode ?? false,
|
darkMode: this._themes.darkMode ?? false,
|
||||||
aria: { show: true },
|
aria: { show: true },
|
||||||
dataZoom: this._getDataZoomConfig(),
|
dataZoom: this._getDataZoomConfig(),
|
||||||
|
|||||||
@@ -167,7 +167,6 @@ export class HaSankeyChart extends LitElement {
|
|||||||
curveness: 0.5,
|
curveness: 0.5,
|
||||||
},
|
},
|
||||||
layoutIterations: 0,
|
layoutIterations: 0,
|
||||||
animationDuration: 500,
|
|
||||||
label: {
|
label: {
|
||||||
formatter: (params) =>
|
formatter: (params) =>
|
||||||
data.nodes.find((node) => node.id === (params.data as Node).id)
|
data.nodes.find((node) => node.id === (params.data as Node).id)
|
||||||
@@ -280,7 +279,6 @@ export class HaSankeyChart extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-width: 100%;
|
|
||||||
background: var(--ha-card-background, var(--card-background-color));
|
background: var(--ha-card-background, var(--card-background-color));
|
||||||
}
|
}
|
||||||
ha-chart-base {
|
ha-chart-base {
|
||||||
|
|||||||
@@ -373,7 +373,6 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||||||
itemName: 3,
|
itemName: 3,
|
||||||
},
|
},
|
||||||
renderItem: this._renderItem,
|
renderItem: this._renderItem,
|
||||||
progressive: 0,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { LitElement, html } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { getAreaContext } from "../common/entity/context/get_area_context";
|
import { getAreaContext } from "../common/entity/context/get_area_context";
|
||||||
|
import { areaCompare } from "../data/area_registry";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import "./ha-expansion-panel";
|
import "./ha-expansion-panel";
|
||||||
import "./ha-items-display-editor";
|
import "./ha-items-display-editor";
|
||||||
@@ -36,7 +37,11 @@ export class HaAreasDisplayEditor extends LitElement {
|
|||||||
public showNavigationButton = false;
|
public showNavigationButton = false;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const areas = Object.values(this.hass.areas);
|
const compare = areaCompare(this.hass.areas);
|
||||||
|
|
||||||
|
const areas = Object.values(this.hass.areas).sort((areaA, areaB) =>
|
||||||
|
compare(areaA.area_id, areaB.area_id)
|
||||||
|
);
|
||||||
|
|
||||||
const items: DisplayItem[] = areas.map((area) => {
|
const items: DisplayItem[] = areas.map((area) => {
|
||||||
const { floor } = getAreaContext(area, this.hass.floors);
|
const { floor } = getAreaContext(area, this.hass.floors);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import memoizeOne from "memoize-one";
|
|||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { computeFloorName } from "../common/entity/compute_floor_name";
|
import { computeFloorName } from "../common/entity/compute_floor_name";
|
||||||
import { getAreaContext } from "../common/entity/context/get_area_context";
|
import { getAreaContext } from "../common/entity/context/get_area_context";
|
||||||
|
import { areaCompare } from "../data/area_registry";
|
||||||
import type { FloorRegistryEntry } from "../data/floor_registry";
|
import type { FloorRegistryEntry } from "../data/floor_registry";
|
||||||
import { getFloors } from "../panels/lovelace/strategies/areas/helpers/areas-strategy-helper";
|
import { getFloors } from "../panels/lovelace/strategies/areas/helpers/areas-strategy-helper";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
@@ -130,8 +131,11 @@ export class HaAreasFloorsDisplayEditor extends LitElement {
|
|||||||
// update items if floors change
|
// update items if floors change
|
||||||
_hassFloors: HomeAssistant["floors"]
|
_hassFloors: HomeAssistant["floors"]
|
||||||
): Record<string, DisplayItem[]> => {
|
): Record<string, DisplayItem[]> => {
|
||||||
const areas = Object.values(hassAreas);
|
const compare = areaCompare(hassAreas);
|
||||||
|
|
||||||
|
const areas = Object.values(hassAreas).sort((areaA, areaB) =>
|
||||||
|
compare(areaA.area_id, areaB.area_id)
|
||||||
|
);
|
||||||
const groupedItems: Record<string, DisplayItem[]> = areas.reduce(
|
const groupedItems: Record<string, DisplayItem[]> = areas.reduce(
|
||||||
(acc, area) => {
|
(acc, area) => {
|
||||||
const { floor } = getAreaContext(area, this.hass.floors);
|
const { floor } = getAreaContext(area, this.hass.floors);
|
||||||
|
|||||||
@@ -659,7 +659,6 @@ export class HaAssistChat extends LitElement {
|
|||||||
--markdown-table-border-color: var(--divider-color);
|
--markdown-table-border-color: var(--divider-color);
|
||||||
--markdown-code-background-color: var(--primary-background-color);
|
--markdown-code-background-color: var(--primary-background-color);
|
||||||
--markdown-code-text-color: var(--primary-text-color);
|
--markdown-code-text-color: var(--primary-text-color);
|
||||||
--markdown-list-indent: 1.15em;
|
|
||||||
&:not(:has(ha-markdown-element)) {
|
&:not(:has(ha-markdown-element)) {
|
||||||
min-height: 1lh;
|
min-height: 1lh;
|
||||||
min-width: 1lh;
|
min-width: 1lh;
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ export class HaBottomSheet extends LitElement {
|
|||||||
|
|
||||||
private _isDragging = false;
|
private _isDragging = false;
|
||||||
|
|
||||||
private _handleAfterHide(afterHideEvent: Event) {
|
private _handleAfterHide() {
|
||||||
afterHideEvent.stopPropagation();
|
|
||||||
this.open = false;
|
this.open = false;
|
||||||
const ev = new Event("closed", {
|
const ev = new Event("closed", {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
|
|||||||
@@ -202,7 +202,6 @@ export class HaControlSelect extends LitElement {
|
|||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
border-radius: var(--control-select-border-radius);
|
|
||||||
}
|
}
|
||||||
:host([vertical]) {
|
:host([vertical]) {
|
||||||
width: var(--control-select-thickness);
|
width: var(--control-select-thickness);
|
||||||
@@ -212,6 +211,7 @@ export class HaControlSelect extends LitElement {
|
|||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
border-radius: var(--control-select-border-radius);
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
@@ -167,33 +167,30 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _labelSelected(ev: CustomEvent<SelectedDetail<Set<number>>>) {
|
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(
|
const filteredLabels = this._filteredLabels(
|
||||||
this._labels,
|
this._labels,
|
||||||
this._filter,
|
this._filter,
|
||||||
this.value
|
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) {
|
for (const index of ev.detail.index) {
|
||||||
const labelId = filteredLabels[index]?.label_id;
|
const labelId = filteredLabels[index].label_id;
|
||||||
if (labelId) {
|
value.push(labelId);
|
||||||
newlySelectedLabels.push(labelId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this.value = value;
|
||||||
const value = [...preservedLabels, ...newlySelectedLabels];
|
|
||||||
this.value = value.length ? value : [];
|
|
||||||
|
|
||||||
fireEvent(this, "data-table-filter-changed", {
|
fireEvent(this, "data-table-filter-changed", {
|
||||||
value: value.length ? value : undefined,
|
value,
|
||||||
items: undefined,
|
items: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
mdiHomeFloor3,
|
mdiHomeFloor3,
|
||||||
mdiHomeFloorNegative1,
|
mdiHomeFloorNegative1,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { LitElement, html, nothing } from "lit";
|
import { LitElement, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import type { FloorRegistryEntry } from "../data/floor_registry";
|
import type { FloorRegistryEntry } from "../data/floor_registry";
|
||||||
import "./ha-icon";
|
import "./ha-icon";
|
||||||
@@ -48,7 +48,7 @@ export const floorDefaultIcon = (floor: Pick<FloorRegistryEntry, "level">) => {
|
|||||||
|
|
||||||
@customElement("ha-floor-icon")
|
@customElement("ha-floor-icon")
|
||||||
export class HaFloorIcon extends LitElement {
|
export class HaFloorIcon extends LitElement {
|
||||||
@property({ attribute: false }) public floor?: Pick<
|
@property({ attribute: false }) public floor!: Pick<
|
||||||
FloorRegistryEntry,
|
FloorRegistryEntry,
|
||||||
"icon" | "level"
|
"icon" | "level"
|
||||||
>;
|
>;
|
||||||
@@ -56,9 +56,6 @@ export class HaFloorIcon extends LitElement {
|
|||||||
@property() public icon?: string;
|
@property() public icon?: string;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.floor) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
if (this.floor.icon) {
|
if (this.floor.icon) {
|
||||||
return html`<ha-icon .icon=${this.floor.icon}></ha-icon>`;
|
return html`<ha-icon .icon=${this.floor.icon}></ha-icon>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export class HaGenericPicker extends LitElement {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private _hidePicker(ev: Event) {
|
private _hidePicker(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
if (this._newValue) {
|
if (this._newValue) {
|
||||||
fireEvent(this, "value-changed", { value: this._newValue });
|
fireEvent(this, "value-changed", { value: this._newValue });
|
||||||
@@ -344,10 +344,7 @@ export class HaGenericPicker extends LitElement {
|
|||||||
|
|
||||||
wa-popover::part(body) {
|
wa-popover::part(body) {
|
||||||
width: max(var(--body-width), 250px);
|
width: max(var(--body-width), 250px);
|
||||||
max-width: var(
|
max-width: max(var(--body-width), 250px);
|
||||||
--ha-generic-picker-max-width,
|
|
||||||
max(var(--body-width), 250px)
|
|
||||||
);
|
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -154,10 +154,7 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this._getLabelsMemoized(
|
return this._getLabelsMemoized(
|
||||||
this.hass.states,
|
this.hass,
|
||||||
this.hass.areas,
|
|
||||||
this.hass.devices,
|
|
||||||
this.hass.entities,
|
|
||||||
this._labels,
|
this._labels,
|
||||||
this.includeDomains,
|
this.includeDomains,
|
||||||
this.excludeDomains,
|
this.excludeDomains,
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ class HaLabel extends LitElement {
|
|||||||
|
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
|
|||||||
@@ -73,8 +73,6 @@ export class HaLanguagePicker extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public required = false;
|
@property({ type: Boolean }) public required = false;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ attribute: "native-name", type: Boolean })
|
@property({ attribute: "native-name", type: Boolean })
|
||||||
public nativeName = false;
|
public nativeName = false;
|
||||||
|
|
||||||
@@ -137,7 +135,6 @@ export class HaLanguagePicker extends LitElement {
|
|||||||
.value=${value}
|
.value=${value}
|
||||||
.valueRenderer=${this._valueRenderer}
|
.valueRenderer=${this._valueRenderer}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.helper=${this.helper}
|
|
||||||
.getItems=${this._getItems}
|
.getItems=${this._getItems}
|
||||||
@value-changed=${this._changed}
|
@value-changed=${this._changed}
|
||||||
hide-clear-icon
|
hide-clear-icon
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class HaMarkdownElement extends ReactiveElement {
|
|||||||
if (!this.innerHTML && this.cache) {
|
if (!this.innerHTML && this.cache) {
|
||||||
const key = this._computeCacheKey();
|
const key = this._computeCacheKey();
|
||||||
if (markdownCache.has(key)) {
|
if (markdownCache.has(key)) {
|
||||||
render(h(unsafeHTML(markdownCache.get(key))), this.renderRoot);
|
render(markdownCache.get(key)!, this.renderRoot);
|
||||||
this._resize();
|
this._resize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,10 @@ class HaMarkdownElement extends ReactiveElement {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
render(h(unsafeHTML(elements.join(""))), this.renderRoot);
|
render(
|
||||||
|
elements.map((e) => h(unsafeHTML(e))),
|
||||||
|
this.renderRoot
|
||||||
|
);
|
||||||
|
|
||||||
this._resize();
|
this._resize();
|
||||||
|
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ export class HaMarkdown extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public cache = false;
|
@property({ type: Boolean }) public cache = false;
|
||||||
|
|
||||||
@query("ha-markdown-element") private _markdownElement?: ReactiveElement;
|
@query("ha-markdown-element") private _markdownElement!: ReactiveElement;
|
||||||
|
|
||||||
protected async getUpdateComplete() {
|
protected async getUpdateComplete() {
|
||||||
const result = await super.getUpdateComplete();
|
const result = await super.getUpdateComplete();
|
||||||
await this._markdownElement?.updateComplete;
|
await this._markdownElement.updateComplete;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,9 +71,14 @@ export class HaMarkdown extends LitElement {
|
|||||||
color: var(--markdown-link-color, var(--primary-color));
|
color: var(--markdown-link-color, var(--primary-color));
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
background-color: var(--markdown-image-background-color);
|
background-color: rgba(10, 10, 10, 0.15);
|
||||||
border-radius: var(--markdown-image-border-radius);
|
border-radius: var(--markdown-image-border-radius);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
min-height: 2lh;
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
text-indent: 4px;
|
||||||
|
transition: height 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
p:first-child > img:first-child {
|
p:first-child > img:first-child {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
@@ -81,8 +86,10 @@ export class HaMarkdown extends LitElement {
|
|||||||
p:first-child > img:last-child {
|
p:first-child > img:last-child {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
ha-markdown-element > :is(ol, ul) {
|
ol,
|
||||||
padding-inline-start: var(--markdown-list-indent, revert);
|
ul {
|
||||||
|
list-style-position: inside;
|
||||||
|
padding-inline-start: 0;
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
&:has(input[type="checkbox"]) {
|
&:has(input[type="checkbox"]) {
|
||||||
@@ -132,34 +139,18 @@ export class HaMarkdown extends LitElement {
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
margin: var(--ha-space-4) 0;
|
margin: var(--ha-space-4) 0;
|
||||||
}
|
}
|
||||||
table[role="presentation"] {
|
|
||||||
--markdown-table-border-collapse: separate;
|
|
||||||
--markdown-table-border-width: attr(border, 0);
|
|
||||||
--markdown-table-padding-inline: 0;
|
|
||||||
--markdown-table-padding-block: 0;
|
|
||||||
th {
|
|
||||||
vertical-align: attr(align, center);
|
|
||||||
}
|
|
||||||
td {
|
|
||||||
vertical-align: attr(align, left);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
table {
|
table {
|
||||||
border-collapse: var(--markdown-table-border-collapse, collapse);
|
border-collapse: collapse;
|
||||||
}
|
display: block;
|
||||||
div:has(> table) {
|
overflow-x: auto;
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
th {
|
th {
|
||||||
text-align: var(--markdown-table-text-align, start);
|
text-align: start;
|
||||||
}
|
}
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
border-width: var(--markdown-table-border-width, 1px);
|
border: 1px solid var(--markdown-table-border-color, transparent);
|
||||||
border-style: var(--markdown-table-border-style, solid);
|
padding: 0.25em 0.5em;
|
||||||
border-color: var(--markdown-table-border-color, var(--divider-color));
|
|
||||||
padding-inline: var(--markdown-table-padding-inline, 0.5em);
|
|
||||||
padding-block: var(--markdown-table-padding-block, 0.25em);
|
|
||||||
}
|
}
|
||||||
blockquote {
|
blockquote {
|
||||||
border-left: 4px solid var(--divider-color);
|
border-left: 4px solid var(--divider-color);
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ export class HaPickerComboBox extends LitElement {
|
|||||||
@focus=${this._focusList}
|
@focus=${this._focusList}
|
||||||
@visibilityChanged=${this._visibilityChanged}
|
@visibilityChanged=${this._visibilityChanged}
|
||||||
>
|
>
|
||||||
</lit-virtualizer>`;
|
</lit-virtualizer> `;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderSectionButtons() {
|
private _renderSectionButtons() {
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { css, html, LitElement } from "lit";
|
|
||||||
import { customElement } from "lit/decorators";
|
|
||||||
|
|
||||||
@customElement("ha-section-title")
|
|
||||||
class HaSectionTitle extends LitElement {
|
|
||||||
protected render() {
|
|
||||||
return html`<slot></slot>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
|
||||||
:host {
|
|
||||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
|
||||||
padding: var(--ha-space-1) var(--ha-space-2);
|
|
||||||
font-weight: var(--ha-font-weight-bold);
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
min-height: var(--ha-space-6);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-section-title": HaSectionTitle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import type { NumberSelector } from "../../data/selector";
|
import type { NumberSelector } from "../../data/selector";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import "../entity/ha-entity-picker";
|
||||||
|
import "../ha-button-toggle-group";
|
||||||
import "../ha-input-helper-text";
|
import "../ha-input-helper-text";
|
||||||
import "../ha-slider";
|
import "../ha-slider";
|
||||||
import "../ha-textfield";
|
import "../ha-textfield";
|
||||||
@@ -15,7 +18,7 @@ export class HaNumberSelector extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public selector!: NumberSelector;
|
@property({ attribute: false }) public selector!: NumberSelector;
|
||||||
|
|
||||||
@property({ type: Number }) public value?: number;
|
@property({ type: Number }) public value?: number | string;
|
||||||
|
|
||||||
@property({ type: Number }) public placeholder?: number;
|
@property({ type: Number }) public placeholder?: number;
|
||||||
|
|
||||||
@@ -30,13 +33,30 @@ export class HaNumberSelector extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@state() private _mode: "number" | "entity" = "number";
|
||||||
|
|
||||||
private _valueStr = "";
|
private _valueStr = "";
|
||||||
|
|
||||||
protected willUpdate(changedProps: PropertyValues) {
|
protected willUpdate(changedProps: PropertyValues) {
|
||||||
|
if (!this.hasUpdated) {
|
||||||
|
if (
|
||||||
|
this.selector.number?.entity?.domains.length &&
|
||||||
|
typeof this.value === "string" &&
|
||||||
|
this.selector.number?.entity?.domains.some((domain) =>
|
||||||
|
(this.value as string).startsWith(`${domain}.`)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this._mode = "entity";
|
||||||
|
}
|
||||||
|
}
|
||||||
if (changedProps.has("value")) {
|
if (changedProps.has("value")) {
|
||||||
if (this._valueStr === "" || this.value !== Number(this._valueStr)) {
|
if (this._valueStr === "" || this.value !== Number(this._valueStr)) {
|
||||||
this._valueStr =
|
this._valueStr =
|
||||||
this.value == null || isNaN(this.value) ? "" : this.value.toString();
|
this.value == null ||
|
||||||
|
typeof this.value === "string" ||
|
||||||
|
isNaN(this.value)
|
||||||
|
? ""
|
||||||
|
: this.value.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,6 +67,8 @@ export class HaNumberSelector extends LitElement {
|
|||||||
this.selector.number?.min === undefined ||
|
this.selector.number?.min === undefined ||
|
||||||
this.selector.number?.max === undefined;
|
this.selector.number?.max === undefined;
|
||||||
|
|
||||||
|
const multiMode = Boolean(this.selector.number?.entity?.domains.length);
|
||||||
|
|
||||||
let sliderStep;
|
let sliderStep;
|
||||||
|
|
||||||
if (!isBox) {
|
if (!isBox) {
|
||||||
@@ -72,51 +94,73 @@ export class HaNumberSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.label && !isBox
|
${this.label && !isBox && !multiMode
|
||||||
? html`${this.label}${this.required ? "*" : ""}`
|
? html`${this.label}${this.required ? "*" : ""}`
|
||||||
: nothing}
|
: nothing}
|
||||||
|
${multiMode
|
||||||
|
? html`<div class="multi-header">
|
||||||
|
<span>${this.label}${this.required ? "*" : ""}</span>
|
||||||
|
<ha-button-toggle-group
|
||||||
|
size="small"
|
||||||
|
.buttons=${this._toggleButtons(this.hass.localize)}
|
||||||
|
.active=${this._mode}
|
||||||
|
@value-changed=${this._modeChanged}
|
||||||
|
></ha-button-toggle-group>
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
<div class="input">
|
<div class="input">
|
||||||
${!isBox
|
${multiMode && this._mode === "entity"
|
||||||
? html`
|
? html`<ha-entity-picker
|
||||||
<ha-slider
|
.hass=${this.hass}
|
||||||
labeled
|
.includeDomains=${this.selector.number!.entity!.domains}
|
||||||
.min=${this.selector.number!.min}
|
.value=${this.value}
|
||||||
.max=${this.selector.number!.max}
|
.placeholder=${this.placeholder}
|
||||||
.value=${this.value}
|
.helper=${this.helper}
|
||||||
.step=${sliderStep}
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required}
|
||||||
|
></ha-entity-picker>`
|
||||||
|
: html`${!isBox
|
||||||
|
? html`
|
||||||
|
<ha-slider
|
||||||
|
labeled
|
||||||
|
.min=${this.selector.number!.min}
|
||||||
|
.max=${this.selector.number!.max}
|
||||||
|
.value=${this.value}
|
||||||
|
.step=${sliderStep}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required}
|
||||||
|
@change=${this._handleSliderChange}
|
||||||
|
.withMarkers=${this.selector.number?.slider_ticks ||
|
||||||
|
false}
|
||||||
|
>
|
||||||
|
</ha-slider>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
<ha-textfield
|
||||||
|
.inputMode=${this.selector.number?.step === "any" ||
|
||||||
|
(this.selector.number?.step ?? 1) % 1 !== 0
|
||||||
|
? "decimal"
|
||||||
|
: "numeric"}
|
||||||
|
.label=${!isBox ? undefined : this.label}
|
||||||
|
.placeholder=${this.placeholder}
|
||||||
|
class=${classMap({ single: isBox })}
|
||||||
|
.min=${this.selector.number?.min}
|
||||||
|
.max=${this.selector.number?.max}
|
||||||
|
.value=${this._valueStr ?? ""}
|
||||||
|
.step=${this.selector.number?.step ?? 1}
|
||||||
|
helperPersistent
|
||||||
|
.helper=${isBox ? this.helper : undefined}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
@change=${this._handleSliderChange}
|
.suffix=${unit}
|
||||||
.withMarkers=${this.selector.number?.slider_ticks || false}
|
type="number"
|
||||||
|
autoValidate
|
||||||
|
?no-spinner=${!isBox}
|
||||||
|
@input=${this._handleInputChange}
|
||||||
>
|
>
|
||||||
</ha-slider>
|
</ha-textfield>`}
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
<ha-textfield
|
|
||||||
.inputMode=${this.selector.number?.step === "any" ||
|
|
||||||
(this.selector.number?.step ?? 1) % 1 !== 0
|
|
||||||
? "decimal"
|
|
||||||
: "numeric"}
|
|
||||||
.label=${!isBox ? undefined : this.label}
|
|
||||||
.placeholder=${this.placeholder}
|
|
||||||
class=${classMap({ single: isBox })}
|
|
||||||
.min=${this.selector.number?.min}
|
|
||||||
.max=${this.selector.number?.max}
|
|
||||||
.value=${this._valueStr ?? ""}
|
|
||||||
.step=${this.selector.number?.step ?? 1}
|
|
||||||
helperPersistent
|
|
||||||
.helper=${isBox ? this.helper : undefined}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
.required=${this.required}
|
|
||||||
.suffix=${unit}
|
|
||||||
type="number"
|
|
||||||
autoValidate
|
|
||||||
?no-spinner=${!isBox}
|
|
||||||
@input=${this._handleInputChange}
|
|
||||||
>
|
|
||||||
</ha-textfield>
|
|
||||||
</div>
|
</div>
|
||||||
${!isBox && this.helper
|
${!isBox && !(multiMode && this._mode === "entity") && this.helper
|
||||||
? html`<ha-input-helper-text .disabled=${this.disabled}
|
? html`<ha-input-helper-text .disabled=${this.disabled}
|
||||||
>${this.helper}</ha-input-helper-text
|
>${this.helper}</ha-input-helper-text
|
||||||
>`
|
>`
|
||||||
@@ -124,6 +168,22 @@ export class HaNumberSelector extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _toggleButtons = memoizeOne((localize: HomeAssistant["localize"]) => [
|
||||||
|
{
|
||||||
|
label: localize("ui.components.selectors.number.value"),
|
||||||
|
value: "number",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: localize("ui.components.selectors.number.entity_value"),
|
||||||
|
value: "entity",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
private _modeChanged(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._mode = ev.detail?.value || ev.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
private _handleInputChange(ev) {
|
private _handleInputChange(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this._valueStr = ev.target.value;
|
this._valueStr = ev.target.value;
|
||||||
@@ -155,17 +215,32 @@ export class HaNumberSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
ha-slider {
|
ha-slider {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-right: 16px;
|
margin-right: var(--ha-space-4);
|
||||||
margin-inline-end: 16px;
|
margin-inline-end: var(--ha-space-4);
|
||||||
margin-inline-start: 0;
|
margin-inline-start: 0;
|
||||||
}
|
}
|
||||||
ha-textfield {
|
ha-textfield {
|
||||||
--ha-textfield-input-width: 40px;
|
--ha-textfield-input-width: 40px;
|
||||||
}
|
}
|
||||||
|
.multi-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--ha-space-2);
|
||||||
|
}
|
||||||
|
|
||||||
.single {
|
.single {
|
||||||
--ha-textfield-input-width: unset;
|
--ha-textfield-input-width: unset;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
ha-entity-picker {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
ha-button-toggle-group {
|
||||||
|
display: block;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -465,16 +465,10 @@ export class HaServiceControl extends LitElement {
|
|||||||
? computeObjectId(this._value.action)
|
? computeObjectId(this._value.action)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const descriptionPlaceholders =
|
|
||||||
domain && serviceName
|
|
||||||
? this.hass.services[domain]?.[serviceName]?.description_placeholders
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const description =
|
const description =
|
||||||
(serviceName &&
|
(serviceName &&
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
`component.${domain}.services.${serviceName}.description`,
|
`component.${domain}.services.${serviceName}.description`
|
||||||
descriptionPlaceholders
|
|
||||||
)) ||
|
)) ||
|
||||||
serviceData?.description;
|
serviceData?.description;
|
||||||
|
|
||||||
@@ -543,8 +537,7 @@ export class HaServiceControl extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.value=${this._value?.data?.entity_id}
|
.value=${this._value?.data?.entity_id}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
`component.${domain}.services.${serviceName}.fields.entity_id.description`,
|
`component.${domain}.services.${serviceName}.fields.entity_id.description`
|
||||||
descriptionPlaceholders
|
|
||||||
) || entityId.description}
|
) || entityId.description}
|
||||||
@value-changed=${this._entityPicked}
|
@value-changed=${this._entityPicked}
|
||||||
allow-custom-entity
|
allow-custom-entity
|
||||||
@@ -582,8 +575,7 @@ export class HaServiceControl extends LitElement {
|
|||||||
left-chevron
|
left-chevron
|
||||||
.expanded=${!dataField.collapsed}
|
.expanded=${!dataField.collapsed}
|
||||||
.header=${this.hass.localize(
|
.header=${this.hass.localize(
|
||||||
`component.${domain}.services.${serviceName}.sections.${dataField.key}.name`,
|
`component.${domain}.services.${serviceName}.sections.${dataField.key}.name`
|
||||||
descriptionPlaceholders
|
|
||||||
) ||
|
) ||
|
||||||
dataField.name ||
|
dataField.name ||
|
||||||
dataField.key}
|
dataField.key}
|
||||||
@@ -619,10 +611,7 @@ export class HaServiceControl extends LitElement {
|
|||||||
serviceName: string | undefined
|
serviceName: string | undefined
|
||||||
) {
|
) {
|
||||||
return this.hass!.localize(
|
return this.hass!.localize(
|
||||||
`component.${domain}.services.${serviceName}.sections.${dataField.key}.description`,
|
`component.${domain}.services.${serviceName}.sections.${dataField.key}.description`
|
||||||
domain && serviceName
|
|
||||||
? this.hass.services[domain][serviceName].description_placeholders
|
|
||||||
: undefined
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,10 +658,6 @@ export class HaServiceControl extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const showOptional = showOptionalToggle(dataField);
|
const showOptional = showOptionalToggle(dataField);
|
||||||
const descriptionPlaceholders =
|
|
||||||
domain && serviceName
|
|
||||||
? this.hass.services[domain][serviceName].description_placeholders
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return dataField.selector &&
|
return dataField.selector &&
|
||||||
(!dataField.advanced ||
|
(!dataField.advanced ||
|
||||||
@@ -694,8 +679,7 @@ export class HaServiceControl extends LitElement {
|
|||||||
></ha-checkbox>`}
|
></ha-checkbox>`}
|
||||||
<span slot="heading"
|
<span slot="heading"
|
||||||
>${this.hass.localize(
|
>${this.hass.localize(
|
||||||
`component.${domain}.services.${serviceName}.fields.${dataField.key}.name`,
|
`component.${domain}.services.${serviceName}.fields.${dataField.key}.name`
|
||||||
descriptionPlaceholders
|
|
||||||
) ||
|
) ||
|
||||||
dataField.name ||
|
dataField.name ||
|
||||||
dataField.key}</span
|
dataField.key}</span
|
||||||
@@ -705,8 +689,7 @@ export class HaServiceControl extends LitElement {
|
|||||||
breaks
|
breaks
|
||||||
allow-svg
|
allow-svg
|
||||||
.content=${this.hass.localize(
|
.content=${this.hass.localize(
|
||||||
`component.${domain}.services.${serviceName}.fields.${dataField.key}.description`,
|
`component.${domain}.services.${serviceName}.fields.${dataField.key}.description`
|
||||||
descriptionPlaceholders
|
|
||||||
) || dataField?.description}
|
) || dataField?.description}
|
||||||
></ha-markdown>
|
></ha-markdown>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -92,14 +92,8 @@ class HaServicePicker extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const descriptionPlaceholders =
|
|
||||||
this.hass.services[domain][service].description_placeholders;
|
|
||||||
|
|
||||||
const serviceName =
|
const serviceName =
|
||||||
localize(
|
localize(`component.${domain}.services.${service}.name`) ||
|
||||||
`component.${domain}.services.${service}.name`,
|
|
||||||
descriptionPlaceholders
|
|
||||||
) ||
|
|
||||||
services[domain][service].name ||
|
services[domain][service].name ||
|
||||||
service;
|
service;
|
||||||
|
|
||||||
@@ -169,21 +163,16 @@ class HaServicePicker extends LitElement {
|
|||||||
const serviceId = `${domain}.${service}`;
|
const serviceId = `${domain}.${service}`;
|
||||||
const domainName = domainToName(localize, domain);
|
const domainName = domainToName(localize, domain);
|
||||||
|
|
||||||
const descriptionPlaceholders =
|
|
||||||
this.hass.services[domain][service].description_placeholders;
|
|
||||||
|
|
||||||
const name =
|
const name =
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
`component.${domain}.services.${service}.name`,
|
`component.${domain}.services.${service}.name`
|
||||||
descriptionPlaceholders
|
|
||||||
) ||
|
) ||
|
||||||
services[domain][service].name ||
|
services[domain][service].name ||
|
||||||
service;
|
service;
|
||||||
|
|
||||||
const description =
|
const description =
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
`component.${domain}.services.${service}.description`,
|
`component.${domain}.services.${service}.description`
|
||||||
descriptionPlaceholders
|
|
||||||
) ||
|
) ||
|
||||||
services[domain][service].description ||
|
services[domain][service].description ||
|
||||||
"";
|
"";
|
||||||
|
|||||||
@@ -197,8 +197,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _mouseLeaveTimeout?: number;
|
private _mouseLeaveTimeout?: number;
|
||||||
|
|
||||||
private _touchendTimeout?: number;
|
|
||||||
|
|
||||||
private _tooltipHideTimeout?: number;
|
private _tooltipHideTimeout?: number;
|
||||||
|
|
||||||
private _recentKeydownActiveUntil = 0;
|
private _recentKeydownActiveUntil = 0;
|
||||||
@@ -239,18 +237,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnectedCallback() {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
// clear timeouts
|
|
||||||
clearTimeout(this._mouseLeaveTimeout);
|
|
||||||
clearTimeout(this._tooltipHideTimeout);
|
|
||||||
clearTimeout(this._touchendTimeout);
|
|
||||||
// set undefined values
|
|
||||||
this._mouseLeaveTimeout = undefined;
|
|
||||||
this._tooltipHideTimeout = undefined;
|
|
||||||
this._touchendTimeout = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return nothing;
|
return nothing;
|
||||||
@@ -420,7 +406,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
class="ha-scrollbar"
|
class="ha-scrollbar"
|
||||||
@focusin=${this._listboxFocusIn}
|
@focusin=${this._listboxFocusIn}
|
||||||
@focusout=${this._listboxFocusOut}
|
@focusout=${this._listboxFocusOut}
|
||||||
@touchend=${this._listboxTouchend}
|
|
||||||
@scroll=${this._listboxScroll}
|
@scroll=${this._listboxScroll}
|
||||||
@keydown=${this._listboxKeydown}
|
@keydown=${this._listboxKeydown}
|
||||||
>
|
>
|
||||||
@@ -635,14 +620,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
this._hideTooltip();
|
this._hideTooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _listboxTouchend() {
|
|
||||||
clearTimeout(this._touchendTimeout);
|
|
||||||
this._touchendTimeout = window.setTimeout(() => {
|
|
||||||
// Allow 1 second for users to read the tooltip on touch devices
|
|
||||||
this._hideTooltip();
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@eventOptions({
|
@eventOptions({
|
||||||
passive: true,
|
passive: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import { subscribeLabFeature } from "../data/labs";
|
import { subscribeLabFeatures } from "../data/labs";
|
||||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||||
|
|
||||||
interface Snowflake {
|
interface Snowflake {
|
||||||
@@ -10,7 +10,7 @@ interface Snowflake {
|
|||||||
size: number;
|
size: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
delay: number;
|
delay: number;
|
||||||
rotation: number;
|
blur: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement("ha-snowflakes")
|
@customElement("ha-snowflakes")
|
||||||
@@ -27,14 +27,13 @@ export class HaSnowflakes extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
public hassSubscribe() {
|
public hassSubscribe() {
|
||||||
return [
|
return [
|
||||||
subscribeLabFeature(
|
subscribeLabFeatures(this.hass!.connection, (features) => {
|
||||||
this.hass!.connection,
|
this._enabled =
|
||||||
"frontend",
|
features.find(
|
||||||
"winter_mode",
|
(f) =>
|
||||||
(feature) => {
|
f.domain === "frontend" && f.preview_feature === "winter_mode"
|
||||||
this._enabled = feature.enabled;
|
)?.enabled ?? false;
|
||||||
}
|
}),
|
||||||
),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +51,7 @@ export class HaSnowflakes extends SubscribeMixin(LitElement) {
|
|||||||
size: Math.random() * 12 + 8, // Random size between 8-20px
|
size: Math.random() * 12 + 8, // Random size between 8-20px
|
||||||
duration: Math.random() * 8 + 8, // Random duration between 8-16s
|
duration: Math.random() * 8 + 8, // Random duration between 8-16s
|
||||||
delay: Math.random() * 8, // Random delay between 0-8s
|
delay: Math.random() * 8, // Random delay between 0-8s
|
||||||
rotation: Math.random() * 720 - 360, // Random starting rotation -360 to 360deg
|
blur: Math.random() * 1, // Random blur between 0-1px
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this._snowflakes = snowflakes;
|
this._snowflakes = snowflakes;
|
||||||
@@ -76,27 +75,20 @@ export class HaSnowflakes extends SubscribeMixin(LitElement) {
|
|||||||
<div class="snowflakes ${isDark ? "dark" : "light"}" aria-hidden="true">
|
<div class="snowflakes ${isDark ? "dark" : "light"}" aria-hidden="true">
|
||||||
${this._snowflakes.map(
|
${this._snowflakes.map(
|
||||||
(flake) => html`
|
(flake) => html`
|
||||||
<svg
|
<div
|
||||||
class="snowflake ${this.narrow && flake.id >= 30
|
class="snowflake ${this.narrow && flake.id >= 30
|
||||||
? "hide-narrow"
|
? "hide-narrow"
|
||||||
: ""}"
|
: ""}"
|
||||||
style="
|
style="
|
||||||
left: ${flake.left}%;
|
left: ${flake.left}%;
|
||||||
width: ${flake.size}px;
|
font-size: ${flake.size}px;
|
||||||
height: ${flake.size}px;
|
|
||||||
animation-duration: ${flake.duration}s;
|
animation-duration: ${flake.duration}s;
|
||||||
animation-delay: ${flake.delay}s;
|
animation-delay: ${flake.delay}s;
|
||||||
--rotation: ${flake.rotation}deg;
|
filter: blur(${flake.blur}px);
|
||||||
"
|
"
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
>
|
||||||
<path
|
❄
|
||||||
d="M7.991 0a.644.644 0 0 1 .283 1.221v2.553l.986-.988a.645.645 0 0 1 .612-.839.644.644 0 1 1-.222 1.247l-1.376 1.38V7.52l1.65-.954.466-1.879a.645.645 0 0 1 .1-1.042.643.643 0 1 1 .445 1.189l-.363 1.356 3.145-1.82a.643.643 0 1 1 .282.49l-2.205 1.277 1.347.361a.643.643 0 1 1-.158.543l-1.88-.505L8.573 8l1.632.945 1.858-.535a.64.64 0 0 1 .95-.434.643.643 0 1 1-.805.98l-1.354.364L14 11.14a.641.641 0 0 1 .914.855.643.643 0 0 1-1.197-.366l-2.205-1.276.36 1.35a.642.642 0 0 1 .419.95.643.643 0 1 1-.967-.816l-.503-1.884L8.273 8.48v1.909l1.39 1.344a.644.644 0 1 1 .208 1.252.644.644 0 0 1-.606-.852l-.991-.994v3.64A.644.644 0 0 1 7.99 16a.644.644 0 0 1-.282-1.221v-2.553l-.986.988a.645.645 0 0 1-.612.839.644.644 0 1 1 .222-1.247l1.376-1.38V8.5l-1.632.945-.467 1.879q.079.068.134.163a.643.643 0 1 1-.68-.31l.364-1.357-3.145 1.82A.643.643 0 1 1 2 11.15l2.205-1.276-1.347-.361a.643.643 0 1 1 .158-.543l1.88.505L7.444 8l-1.65-.954-1.857.534a.64.64 0 0 1-.95.434.643.643 0 1 1 .805-.98l1.354-.364L2 4.85a.641.641 0 0 1-.914-.855.643.643 0 0 1 1.197.366l2.205 1.276-.36-1.35a.642.642 0 0 1-.419-.95.643.643 0 1 1 .967.816l.503 1.884L7.71 7.5V5.611L6.32 4.267a.644.644 0 1 1-.208-1.252.644.644 0 0 1 .607.852l.991.994V1.22A.644.644 0 0 1 7.991 0"
|
</div>
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -136,10 +128,16 @@ export class HaSnowflakes extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
.light .snowflake {
|
.light .snowflake {
|
||||||
color: #00bcd4;
|
color: #00bcd4;
|
||||||
|
text-shadow:
|
||||||
|
0 0 5px #00bcd4,
|
||||||
|
0 0 10px #00e5ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .snowflake {
|
.dark .snowflake {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
text-shadow:
|
||||||
|
0 0 5px rgba(255, 255, 255, 0.8),
|
||||||
|
0 0 10px rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.snowflake.hide-narrow {
|
.snowflake.hide-narrow {
|
||||||
@@ -148,23 +146,19 @@ export class HaSnowflakes extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@keyframes fall {
|
@keyframes fall {
|
||||||
0% {
|
0% {
|
||||||
transform: translateY(-10vh) translateX(0) rotate(var(--rotation));
|
transform: translateY(-10vh) translateX(0);
|
||||||
}
|
}
|
||||||
25% {
|
25% {
|
||||||
transform: translateY(30vh) translateX(10px)
|
transform: translateY(30vh) translateX(10px);
|
||||||
rotate(calc(var(--rotation) + 25deg));
|
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
transform: translateY(60vh) translateX(-10px)
|
transform: translateY(60vh) translateX(-10px);
|
||||||
rotate(calc(var(--rotation) + 50deg));
|
|
||||||
}
|
}
|
||||||
75% {
|
75% {
|
||||||
transform: translateY(85vh) translateX(10px)
|
transform: translateY(85vh) translateX(10px);
|
||||||
rotate(calc(var(--rotation) + 75deg));
|
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: translateY(120vh) translateX(0)
|
transform: translateY(120vh) translateX(0);
|
||||||
rotate(calc(var(--rotation) + 100deg));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import {
|
|||||||
areaMeetsFilter,
|
areaMeetsFilter,
|
||||||
deviceMeetsFilter,
|
deviceMeetsFilter,
|
||||||
entityRegMeetsFilter,
|
entityRegMeetsFilter,
|
||||||
getTargetComboBoxItemType,
|
|
||||||
type TargetType,
|
type TargetType,
|
||||||
type TargetTypeFloorless,
|
type TargetTypeFloorless,
|
||||||
} from "../data/target";
|
} from "../data/target";
|
||||||
@@ -48,6 +47,7 @@ import "./ha-tree-indicator";
|
|||||||
import "./target-picker/ha-target-picker-item-group";
|
import "./target-picker/ha-target-picker-item-group";
|
||||||
import "./target-picker/ha-target-picker-value-chip";
|
import "./target-picker/ha-target-picker-value-chip";
|
||||||
|
|
||||||
|
const EMPTY_SEARCH = "___EMPTY_SEARCH___";
|
||||||
const SEPARATOR = "________";
|
const SEPARATOR = "________";
|
||||||
const CREATE_ID = "___create-new-entity___";
|
const CREATE_ID = "___create-new-entity___";
|
||||||
|
|
||||||
@@ -634,6 +634,35 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getRowType = (
|
||||||
|
item:
|
||||||
|
| PickerComboBoxItem
|
||||||
|
| (FloorComboBoxItem & { last?: boolean | undefined })
|
||||||
|
| EntityComboBoxItem
|
||||||
|
| DevicePickerItem
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
(item as FloorComboBoxItem).type === "area" ||
|
||||||
|
(item as FloorComboBoxItem).type === "floor"
|
||||||
|
) {
|
||||||
|
return (item as FloorComboBoxItem).type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("domain" in item) {
|
||||||
|
return "device";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("stateObj" in item) {
|
||||||
|
return "entity";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.id === EMPTY_SEARCH) {
|
||||||
|
return "empty";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "label";
|
||||||
|
};
|
||||||
|
|
||||||
private _sectionTitleFunction = ({
|
private _sectionTitleFunction = ({
|
||||||
firstIndex,
|
firstIndex,
|
||||||
lastIndex,
|
lastIndex,
|
||||||
@@ -657,7 +686,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = getTargetComboBoxItemType(firstItem as PickerComboBoxItem);
|
const type = this._getRowType(firstItem as PickerComboBoxItem);
|
||||||
const translationType:
|
const translationType:
|
||||||
| "areas"
|
| "areas"
|
||||||
| "entities"
|
| "entities"
|
||||||
@@ -829,10 +858,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
if (!filterType || filterType === "label") {
|
if (!filterType || filterType === "label") {
|
||||||
let labels = this._getLabelsMemoized(
|
let labels = this._getLabelsMemoized(
|
||||||
this.hass.states,
|
this.hass,
|
||||||
this.hass.areas,
|
|
||||||
this.hass.devices,
|
|
||||||
this.hass.entities,
|
|
||||||
this._labelRegistry,
|
this._labelRegistry,
|
||||||
includeDomains,
|
includeDomains,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -948,11 +974,14 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = getTargetComboBoxItemType(item);
|
const type = this._getRowType(item);
|
||||||
let hasFloor = false;
|
let hasFloor = false;
|
||||||
let rtl = false;
|
let rtl = false;
|
||||||
let showEntityId = false;
|
let showEntityId = false;
|
||||||
|
|
||||||
if (type === "area" || type === "floor") {
|
if (type === "area" || type === "floor") {
|
||||||
|
item.id = item[type]?.[`${type}_id`];
|
||||||
|
|
||||||
rtl = computeRTL(this.hass);
|
rtl = computeRTL(this.hass);
|
||||||
hasFloor =
|
hasFloor =
|
||||||
type === "area" && !!(item as FloorComboBoxItem).area?.floor_id;
|
type === "area" && !!(item as FloorComboBoxItem).area?.floor_id;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export class HaToast extends Snackbar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mdc-snackbar {
|
.mdc-snackbar {
|
||||||
z-index: 10;
|
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
right: calc(8px + var(--safe-area-inset-right));
|
right: calc(8px + var(--safe-area-inset-right));
|
||||||
bottom: calc(8px + var(--safe-area-inset-bottom));
|
bottom: calc(8px + var(--safe-area-inset-bottom));
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
mdiDevices,
|
mdiDevices,
|
||||||
mdiFormatListBulleted,
|
mdiFormatListBulleted,
|
||||||
mdiGestureDoubleTap,
|
mdiGestureDoubleTap,
|
||||||
|
mdiHomeAssistant,
|
||||||
mdiMapMarker,
|
mdiMapMarker,
|
||||||
mdiMapMarkerRadius,
|
mdiMapMarkerRadius,
|
||||||
mdiMessageAlert,
|
mdiMessageAlert,
|
||||||
@@ -22,7 +23,6 @@ import { customElement, property } from "lit/decorators";
|
|||||||
import { until } from "lit/directives/until";
|
import { until } from "lit/directives/until";
|
||||||
import { computeDomain } from "../common/entity/compute_domain";
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
import { FALLBACK_DOMAIN_ICONS, triggerIcon } from "../data/icons";
|
import { FALLBACK_DOMAIN_ICONS, triggerIcon } from "../data/icons";
|
||||||
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import "./ha-icon";
|
import "./ha-icon";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import "@home-assistant/webawesome/dist/components/dialog/dialog";
|
|
||||||
import { mdiClose } from "@mdi/js";
|
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import {
|
import {
|
||||||
customElement,
|
customElement,
|
||||||
@@ -9,6 +7,8 @@ import {
|
|||||||
state,
|
state,
|
||||||
} from "lit/decorators";
|
} from "lit/decorators";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
|
import { mdiClose } from "@mdi/js";
|
||||||
|
import "@home-assistant/webawesome/dist/components/dialog/dialog";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { haStyleScrollbar } from "../resources/styles";
|
import { haStyleScrollbar } from "../resources/styles";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
@@ -49,10 +49,10 @@ export type DialogWidth = "small" | "medium" | "large" | "full";
|
|||||||
* @cssprop --ha-dialog-surface-background - Dialog background color.
|
* @cssprop --ha-dialog-surface-background - Dialog background color.
|
||||||
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface.
|
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface.
|
||||||
* @cssprop --dialog-z-index - Z-index for the dialog.
|
* @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.
|
* @cssprop --dialog-surface-margin-top - Top margin for the dialog surface.
|
||||||
*
|
*
|
||||||
* @attr {boolean} open - Controls the dialog open state.
|
* @attr {boolean} open - Controls the dialog open state.
|
||||||
* @attr {("alert"|"standard")} type - Dialog type. Defaults to "standard".
|
|
||||||
* @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset. Defaults to "medium".
|
* @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset. Defaults to "medium".
|
||||||
* @attr {boolean} prevent-scrim-close - Prevents closing the dialog by clicking the scrim/overlay. Defaults to false.
|
* @attr {boolean} prevent-scrim-close - Prevents closing the dialog by clicking the scrim/overlay. Defaults to false.
|
||||||
* @attr {string} header-title - Header title text. If not set, the headerTitle slot is used.
|
* @attr {string} header-title - Header title text. If not set, the headerTitle slot is used.
|
||||||
@@ -84,9 +84,6 @@ export class HaWaDialog extends LitElement {
|
|||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
public open = false;
|
public open = false;
|
||||||
|
|
||||||
@property({ reflect: true })
|
|
||||||
public type: "alert" | "standard" = "standard";
|
|
||||||
|
|
||||||
@property({ type: String, reflect: true, attribute: "width" })
|
@property({ type: String, reflect: true, attribute: "width" })
|
||||||
public width: DialogWidth = "medium";
|
public width: DialogWidth = "medium";
|
||||||
|
|
||||||
@@ -175,9 +172,7 @@ export class HaWaDialog extends LitElement {
|
|||||||
|
|
||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
(this.querySelector("[autofocus]") as HTMLElement | null)?.focus();
|
||||||
(this.querySelector("[autofocus]") as HTMLElement | null)?.focus();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private _handleAfterShow = () => {
|
private _handleAfterShow = () => {
|
||||||
@@ -203,7 +198,18 @@ export class HaWaDialog extends LitElement {
|
|||||||
haStyleScrollbar,
|
haStyleScrollbar,
|
||||||
css`
|
css`
|
||||||
wa-dialog {
|
wa-dialog {
|
||||||
--full-width: var(--ha-dialog-width-full, min(95vw, var(--safe-width)));
|
--full-width: var(
|
||||||
|
--ha-dialog-width-full,
|
||||||
|
min(
|
||||||
|
95vw,
|
||||||
|
calc(
|
||||||
|
100vw - var(--safe-area-inset-left, var(--ha-space-0)) - var(
|
||||||
|
--safe-area-inset-right,
|
||||||
|
var(--ha-space-0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
|
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
|
||||||
--spacing: var(--dialog-content-padding, var(--ha-space-6));
|
--spacing: var(--dialog-content-padding, var(--ha-space-6));
|
||||||
--show-duration: var(--ha-dialog-show-duration, 200ms);
|
--show-duration: var(--ha-dialog-show-duration, 200ms);
|
||||||
@@ -220,7 +226,8 @@ export class HaWaDialog extends LitElement {
|
|||||||
--ha-dialog-border-radius,
|
--ha-dialog-border-radius,
|
||||||
var(--ha-border-radius-3xl)
|
var(--ha-border-radius-3xl)
|
||||||
);
|
);
|
||||||
max-width: var(--ha-dialog-max-width, var(--safe-width));
|
max-width: var(--ha-dialog-max-width, 100vw);
|
||||||
|
max-width: var(--ha-dialog-max-width, 100svw);
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([width="small"]) wa-dialog {
|
:host([width="small"]) wa-dialog {
|
||||||
@@ -228,7 +235,7 @@ export class HaWaDialog extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host([width="large"]) wa-dialog {
|
:host([width="large"]) wa-dialog {
|
||||||
--width: min(var(--ha-dialog-width-lg, 1024px), var(--full-width));
|
--width: min(var(--ha-dialog-width-lg, 720px), var(--full-width));
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([width="full"]) wa-dialog {
|
:host([width="full"]) wa-dialog {
|
||||||
@@ -240,56 +247,34 @@ export class HaWaDialog extends LitElement {
|
|||||||
max-width: var(--width, var(--full-width));
|
max-width: var(--width, var(--full-width));
|
||||||
max-height: var(
|
max-height: var(
|
||||||
--ha-dialog-max-height,
|
--ha-dialog-max-height,
|
||||||
calc(var(--safe-height) - var(--ha-space-20))
|
calc(100% - var(--ha-space-20))
|
||||||
);
|
);
|
||||||
min-height: var(--ha-dialog-min-height);
|
min-height: var(--ha-dialog-min-height);
|
||||||
|
position: var(--dialog-surface-position, relative);
|
||||||
margin-top: var(--dialog-surface-margin-top, auto);
|
margin-top: var(--dialog-surface-margin-top, auto);
|
||||||
/* Used to offset the dialog from the safe areas when space is limited */
|
|
||||||
transform: translate(
|
|
||||||
calc(
|
|
||||||
var(--safe-area-offset-left, var(--ha-space-0)) - var(
|
|
||||||
--safe-area-offset-right,
|
|
||||||
var(--ha-space-0)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
calc(
|
|
||||||
var(--safe-area-offset-top, var(--ha-space-0)) - var(
|
|
||||||
--safe-area-offset-bottom,
|
|
||||||
var(--ha-space-0)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
:host([type="standard"]) {
|
:host {
|
||||||
--ha-dialog-border-radius: var(--ha-space-0);
|
--ha-dialog-border-radius: var(--ha-space-0);
|
||||||
|
}
|
||||||
|
|
||||||
wa-dialog {
|
wa-dialog {
|
||||||
/* Make the container fill the whole screen width and not the safe width */
|
--full-width: var(--ha-dialog-width-full, 100vw);
|
||||||
--full-width: var(--ha-dialog-width-full, 100vw);
|
}
|
||||||
--width: var(--full-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
wa-dialog::part(dialog) {
|
wa-dialog::part(dialog) {
|
||||||
/* Make the dialog fill the whole screen height and not the safe height */
|
min-height: var(--ha-dialog-min-height, 100vh);
|
||||||
min-height: var(--ha-dialog-min-height, 100vh);
|
min-height: var(--ha-dialog-min-height, 100svh);
|
||||||
min-height: var(--ha-dialog-min-height, 100dvh);
|
max-height: var(--ha-dialog-max-height, 100vh);
|
||||||
max-height: var(--ha-dialog-max-height, 100vh);
|
max-height: var(--ha-dialog-max-height, 100svh);
|
||||||
max-height: var(--ha-dialog-max-height, 100dvh);
|
padding-top: var(--safe-area-inset-top, var(--ha-space-0));
|
||||||
margin-top: 0;
|
padding-bottom: var(--safe-area-inset-bottom, var(--ha-space-0));
|
||||||
margin-bottom: 0;
|
padding-left: var(--safe-area-inset-left, var(--ha-space-0));
|
||||||
/* Use safe area as padding instead of the container size */
|
padding-right: var(--safe-area-inset-right, var(--ha-space-0));
|
||||||
padding-top: var(--safe-area-inset-top);
|
|
||||||
padding-bottom: var(--safe-area-inset-bottom);
|
|
||||||
padding-left: var(--safe-area-inset-left);
|
|
||||||
padding-right: var(--safe-area-inset-right);
|
|
||||||
/* Reset the transform to center the dialog */
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,52 +21,11 @@ export interface FloorComboBoxItem extends PickerComboBoxItem {
|
|||||||
area?: AreaRegistryEntry;
|
area?: AreaRegistryEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FloorNestedComboBoxItem extends PickerComboBoxItem {
|
|
||||||
floor?: FloorRegistryEntry;
|
|
||||||
areas: FloorComboBoxItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UnassignedAreasFloorComboBoxItem extends PickerComboBoxItem {
|
|
||||||
areas: FloorComboBoxItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AreaFloorValue {
|
export interface AreaFloorValue {
|
||||||
id: string;
|
id: string;
|
||||||
type: "floor" | "area";
|
type: "floor" | "area";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAreasNestedInFloors = (
|
|
||||||
states: HomeAssistant["states"],
|
|
||||||
haFloors: HomeAssistant["floors"],
|
|
||||||
haAreas: HomeAssistant["areas"],
|
|
||||||
haDevices: HomeAssistant["devices"],
|
|
||||||
haEntities: HomeAssistant["entities"],
|
|
||||||
formatId: (value: AreaFloorValue) => string,
|
|
||||||
includeDomains?: string[],
|
|
||||||
excludeDomains?: string[],
|
|
||||||
includeDeviceClasses?: string[],
|
|
||||||
deviceFilter?: HaDevicePickerDeviceFilterFunc,
|
|
||||||
entityFilter?: HaEntityPickerEntityFilterFunc,
|
|
||||||
excludeAreas?: string[],
|
|
||||||
excludeFloors?: string[]
|
|
||||||
) =>
|
|
||||||
getAreasAndFloorsItems(
|
|
||||||
states,
|
|
||||||
haFloors,
|
|
||||||
haAreas,
|
|
||||||
haDevices,
|
|
||||||
haEntities,
|
|
||||||
formatId,
|
|
||||||
includeDomains,
|
|
||||||
excludeDomains,
|
|
||||||
includeDeviceClasses,
|
|
||||||
deviceFilter,
|
|
||||||
entityFilter,
|
|
||||||
excludeAreas,
|
|
||||||
excludeFloors,
|
|
||||||
true
|
|
||||||
) as (FloorNestedComboBoxItem | UnassignedAreasFloorComboBoxItem)[];
|
|
||||||
|
|
||||||
export const getAreasAndFloors = (
|
export const getAreasAndFloors = (
|
||||||
states: HomeAssistant["states"],
|
states: HomeAssistant["states"],
|
||||||
haFloors: HomeAssistant["floors"],
|
haFloors: HomeAssistant["floors"],
|
||||||
@@ -81,43 +40,7 @@ export const getAreasAndFloors = (
|
|||||||
entityFilter?: HaEntityPickerEntityFilterFunc,
|
entityFilter?: HaEntityPickerEntityFilterFunc,
|
||||||
excludeAreas?: string[],
|
excludeAreas?: string[],
|
||||||
excludeFloors?: string[]
|
excludeFloors?: string[]
|
||||||
) =>
|
): FloorComboBoxItem[] => {
|
||||||
getAreasAndFloorsItems(
|
|
||||||
states,
|
|
||||||
haFloors,
|
|
||||||
haAreas,
|
|
||||||
haDevices,
|
|
||||||
haEntities,
|
|
||||||
formatId,
|
|
||||||
includeDomains,
|
|
||||||
excludeDomains,
|
|
||||||
includeDeviceClasses,
|
|
||||||
deviceFilter,
|
|
||||||
entityFilter,
|
|
||||||
excludeAreas,
|
|
||||||
excludeFloors
|
|
||||||
) as FloorComboBoxItem[];
|
|
||||||
|
|
||||||
const getAreasAndFloorsItems = (
|
|
||||||
states: HomeAssistant["states"],
|
|
||||||
haFloors: HomeAssistant["floors"],
|
|
||||||
haAreas: HomeAssistant["areas"],
|
|
||||||
haDevices: HomeAssistant["devices"],
|
|
||||||
haEntities: HomeAssistant["entities"],
|
|
||||||
formatId: (value: AreaFloorValue) => string,
|
|
||||||
includeDomains?: string[],
|
|
||||||
excludeDomains?: string[],
|
|
||||||
includeDeviceClasses?: string[],
|
|
||||||
deviceFilter?: HaDevicePickerDeviceFilterFunc,
|
|
||||||
entityFilter?: HaEntityPickerEntityFilterFunc,
|
|
||||||
excludeAreas?: string[],
|
|
||||||
excludeFloors?: string[],
|
|
||||||
nested = false
|
|
||||||
): (
|
|
||||||
| FloorComboBoxItem
|
|
||||||
| FloorNestedComboBoxItem
|
|
||||||
| UnassignedAreasFloorComboBoxItem
|
|
||||||
)[] => {
|
|
||||||
const floors = Object.values(haFloors);
|
const floors = Object.values(haFloors);
|
||||||
const areas = Object.values(haAreas);
|
const areas = Object.values(haAreas);
|
||||||
const devices = Object.values(haDevices);
|
const devices = Object.values(haDevices);
|
||||||
@@ -223,7 +146,6 @@ const getAreasAndFloorsItems = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
let outputAreas = areas;
|
let outputAreas = areas;
|
||||||
let outputFloors = floors;
|
|
||||||
|
|
||||||
let areaIds: string[] | undefined;
|
let areaIds: string[] | undefined;
|
||||||
|
|
||||||
@@ -255,35 +177,11 @@ const getAreasAndFloorsItems = (
|
|||||||
outputAreas = outputAreas.filter(
|
outputAreas = outputAreas.filter(
|
||||||
(area) => !area.floor_id || !excludeFloors!.includes(area.floor_id)
|
(area) => !area.floor_id || !excludeFloors!.includes(area.floor_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
outputFloors = outputFloors.filter(
|
|
||||||
(floor) => !excludeFloors!.includes(floor.floor_id)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const hierarchy = getAreasFloorHierarchy(floors, outputAreas);
|
||||||
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[] = [];
|
||||||
|
|
||||||
const items: (
|
|
||||||
| FloorComboBoxItem
|
|
||||||
| FloorNestedComboBoxItem
|
|
||||||
| UnassignedAreasFloorComboBoxItem
|
|
||||||
)[] = [];
|
|
||||||
|
|
||||||
hierarchy.floors.forEach((f) => {
|
hierarchy.floors.forEach((f) => {
|
||||||
const floor = haFloors[f.id];
|
const floor = haFloors[f.id];
|
||||||
@@ -298,7 +196,7 @@ const getAreasAndFloorsItems = (
|
|||||||
})
|
})
|
||||||
.flat();
|
.flat();
|
||||||
|
|
||||||
const floorItem: FloorComboBoxItem | FloorNestedComboBoxItem = {
|
items.push({
|
||||||
id: formatId({ id: floor.floor_id, type: "floor" }),
|
id: formatId({ id: floor.floor_id, type: "floor" }),
|
||||||
type: "floor",
|
type: "floor",
|
||||||
primary: floorName,
|
primary: floorName,
|
||||||
@@ -310,53 +208,41 @@ const getAreasAndFloorsItems = (
|
|||||||
...floor.aliases,
|
...floor.aliases,
|
||||||
...areaSearchLabels,
|
...areaSearchLabels,
|
||||||
],
|
],
|
||||||
};
|
});
|
||||||
|
|
||||||
items.push(floorItem);
|
items.push(
|
||||||
|
...floorAreas.map((area) => {
|
||||||
|
const areaName = computeAreaName(area);
|
||||||
|
return {
|
||||||
|
id: formatId({ id: area.area_id, type: "area" }),
|
||||||
|
type: "area" as const,
|
||||||
|
primary: areaName || area.area_id,
|
||||||
|
area: area,
|
||||||
|
icon: area.icon || undefined,
|
||||||
|
search_labels: [
|
||||||
|
area.area_id,
|
||||||
|
...(areaName ? [areaName] : []),
|
||||||
|
...area.aliases,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const floorAreasItems = floorAreas.map((area) => {
|
items.push(
|
||||||
const areaName = computeAreaName(area);
|
...hierarchy.areas.map((areaId) => {
|
||||||
|
const area = haAreas[areaId];
|
||||||
|
const areaName = computeAreaName(area) || area.area_id;
|
||||||
return {
|
return {
|
||||||
id: formatId({ id: area.area_id, type: "area" }),
|
id: formatId({ id: area.area_id, type: "area" }),
|
||||||
type: "area" as const,
|
type: "area" as const,
|
||||||
primary: areaName || area.area_id,
|
primary: areaName,
|
||||||
area: area,
|
area: area,
|
||||||
icon: area.icon || undefined,
|
icon: area.icon || undefined,
|
||||||
search_labels: [
|
search_labels: [area.area_id, areaName, ...area.aliases],
|
||||||
area.area_id,
|
|
||||||
...(areaName ? [areaName] : []),
|
|
||||||
...area.aliases,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
});
|
})
|
||||||
|
);
|
||||||
if (nested && floor) {
|
|
||||||
(floorItem as unknown as FloorNestedComboBoxItem).areas = floorAreasItems;
|
|
||||||
} else {
|
|
||||||
items.push(...floorAreasItems);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const unassignedAreaItems = hierarchy.areas.map((areaId) => {
|
|
||||||
const area = haAreas[areaId];
|
|
||||||
const areaName = computeAreaName(area) || area.area_id;
|
|
||||||
return {
|
|
||||||
id: formatId({ id: area.area_id, type: "area" }),
|
|
||||||
type: "area" as const,
|
|
||||||
primary: areaName,
|
|
||||||
area: area,
|
|
||||||
icon: area.icon || undefined,
|
|
||||||
search_labels: [area.area_id, areaName, ...area.aliases],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
if (nested && unassignedAreaItems.length) {
|
|
||||||
items.push({
|
|
||||||
areas: unassignedAreaItems,
|
|
||||||
} as UnassignedAreasFloorComboBoxItem);
|
|
||||||
} else {
|
|
||||||
items.push(...unassignedAreaItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
|
import { stringCompare } from "../common/string/compare";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import type { DeviceRegistryEntry } from "./device_registry";
|
import type { DeviceRegistryEntry } from "./device_registry";
|
||||||
import type {
|
import type { EntityRegistryEntry } from "./entity_registry";
|
||||||
EntityRegistryDisplayEntry,
|
|
||||||
EntityRegistryEntry,
|
|
||||||
} from "./entity_registry";
|
|
||||||
import type { RegistryEntry } from "./registry";
|
import type { RegistryEntry } from "./registry";
|
||||||
|
|
||||||
export { subscribeAreaRegistry } from "./ws-area_registry";
|
export { subscribeAreaRegistry } from "./ws-area_registry";
|
||||||
@@ -20,10 +18,7 @@ export interface AreaRegistryEntry extends RegistryEntry {
|
|||||||
temperature_entity_id: string | null;
|
temperature_entity_id: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AreaEntityLookup = Record<
|
export type AreaEntityLookup = Record<string, EntityRegistryEntry[]>;
|
||||||
string,
|
|
||||||
(EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type AreaDeviceLookup = Record<string, DeviceRegistryEntry[]>;
|
export type AreaDeviceLookup = Record<string, DeviceRegistryEntry[]>;
|
||||||
|
|
||||||
@@ -74,7 +69,7 @@ export const reorderAreaRegistryEntries = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getAreaEntityLookup = (
|
export const getAreaEntityLookup = (
|
||||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
entities: EntityRegistryEntry[]
|
||||||
): AreaEntityLookup => {
|
): AreaEntityLookup => {
|
||||||
const areaEntityLookup: AreaEntityLookup = {};
|
const areaEntityLookup: AreaEntityLookup = {};
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
@@ -104,3 +99,22 @@ export const getAreaDeviceLookup = (
|
|||||||
}
|
}
|
||||||
return areaDeviceLookup;
|
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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -114,6 +114,12 @@ export interface StateTrigger extends BaseTrigger {
|
|||||||
for?: string | number | ForDict;
|
for?: string | number | ForDict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MqttTrigger extends BaseTrigger {
|
||||||
|
trigger: "mqtt";
|
||||||
|
topic: string;
|
||||||
|
payload?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GeoLocationTrigger extends BaseTrigger {
|
export interface GeoLocationTrigger extends BaseTrigger {
|
||||||
trigger: "geo_location";
|
trigger: "geo_location";
|
||||||
source: string;
|
source: string;
|
||||||
@@ -121,12 +127,6 @@ export interface GeoLocationTrigger extends BaseTrigger {
|
|||||||
event: "enter" | "leave";
|
event: "enter" | "leave";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MqttTrigger extends BaseTrigger {
|
|
||||||
trigger: "mqtt";
|
|
||||||
topic: string;
|
|
||||||
payload?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HassTrigger extends BaseTrigger {
|
export interface HassTrigger extends BaseTrigger {
|
||||||
trigger: "homeassistant";
|
trigger: "homeassistant";
|
||||||
event: "start" | "shutdown";
|
event: "start" | "shutdown";
|
||||||
|
|||||||
@@ -144,7 +144,9 @@ const tryDescribeTrigger = (
|
|||||||
const type = getTriggerObjectId(trigger.trigger);
|
const type = getTriggerObjectId(trigger.trigger);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
hass.localize(`component.${domain}.triggers.${type}.name`) ||
|
hass.localize(
|
||||||
|
`component.${domain}.triggers.${type}.description_configured`
|
||||||
|
) ||
|
||||||
hass.localize(
|
hass.localize(
|
||||||
`ui.panel.config.automation.editor.triggers.type.${triggerType as LegacyTrigger["trigger"]}.label`
|
`ui.panel.config.automation.editor.triggers.type.${triggerType as LegacyTrigger["trigger"]}.label`
|
||||||
) ||
|
) ||
|
||||||
@@ -917,7 +919,9 @@ const tryDescribeCondition = (
|
|||||||
const type = getConditionObjectId(condition.condition);
|
const type = getConditionObjectId(condition.condition);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
hass.localize(`component.${domain}.conditions.${type}.name`) ||
|
hass.localize(
|
||||||
|
`component.${domain}.conditions.${type}.description_configured`
|
||||||
|
) ||
|
||||||
hass.localize(
|
hass.localize(
|
||||||
`ui.panel.config.automation.editor.conditions.type.${conditionType as LegacyCondition["condition"]}.label`
|
`ui.panel.config.automation.editor.conditions.type.${conditionType as LegacyCondition["condition"]}.label`
|
||||||
) ||
|
) ||
|
||||||
|
|||||||
@@ -137,12 +137,8 @@ const getCalendarDate = (dateObj: any): string | undefined => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCalendars = (
|
export const getCalendars = (hass: HomeAssistant): Calendar[] =>
|
||||||
hass: HomeAssistant,
|
Object.keys(hass.states)
|
||||||
element: Element
|
|
||||||
): Calendar[] => {
|
|
||||||
const computedStyles = getComputedStyle(element);
|
|
||||||
return Object.keys(hass.states)
|
|
||||||
.filter(
|
.filter(
|
||||||
(eid) =>
|
(eid) =>
|
||||||
computeDomain(eid) === "calendar" &&
|
computeDomain(eid) === "calendar" &&
|
||||||
@@ -153,9 +149,8 @@ export const getCalendars = (
|
|||||||
.map((eid, idx) => ({
|
.map((eid, idx) => ({
|
||||||
...hass.states[eid],
|
...hass.states[eid],
|
||||||
name: computeStateName(hass.states[eid]),
|
name: computeStateName(hass.states[eid]),
|
||||||
backgroundColor: getColorByIndex(idx, computedStyles),
|
backgroundColor: getColorByIndex(idx),
|
||||||
}));
|
}));
|
||||||
};
|
|
||||||
|
|
||||||
export const createCalendarEvent = (
|
export const createCalendarEvent = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
|||||||
@@ -17,19 +17,11 @@ export const CONDITION_COLLECTIONS: AutomationElementGroupCollection[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
titleKey:
|
|
||||||
"ui.panel.config.automation.editor.conditions.groups.helpers.label",
|
|
||||||
groups: {
|
|
||||||
helpers: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
titleKey: "ui.panel.config.automation.editor.conditions.groups.other.label",
|
titleKey: "ui.panel.config.automation.editor.conditions.groups.other.label",
|
||||||
groups: {
|
groups: {
|
||||||
template: {},
|
template: {},
|
||||||
trigger: {},
|
trigger: {},
|
||||||
other: {},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@@ -50,11 +50,7 @@ export type DeviceEntityDisplayLookup = Record<
|
|||||||
EntityRegistryDisplayEntry[]
|
EntityRegistryDisplayEntry[]
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type DeviceEntityLookup<
|
export type DeviceEntityLookup = Record<string, EntityRegistryEntry[]>;
|
||||||
T extends EntityRegistryEntry | EntityRegistryDisplayEntry =
|
|
||||||
| EntityRegistryEntry
|
|
||||||
| EntityRegistryDisplayEntry,
|
|
||||||
> = Record<string, T[]>;
|
|
||||||
|
|
||||||
export interface DeviceRegistryEntryMutableParams {
|
export interface DeviceRegistryEntryMutableParams {
|
||||||
area_id?: string | null;
|
area_id?: string | null;
|
||||||
@@ -111,7 +107,7 @@ export const sortDeviceRegistryByName = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const getDeviceEntityLookup = (
|
export const getDeviceEntityLookup = (
|
||||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
entities: EntityRegistryEntry[]
|
||||||
): DeviceEntityLookup => {
|
): DeviceEntityLookup => {
|
||||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
isLastDayOfMonth,
|
isLastDayOfMonth,
|
||||||
addYears,
|
addYears,
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import type { Collection, HassEntity } from "home-assistant-js-websocket";
|
import type { Collection } from "home-assistant-js-websocket";
|
||||||
import { getCollection } from "home-assistant-js-websocket";
|
import { getCollection } from "home-assistant-js-websocket";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import {
|
import {
|
||||||
@@ -1361,37 +1361,3 @@ export const calculateSolarConsumedGauge = (
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current power value from entity state, normalized to kW
|
|
||||||
* @param stateObj - The entity state object to get power value from
|
|
||||||
* @returns Power value in kW, or 0 if entity not found or invalid
|
|
||||||
*/
|
|
||||||
export const getPowerFromState = (stateObj: HassEntity): number | undefined => {
|
|
||||||
if (!stateObj) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const value = parseFloat(stateObj.state);
|
|
||||||
if (isNaN(value)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize to kW based on unit of measurement (case-sensitive)
|
|
||||||
// Supported units: GW, kW, MW, mW, TW, W
|
|
||||||
const unit = stateObj.attributes.unit_of_measurement;
|
|
||||||
switch (unit) {
|
|
||||||
case "W":
|
|
||||||
return value / 1000;
|
|
||||||
case "mW":
|
|
||||||
return value / 1000000;
|
|
||||||
case "MW":
|
|
||||||
return value * 1000;
|
|
||||||
case "GW":
|
|
||||||
return value * 1000000;
|
|
||||||
case "TW":
|
|
||||||
return value * 1000000000;
|
|
||||||
default:
|
|
||||||
// Assume kW if no unit or unit is kW
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import type { HomeAssistant } from "../types";
|
|
||||||
|
|
||||||
export interface ESPHomeEncryptionKey {
|
|
||||||
encryption_key: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchESPHomeEncryptionKey = (
|
|
||||||
hass: HomeAssistant,
|
|
||||||
entry_id: string
|
|
||||||
): Promise<ESPHomeEncryptionKey> =>
|
|
||||||
hass.callWS({
|
|
||||||
type: "esphome/get_encryption_key",
|
|
||||||
entry_id,
|
|
||||||
});
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { stringCompare } from "../common/string/compare";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import type { AreaRegistryEntry } from "./area_registry";
|
import type { AreaRegistryEntry } from "./area_registry";
|
||||||
import type { RegistryEntry } from "./registry";
|
import type { RegistryEntry } from "./registry";
|
||||||
@@ -74,3 +75,27 @@ export const getFloorAreaLookup = (
|
|||||||
}
|
}
|
||||||
return floorAreaLookup;
|
return floorAreaLookup;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const floorCompare =
|
||||||
|
(entries?: HomeAssistant["floors"], 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 floorA = entries?.[a];
|
||||||
|
const floorB = entries?.[b];
|
||||||
|
if (floorA && floorB && floorA.level !== floorB.level) {
|
||||||
|
return (floorB.level ?? -9999) - (floorA.level ?? -9999);
|
||||||
|
}
|
||||||
|
const nameA = floorA?.name ?? a;
|
||||||
|
const nameB = floorB?.name ?? b;
|
||||||
|
return stringCompare(nameA, nameB);
|
||||||
|
}
|
||||||
|
if (indexA === -1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (indexB === -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return indexA - indexB;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { Connection } from "home-assistant-js-websocket";
|
import type { Connection } from "home-assistant-js-websocket";
|
||||||
import { createCollection } from "home-assistant-js-websocket";
|
import { createCollection } from "home-assistant-js-websocket";
|
||||||
import type { LocalizeFunc } from "../common/translations/localize";
|
import type { LocalizeFunc } from "../common/translations/localize";
|
||||||
import { debounce } from "../common/util/debounce";
|
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
|
import { debounce } from "../common/util/debounce";
|
||||||
|
|
||||||
export const integrationsWithPanel = {
|
export const integrationsWithPanel = {
|
||||||
bluetooth: "config/bluetooth",
|
bluetooth: "config/bluetooth",
|
||||||
@@ -25,8 +25,6 @@ export type IntegrationType =
|
|||||||
| "entity"
|
| "entity"
|
||||||
| "system";
|
| "system";
|
||||||
|
|
||||||
export type DomainManifestLookup = Record<string, IntegrationManifest>;
|
|
||||||
|
|
||||||
export interface IntegrationManifest {
|
export interface IntegrationManifest {
|
||||||
is_built_in: boolean;
|
is_built_in: boolean;
|
||||||
overwrites_built_in?: boolean;
|
overwrites_built_in?: boolean;
|
||||||
|
|||||||
@@ -101,10 +101,7 @@ export const deleteLabelRegistryEntry = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getLabels = (
|
export const getLabels = (
|
||||||
hassStates: HomeAssistant["states"],
|
hass: HomeAssistant,
|
||||||
hassAreas: HomeAssistant["areas"],
|
|
||||||
hassDevices: HomeAssistant["devices"],
|
|
||||||
hassEntities: HomeAssistant["entities"],
|
|
||||||
labels?: LabelRegistryEntry[],
|
labels?: LabelRegistryEntry[],
|
||||||
includeDomains?: string[],
|
includeDomains?: string[],
|
||||||
excludeDomains?: string[],
|
excludeDomains?: string[],
|
||||||
@@ -118,8 +115,8 @@ export const getLabels = (
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const devices = Object.values(hassDevices);
|
const devices = Object.values(hass.devices);
|
||||||
const entities = Object.values(hassEntities);
|
const entities = Object.values(hass.entities);
|
||||||
|
|
||||||
let deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
let deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
||||||
let inputDevices: DeviceRegistryEntry[] | undefined;
|
let inputDevices: DeviceRegistryEntry[] | undefined;
|
||||||
@@ -173,7 +170,7 @@ export const getLabels = (
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return deviceEntityLookup[device.id].some((entity) => {
|
return deviceEntityLookup[device.id].some((entity) => {
|
||||||
const stateObj = hassStates[entity.entity_id];
|
const stateObj = hass.states[entity.entity_id];
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -184,9 +181,8 @@ export const getLabels = (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
inputEntities = inputEntities!.filter((entity) => {
|
inputEntities = inputEntities!.filter((entity) => {
|
||||||
const stateObj = hassStates[entity.entity_id];
|
const stateObj = hass.states[entity.entity_id];
|
||||||
return (
|
return (
|
||||||
stateObj &&
|
|
||||||
stateObj.attributes.device_class &&
|
stateObj.attributes.device_class &&
|
||||||
includeDeviceClasses.includes(stateObj.attributes.device_class)
|
includeDeviceClasses.includes(stateObj.attributes.device_class)
|
||||||
);
|
);
|
||||||
@@ -204,7 +200,7 @@ export const getLabels = (
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return deviceEntityLookup[device.id].some((entity) => {
|
return deviceEntityLookup[device.id].some((entity) => {
|
||||||
const stateObj = hassStates[entity.entity_id];
|
const stateObj = hass.states[entity.entity_id];
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -212,7 +208,7 @@ export const getLabels = (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
inputEntities = inputEntities!.filter((entity) => {
|
inputEntities = inputEntities!.filter((entity) => {
|
||||||
const stateObj = hassStates[entity.entity_id];
|
const stateObj = hass.states[entity.entity_id];
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -249,8 +245,8 @@ export const getLabels = (
|
|||||||
|
|
||||||
if (areaIds) {
|
if (areaIds) {
|
||||||
areaIds.forEach((areaId) => {
|
areaIds.forEach((areaId) => {
|
||||||
const area = hassAreas[areaId];
|
const area = hass.areas[areaId];
|
||||||
area?.labels.forEach((label) => usedLabels.add(label));
|
area.labels.forEach((label) => usedLabels.add(label));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,6 @@ export interface LabPreviewFeaturesResponse {
|
|||||||
features: LabPreviewFeature[];
|
features: LabPreviewFeature[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch all lab features
|
|
||||||
* @param hass - The Home Assistant instance
|
|
||||||
* @returns A promise to fetch the lab features
|
|
||||||
*/
|
|
||||||
export const fetchLabFeatures = async (
|
export const fetchLabFeatures = async (
|
||||||
hass: HomeAssistant
|
hass: HomeAssistant
|
||||||
): Promise<LabPreviewFeature[]> => {
|
): Promise<LabPreviewFeature[]> => {
|
||||||
@@ -32,15 +27,6 @@ export const fetchLabFeatures = async (
|
|||||||
return response.features;
|
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 = (
|
export const labsUpdatePreviewFeature = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
domain: string,
|
domain: string,
|
||||||
@@ -79,12 +65,6 @@ const subscribeLabUpdates = (
|
|||||||
"labs_updated"
|
"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 = (
|
export const subscribeLabFeatures = (
|
||||||
conn: Connection,
|
conn: Connection,
|
||||||
onChange: (features: LabPreviewFeature[]) => void
|
onChange: (features: LabPreviewFeature[]) => void
|
||||||
@@ -96,23 +76,3 @@ export const subscribeLabFeatures = (
|
|||||||
conn,
|
conn,
|
||||||
onChange
|
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 identifier
|
|
||||||
* @param onChange - The function to call when the lab feature changes
|
|
||||||
* @returns A promise that resolves to the unsubscribe function
|
|
||||||
*/
|
|
||||||
export const subscribeLabFeature = (
|
|
||||||
conn: Connection,
|
|
||||||
domain: string,
|
|
||||||
previewFeature: string,
|
|
||||||
onChange: (feature: LabPreviewFeature) => void
|
|
||||||
): Promise<() => void> =>
|
|
||||||
conn.subscribeMessage<LabPreviewFeature>(onChange, {
|
|
||||||
type: "labs/subscribe",
|
|
||||||
domain,
|
|
||||||
preview_feature: previewFeature,
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import type { MediaSelectorValue } from "../../selector";
|
import type { MediaSelectorValue } from "../../selector";
|
||||||
import type { LovelaceBadgeConfig } from "./badge";
|
import type { LovelaceBadgeConfig } from "./badge";
|
||||||
import type { LovelaceCardConfig } from "./card";
|
import type { LovelaceCardConfig } from "./card";
|
||||||
import type {
|
import type { LovelaceSectionRawConfig } from "./section";
|
||||||
LovelaceSectionConfig,
|
|
||||||
LovelaceSectionRawConfig,
|
|
||||||
} from "./section";
|
|
||||||
import type { LovelaceStrategyConfig } from "./strategy";
|
import type { LovelaceStrategyConfig } from "./strategy";
|
||||||
|
|
||||||
export interface ShowViewConfig {
|
export interface ShowViewConfig {
|
||||||
@@ -36,12 +33,6 @@ export interface LovelaceViewHeaderConfig {
|
|||||||
badges_wrap?: "wrap" | "scroll";
|
badges_wrap?: "wrap" | "scroll";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LovelaceViewSidebarConfig {
|
|
||||||
sections?: LovelaceSectionConfig[];
|
|
||||||
content_label?: string;
|
|
||||||
sidebar_label?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LovelaceBaseViewConfig {
|
export interface LovelaceBaseViewConfig {
|
||||||
index?: number;
|
index?: number;
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -65,8 +56,6 @@ export interface LovelaceViewConfig extends LovelaceBaseViewConfig {
|
|||||||
cards?: LovelaceCardConfig[];
|
cards?: LovelaceCardConfig[];
|
||||||
sections?: LovelaceSectionRawConfig[];
|
sections?: LovelaceSectionRawConfig[];
|
||||||
header?: LovelaceViewHeaderConfig;
|
header?: LovelaceViewHeaderConfig;
|
||||||
// Only used for section view, it should move to a section view config type when the views will have dedicated editor.
|
|
||||||
sidebar?: LovelaceViewSidebarConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LovelaceStrategyViewConfig extends LovelaceBaseViewConfig {
|
export interface LovelaceStrategyViewConfig extends LovelaceBaseViewConfig {
|
||||||
|
|||||||
@@ -219,13 +219,9 @@ const tryDescribeAction = <T extends ActionType>(
|
|||||||
|
|
||||||
if (config.action) {
|
if (config.action) {
|
||||||
const [domain, serviceName] = config.action.split(".", 2);
|
const [domain, serviceName] = config.action.split(".", 2);
|
||||||
const descriptionPlaceholders =
|
|
||||||
hass.services[domain]?.[serviceName]?.description_placeholders;
|
|
||||||
const service =
|
const service =
|
||||||
hass.localize(
|
hass.localize(`component.${domain}.services.${serviceName}.name`) ||
|
||||||
`component.${domain}.services.${serviceName}.name`,
|
hass.services[domain][serviceName]?.name;
|
||||||
descriptionPlaceholders
|
|
||||||
) || hass.services[domain]?.[serviceName]?.name;
|
|
||||||
|
|
||||||
if (config.metadata) {
|
if (config.metadata) {
|
||||||
return hass.localize(
|
return hass.localize(
|
||||||
|
|||||||
@@ -332,6 +332,7 @@ export interface NumberSelector {
|
|||||||
max?: number;
|
max?: number;
|
||||||
step?: number | "any";
|
step?: number | "any";
|
||||||
mode?: "box" | "slider";
|
mode?: "box" | "slider";
|
||||||
|
entity?: { domains: readonly string[] };
|
||||||
unit_of_measurement?: string;
|
unit_of_measurement?: string;
|
||||||
slider_ticks?: boolean;
|
slider_ticks?: boolean;
|
||||||
translation_key?: string;
|
translation_key?: string;
|
||||||
|
|||||||
@@ -1,30 +1,15 @@
|
|||||||
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
||||||
import { computeDomain } from "../common/entity/compute_domain";
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
|
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
|
||||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import type { FloorComboBoxItem } from "./area_floor";
|
|
||||||
import type { AreaRegistryEntry } from "./area_registry";
|
import type { AreaRegistryEntry } from "./area_registry";
|
||||||
import type { DevicePickerItem, DeviceRegistryEntry } from "./device_registry";
|
import type { DeviceRegistryEntry } from "./device_registry";
|
||||||
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
||||||
import type {
|
import type { EntityRegistryDisplayEntry } from "./entity_registry";
|
||||||
EntityComboBoxItem,
|
|
||||||
EntityRegistryDisplayEntry,
|
|
||||||
} from "./entity_registry";
|
|
||||||
|
|
||||||
export const TARGET_SEPARATOR = "________";
|
|
||||||
|
|
||||||
export type TargetType = "entity" | "device" | "area" | "label" | "floor";
|
export type TargetType = "entity" | "device" | "area" | "label" | "floor";
|
||||||
export type TargetTypeFloorless = Exclude<TargetType, "floor">;
|
export type TargetTypeFloorless = Exclude<TargetType, "floor">;
|
||||||
|
|
||||||
export interface SingleHassServiceTarget {
|
|
||||||
entity_id?: string;
|
|
||||||
device_id?: string;
|
|
||||||
area_id?: string;
|
|
||||||
floor_id?: string;
|
|
||||||
label_id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExtractFromTargetResult {
|
export interface ExtractFromTargetResult {
|
||||||
missing_areas: string[];
|
missing_areas: string[];
|
||||||
missing_devices: string[];
|
missing_devices: string[];
|
||||||
@@ -50,39 +35,6 @@ export const extractFromTarget = async (
|
|||||||
target,
|
target,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getTriggersForTarget = async (
|
|
||||||
callWS: HomeAssistant["callWS"],
|
|
||||||
target: HassServiceTarget,
|
|
||||||
expandGroup = true
|
|
||||||
) =>
|
|
||||||
callWS<string[]>({
|
|
||||||
type: "get_triggers_for_target",
|
|
||||||
target,
|
|
||||||
expand_group: expandGroup,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getConditionsForTarget = async (
|
|
||||||
callWS: HomeAssistant["callWS"],
|
|
||||||
target: HassServiceTarget,
|
|
||||||
expandGroup = true
|
|
||||||
) =>
|
|
||||||
callWS<string[]>({
|
|
||||||
type: "get_conditions_for_target",
|
|
||||||
target,
|
|
||||||
expand_group: expandGroup,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getServicesForTarget = async (
|
|
||||||
callWS: HomeAssistant["callWS"],
|
|
||||||
target: HassServiceTarget,
|
|
||||||
expandGroup = true
|
|
||||||
) =>
|
|
||||||
callWS<string[]>({
|
|
||||||
type: "get_services_for_target",
|
|
||||||
target,
|
|
||||||
expand_group: expandGroup,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const areaMeetsFilter = (
|
export const areaMeetsFilter = (
|
||||||
area: AreaRegistryEntry,
|
area: AreaRegistryEntry,
|
||||||
devices: HomeAssistant["devices"],
|
devices: HomeAssistant["devices"],
|
||||||
@@ -210,32 +162,3 @@ export const entityRegMeetsFilter = (
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTargetComboBoxItemType = (
|
|
||||||
item:
|
|
||||||
| PickerComboBoxItem
|
|
||||||
| (FloorComboBoxItem & { last?: boolean | undefined })
|
|
||||||
| EntityComboBoxItem
|
|
||||||
| DevicePickerItem
|
|
||||||
) => {
|
|
||||||
if (
|
|
||||||
(item as FloorComboBoxItem).type === "area" ||
|
|
||||||
(item as FloorComboBoxItem).type === "floor"
|
|
||||||
) {
|
|
||||||
return (item as FloorComboBoxItem).type;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("domain" in item) {
|
|
||||||
return "device";
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("stateObj" in item) {
|
|
||||||
return "entity";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.id === "___EMPTY_SEARCH___") {
|
|
||||||
return "empty";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "label";
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -28,24 +28,18 @@ export const TRIGGER_COLLECTIONS: AutomationElementGroupCollection[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
titleKey: "ui.panel.config.automation.editor.triggers.groups.helpers.label",
|
|
||||||
groups: {
|
|
||||||
helpers: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
titleKey: "ui.panel.config.automation.editor.triggers.groups.other.label",
|
titleKey: "ui.panel.config.automation.editor.triggers.groups.other.label",
|
||||||
groups: {
|
groups: {
|
||||||
event: {},
|
event: {},
|
||||||
geo_location: {},
|
geo_location: {},
|
||||||
homeassistant: {},
|
homeassistant: {},
|
||||||
|
mqtt: {},
|
||||||
conversation: {},
|
conversation: {},
|
||||||
tag: {},
|
tag: {},
|
||||||
template: {},
|
template: {},
|
||||||
webhook: {},
|
webhook: {},
|
||||||
persistent_notification: {},
|
persistent_notification: {},
|
||||||
other: {},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { mdiAlertOutline, mdiClose } from "@mdi/js";
|
import { mdiAlertOutline } from "@mdi/js";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../../components/ha-button";
|
import "../../components/ha-button";
|
||||||
import "../../components/ha-dialog-footer";
|
|
||||||
import "../../components/ha-dialog-header";
|
import "../../components/ha-dialog-header";
|
||||||
|
import "../../components/ha-md-dialog";
|
||||||
|
import type { HaMdDialog } from "../../components/ha-md-dialog";
|
||||||
import "../../components/ha-svg-icon";
|
import "../../components/ha-svg-icon";
|
||||||
import "../../components/ha-textfield";
|
import "../../components/ha-textfield";
|
||||||
import type { HaTextField } from "../../components/ha-textfield";
|
import type { HaTextField } from "../../components/ha-textfield";
|
||||||
import "../../components/ha-wa-dialog";
|
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import type { DialogBoxParams } from "./show-dialog-box";
|
import type { DialogBoxParams } from "./show-dialog-box";
|
||||||
|
|
||||||
@@ -20,12 +19,12 @@ class DialogBox extends LitElement {
|
|||||||
|
|
||||||
@state() private _params?: DialogBoxParams;
|
@state() private _params?: DialogBoxParams;
|
||||||
|
|
||||||
@state() private _open = false;
|
|
||||||
|
|
||||||
@state() private _closeState?: "canceled" | "confirmed";
|
@state() private _closeState?: "canceled" | "confirmed";
|
||||||
|
|
||||||
@query("ha-textfield") private _textField?: HaTextField;
|
@query("ha-textfield") private _textField?: HaTextField;
|
||||||
|
|
||||||
|
@query("ha-md-dialog") private _dialog?: HaMdDialog;
|
||||||
|
|
||||||
private _closePromise?: Promise<void>;
|
private _closePromise?: Promise<void>;
|
||||||
|
|
||||||
private _closeResolve?: () => void;
|
private _closeResolve?: () => void;
|
||||||
@@ -35,7 +34,6 @@ class DialogBox extends LitElement {
|
|||||||
await this._closePromise;
|
await this._closePromise;
|
||||||
}
|
}
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._open = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog(): boolean {
|
public closeDialog(): boolean {
|
||||||
@@ -62,30 +60,16 @@ class DialogBox extends LitElement {
|
|||||||
this.hass.localize("ui.dialogs.generic.default_confirmation_title"));
|
this.hass.localize("ui.dialogs.generic.default_confirmation_title"));
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-wa-dialog
|
<ha-md-dialog
|
||||||
.hass=${this.hass}
|
open
|
||||||
.open=${this._open}
|
.disableCancelAction=${confirmPrompt}
|
||||||
type=${confirmPrompt ? "alert" : "standard"}
|
|
||||||
?prevent-scrim-close=${confirmPrompt}
|
|
||||||
@closed=${this._dialogClosed}
|
@closed=${this._dialogClosed}
|
||||||
|
type="alert"
|
||||||
aria-labelledby="dialog-box-title"
|
aria-labelledby="dialog-box-title"
|
||||||
aria-describedby="dialog-box-description"
|
aria-describedby="dialog-box-description"
|
||||||
>
|
>
|
||||||
<ha-dialog-header slot="header">
|
<div slot="headline">
|
||||||
${!confirmPrompt
|
<span .title=${dialogTitle} id="dialog-box-title">
|
||||||
? html`<slot name="headerNavigationIcon" slot="navigationIcon">
|
|
||||||
<ha-icon-button
|
|
||||||
data-dialog="close"
|
|
||||||
.label=${this.hass?.localize("ui.common.close") ?? "Close"}
|
|
||||||
.path=${mdiClose}
|
|
||||||
></ha-icon-button
|
|
||||||
></slot>`
|
|
||||||
: nothing}
|
|
||||||
<span
|
|
||||||
class=${classMap({ title: true, alert: confirmPrompt })}
|
|
||||||
slot="title"
|
|
||||||
id="dialog-box-title"
|
|
||||||
>
|
|
||||||
${this._params.warning
|
${this._params.warning
|
||||||
? html`<ha-svg-icon
|
? html`<ha-svg-icon
|
||||||
.path=${mdiAlertOutline}
|
.path=${mdiAlertOutline}
|
||||||
@@ -94,13 +78,13 @@ class DialogBox extends LitElement {
|
|||||||
: nothing}
|
: nothing}
|
||||||
${dialogTitle}
|
${dialogTitle}
|
||||||
</span>
|
</span>
|
||||||
</ha-dialog-header>
|
</div>
|
||||||
<div id="dialog-box-description">
|
<div slot="content" id="dialog-box-description">
|
||||||
${this._params.text ? html` <p>${this._params.text}</p> ` : ""}
|
${this._params.text ? html` <p>${this._params.text}</p> ` : ""}
|
||||||
${this._params.prompt
|
${this._params.prompt
|
||||||
? html`
|
? html`
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
autofocus
|
dialogInitialFocus
|
||||||
value=${ifDefined(this._params.defaultValue)}
|
value=${ifDefined(this._params.defaultValue)}
|
||||||
.placeholder=${this._params.placeholder}
|
.placeholder=${this._params.placeholder}
|
||||||
.label=${this._params.inputLabel
|
.label=${this._params.inputLabel
|
||||||
@@ -115,11 +99,10 @@ class DialogBox extends LitElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
<ha-dialog-footer slot="footer">
|
<div slot="actions">
|
||||||
${confirmPrompt
|
${confirmPrompt
|
||||||
? html`
|
? html`
|
||||||
<ha-button
|
<ha-button
|
||||||
slot="secondaryAction"
|
|
||||||
@click=${this._dismiss}
|
@click=${this._dismiss}
|
||||||
?autofocus=${!this._params.prompt && this._params.destructive}
|
?autofocus=${!this._params.prompt && this._params.destructive}
|
||||||
appearance="plain"
|
appearance="plain"
|
||||||
@@ -131,7 +114,6 @@ class DialogBox extends LitElement {
|
|||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
<ha-button
|
<ha-button
|
||||||
slot="primaryAction"
|
|
||||||
@click=${this._confirm}
|
@click=${this._confirm}
|
||||||
?autofocus=${!this._params.prompt && !this._params.destructive}
|
?autofocus=${!this._params.prompt && !this._params.destructive}
|
||||||
variant=${this._params.destructive ? "danger" : "brand"}
|
variant=${this._params.destructive ? "danger" : "brand"}
|
||||||
@@ -140,8 +122,8 @@ class DialogBox extends LitElement {
|
|||||||
? this._params.confirmText
|
? this._params.confirmText
|
||||||
: this.hass.localize("ui.common.ok")}
|
: this.hass.localize("ui.common.ok")}
|
||||||
</ha-button>
|
</ha-button>
|
||||||
</ha-dialog-footer>
|
</div>
|
||||||
</ha-wa-dialog>
|
</ha-md-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,20 +148,20 @@ class DialogBox extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _closeDialog() {
|
private _closeDialog() {
|
||||||
this._open = false;
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
this._dialog?.close();
|
||||||
this._closePromise = new Promise((resolve) => {
|
this._closePromise = new Promise((resolve) => {
|
||||||
this._closeResolve = resolve;
|
this._closeResolve = resolve;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dialogClosed() {
|
private _dialogClosed() {
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
|
||||||
if (!this._closeState) {
|
if (!this._closeState) {
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
this._cancel();
|
this._cancel();
|
||||||
}
|
}
|
||||||
this._closeState = undefined;
|
this._closeState = undefined;
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
this._open = false;
|
|
||||||
this._closeResolve?.();
|
this._closeResolve?.();
|
||||||
this._closeResolve = undefined;
|
this._closeResolve = undefined;
|
||||||
}
|
}
|
||||||
@@ -205,14 +187,6 @@ class DialogBox extends LitElement {
|
|||||||
ha-textfield {
|
ha-textfield {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.title.alert {
|
|
||||||
padding: 0 var(--ha-space-2);
|
|
||||||
}
|
|
||||||
@media all and (min-width: 450px) and (min-height: 500px) {
|
|
||||||
.title.alert {
|
|
||||||
padding: 0 var(--ha-space-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { ancestorsWithProperty } from "../common/dom/ancestors-with-property";
|
|
||||||
import { deepActiveElement } from "../common/dom/deep-active-element";
|
|
||||||
import type { HASSDomEvent, ValidHassDomEvent } from "../common/dom/fire_event";
|
import type { HASSDomEvent, ValidHassDomEvent } from "../common/dom/fire_event";
|
||||||
import { mainWindow } from "../common/dom/get_main_window";
|
import { mainWindow } from "../common/dom/get_main_window";
|
||||||
import { nextRender } from "../common/util/render-status";
|
|
||||||
import type { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
|
import type { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
|
||||||
|
import { ancestorsWithProperty } from "../common/dom/ancestors-with-property";
|
||||||
|
import { deepActiveElement } from "../common/dom/deep-active-element";
|
||||||
|
import { nextRender } from "../common/util/render-status";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@@ -22,7 +22,7 @@ declare global {
|
|||||||
export interface HassDialog<T = HASSDomEvents[ValidHassDomEvent]>
|
export interface HassDialog<T = HASSDomEvents[ValidHassDomEvent]>
|
||||||
extends HTMLElement {
|
extends HTMLElement {
|
||||||
showDialog(params: T);
|
showDialog(params: T);
|
||||||
closeDialog?: (historyState?: any) => boolean;
|
closeDialog?: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ShowDialogParams<T> {
|
interface ShowDialogParams<T> {
|
||||||
@@ -143,32 +143,27 @@ export const showDialog = async (
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const closeDialog = async (
|
export const closeDialog = async (dialogTag: string): Promise<boolean> => {
|
||||||
dialogTag: string,
|
|
||||||
historyState?: any
|
|
||||||
): Promise<boolean> => {
|
|
||||||
if (!(dialogTag in LOADED)) {
|
if (!(dialogTag in LOADED)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const dialogElement = await LOADED[dialogTag].element;
|
const dialogElement = await LOADED[dialogTag].element;
|
||||||
if (dialogElement.closeDialog) {
|
if (dialogElement.closeDialog) {
|
||||||
return dialogElement.closeDialog(historyState) !== false;
|
return dialogElement.closeDialog() !== false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// called on back()
|
// called on back()
|
||||||
export const closeLastDialog = async (historyState?: any) => {
|
export const closeLastDialog = async () => {
|
||||||
if (OPEN_DIALOG_STACK.length) {
|
if (OPEN_DIALOG_STACK.length) {
|
||||||
const lastDialog = OPEN_DIALOG_STACK.pop() as DialogState;
|
const lastDialog = OPEN_DIALOG_STACK.pop();
|
||||||
const closed = await closeDialog(lastDialog.dialogTag, historyState);
|
const closed = await closeDialog(lastDialog!.dialogTag);
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
// if the dialog was not closed, put it back on the stack
|
// if the dialog was not closed, put it back on the stack
|
||||||
OPEN_DIALOG_STACK.push(lastDialog);
|
OPEN_DIALOG_STACK.push(lastDialog!);
|
||||||
} else if (
|
}
|
||||||
OPEN_DIALOG_STACK.length &&
|
if (OPEN_DIALOG_STACK.length && mainWindow.history.state?.opensDialog) {
|
||||||
mainWindow.history.state?.opensDialog
|
|
||||||
) {
|
|
||||||
// if there are more dialogs open, push a new state so back() will close the next top dialog
|
// if there are more dialogs open, push a new state so back() will close the next top dialog
|
||||||
mainWindow.history.pushState(
|
mainWindow.history.pushState(
|
||||||
{ dialog: OPEN_DIALOG_STACK[OPEN_DIALOG_STACK.length - 1].dialogTag },
|
{ dialog: OPEN_DIALOG_STACK[OPEN_DIALOG_STACK.length - 1].dialogTag },
|
||||||
|
|||||||
@@ -302,9 +302,7 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _goToAddEntityTo(ev) {
|
private _goToAddEntityTo(ev) {
|
||||||
// Only check for request-selected events (from menu items), not regular clicks (from icon button)
|
if (!shouldHandleRequestSelectedEvent(ev)) return;
|
||||||
if (ev.type === "request-selected" && !shouldHandleRequestSelectedEvent(ev))
|
|
||||||
return;
|
|
||||||
this._setView("add_to");
|
this._setView("add_to");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,18 +550,7 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
: nothing}
|
: nothing}
|
||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
`
|
`
|
||||||
: !__DEMO__ && this._shouldShowAddEntityTo()
|
: nothing}
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
slot="actionItems"
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.dialogs.more_info_control.add_entity_to"
|
|
||||||
)}
|
|
||||||
.path=${mdiPlusBoxMultipleOutline}
|
|
||||||
@click=${this._goToAddEntityTo}
|
|
||||||
></ha-icon-button>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
`
|
`
|
||||||
: isSpecificInitialView
|
: isSpecificInitialView
|
||||||
? html`
|
? html`
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ class HassSubpage extends LitElement {
|
|||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
padding-bottom: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class PanelCalendar extends LitElement {
|
|||||||
public willUpdate(changedProps: PropertyValues): void {
|
public willUpdate(changedProps: PropertyValues): void {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
if (!this.hasUpdated) {
|
if (!this.hasUpdated) {
|
||||||
this._calendars = getCalendars(this.hass, this);
|
this._calendars = getCalendars(this.hass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ class PanelCalendar extends LitElement {
|
|||||||
manifest: await fetchIntegrationManifest(this.hass, "local_calendar"),
|
manifest: await fetchIntegrationManifest(this.hass, "local_calendar"),
|
||||||
dialogClosedCallback: ({ flowFinished }) => {
|
dialogClosedCallback: ({ flowFinished }) => {
|
||||||
if (flowFinished) {
|
if (flowFinished) {
|
||||||
this._calendars = getCalendars(this.hass, this);
|
this._calendars = getCalendars(this.hass);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { generateLovelaceViewStrategy } from "../lovelace/strategies/get-strateg
|
|||||||
import type { Lovelace } from "../lovelace/types";
|
import type { Lovelace } from "../lovelace/types";
|
||||||
import "../lovelace/views/hui-view";
|
import "../lovelace/views/hui-view";
|
||||||
import "../lovelace/views/hui-view-container";
|
import "../lovelace/views/hui-view-container";
|
||||||
import "../lovelace/views/hui-view-background";
|
|
||||||
|
|
||||||
const CLIMATE_LOVELACE_VIEW_CONFIG: LovelaceStrategyViewConfig = {
|
const CLIMATE_LOVELACE_VIEW_CONFIG: LovelaceStrategyViewConfig = {
|
||||||
strategy: {
|
strategy: {
|
||||||
@@ -116,7 +115,6 @@ class PanelClimate extends LitElement {
|
|||||||
this._lovelace
|
this._lovelace
|
||||||
? html`
|
? html`
|
||||||
<hui-view-container .hass=${this.hass}>
|
<hui-view-container .hass=${this.hass}>
|
||||||
<hui-view-background .hass=${this.hass}> </hui-view-background>
|
|
||||||
<hui-view
|
<hui-view
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
|
|||||||
@@ -1,496 +0,0 @@
|
|||||||
import { mdiClose, mdiDragHorizontalVariant, mdiTextureBox } from "@mdi/js";
|
|
||||||
import type { CSSResultGroup } from "lit";
|
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
|
||||||
import { repeat } from "lit/directives/repeat";
|
|
||||||
import {
|
|
||||||
type AreasFloorHierarchy,
|
|
||||||
getAreasFloorHierarchy,
|
|
||||||
getAreasOrder,
|
|
||||||
getFloorOrder,
|
|
||||||
} from "../../../common/areas/areas-floor-hierarchy";
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
|
||||||
import "../../../components/ha-button";
|
|
||||||
import "../../../components/ha-dialog-header";
|
|
||||||
import "../../../components/ha-floor-icon";
|
|
||||||
import "../../../components/ha-icon";
|
|
||||||
import "../../../components/ha-icon-button";
|
|
||||||
import "../../../components/ha-md-dialog";
|
|
||||||
import type { HaMdDialog } from "../../../components/ha-md-dialog";
|
|
||||||
import "../../../components/ha-md-list";
|
|
||||||
import "../../../components/ha-md-list-item";
|
|
||||||
import "../../../components/ha-sortable";
|
|
||||||
import "../../../components/ha-svg-icon";
|
|
||||||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
|
||||||
import {
|
|
||||||
reorderAreaRegistryEntries,
|
|
||||||
updateAreaRegistryEntry,
|
|
||||||
} from "../../../data/area_registry";
|
|
||||||
import { reorderFloorRegistryEntries } from "../../../data/floor_registry";
|
|
||||||
import { haStyle, haStyleDialog } from "../../../resources/styles";
|
|
||||||
import type { HomeAssistant } from "../../../types";
|
|
||||||
import { showToast } from "../../../util/toast";
|
|
||||||
import type { AreasFloorsOrderDialogParams } from "./show-dialog-areas-floors-order";
|
|
||||||
|
|
||||||
const UNASSIGNED_FLOOR = "__unassigned__";
|
|
||||||
|
|
||||||
interface FloorChange {
|
|
||||||
areaId: string;
|
|
||||||
floorId: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("dialog-areas-floors-order")
|
|
||||||
class DialogAreasFloorsOrder extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@state() private _open = false;
|
|
||||||
|
|
||||||
@state() private _hierarchy?: AreasFloorHierarchy;
|
|
||||||
|
|
||||||
@state() private _saving = false;
|
|
||||||
|
|
||||||
@query("ha-md-dialog") private _dialog?: HaMdDialog;
|
|
||||||
|
|
||||||
public async showDialog(
|
|
||||||
_params: AreasFloorsOrderDialogParams
|
|
||||||
): Promise<void> {
|
|
||||||
this._open = true;
|
|
||||||
this._computeHierarchy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeHierarchy(): void {
|
|
||||||
this._hierarchy = getAreasFloorHierarchy(
|
|
||||||
Object.values(this.hass.floors),
|
|
||||||
Object.values(this.hass.areas)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeDialog(): void {
|
|
||||||
this._dialog?.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _dialogClosed(): void {
|
|
||||||
this._open = false;
|
|
||||||
this._hierarchy = undefined;
|
|
||||||
this._saving = false;
|
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
if (!this._open || !this._hierarchy) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasFloors = this._hierarchy.floors.length > 0;
|
|
||||||
const dialogTitle = this.hass.localize(
|
|
||||||
hasFloors
|
|
||||||
? "ui.panel.config.areas.dialog.reorder_floors_areas_title"
|
|
||||||
: "ui.panel.config.areas.dialog.reorder_areas_title"
|
|
||||||
);
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ha-md-dialog open @closed=${this._dialogClosed}>
|
|
||||||
<ha-dialog-header slot="headline">
|
|
||||||
<ha-icon-button
|
|
||||||
slot="navigationIcon"
|
|
||||||
.label=${this.hass.localize("ui.common.close")}
|
|
||||||
.path=${mdiClose}
|
|
||||||
@click=${this.closeDialog}
|
|
||||||
></ha-icon-button>
|
|
||||||
<span slot="title" .title=${dialogTitle}>${dialogTitle}</span>
|
|
||||||
</ha-dialog-header>
|
|
||||||
<div slot="content" class="content">
|
|
||||||
<ha-sortable
|
|
||||||
handle-selector=".floor-handle"
|
|
||||||
draggable-selector=".floor"
|
|
||||||
@item-moved=${this._floorMoved}
|
|
||||||
invert-swap
|
|
||||||
>
|
|
||||||
<div class="floors">
|
|
||||||
${repeat(
|
|
||||||
this._hierarchy.floors,
|
|
||||||
(floor) => floor.id,
|
|
||||||
(floor) => this._renderFloor(floor)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</ha-sortable>
|
|
||||||
${this._renderUnassignedAreas()}
|
|
||||||
</div>
|
|
||||||
<div slot="actions">
|
|
||||||
<ha-button @click=${this.closeDialog} appearance="plain">
|
|
||||||
${this.hass.localize("ui.common.cancel")}
|
|
||||||
</ha-button>
|
|
||||||
<ha-button @click=${this._save} .disabled=${this._saving}>
|
|
||||||
${this.hass.localize("ui.common.save")}
|
|
||||||
</ha-button>
|
|
||||||
</div>
|
|
||||||
</ha-md-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderFloor(floor: { id: string; areas: string[] }) {
|
|
||||||
const floorEntry = this.hass.floors[floor.id];
|
|
||||||
if (!floorEntry) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="floor">
|
|
||||||
<div class="floor-header">
|
|
||||||
<ha-floor-icon .floor=${floorEntry}></ha-floor-icon>
|
|
||||||
<span class="floor-name">${floorEntry.name}</span>
|
|
||||||
<ha-svg-icon
|
|
||||||
class="floor-handle"
|
|
||||||
.path=${mdiDragHorizontalVariant}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</div>
|
|
||||||
<ha-sortable
|
|
||||||
handle-selector=".area-handle"
|
|
||||||
draggable-selector="ha-md-list-item"
|
|
||||||
@item-moved=${this._areaMoved}
|
|
||||||
@item-added=${this._areaAdded}
|
|
||||||
group="areas"
|
|
||||||
.floor=${floor.id}
|
|
||||||
>
|
|
||||||
<ha-md-list>
|
|
||||||
${floor.areas.length > 0
|
|
||||||
? floor.areas.map((areaId) => this._renderArea(areaId))
|
|
||||||
: html`<p class="empty">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.areas.dialog.empty_floor"
|
|
||||||
)}
|
|
||||||
</p>`}
|
|
||||||
</ha-md-list>
|
|
||||||
</ha-sortable>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderUnassignedAreas() {
|
|
||||||
const hasFloors = this._hierarchy!.floors.length > 0;
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="floor unassigned">
|
|
||||||
${hasFloors
|
|
||||||
? html`<div class="floor-header">
|
|
||||||
<span class="floor-name">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.areas.dialog.other_areas"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>`
|
|
||||||
: nothing}
|
|
||||||
<ha-sortable
|
|
||||||
handle-selector=".area-handle"
|
|
||||||
draggable-selector="ha-md-list-item"
|
|
||||||
@item-moved=${this._areaMoved}
|
|
||||||
@item-added=${this._areaAdded}
|
|
||||||
group="areas"
|
|
||||||
.floor=${UNASSIGNED_FLOOR}
|
|
||||||
>
|
|
||||||
<ha-md-list>
|
|
||||||
${this._hierarchy!.areas.length > 0
|
|
||||||
? this._hierarchy!.areas.map((areaId) => this._renderArea(areaId))
|
|
||||||
: html`<p class="empty">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.areas.dialog.empty_unassigned"
|
|
||||||
)}
|
|
||||||
</p>`}
|
|
||||||
</ha-md-list>
|
|
||||||
</ha-sortable>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderArea(areaId: string) {
|
|
||||||
const area = this.hass.areas[areaId];
|
|
||||||
if (!area) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ha-md-list-item .sortableData=${area}>
|
|
||||||
${area.icon
|
|
||||||
? html`<ha-icon slot="start" .icon=${area.icon}></ha-icon>`
|
|
||||||
: html`<ha-svg-icon
|
|
||||||
slot="start"
|
|
||||||
.path=${mdiTextureBox}
|
|
||||||
></ha-svg-icon>`}
|
|
||||||
<span slot="headline">${area.name}</span>
|
|
||||||
<ha-svg-icon
|
|
||||||
class="area-handle"
|
|
||||||
slot="end"
|
|
||||||
.path=${mdiDragHorizontalVariant}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</ha-md-list-item>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _floorMoved(ev: CustomEvent): void {
|
|
||||||
ev.stopPropagation();
|
|
||||||
if (!this._hierarchy) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { oldIndex, newIndex } = ev.detail;
|
|
||||||
const newFloors = [...this._hierarchy.floors];
|
|
||||||
const [movedFloor] = newFloors.splice(oldIndex, 1);
|
|
||||||
newFloors.splice(newIndex, 0, movedFloor);
|
|
||||||
|
|
||||||
this._hierarchy = {
|
|
||||||
...this._hierarchy,
|
|
||||||
floors: newFloors,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private _areaMoved(ev: CustomEvent): void {
|
|
||||||
ev.stopPropagation();
|
|
||||||
if (!this._hierarchy) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { floor } = ev.currentTarget as HTMLElement & { floor: string };
|
|
||||||
const { oldIndex, newIndex } = ev.detail;
|
|
||||||
const floorId = floor === UNASSIGNED_FLOOR ? null : floor;
|
|
||||||
|
|
||||||
if (floorId === null) {
|
|
||||||
// Reorder unassigned areas
|
|
||||||
const newAreas = [...this._hierarchy.areas];
|
|
||||||
const [movedArea] = newAreas.splice(oldIndex, 1);
|
|
||||||
newAreas.splice(newIndex, 0, movedArea);
|
|
||||||
|
|
||||||
this._hierarchy = {
|
|
||||||
...this._hierarchy,
|
|
||||||
areas: newAreas,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// Reorder areas within a floor
|
|
||||||
this._hierarchy = {
|
|
||||||
...this._hierarchy,
|
|
||||||
floors: this._hierarchy.floors.map((f) => {
|
|
||||||
if (f.id === floorId) {
|
|
||||||
const newAreas = [...f.areas];
|
|
||||||
const [movedArea] = newAreas.splice(oldIndex, 1);
|
|
||||||
newAreas.splice(newIndex, 0, movedArea);
|
|
||||||
return { ...f, areas: newAreas };
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _areaAdded(ev: CustomEvent): void {
|
|
||||||
ev.stopPropagation();
|
|
||||||
if (!this._hierarchy) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { floor } = ev.currentTarget as HTMLElement & { floor: string };
|
|
||||||
const { data: area, index } = ev.detail as {
|
|
||||||
data: AreaRegistryEntry;
|
|
||||||
index: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const newFloorId = floor === UNASSIGNED_FLOOR ? null : floor;
|
|
||||||
|
|
||||||
// Update hierarchy
|
|
||||||
const newUnassignedAreas = this._hierarchy.areas.filter(
|
|
||||||
(id) => id !== area.area_id
|
|
||||||
);
|
|
||||||
if (newFloorId === null) {
|
|
||||||
// Add to unassigned at the specified index
|
|
||||||
newUnassignedAreas.splice(index, 0, area.area_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._hierarchy = {
|
|
||||||
...this._hierarchy,
|
|
||||||
floors: this._hierarchy.floors.map((f) => {
|
|
||||||
if (f.id === newFloorId) {
|
|
||||||
// Add to new floor at the specified index
|
|
||||||
const newAreas = [...f.areas];
|
|
||||||
newAreas.splice(index, 0, area.area_id);
|
|
||||||
return { ...f, areas: newAreas };
|
|
||||||
}
|
|
||||||
// Remove from old floor
|
|
||||||
return {
|
|
||||||
...f,
|
|
||||||
areas: f.areas.filter((id) => id !== area.area_id),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
areas: newUnassignedAreas,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeFloorChanges(): FloorChange[] {
|
|
||||||
if (!this._hierarchy) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const changes: FloorChange[] = [];
|
|
||||||
|
|
||||||
// Check areas assigned to floors
|
|
||||||
for (const floor of this._hierarchy.floors) {
|
|
||||||
for (const areaId of floor.areas) {
|
|
||||||
const originalFloorId = this.hass.areas[areaId]?.floor_id ?? null;
|
|
||||||
if (floor.id !== originalFloorId) {
|
|
||||||
changes.push({ areaId, floorId: floor.id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check unassigned areas
|
|
||||||
for (const areaId of this._hierarchy.areas) {
|
|
||||||
const originalFloorId = this.hass.areas[areaId]?.floor_id ?? null;
|
|
||||||
if (originalFloorId !== null) {
|
|
||||||
changes.push({ areaId, floorId: null });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return changes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _save(): Promise<void> {
|
|
||||||
if (!this._hierarchy || this._saving) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._saving = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const areaOrder = getAreasOrder(this._hierarchy);
|
|
||||||
const floorOrder = getFloorOrder(this._hierarchy);
|
|
||||||
|
|
||||||
// Update floor assignments for areas that changed floors
|
|
||||||
const floorChanges = this._computeFloorChanges();
|
|
||||||
const floorChangePromises = floorChanges.map(({ areaId, floorId }) =>
|
|
||||||
updateAreaRegistryEntry(this.hass, areaId, {
|
|
||||||
floor_id: floorId,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(floorChangePromises);
|
|
||||||
|
|
||||||
// Reorder areas and floors
|
|
||||||
await reorderAreaRegistryEntries(this.hass, areaOrder);
|
|
||||||
await reorderFloorRegistryEntries(this.hass, floorOrder);
|
|
||||||
|
|
||||||
this.closeDialog();
|
|
||||||
} catch (err: any) {
|
|
||||||
showToast(this, {
|
|
||||||
message:
|
|
||||||
err.message ||
|
|
||||||
this.hass.localize("ui.panel.config.areas.dialog.reorder_failed"),
|
|
||||||
});
|
|
||||||
this._saving = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return [
|
|
||||||
haStyle,
|
|
||||||
haStyleDialog,
|
|
||||||
css`
|
|
||||||
ha-md-dialog {
|
|
||||||
min-width: 600px;
|
|
||||||
max-height: 90%;
|
|
||||||
--dialog-content-padding: 8px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media all and (max-width: 600px), all and (max-height: 500px) {
|
|
||||||
ha-md-dialog {
|
|
||||||
--md-dialog-container-shape: 0;
|
|
||||||
min-width: 100%;
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.floors {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floor {
|
|
||||||
border: 1px solid var(--divider-color);
|
|
||||||
border-radius: var(
|
|
||||||
--ha-card-border-radius,
|
|
||||||
var(--ha-border-radius-lg)
|
|
||||||
);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floor.unassigned {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floor-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 12px 16px;
|
|
||||||
background-color: var(--secondary-background-color);
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floor-name {
|
|
||||||
flex: 1;
|
|
||||||
font-weight: var(--ha-font-weight-medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
.floor-handle {
|
|
||||||
cursor: grab;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-md-list {
|
|
||||||
padding: 0;
|
|
||||||
--md-list-item-leading-space: 16px;
|
|
||||||
--md-list-item-trailing-space: 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-md-list-item {
|
|
||||||
--md-list-item-one-line-container-height: 48px;
|
|
||||||
--md-list-item-container-shape: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-md-list-item.sortable-ghost {
|
|
||||||
border-radius: calc(
|
|
||||||
var(--ha-card-border-radius, var(--ha-border-radius-lg)) - 1px
|
|
||||||
);
|
|
||||||
box-shadow: inset 0 0 0 2px var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-handle {
|
|
||||||
cursor: grab;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
font-style: italic;
|
|
||||||
margin: 0;
|
|
||||||
padding: 12px 16px;
|
|
||||||
order: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-md-list:has(ha-md-list-item) .empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding-top: 16px;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"dialog-areas-floors-order": DialogAreasFloorsOrder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -144,10 +144,6 @@ class DialogFloorDetail extends LitElement {
|
|||||||
"ui.panel.config.floors.editor.level"
|
"ui.panel.config.floors.editor.level"
|
||||||
)}
|
)}
|
||||||
type="number"
|
type="number"
|
||||||
.helper=${this.hass.localize(
|
|
||||||
"ui.panel.config.floors.editor.level_helper"
|
|
||||||
)}
|
|
||||||
helperPersistent
|
|
||||||
></ha-textfield>
|
></ha-textfield>
|
||||||
|
|
||||||
<ha-icon-picker
|
<ha-icon-picker
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import type { ActionDetail } from "@material/mwc-list";
|
|||||||
import {
|
import {
|
||||||
mdiDelete,
|
mdiDelete,
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
|
mdiDragHorizontalVariant,
|
||||||
mdiHelpCircle,
|
mdiHelpCircle,
|
||||||
mdiPencil,
|
mdiPencil,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
mdiSort,
|
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@@ -21,6 +21,7 @@ import memoizeOne from "memoize-one";
|
|||||||
import {
|
import {
|
||||||
getAreasFloorHierarchy,
|
getAreasFloorHierarchy,
|
||||||
getAreasOrder,
|
getAreasOrder,
|
||||||
|
getFloorOrder,
|
||||||
type AreasFloorHierarchy,
|
type AreasFloorHierarchy,
|
||||||
} from "../../../common/areas/areas-floor-hierarchy";
|
} from "../../../common/areas/areas-floor-hierarchy";
|
||||||
import { formatListWithAnds } from "../../../common/string/format-list";
|
import { formatListWithAnds } from "../../../common/string/format-list";
|
||||||
@@ -41,6 +42,7 @@ import type { FloorRegistryEntry } from "../../../data/floor_registry";
|
|||||||
import {
|
import {
|
||||||
createFloorRegistryEntry,
|
createFloorRegistryEntry,
|
||||||
deleteFloorRegistryEntry,
|
deleteFloorRegistryEntry,
|
||||||
|
reorderFloorRegistryEntries,
|
||||||
updateFloorRegistryEntry,
|
updateFloorRegistryEntry,
|
||||||
} from "../../../data/floor_registry";
|
} from "../../../data/floor_registry";
|
||||||
import {
|
import {
|
||||||
@@ -56,7 +58,6 @@ import {
|
|||||||
loadAreaRegistryDetailDialog,
|
loadAreaRegistryDetailDialog,
|
||||||
showAreaRegistryDetailDialog,
|
showAreaRegistryDetailDialog,
|
||||||
} from "./show-dialog-area-registry-detail";
|
} from "./show-dialog-area-registry-detail";
|
||||||
import { showAreasFloorsOrderDialog } from "./show-dialog-areas-floors-order";
|
|
||||||
import { showFloorRegistryDetailDialog } from "./show-dialog-floor-registry-detail";
|
import { showFloorRegistryDetailDialog } from "./show-dialog-floor-registry-detail";
|
||||||
|
|
||||||
const UNASSIGNED_FLOOR = "__unassigned__";
|
const UNASSIGNED_FLOOR = "__unassigned__";
|
||||||
@@ -83,8 +84,6 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public route!: Route;
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
private _searchParms = new URLSearchParams(window.location.search);
|
|
||||||
|
|
||||||
@state() private _hierarchy?: AreasFloorHierarchy;
|
@state() private _hierarchy?: AreasFloorHierarchy;
|
||||||
|
|
||||||
private _blockHierarchyUpdate = false;
|
private _blockHierarchyUpdate = false;
|
||||||
@@ -168,9 +167,7 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.isWide=${this.isWide}
|
.isWide=${this.isWide}
|
||||||
.backPath=${this._searchParms.has("historyBack")
|
back-path="/config"
|
||||||
? undefined
|
|
||||||
: "/config"}
|
|
||||||
.tabs=${configSections.areas}
|
.tabs=${configSections.areas}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
has-fab
|
has-fab
|
||||||
@@ -182,84 +179,87 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
@click=${this._showHelp}
|
@click=${this._showHelp}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="floors">
|
<ha-sortable
|
||||||
${this._hierarchy.floors.map(({ areas, id }) => {
|
handle-selector=".handle"
|
||||||
const floor = this.hass.floors[id];
|
draggable-selector=".floor"
|
||||||
if (!floor) {
|
@item-moved=${this._floorMoved}
|
||||||
return nothing;
|
.options=${SORT_OPTIONS}
|
||||||
}
|
group="floors"
|
||||||
return html`
|
invert-swap
|
||||||
<div class="floor">
|
>
|
||||||
<div class="header">
|
<div class="floors">
|
||||||
<h2>
|
${this._hierarchy.floors.map(({ areas, id }) => {
|
||||||
<ha-floor-icon .floor=${floor}></ha-floor-icon>
|
const floor = this.hass.floors[id];
|
||||||
${floor.name}
|
if (!floor) {
|
||||||
</h2>
|
return nothing;
|
||||||
<div class="actions">
|
}
|
||||||
<ha-button-menu
|
return html`
|
||||||
.floor=${floor}
|
<div class="floor">
|
||||||
@action=${this._handleFloorAction}
|
<div class="header">
|
||||||
>
|
<h2>
|
||||||
<ha-icon-button
|
<ha-floor-icon .floor=${floor}></ha-floor-icon>
|
||||||
slot="trigger"
|
${floor.name}
|
||||||
.path=${mdiDotsVertical}
|
</h2>
|
||||||
></ha-icon-button>
|
<div class="actions">
|
||||||
<ha-list-item graphic="icon"
|
<ha-svg-icon
|
||||||
><ha-svg-icon
|
class="handle"
|
||||||
.path=${mdiSort}
|
.path=${mdiDragHorizontalVariant}
|
||||||
slot="graphic"
|
></ha-svg-icon>
|
||||||
></ha-svg-icon
|
<ha-button-menu
|
||||||
>${this.hass.localize(
|
.floor=${floor}
|
||||||
"ui.panel.config.areas.picker.reorder"
|
@action=${this._handleFloorAction}
|
||||||
)}</ha-list-item
|
|
||||||
>
|
>
|
||||||
<li divider role="separator"></li>
|
<ha-icon-button
|
||||||
<ha-list-item graphic="icon"
|
slot="trigger"
|
||||||
><ha-svg-icon
|
.path=${mdiDotsVertical}
|
||||||
.path=${mdiPencil}
|
></ha-icon-button>
|
||||||
slot="graphic"
|
<ha-list-item graphic="icon"
|
||||||
></ha-svg-icon
|
><ha-svg-icon
|
||||||
>${this.hass.localize(
|
.path=${mdiPencil}
|
||||||
"ui.panel.config.areas.picker.floor.edit_floor"
|
slot="graphic"
|
||||||
)}</ha-list-item
|
></ha-svg-icon
|
||||||
>
|
>${this.hass.localize(
|
||||||
<ha-list-item class="warning" graphic="icon"
|
"ui.panel.config.areas.picker.floor.edit_floor"
|
||||||
><ha-svg-icon
|
)}</ha-list-item
|
||||||
class="warning"
|
>
|
||||||
.path=${mdiDelete}
|
<ha-list-item class="warning" graphic="icon"
|
||||||
slot="graphic"
|
><ha-svg-icon
|
||||||
></ha-svg-icon
|
class="warning"
|
||||||
>${this.hass.localize(
|
.path=${mdiDelete}
|
||||||
"ui.panel.config.areas.picker.floor.delete_floor"
|
slot="graphic"
|
||||||
)}</ha-list-item
|
></ha-svg-icon
|
||||||
>
|
>${this.hass.localize(
|
||||||
</ha-button-menu>
|
"ui.panel.config.areas.picker.floor.delete_floor"
|
||||||
|
)}</ha-list-item
|
||||||
|
>
|
||||||
|
</ha-button-menu>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ha-sortable
|
||||||
|
handle-selector="a"
|
||||||
|
draggable-selector="a"
|
||||||
|
@item-added=${this._areaAdded}
|
||||||
|
@item-moved=${this._areaMoved}
|
||||||
|
group="areas"
|
||||||
|
.options=${SORT_OPTIONS}
|
||||||
|
.floor=${floor.floor_id}
|
||||||
|
>
|
||||||
|
<div class="areas">
|
||||||
|
${areas.map((areaId) => {
|
||||||
|
const area = this.hass.areas[areaId];
|
||||||
|
if (!area) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const stats = areasStats.get(area.area_id);
|
||||||
|
return this._renderArea(area, stats);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</ha-sortable>
|
||||||
</div>
|
</div>
|
||||||
<ha-sortable
|
`;
|
||||||
handle-selector="a"
|
})}
|
||||||
draggable-selector="a"
|
</div>
|
||||||
@item-added=${this._areaAdded}
|
</ha-sortable>
|
||||||
@item-moved=${this._areaMoved}
|
|
||||||
group="areas"
|
|
||||||
.options=${SORT_OPTIONS}
|
|
||||||
.floor=${floor.floor_id}
|
|
||||||
>
|
|
||||||
<div class="areas">
|
|
||||||
${areas.map((areaId) => {
|
|
||||||
const area = this.hass.areas[areaId];
|
|
||||||
if (!area) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
const stats = areasStats.get(area.area_id);
|
|
||||||
return this._renderArea(area, stats);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</ha-sortable>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${this._hierarchy.areas.length
|
${this._hierarchy.areas.length
|
||||||
? html`
|
? html`
|
||||||
@@ -267,30 +267,9 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<h2>
|
<h2>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
this._hierarchy.floors.length
|
"ui.panel.config.areas.picker.unassigned_areas"
|
||||||
? "ui.panel.config.areas.picker.other_areas"
|
|
||||||
: "ui.panel.config.areas.picker.header"
|
|
||||||
)}
|
)}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="actions">
|
|
||||||
<ha-button-menu
|
|
||||||
@action=${this._handleUnassignedAreasAction}
|
|
||||||
>
|
|
||||||
<ha-icon-button
|
|
||||||
slot="trigger"
|
|
||||||
.path=${mdiDotsVertical}
|
|
||||||
></ha-icon-button>
|
|
||||||
<ha-list-item graphic="icon"
|
|
||||||
><ha-svg-icon
|
|
||||||
.path=${mdiSort}
|
|
||||||
slot="graphic"
|
|
||||||
></ha-svg-icon
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.areas.picker.reorder"
|
|
||||||
)}</ha-list-item
|
|
||||||
>
|
|
||||||
</ha-button-menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<ha-sortable
|
<ha-sortable
|
||||||
handle-selector="a"
|
handle-selector="a"
|
||||||
@@ -412,6 +391,51 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _floorMoved(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (!this.hass || !this._hierarchy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { oldIndex, newIndex } = ev.detail;
|
||||||
|
|
||||||
|
const reorderFloors = (
|
||||||
|
floors: AreasFloorHierarchy["floors"],
|
||||||
|
oldIdx: number,
|
||||||
|
newIdx: number
|
||||||
|
) => {
|
||||||
|
const newFloors = [...floors];
|
||||||
|
const [movedFloor] = newFloors.splice(oldIdx, 1);
|
||||||
|
newFloors.splice(newIdx, 0, movedFloor);
|
||||||
|
return newFloors;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Optimistically update UI
|
||||||
|
this._hierarchy = {
|
||||||
|
...this._hierarchy,
|
||||||
|
floors: reorderFloors(this._hierarchy.floors, oldIndex, newIndex),
|
||||||
|
};
|
||||||
|
|
||||||
|
const areaOrder = getAreasOrder(this._hierarchy);
|
||||||
|
const floorOrder = getFloorOrder(this._hierarchy);
|
||||||
|
|
||||||
|
// Block hierarchy updates for 500ms to avoid flickering
|
||||||
|
// because of multiple async updates
|
||||||
|
this._blockHierarchyUpdateFor(500);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await reorderAreaRegistryEntries(this.hass, areaOrder);
|
||||||
|
await reorderFloorRegistryEntries(this.hass, floorOrder);
|
||||||
|
} catch {
|
||||||
|
showToast(this, {
|
||||||
|
message: this.hass.localize(
|
||||||
|
"ui.panel.config.areas.picker.floor_reorder_failed"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
// Revert on error
|
||||||
|
this._computeHierarchy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async _areaMoved(ev) {
|
private async _areaMoved(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
if (!this.hass || !this._hierarchy) {
|
if (!this.hass || !this._hierarchy) {
|
||||||
@@ -537,23 +561,14 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
const floor = (ev.currentTarget as any).floor;
|
const floor = (ev.currentTarget as any).floor;
|
||||||
switch (ev.detail.index) {
|
switch (ev.detail.index) {
|
||||||
case 0:
|
case 0:
|
||||||
this._showReorderDialog();
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
this._editFloor(floor);
|
this._editFloor(floor);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 1:
|
||||||
this._deleteFloor(floor);
|
this._deleteFloor(floor);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleUnassignedAreasAction(ev: CustomEvent<ActionDetail>) {
|
|
||||||
if (ev.detail.index === 0) {
|
|
||||||
this._showReorderDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createFloor() {
|
private _createFloor() {
|
||||||
this._openFloorDialog();
|
this._openFloorDialog();
|
||||||
}
|
}
|
||||||
@@ -583,10 +598,6 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
this._openAreaDialog();
|
this._openAreaDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _showReorderDialog() {
|
|
||||||
showAreasFloorsOrderDialog(this, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _showHelp() {
|
private _showHelp() {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
title: this.hass.localize("ui.panel.config.areas.caption"),
|
title: this.hass.localize("ui.panel.config.areas.caption"),
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
|
||||||
|
|
||||||
export interface AreasFloorsOrderDialogParams {}
|
|
||||||
|
|
||||||
export const loadAreasFloorsOrderDialog = () =>
|
|
||||||
import("./dialog-areas-floors-order");
|
|
||||||
|
|
||||||
export const showAreasFloorsOrderDialog = (
|
|
||||||
element: HTMLElement,
|
|
||||||
params: AreasFloorsOrderDialogParams
|
|
||||||
): void => {
|
|
||||||
fireEvent(element, "show-dialog", {
|
|
||||||
dialogTag: "dialog-areas-floors-order",
|
|
||||||
dialogImport: loadAreasFloorsOrderDialog,
|
|
||||||
dialogParams: params,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
|
||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { html, LitElement, nothing } from "lit";
|
import { html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, queryAll, state } from "lit/decorators";
|
import { customElement, property, queryAll, state } from "lit/decorators";
|
||||||
@@ -214,7 +213,7 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addAction = (action: string, target?: HassServiceTarget) => {
|
private _addAction = (action: string) => {
|
||||||
let actions: Action[];
|
let actions: Action[];
|
||||||
if (action === PASTE_VALUE) {
|
if (action === PASTE_VALUE) {
|
||||||
actions = this.actions.concat(deepClone(this._clipboard!.action));
|
actions = this.actions.concat(deepClone(this._clipboard!.action));
|
||||||
@@ -224,7 +223,6 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
actions = this.actions.concat({
|
actions = this.actions.concat({
|
||||||
action: getValueFromDynamic(action),
|
action: getValueFromDynamic(action),
|
||||||
metadata: {},
|
metadata: {},
|
||||||
target,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const elClass = customElements.get(
|
const elClass = customElements.get(
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,391 +0,0 @@
|
|||||||
import {
|
|
||||||
mdiInformationOutline,
|
|
||||||
mdiLabel,
|
|
||||||
mdiPlus,
|
|
||||||
mdiTextureBox,
|
|
||||||
} from "@mdi/js";
|
|
||||||
import { LitElement, css, html, nothing, type TemplateResult } from "lit";
|
|
||||||
import {
|
|
||||||
customElement,
|
|
||||||
eventOptions,
|
|
||||||
property,
|
|
||||||
query,
|
|
||||||
state,
|
|
||||||
} from "lit/decorators";
|
|
||||||
import { classMap } from "lit/directives/class-map";
|
|
||||||
import { repeat } from "lit/directives/repeat";
|
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
|
||||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
|
||||||
import "../../../../components/entity/state-badge";
|
|
||||||
import "../../../../components/ha-domain-icon";
|
|
||||||
import "../../../../components/ha-floor-icon";
|
|
||||||
import "../../../../components/ha-icon-next";
|
|
||||||
import "../../../../components/ha-md-list";
|
|
||||||
import "../../../../components/ha-md-list-item";
|
|
||||||
import "../../../../components/ha-svg-icon";
|
|
||||||
import "../../../../components/ha-tooltip";
|
|
||||||
import type { ConfigEntry } from "../../../../data/config_entries";
|
|
||||||
import type { HomeAssistant } from "../../../../types";
|
|
||||||
import type { AddAutomationElementListItem } from "../add-automation-element-dialog";
|
|
||||||
|
|
||||||
type Target = [string, string | undefined, string | undefined];
|
|
||||||
|
|
||||||
@customElement("ha-automation-add-items")
|
|
||||||
export class HaAutomationAddItems extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public items?: {
|
|
||||||
title: string;
|
|
||||||
items: AddAutomationElementListItem[];
|
|
||||||
}[];
|
|
||||||
|
|
||||||
@property() public error?: string;
|
|
||||||
|
|
||||||
@property({ attribute: "select-label" }) public selectLabel!: string;
|
|
||||||
|
|
||||||
@property({ attribute: "empty-label" }) public emptyLabel!: string;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public target?: Target;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public getLabel!: (
|
|
||||||
id: string
|
|
||||||
) => { name: string; icon?: string } | undefined;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public configEntryLookup: Record<
|
|
||||||
string,
|
|
||||||
ConfigEntry
|
|
||||||
> = {};
|
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "tooltip-description" })
|
|
||||||
public tooltipDescription = false;
|
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) scrollable = false;
|
|
||||||
|
|
||||||
@state() private _itemsScrolled = false;
|
|
||||||
|
|
||||||
@query(".items")
|
|
||||||
private _itemsDiv!: HTMLDivElement;
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
return html`<div
|
|
||||||
class=${classMap({
|
|
||||||
items: true,
|
|
||||||
blank: this.error || !this.items || !this.items.length,
|
|
||||||
error: this.error,
|
|
||||||
scrolled: this._itemsScrolled,
|
|
||||||
})}
|
|
||||||
@scroll=${this._onItemsScroll}
|
|
||||||
>
|
|
||||||
${!this.items && !this.error
|
|
||||||
? this.selectLabel
|
|
||||||
: this.error
|
|
||||||
? html`${this.error}
|
|
||||||
<div>${this._renderTarget(this.target)}</div>`
|
|
||||||
: this.items && !this.items.length
|
|
||||||
? html`${this.emptyLabel}
|
|
||||||
${this.target
|
|
||||||
? html`<div>${this._renderTarget(this.target)}</div>`
|
|
||||||
: nothing}`
|
|
||||||
: repeat(
|
|
||||||
this.items,
|
|
||||||
(_, index) => `item-group-${index}`,
|
|
||||||
(itemGroup) =>
|
|
||||||
this._renderItemList(itemGroup.title, itemGroup.items)
|
|
||||||
)}
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderItemList(title, items?: AddAutomationElementListItem[]) {
|
|
||||||
if (!items || !items.length) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="items-title">${title}</div>
|
|
||||||
<ha-md-list>
|
|
||||||
${repeat(
|
|
||||||
items,
|
|
||||||
(item) => item.key,
|
|
||||||
(item) => html`
|
|
||||||
<ha-md-list-item
|
|
||||||
interactive
|
|
||||||
type="button"
|
|
||||||
.value=${item.key}
|
|
||||||
@click=${this._selected}
|
|
||||||
>
|
|
||||||
<div slot="headline" class=${this.target ? "item-headline" : ""}>
|
|
||||||
${item.name}${this._renderTarget(this.target)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${!this.tooltipDescription && item.description
|
|
||||||
? html`<div slot="supporting-text">${item.description}</div>`
|
|
||||||
: nothing}
|
|
||||||
${item.icon
|
|
||||||
? html`<span slot="start">${item.icon}</span>`
|
|
||||||
: item.iconPath
|
|
||||||
? html`<ha-svg-icon
|
|
||||||
slot="start"
|
|
||||||
.path=${item.iconPath}
|
|
||||||
></ha-svg-icon>`
|
|
||||||
: nothing}
|
|
||||||
${this.tooltipDescription && item.description
|
|
||||||
? html`<ha-svg-icon
|
|
||||||
tabindex="0"
|
|
||||||
id=${`description-tooltip-${item.key}`}
|
|
||||||
slot="end"
|
|
||||||
.path=${mdiInformationOutline}
|
|
||||||
@click=${stopPropagation}
|
|
||||||
></ha-svg-icon>
|
|
||||||
<ha-tooltip
|
|
||||||
.for=${`description-tooltip-${item.key}`}
|
|
||||||
@wa-show=${stopPropagation}
|
|
||||||
@wa-hide=${stopPropagation}
|
|
||||||
@wa-after-hide=${stopPropagation}
|
|
||||||
@wa-after-show=${stopPropagation}
|
|
||||||
>${item.description}</ha-tooltip
|
|
||||||
> `
|
|
||||||
: nothing}
|
|
||||||
<ha-svg-icon
|
|
||||||
slot="end"
|
|
||||||
class="plus"
|
|
||||||
.path=${mdiPlus}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</ha-md-list-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</ha-md-list>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderTarget = memoizeOne((target?: Target) => {
|
|
||||||
if (!target) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`<div class="selected-target">
|
|
||||||
${this._getSelectedTargetIcon(target[0], target[1])}
|
|
||||||
<div class="label">${target[2]}</div>
|
|
||||||
</div>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
private _getSelectedTargetIcon(
|
|
||||||
targetType: string,
|
|
||||||
targetId: string | undefined
|
|
||||||
): TemplateResult | typeof nothing {
|
|
||||||
if (!targetId) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetType === "floor") {
|
|
||||||
return html`<ha-floor-icon
|
|
||||||
.floor=${this.hass.floors[targetId]}
|
|
||||||
></ha-floor-icon>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetType === "area" && this.hass.areas[targetId]) {
|
|
||||||
const area = this.hass.areas[targetId];
|
|
||||||
if (area.icon) {
|
|
||||||
return html`<ha-icon .icon=${area.icon}></ha-icon>`;
|
|
||||||
}
|
|
||||||
return html`<ha-svg-icon .path=${mdiTextureBox}></ha-svg-icon>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetType === "device" && this.hass.devices[targetId]) {
|
|
||||||
const device = this.hass.devices[targetId];
|
|
||||||
const configEntry = device.primary_config_entry
|
|
||||||
? this.configEntryLookup[device.primary_config_entry]
|
|
||||||
: undefined;
|
|
||||||
const domain = configEntry?.domain;
|
|
||||||
|
|
||||||
if (domain) {
|
|
||||||
return html`<ha-domain-icon
|
|
||||||
slot="start"
|
|
||||||
.hass=${this.hass}
|
|
||||||
.domain=${domain}
|
|
||||||
brand-fallback
|
|
||||||
></ha-domain-icon>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetType === "entity" && this.hass.states[targetId]) {
|
|
||||||
const stateObj = this.hass.states[targetId];
|
|
||||||
if (stateObj) {
|
|
||||||
return html`<state-badge
|
|
||||||
.stateObj=${stateObj}
|
|
||||||
.hass=${this.hass}
|
|
||||||
.stateColor=${false}
|
|
||||||
></state-badge>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetType === "label") {
|
|
||||||
const label = this.getLabel(targetId);
|
|
||||||
if (label?.icon) {
|
|
||||||
return html`<ha-icon .icon=${label.icon}></ha-icon>`;
|
|
||||||
}
|
|
||||||
return html`<ha-svg-icon .path=${mdiLabel}></ha-svg-icon>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _selected(ev) {
|
|
||||||
const item = ev.currentTarget;
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: item.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@eventOptions({ passive: true })
|
|
||||||
private _onItemsScroll(ev) {
|
|
||||||
const top = ev.target.scrollTop ?? 0;
|
|
||||||
this._itemsScrolled = top > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override scrollTo(options?: ScrollToOptions): void;
|
|
||||||
|
|
||||||
public override scrollTo(x: number, y: number): void;
|
|
||||||
|
|
||||||
public override scrollTo(
|
|
||||||
xOrOptions?: number | ScrollToOptions,
|
|
||||||
y?: number
|
|
||||||
): void {
|
|
||||||
if (typeof xOrOptions === "number") {
|
|
||||||
this._itemsDiv?.scrollTo(xOrOptions, y!);
|
|
||||||
} else {
|
|
||||||
this._itemsDiv?.scrollTo(xOrOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
|
||||||
:host {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
:host([scrollable]) .items {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
.items {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.items.blank {
|
|
||||||
border-radius: var(--ha-border-radius-xl);
|
|
||||||
background-color: var(--ha-color-surface-default);
|
|
||||||
align-items: center;
|
|
||||||
color: var(--ha-color-text-secondary);
|
|
||||||
padding: var(--ha-space-0);
|
|
||||||
margin: var(--ha-space-0) var(--ha-space-4)
|
|
||||||
max(var(--safe-area-inset-bottom), var(--ha-space-3));
|
|
||||||
line-height: var(--ha-line-height-expanded);
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items.error {
|
|
||||||
background-color: var(--ha-color-fill-danger-quiet-resting);
|
|
||||||
color: var(--ha-color-on-danger-normal);
|
|
||||||
}
|
|
||||||
.items ha-md-list {
|
|
||||||
--md-list-item-two-line-container-height: var(--ha-space-12);
|
|
||||||
--md-list-item-leading-space: var(--ha-space-3);
|
|
||||||
--md-list-item-trailing-space: var(--md-list-item-leading-space);
|
|
||||||
--md-list-item-bottom-space: var(--ha-space-2);
|
|
||||||
--md-list-item-top-space: var(--md-list-item-bottom-space);
|
|
||||||
--md-list-item-supporting-text-font: var(--ha-font-family-body);
|
|
||||||
--ha-md-list-item-gap: var(--ha-space-3);
|
|
||||||
gap: var(--ha-space-2);
|
|
||||||
padding: var(--ha-space-0) var(--ha-space-4);
|
|
||||||
}
|
|
||||||
.items ha-md-list ha-md-list-item {
|
|
||||||
border-radius: var(--ha-border-radius-lg);
|
|
||||||
border: 1px solid var(--ha-color-border-neutral-quiet);
|
|
||||||
}
|
|
||||||
|
|
||||||
.items ha-md-list {
|
|
||||||
padding-bottom: max(var(--safe-area-inset-bottom), var(--ha-space-3));
|
|
||||||
}
|
|
||||||
|
|
||||||
.items .item-headline {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--ha-space-2);
|
|
||||||
min-height: var(--ha-space-9);
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-title {
|
|
||||||
position: sticky;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-weight: var(--ha-font-weight-medium);
|
|
||||||
padding-top: var(--ha-space-2);
|
|
||||||
padding-bottom: var(--ha-space-2);
|
|
||||||
padding-inline-start: var(--ha-space-8);
|
|
||||||
padding-inline-end: var(--ha-space-3);
|
|
||||||
top: 0;
|
|
||||||
z-index: 1;
|
|
||||||
background-color: var(--card-background-color);
|
|
||||||
}
|
|
||||||
ha-bottom-sheet .items-title {
|
|
||||||
padding-top: var(--ha-space-3);
|
|
||||||
}
|
|
||||||
.scrolled .items-title:first-of-type {
|
|
||||||
box-shadow: var(--bar-box-shadow);
|
|
||||||
border-bottom: 1px solid var(--ha-color-border-neutral-quiet);
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-icon-next {
|
|
||||||
width: var(--ha-space-6);
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-svg-icon.plus {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-target {
|
|
||||||
display: inline-flex;
|
|
||||||
gap: var(--ha-space-1);
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: var(--ha-border-radius-md);
|
|
||||||
background: var(--ha-color-fill-neutral-normal-resting);
|
|
||||||
padding: 0 var(--ha-space-2) 0 var(--ha-space-1);
|
|
||||||
color: var(--ha-color-on-neutral-normal);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.selected-target .label {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-target ha-icon,
|
|
||||||
.selected-target ha-svg-icon,
|
|
||||||
.selected-target state-badge,
|
|
||||||
.selected-target ha-domain-icon {
|
|
||||||
display: flex;
|
|
||||||
padding: var(--ha-space-1) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-target state-badge {
|
|
||||||
--mdc-icon-size: 24px;
|
|
||||||
}
|
|
||||||
.selected-target state-badge,
|
|
||||||
.selected-target ha-floor-icon {
|
|
||||||
display: flex;
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.selected-target ha-domain-icon {
|
|
||||||
filter: grayscale(100%);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-automation-add-items": HaAutomationAddItems;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,5 @@
|
|||||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import type {
|
|
||||||
HassServiceTarget,
|
|
||||||
UnsubscribeFunc,
|
|
||||||
} from "home-assistant-js-websocket";
|
|
||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { html, LitElement, nothing } from "lit";
|
import { html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, queryAll, state } from "lit/decorators";
|
import { customElement, property, queryAll, state } from "lit/decorators";
|
||||||
@@ -28,7 +24,6 @@ import {
|
|||||||
CONDITION_BUILDING_BLOCKS,
|
CONDITION_BUILDING_BLOCKS,
|
||||||
subscribeConditions,
|
subscribeConditions,
|
||||||
} from "../../../../data/condition";
|
} from "../../../../data/condition";
|
||||||
import { subscribeLabFeature } from "../../../../data/labs";
|
|
||||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import {
|
import {
|
||||||
@@ -78,52 +73,19 @@ export default class HaAutomationCondition extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _conditionKeys = new WeakMap<Condition, string>();
|
private _conditionKeys = new WeakMap<Condition, string>();
|
||||||
|
|
||||||
private _unsub?: Promise<UnsubscribeFunc>;
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
@state() private _newTriggersAndConditions = false;
|
|
||||||
|
|
||||||
public disconnectedCallback() {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
this._unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected hassSubscribe() {
|
protected hassSubscribe() {
|
||||||
return [
|
return [
|
||||||
subscribeLabFeature(
|
subscribeConditions(this.hass, (conditions) =>
|
||||||
this.hass!.connection,
|
this._addConditions(conditions)
|
||||||
"automation",
|
|
||||||
"new_triggers_conditions",
|
|
||||||
(feature) => {
|
|
||||||
this._newTriggersAndConditions = feature.enabled;
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _subscribeDescriptions() {
|
private _addConditions(conditions: ConditionDescriptions) {
|
||||||
this._unsubscribe();
|
this._conditionDescriptions = {
|
||||||
this._conditionDescriptions = {};
|
...this._conditionDescriptions,
|
||||||
this._unsub = subscribeConditions(this.hass, (descriptions) => {
|
...conditions,
|
||||||
this._conditionDescriptions = {
|
};
|
||||||
...this._conditionDescriptions,
|
|
||||||
...descriptions,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _unsubscribe() {
|
|
||||||
if (this._unsub) {
|
|
||||||
this._unsub.then((unsub) => unsub());
|
|
||||||
this._unsub = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected willUpdate(changedProperties: PropertyValues): void {
|
|
||||||
super.willUpdate(changedProperties);
|
|
||||||
if (changedProperties.has("_newTriggersAndConditions")) {
|
|
||||||
this._subscribeDescriptions();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
@@ -299,7 +261,7 @@ export default class HaAutomationCondition extends SubscribeMixin(LitElement) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addCondition = (value: string, target?: HassServiceTarget) => {
|
private _addCondition = (value) => {
|
||||||
let conditions: Condition[];
|
let conditions: Condition[];
|
||||||
if (value === PASTE_VALUE) {
|
if (value === PASTE_VALUE) {
|
||||||
conditions = this.conditions.concat(
|
conditions = this.conditions.concat(
|
||||||
@@ -308,7 +270,6 @@ export default class HaAutomationCondition extends SubscribeMixin(LitElement) {
|
|||||||
} else if (isDynamic(value)) {
|
} else if (isDynamic(value)) {
|
||||||
conditions = this.conditions.concat({
|
conditions = this.conditions.concat({
|
||||||
condition: getValueFromDynamic(value),
|
condition: getValueFromDynamic(value),
|
||||||
target,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const condition = value as Condition["condition"];
|
const condition = value as Condition["condition"];
|
||||||
|
|||||||
@@ -69,45 +69,6 @@ export class HaPlatformCondition extends LitElement {
|
|||||||
} else {
|
} else {
|
||||||
this._manifest = undefined;
|
this._manifest = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
oldValue?.condition !== this.condition?.condition &&
|
|
||||||
this.condition &&
|
|
||||||
this.description?.fields
|
|
||||||
) {
|
|
||||||
let updatedDefaultValue = false;
|
|
||||||
const updatedOptions = {};
|
|
||||||
const loadDefaults = !("options" in this.condition);
|
|
||||||
// Set mandatory bools without a default value to false
|
|
||||||
Object.entries(this.description.fields).forEach(([key, field]) => {
|
|
||||||
if (
|
|
||||||
field.selector &&
|
|
||||||
field.required &&
|
|
||||||
field.default === undefined &&
|
|
||||||
"boolean" in field.selector &&
|
|
||||||
updatedOptions[key] === undefined
|
|
||||||
) {
|
|
||||||
updatedDefaultValue = true;
|
|
||||||
updatedOptions[key] = false;
|
|
||||||
} else if (
|
|
||||||
loadDefaults &&
|
|
||||||
field.selector &&
|
|
||||||
field.default !== undefined &&
|
|
||||||
updatedOptions[key] === undefined
|
|
||||||
) {
|
|
||||||
updatedDefaultValue = true;
|
|
||||||
updatedOptions[key] = field.default;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (updatedDefaultValue) {
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: {
|
|
||||||
...this.condition,
|
|
||||||
options: updatedOptions,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -393,10 +354,6 @@ export class HaPlatformCondition extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
margin: 0px calc(-1 * var(--ha-space-4));
|
|
||||||
}
|
|
||||||
ha-settings-row {
|
ha-settings-row {
|
||||||
padding: 0 var(--ha-space-4);
|
padding: 0 var(--ha-space-4);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
|
||||||
export const PASTE_VALUE = "__paste__";
|
export const PASTE_VALUE = "__paste__";
|
||||||
|
|
||||||
export interface AddAutomationElementDialogParams {
|
export interface AddAutomationElementDialogParams {
|
||||||
type: "trigger" | "condition" | "action";
|
type: "trigger" | "condition" | "action";
|
||||||
add: (key: string, target?: HassServiceTarget) => void;
|
add: (key: string) => void;
|
||||||
clipboardItem: string | undefined;
|
clipboardItem: string | undefined;
|
||||||
}
|
}
|
||||||
const loadDialog = () => import("./add-automation-element-dialog");
|
const loadDialog = () => import("./add-automation-element-dialog");
|
||||||
|
|||||||
@@ -93,13 +93,9 @@ export default class HaAutomationSidebarAction extends LitElement {
|
|||||||
".",
|
".",
|
||||||
2
|
2
|
||||||
);
|
);
|
||||||
|
|
||||||
title = `${domainToName(this.hass.localize, domain)}: ${
|
title = `${domainToName(this.hass.localize, domain)}: ${
|
||||||
this.hass.localize(
|
this.hass.localize(`component.${domain}.services.${service}.name`) ||
|
||||||
`component.${domain}.services.${service}.name`,
|
this.hass.services[domain][service]?.name ||
|
||||||
this.hass.services[domain]?.[service]?.description_placeholders
|
|
||||||
) ||
|
|
||||||
this.hass.services[domain]?.[service]?.name ||
|
|
||||||
title
|
title
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ import "./types/ha-automation-trigger-event";
|
|||||||
import "./types/ha-automation-trigger-geo_location";
|
import "./types/ha-automation-trigger-geo_location";
|
||||||
import "./types/ha-automation-trigger-homeassistant";
|
import "./types/ha-automation-trigger-homeassistant";
|
||||||
import "./types/ha-automation-trigger-list";
|
import "./types/ha-automation-trigger-list";
|
||||||
|
import "./types/ha-automation-trigger-mqtt";
|
||||||
import "./types/ha-automation-trigger-numeric_state";
|
import "./types/ha-automation-trigger-numeric_state";
|
||||||
import "./types/ha-automation-trigger-persistent_notification";
|
import "./types/ha-automation-trigger-persistent_notification";
|
||||||
import "./types/ha-automation-trigger-platform";
|
import "./types/ha-automation-trigger-platform";
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import type {
|
|
||||||
HassServiceTarget,
|
|
||||||
UnsubscribeFunc,
|
|
||||||
} from "home-assistant-js-websocket";
|
|
||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { html, LitElement, nothing } from "lit";
|
import { html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
@@ -24,7 +20,6 @@ import {
|
|||||||
type Trigger,
|
type Trigger,
|
||||||
type TriggerList,
|
type TriggerList,
|
||||||
} from "../../../../data/automation";
|
} from "../../../../data/automation";
|
||||||
import { subscribeLabFeature } from "../../../../data/labs";
|
|
||||||
import type { TriggerDescriptions } from "../../../../data/trigger";
|
import type { TriggerDescriptions } from "../../../../data/trigger";
|
||||||
import { isTriggerList, subscribeTriggers } from "../../../../data/trigger";
|
import { isTriggerList, subscribeTriggers } from "../../../../data/trigger";
|
||||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
@@ -71,54 +66,16 @@ export default class HaAutomationTrigger extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _triggerKeys = new WeakMap<Trigger, string>();
|
private _triggerKeys = new WeakMap<Trigger, string>();
|
||||||
|
|
||||||
private _unsub?: Promise<UnsubscribeFunc>;
|
|
||||||
|
|
||||||
@state() private _triggerDescriptions: TriggerDescriptions = {};
|
@state() private _triggerDescriptions: TriggerDescriptions = {};
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
@state() private _newTriggersAndConditions = false;
|
|
||||||
|
|
||||||
public disconnectedCallback() {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
this._unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected hassSubscribe() {
|
protected hassSubscribe() {
|
||||||
return [
|
return [
|
||||||
subscribeLabFeature(
|
subscribeTriggers(this.hass, (triggers) => this._addTriggers(triggers)),
|
||||||
this.hass!.connection,
|
|
||||||
"automation",
|
|
||||||
"new_triggers_conditions",
|
|
||||||
(feature) => {
|
|
||||||
this._newTriggersAndConditions = feature.enabled;
|
|
||||||
}
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _subscribeDescriptions() {
|
private _addTriggers(triggers: TriggerDescriptions) {
|
||||||
this._unsubscribe();
|
this._triggerDescriptions = { ...this._triggerDescriptions, ...triggers };
|
||||||
this._triggerDescriptions = {};
|
|
||||||
this._unsub = subscribeTriggers(this.hass, (descriptions) => {
|
|
||||||
this._triggerDescriptions = {
|
|
||||||
...this._triggerDescriptions,
|
|
||||||
...descriptions,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _unsubscribe() {
|
|
||||||
if (this._unsub) {
|
|
||||||
this._unsub.then((unsub) => unsub());
|
|
||||||
this._unsub = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected willUpdate(changedProperties: PropertyValues): void {
|
|
||||||
super.willUpdate(changedProperties);
|
|
||||||
if (changedProperties.has("_newTriggersAndConditions")) {
|
|
||||||
this._subscribeDescriptions();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
@@ -217,14 +174,13 @@ export default class HaAutomationTrigger extends SubscribeMixin(LitElement) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addTrigger = (value: string, target?: HassServiceTarget) => {
|
private _addTrigger = (value: string) => {
|
||||||
let triggers: Trigger[];
|
let triggers: Trigger[];
|
||||||
if (value === PASTE_VALUE) {
|
if (value === PASTE_VALUE) {
|
||||||
triggers = this.triggers.concat(deepClone(this._clipboard!.trigger));
|
triggers = this.triggers.concat(deepClone(this._clipboard!.trigger));
|
||||||
} else if (isDynamic(value)) {
|
} else if (isDynamic(value)) {
|
||||||
triggers = this.triggers.concat({
|
triggers = this.triggers.concat({
|
||||||
trigger: getValueFromDynamic(value),
|
trigger: getValueFromDynamic(value),
|
||||||
target,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const trigger = value as Exclude<Trigger, TriggerList>["trigger"];
|
const trigger = value as Exclude<Trigger, TriggerList>["trigger"];
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import "../../../../../components/ha-form/ha-form";
|
||||||
|
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||||
|
import type { MqttTrigger } from "../../../../../data/automation";
|
||||||
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
import type { TriggerElement } from "../ha-automation-trigger-row";
|
||||||
|
|
||||||
|
const SCHEMA = [
|
||||||
|
{ name: "topic", required: true, selector: { text: {} } },
|
||||||
|
{ name: "payload", selector: { text: {} } },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
@customElement("ha-automation-trigger-mqtt")
|
||||||
|
export class HaMQTTTrigger extends LitElement implements TriggerElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public trigger!: MqttTrigger;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
public static get defaultConfig(): MqttTrigger {
|
||||||
|
return { trigger: "mqtt", topic: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<ha-form
|
||||||
|
.schema=${SCHEMA}
|
||||||
|
.data=${this.trigger}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.computeLabel=${this._computeLabelCallback}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-form>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const newTrigger = ev.detail.value;
|
||||||
|
fireEvent(this, "value-changed", { value: newTrigger });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeLabelCallback = (
|
||||||
|
schema: SchemaUnion<typeof SCHEMA>
|
||||||
|
): string =>
|
||||||
|
this.hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.triggers.type.mqtt.${schema.name}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-trigger-mqtt": HaMQTTTrigger;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,149 @@
|
|||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||||
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
|
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import { hasTemplate } from "../../../../../common/string/has-template";
|
import { hasTemplate } from "../../../../../common/string/has-template";
|
||||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
|
||||||
import "../../../../../components/ha-form/ha-form";
|
import "../../../../../components/ha-form/ha-form";
|
||||||
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||||
import type { NumericStateTrigger } from "../../../../../data/automation";
|
import type { NumericStateTrigger } from "../../../../../data/automation";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
|
||||||
|
const SCHEMA = [
|
||||||
|
{
|
||||||
|
name: "entity_id",
|
||||||
|
required: true,
|
||||||
|
selector: { entity: { multiple: true } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "attribute",
|
||||||
|
context: { filter_entity: "entity_id" },
|
||||||
|
selector: {
|
||||||
|
attribute: {
|
||||||
|
hide_attributes: [
|
||||||
|
"access_token",
|
||||||
|
"auto_update",
|
||||||
|
"available_modes",
|
||||||
|
"away_mode",
|
||||||
|
"changed_by",
|
||||||
|
"code_arm_required",
|
||||||
|
"code_format",
|
||||||
|
"color_mode",
|
||||||
|
"color_modes",
|
||||||
|
"current_activity",
|
||||||
|
"device_class",
|
||||||
|
"editable",
|
||||||
|
"effect_list",
|
||||||
|
"effect",
|
||||||
|
"entity_id",
|
||||||
|
"entity_picture",
|
||||||
|
"event_type",
|
||||||
|
"event_types",
|
||||||
|
"fan_mode",
|
||||||
|
"fan_modes",
|
||||||
|
"fan_speed_list",
|
||||||
|
"forecast",
|
||||||
|
"friendly_name",
|
||||||
|
"frontend_stream_type",
|
||||||
|
"has_date",
|
||||||
|
"has_time",
|
||||||
|
"hs_color",
|
||||||
|
"hvac_mode",
|
||||||
|
"hvac_modes",
|
||||||
|
"icon",
|
||||||
|
"id",
|
||||||
|
"latest_version",
|
||||||
|
"max_color_temp_kelvin",
|
||||||
|
"max_mireds",
|
||||||
|
"max_temp",
|
||||||
|
"media_album_name",
|
||||||
|
"media_artist",
|
||||||
|
"media_content_type",
|
||||||
|
"media_position_updated_at",
|
||||||
|
"media_title",
|
||||||
|
"min_color_temp_kelvin",
|
||||||
|
"min_mireds",
|
||||||
|
"min_temp",
|
||||||
|
"mode",
|
||||||
|
"next_dawn",
|
||||||
|
"next_dusk",
|
||||||
|
"next_midnight",
|
||||||
|
"next_noon",
|
||||||
|
"next_rising",
|
||||||
|
"next_setting",
|
||||||
|
"operation_list",
|
||||||
|
"operation_mode",
|
||||||
|
"options",
|
||||||
|
"percentage_step",
|
||||||
|
"precipitation_unit",
|
||||||
|
"preset_mode",
|
||||||
|
"preset_modes",
|
||||||
|
"pressure_unit",
|
||||||
|
"release_notes",
|
||||||
|
"release_summary",
|
||||||
|
"release_url",
|
||||||
|
"restored",
|
||||||
|
"rgb_color",
|
||||||
|
"rgbw_color",
|
||||||
|
"shuffle",
|
||||||
|
"skipped_version",
|
||||||
|
"sound_mode_list",
|
||||||
|
"sound_mode",
|
||||||
|
"source_list",
|
||||||
|
"source_type",
|
||||||
|
"source",
|
||||||
|
"state_class",
|
||||||
|
"step",
|
||||||
|
"supported_color_modes",
|
||||||
|
"supported_features",
|
||||||
|
"swing_mode",
|
||||||
|
"swing_modes",
|
||||||
|
"target_temp_step",
|
||||||
|
"temperature_unit",
|
||||||
|
"title",
|
||||||
|
"token",
|
||||||
|
"unit_of_measurement",
|
||||||
|
"user_id",
|
||||||
|
"uuid",
|
||||||
|
"visibility_unit",
|
||||||
|
"wind_speed_unit",
|
||||||
|
"xy_color",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "above",
|
||||||
|
selector: {
|
||||||
|
number: {
|
||||||
|
mode: "box",
|
||||||
|
min: Number.MIN_SAFE_INTEGER,
|
||||||
|
max: Number.MAX_SAFE_INTEGER,
|
||||||
|
step: 0.1,
|
||||||
|
entity: { domains: ["input_number", "number", "sensor"] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "below",
|
||||||
|
selector: {
|
||||||
|
number: {
|
||||||
|
mode: "box",
|
||||||
|
min: Number.MIN_SAFE_INTEGER,
|
||||||
|
max: Number.MAX_SAFE_INTEGER,
|
||||||
|
step: 0.1,
|
||||||
|
entity: { domains: ["input_number", "number", "sensor"] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "value_template",
|
||||||
|
selector: { template: {} },
|
||||||
|
},
|
||||||
|
{ name: "for", selector: { duration: {} } },
|
||||||
|
] as const;
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-numeric_state")
|
@customElement("ha-automation-trigger-numeric_state")
|
||||||
export class HaNumericStateTrigger extends LitElement {
|
export class HaNumericStateTrigger extends LitElement {
|
||||||
@@ -20,224 +153,7 @@ export class HaNumericStateTrigger extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@state() private _inputAboveIsEntity?: boolean;
|
|
||||||
|
|
||||||
@state() private _inputBelowIsEntity?: boolean;
|
|
||||||
|
|
||||||
private _schema = memoizeOne(
|
|
||||||
(
|
|
||||||
localize: LocalizeFunc,
|
|
||||||
entityId: string | string[],
|
|
||||||
inputAboveIsEntity?: boolean,
|
|
||||||
inputBelowIsEntity?: boolean
|
|
||||||
) =>
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: "entity_id",
|
|
||||||
required: true,
|
|
||||||
selector: { entity: { multiple: true } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "attribute",
|
|
||||||
selector: {
|
|
||||||
attribute: {
|
|
||||||
entity_id: entityId ? entityId[0] : undefined,
|
|
||||||
hide_attributes: [
|
|
||||||
"access_token",
|
|
||||||
"auto_update",
|
|
||||||
"available_modes",
|
|
||||||
"away_mode",
|
|
||||||
"changed_by",
|
|
||||||
"code_arm_required",
|
|
||||||
"code_format",
|
|
||||||
"color_mode",
|
|
||||||
"color_modes",
|
|
||||||
"current_activity",
|
|
||||||
"device_class",
|
|
||||||
"editable",
|
|
||||||
"effect_list",
|
|
||||||
"effect",
|
|
||||||
"entity_id",
|
|
||||||
"entity_picture",
|
|
||||||
"event_type",
|
|
||||||
"event_types",
|
|
||||||
"fan_mode",
|
|
||||||
"fan_modes",
|
|
||||||
"fan_speed_list",
|
|
||||||
"forecast",
|
|
||||||
"friendly_name",
|
|
||||||
"frontend_stream_type",
|
|
||||||
"has_date",
|
|
||||||
"has_time",
|
|
||||||
"hs_color",
|
|
||||||
"hvac_mode",
|
|
||||||
"hvac_modes",
|
|
||||||
"icon",
|
|
||||||
"id",
|
|
||||||
"latest_version",
|
|
||||||
"max_color_temp_kelvin",
|
|
||||||
"max_mireds",
|
|
||||||
"max_temp",
|
|
||||||
"media_album_name",
|
|
||||||
"media_artist",
|
|
||||||
"media_content_type",
|
|
||||||
"media_position_updated_at",
|
|
||||||
"media_title",
|
|
||||||
"min_color_temp_kelvin",
|
|
||||||
"min_mireds",
|
|
||||||
"min_temp",
|
|
||||||
"mode",
|
|
||||||
"next_dawn",
|
|
||||||
"next_dusk",
|
|
||||||
"next_midnight",
|
|
||||||
"next_noon",
|
|
||||||
"next_rising",
|
|
||||||
"next_setting",
|
|
||||||
"operation_list",
|
|
||||||
"operation_mode",
|
|
||||||
"options",
|
|
||||||
"percentage_step",
|
|
||||||
"precipitation_unit",
|
|
||||||
"preset_mode",
|
|
||||||
"preset_modes",
|
|
||||||
"pressure_unit",
|
|
||||||
"release_notes",
|
|
||||||
"release_summary",
|
|
||||||
"release_url",
|
|
||||||
"restored",
|
|
||||||
"rgb_color",
|
|
||||||
"rgbw_color",
|
|
||||||
"shuffle",
|
|
||||||
"skipped_version",
|
|
||||||
"sound_mode_list",
|
|
||||||
"sound_mode",
|
|
||||||
"source_list",
|
|
||||||
"source_type",
|
|
||||||
"source",
|
|
||||||
"state_class",
|
|
||||||
"step",
|
|
||||||
"supported_color_modes",
|
|
||||||
"supported_features",
|
|
||||||
"swing_mode",
|
|
||||||
"swing_modes",
|
|
||||||
"target_temp_step",
|
|
||||||
"temperature_unit",
|
|
||||||
"title",
|
|
||||||
"token",
|
|
||||||
"unit_of_measurement",
|
|
||||||
"user_id",
|
|
||||||
"uuid",
|
|
||||||
"visibility_unit",
|
|
||||||
"wind_speed_unit",
|
|
||||||
"xy_color",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "lower_limit",
|
|
||||||
type: "select",
|
|
||||||
required: true,
|
|
||||||
options: [
|
|
||||||
[
|
|
||||||
"value",
|
|
||||||
localize(
|
|
||||||
"ui.panel.config.automation.editor.triggers.type.numeric_state.type_value"
|
|
||||||
),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"input",
|
|
||||||
localize(
|
|
||||||
"ui.panel.config.automation.editor.triggers.type.numeric_state.type_input"
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
...(inputAboveIsEntity
|
|
||||||
? ([
|
|
||||||
{
|
|
||||||
name: "above",
|
|
||||||
selector: {
|
|
||||||
entity: { domain: ["input_number", "number", "sensor"] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as const)
|
|
||||||
: ([
|
|
||||||
{
|
|
||||||
name: "above",
|
|
||||||
selector: {
|
|
||||||
number: {
|
|
||||||
mode: "box",
|
|
||||||
min: Number.MIN_SAFE_INTEGER,
|
|
||||||
max: Number.MAX_SAFE_INTEGER,
|
|
||||||
step: 0.1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as const)),
|
|
||||||
{
|
|
||||||
name: "upper_limit",
|
|
||||||
type: "select",
|
|
||||||
required: true,
|
|
||||||
options: [
|
|
||||||
[
|
|
||||||
"value",
|
|
||||||
localize(
|
|
||||||
"ui.panel.config.automation.editor.triggers.type.numeric_state.type_value"
|
|
||||||
),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"input",
|
|
||||||
localize(
|
|
||||||
"ui.panel.config.automation.editor.triggers.type.numeric_state.type_input"
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
...(inputBelowIsEntity
|
|
||||||
? ([
|
|
||||||
{
|
|
||||||
name: "below",
|
|
||||||
selector: {
|
|
||||||
entity: { domain: ["input_number", "number", "sensor"] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as const)
|
|
||||||
: ([
|
|
||||||
{
|
|
||||||
name: "below",
|
|
||||||
selector: {
|
|
||||||
number: {
|
|
||||||
mode: "box",
|
|
||||||
min: Number.MIN_SAFE_INTEGER,
|
|
||||||
max: Number.MAX_SAFE_INTEGER,
|
|
||||||
step: 0.1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as const)),
|
|
||||||
{
|
|
||||||
name: "value_template",
|
|
||||||
selector: { template: {} },
|
|
||||||
},
|
|
||||||
{ name: "for", selector: { duration: {} } },
|
|
||||||
] as const
|
|
||||||
);
|
|
||||||
|
|
||||||
public willUpdate(changedProperties: PropertyValues) {
|
public willUpdate(changedProperties: PropertyValues) {
|
||||||
this._inputAboveIsEntity =
|
|
||||||
this._inputAboveIsEntity ??
|
|
||||||
(typeof this.trigger.above === "string" &&
|
|
||||||
((this.trigger.above as string).startsWith("input_number.") ||
|
|
||||||
(this.trigger.above as string).startsWith("number.") ||
|
|
||||||
(this.trigger.above as string).startsWith("sensor.")));
|
|
||||||
this._inputBelowIsEntity =
|
|
||||||
this._inputBelowIsEntity ??
|
|
||||||
(typeof this.trigger.below === "string" &&
|
|
||||||
((this.trigger.below as string).startsWith("input_number.") ||
|
|
||||||
(this.trigger.below as string).startsWith("number.") ||
|
|
||||||
(this.trigger.below as string).startsWith("sensor.")));
|
|
||||||
|
|
||||||
if (!changedProperties.has("trigger")) {
|
if (!changedProperties.has("trigger")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -258,39 +174,20 @@ export class HaNumericStateTrigger extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _data = memoizeOne(
|
private _data = memoizeOne((trigger: NumericStateTrigger) => ({
|
||||||
(
|
...trigger,
|
||||||
inputAboveIsEntity: boolean,
|
entity_id: ensureArray(trigger.entity_id),
|
||||||
inputBelowIsEntity: boolean,
|
for: createDurationData(trigger.for),
|
||||||
trigger: NumericStateTrigger
|
}));
|
||||||
) => ({
|
|
||||||
lower_limit: inputAboveIsEntity ? "input" : "value",
|
|
||||||
upper_limit: inputBelowIsEntity ? "input" : "value",
|
|
||||||
...trigger,
|
|
||||||
entity_id: ensureArray(trigger.entity_id),
|
|
||||||
for: createDurationData(trigger.for),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const schema = this._schema(
|
const data = this._data(this.trigger);
|
||||||
this.hass.localize,
|
|
||||||
this.trigger.entity_id,
|
|
||||||
this._inputAboveIsEntity,
|
|
||||||
this._inputBelowIsEntity
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = this._data(
|
|
||||||
this._inputAboveIsEntity!,
|
|
||||||
this._inputBelowIsEntity!,
|
|
||||||
this.trigger
|
|
||||||
);
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-form
|
<ha-form
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.data=${data}
|
.data=${data}
|
||||||
.schema=${schema}
|
.schema=${SCHEMA}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
.computeLabel=${this._computeLabelCallback}
|
.computeLabel=${this._computeLabelCallback}
|
||||||
@@ -302,12 +199,6 @@ export class HaNumericStateTrigger extends LitElement {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const newTrigger = { ...ev.detail.value };
|
const newTrigger = { ...ev.detail.value };
|
||||||
|
|
||||||
this._inputAboveIsEntity = newTrigger.lower_limit === "input";
|
|
||||||
this._inputBelowIsEntity = newTrigger.upper_limit === "input";
|
|
||||||
|
|
||||||
delete newTrigger.lower_limit;
|
|
||||||
delete newTrigger.upper_limit;
|
|
||||||
|
|
||||||
if (newTrigger.value_template === "") {
|
if (newTrigger.value_template === "") {
|
||||||
delete newTrigger.value_template;
|
delete newTrigger.value_template;
|
||||||
}
|
}
|
||||||
@@ -316,7 +207,7 @@ export class HaNumericStateTrigger extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _computeLabelCallback = (
|
private _computeLabelCallback = (
|
||||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
schema: SchemaUnion<typeof SCHEMA>
|
||||||
): string => {
|
): string => {
|
||||||
switch (schema.name) {
|
switch (schema.name) {
|
||||||
case "entity_id":
|
case "entity_id":
|
||||||
|
|||||||
@@ -25,16 +25,6 @@ const showOptionalToggle = (field: TriggerDescription["fields"][string]) =>
|
|||||||
!field.required &&
|
!field.required &&
|
||||||
!("boolean" in field.selector && field.default);
|
!("boolean" in field.selector && field.default);
|
||||||
|
|
||||||
const DEFAULT_KEYS: (keyof PlatformTrigger)[] = [
|
|
||||||
"trigger",
|
|
||||||
"target",
|
|
||||||
"alias",
|
|
||||||
"id",
|
|
||||||
"variables",
|
|
||||||
"enabled",
|
|
||||||
"options",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-platform")
|
@customElement("ha-automation-trigger-platform")
|
||||||
export class HaPlatformTrigger extends LitElement {
|
export class HaPlatformTrigger extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -62,31 +52,6 @@ export class HaPlatformTrigger extends LitElement {
|
|||||||
if (!changedProperties.has("trigger")) {
|
if (!changedProperties.has("trigger")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newValue: PlatformTrigger | undefined;
|
|
||||||
|
|
||||||
for (const key in this.trigger) {
|
|
||||||
// Migrate old options to `options`
|
|
||||||
if (DEFAULT_KEYS.includes(key as keyof PlatformTrigger)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (newValue === undefined) {
|
|
||||||
newValue = {
|
|
||||||
...this.trigger,
|
|
||||||
options: { [key]: this.trigger[key] },
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
newValue.options![key] = this.trigger[key];
|
|
||||||
}
|
|
||||||
delete newValue[key];
|
|
||||||
}
|
|
||||||
if (newValue !== undefined) {
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: newValue,
|
|
||||||
});
|
|
||||||
this.trigger = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldValue = changedProperties.get("trigger") as
|
const oldValue = changedProperties.get("trigger") as
|
||||||
| undefined
|
| undefined
|
||||||
| this["trigger"];
|
| this["trigger"];
|
||||||
@@ -104,46 +69,6 @@ export class HaPlatformTrigger extends LitElement {
|
|||||||
} else {
|
} else {
|
||||||
this._manifest = undefined;
|
this._manifest = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
oldValue?.trigger !== this.trigger?.trigger &&
|
|
||||||
this.trigger &&
|
|
||||||
this.description?.fields
|
|
||||||
) {
|
|
||||||
let updatedDefaultValue = false;
|
|
||||||
const updatedOptions = {};
|
|
||||||
const loadDefaults = !("options" in this.trigger);
|
|
||||||
// Set mandatory bools without a default value to false
|
|
||||||
Object.entries(this.description.fields).forEach(([key, field]) => {
|
|
||||||
if (
|
|
||||||
field.selector &&
|
|
||||||
field.required &&
|
|
||||||
field.default === undefined &&
|
|
||||||
"boolean" in field.selector &&
|
|
||||||
updatedOptions[key] === undefined
|
|
||||||
) {
|
|
||||||
updatedDefaultValue = true;
|
|
||||||
updatedOptions[key] = false;
|
|
||||||
} else if (
|
|
||||||
loadDefaults &&
|
|
||||||
field.selector &&
|
|
||||||
field.default !== undefined &&
|
|
||||||
updatedOptions[key] === undefined
|
|
||||||
) {
|
|
||||||
updatedDefaultValue = true;
|
|
||||||
updatedOptions[key] = field.default;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (updatedDefaultValue) {
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: {
|
|
||||||
...this.trigger,
|
|
||||||
options: updatedOptions,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -429,10 +354,6 @@ export class HaPlatformTrigger extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
margin: 0px calc(-1 * var(--ha-space-4));
|
|
||||||
}
|
|
||||||
ha-settings-row {
|
ha-settings-row {
|
||||||
padding: 0 var(--ha-space-4);
|
padding: 0 var(--ha-space-4);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
|
||||||
import { mdiDotsVertical, mdiRefresh } from "@mdi/js";
|
import { mdiDotsVertical, mdiRefresh } from "@mdi/js";
|
||||||
import type { HassEntities } from "home-assistant-js-websocket";
|
import type { HassEntities } from "home-assistant-js-websocket";
|
||||||
import type { TemplateResult } from "lit";
|
import type { TemplateResult } from "lit";
|
||||||
@@ -6,13 +5,9 @@ import { LitElement, css, html } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
|
||||||
import "../../../components/ha-alert";
|
import "../../../components/ha-alert";
|
||||||
import "../../../components/ha-bar";
|
import "../../../components/ha-bar";
|
||||||
import "../../../components/ha-button-menu";
|
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-check-list-item";
|
|
||||||
import "../../../components/ha-list-item";
|
|
||||||
import "../../../components/ha-metric";
|
import "../../../components/ha-metric";
|
||||||
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
||||||
import type {
|
import type {
|
||||||
@@ -33,6 +28,9 @@ import "../../../layouts/hass-subpage";
|
|||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import "../dashboard/ha-config-updates";
|
import "../dashboard/ha-config-updates";
|
||||||
import { showJoinBetaDialog } from "./updates/show-dialog-join-beta";
|
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")
|
@customElement("ha-config-section-updates")
|
||||||
class HaConfigSectionUpdates extends LitElement {
|
class HaConfigSectionUpdates extends LitElement {
|
||||||
@@ -73,24 +71,25 @@ class HaConfigSectionUpdates extends LitElement {
|
|||||||
.path=${mdiRefresh}
|
.path=${mdiRefresh}
|
||||||
@click=${this._checkUpdates}
|
@click=${this._checkUpdates}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<ha-button-menu multi>
|
<ha-dropdown @wa-select=${this._handleOverflowAction}>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
slot="trigger"
|
slot="trigger"
|
||||||
.label=${this.hass.localize("ui.common.menu")}
|
.label=${this.hass.localize("ui.common.menu")}
|
||||||
.path=${mdiDotsVertical}
|
.path=${mdiDotsVertical}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<ha-check-list-item
|
|
||||||
left
|
<ha-dropdown-item
|
||||||
@request-selected=${this._toggleSkipped}
|
type="checkbox"
|
||||||
.selected=${this._showSkipped}
|
value="show_skipped"
|
||||||
|
.checked=${this._showSkipped}
|
||||||
>
|
>
|
||||||
${this.hass.localize("ui.panel.config.updates.show_skipped")}
|
${this.hass.localize("ui.panel.config.updates.show_skipped")}
|
||||||
</ha-check-list-item>
|
</ha-dropdown-item>
|
||||||
${this._supervisorInfo
|
${this._supervisorInfo
|
||||||
? html`
|
? html`
|
||||||
<li divider role="separator"></li>
|
<wa-divider></wa-divider>
|
||||||
<ha-list-item
|
<ha-dropdown-item
|
||||||
@request-selected=${this._toggleBeta}
|
value="toggle_beta"
|
||||||
.disabled=${this._supervisorInfo.channel === "dev"}
|
.disabled=${this._supervisorInfo.channel === "dev"}
|
||||||
>
|
>
|
||||||
${this._supervisorInfo.channel === "stable"
|
${this._supervisorInfo.channel === "stable"
|
||||||
@@ -98,10 +97,10 @@ class HaConfigSectionUpdates extends LitElement {
|
|||||||
: this.hass.localize(
|
: this.hass.localize(
|
||||||
"ui.panel.config.updates.leave_beta"
|
"ui.panel.config.updates.leave_beta"
|
||||||
)}
|
)}
|
||||||
</ha-list-item>
|
</ha-dropdown-item>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</ha-button-menu>
|
</ha-dropdown>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ha-card outlined>
|
<ha-card outlined>
|
||||||
@@ -133,27 +132,19 @@ class HaConfigSectionUpdates extends LitElement {
|
|||||||
this._supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
this._supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _toggleSkipped(ev: CustomEvent<RequestSelectedDetail>): void {
|
private async _handleOverflowAction(
|
||||||
if (ev.detail.source !== "property") {
|
ev: CustomEvent<{ item: { value: string } }>
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._showSkipped = !this._showSkipped;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _toggleBeta(
|
|
||||||
ev: CustomEvent<RequestSelectedDetail>
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
if (ev.detail.item.value === "toggle_beta") {
|
||||||
return;
|
if (this._supervisorInfo!.channel === "stable") {
|
||||||
}
|
showJoinBetaDialog(this, {
|
||||||
|
join: async () => this._setChannel("beta"),
|
||||||
if (this._supervisorInfo!.channel === "stable") {
|
});
|
||||||
showJoinBetaDialog(this, {
|
} else {
|
||||||
join: async () => this._setChannel("beta"),
|
this._setChannel("stable");
|
||||||
});
|
}
|
||||||
} else {
|
} else if (ev.detail.item.value === "show_skipped") {
|
||||||
this._setChannel("stable");
|
this._showSkipped = !this._showSkipped;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
import { mdiKey } from "@mdi/js";
|
|
||||||
import { getConfigEntries } from "../../../../../../data/config_entries";
|
|
||||||
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
|
||||||
import { fetchESPHomeEncryptionKey } from "../../../../../../data/esphome";
|
|
||||||
import type { HomeAssistant } from "../../../../../../types";
|
|
||||||
import { showESPHomeEncryptionKeyDialog } from "../../../../integrations/integration-panels/esphome/show-dialog-esphome-encryption-key";
|
|
||||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
|
||||||
|
|
||||||
export const getESPHomeDeviceActions = async (
|
|
||||||
el: HTMLElement,
|
|
||||||
hass: HomeAssistant,
|
|
||||||
device: DeviceRegistryEntry
|
|
||||||
): Promise<DeviceAction[]> => {
|
|
||||||
const actions: DeviceAction[] = [];
|
|
||||||
|
|
||||||
const configEntries = await getConfigEntries(hass, {
|
|
||||||
domain: "esphome",
|
|
||||||
});
|
|
||||||
|
|
||||||
const configEntry = configEntries.find((entry) =>
|
|
||||||
device.config_entries.includes(entry.entry_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!configEntry) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const entryId = configEntry.entry_id;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const encryptionKey = await fetchESPHomeEncryptionKey(hass, entryId);
|
|
||||||
|
|
||||||
if (encryptionKey.encryption_key) {
|
|
||||||
actions.push({
|
|
||||||
label: hass.localize(
|
|
||||||
"ui.panel.config.devices.esphome.show_encryption_key"
|
|
||||||
),
|
|
||||||
icon: mdiKey,
|
|
||||||
action: () =>
|
|
||||||
showESPHomeEncryptionKeyDialog(el, {
|
|
||||||
entry_id: entryId,
|
|
||||||
encryption_key: encryptionKey.encryption_key,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error("Failed to fetch ESPHome encryption key:", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions;
|
|
||||||
};
|
|
||||||
@@ -1162,17 +1162,6 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
);
|
);
|
||||||
deviceActions.push(...actions);
|
deviceActions.push(...actions);
|
||||||
}
|
}
|
||||||
if (domains.includes("esphome")) {
|
|
||||||
const esphome = await import(
|
|
||||||
"./device-detail/integration-elements/esphome/device-actions"
|
|
||||||
);
|
|
||||||
const actions = await esphome.getESPHomeDeviceActions(
|
|
||||||
this,
|
|
||||||
this.hass,
|
|
||||||
device
|
|
||||||
);
|
|
||||||
deviceActions.push(...actions);
|
|
||||||
}
|
|
||||||
if (domains.includes("matter")) {
|
if (domains.includes("matter")) {
|
||||||
const matter = await import(
|
const matter = await import(
|
||||||
"./device-detail/integration-elements/matter/device-actions"
|
"./device-detail/integration-elements/matter/device-actions"
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { consume } from "@lit/context";
|
import { consume } from "@lit/context";
|
||||||
import {
|
import {
|
||||||
mdiCancel,
|
|
||||||
mdiChevronRight,
|
mdiChevronRight,
|
||||||
mdiDelete,
|
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
mdiMenuDown,
|
mdiMenuDown,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
mdiTextureBox,
|
mdiTextureBox,
|
||||||
|
mdiCancel,
|
||||||
|
mdiDelete,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
|
|
||||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, state, query } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { computeCssColor } from "../../../common/color/compute-color";
|
import { computeCssColor } from "../../../common/color/compute-color";
|
||||||
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
||||||
@@ -68,8 +68,8 @@ import type {
|
|||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
} from "../../../data/device_registry";
|
} from "../../../data/device_registry";
|
||||||
import {
|
import {
|
||||||
removeConfigEntryFromDevice,
|
|
||||||
updateDeviceRegistryEntry,
|
updateDeviceRegistryEntry,
|
||||||
|
removeConfigEntryFromDevice,
|
||||||
} from "../../../data/device_registry";
|
} from "../../../data/device_registry";
|
||||||
import type { EntityRegistryEntry } from "../../../data/entity_registry";
|
import type { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||||
import {
|
import {
|
||||||
@@ -86,8 +86,8 @@ import {
|
|||||||
showAlertDialog,
|
showAlertDialog,
|
||||||
showConfirmationDialog,
|
showConfirmationDialog,
|
||||||
} from "../../../dialogs/generic/show-dialog-box";
|
} from "../../../dialogs/generic/show-dialog-box";
|
||||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
|
||||||
import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
|
import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
|
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../../types";
|
import type { HomeAssistant, Route } from "../../../types";
|
||||||
@@ -318,7 +318,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const deviceEntityLookup: DeviceEntityLookup<EntityRegistryEntry> = {};
|
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
if (!entity.device_id) {
|
if (!entity.device_id) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
import { mdiClose, mdiContentCopy } from "@mdi/js";
|
|
||||||
import type { CSSResultGroup } from "lit";
|
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
|
||||||
import { copyToClipboard } from "../../../../../common/util/copy-clipboard";
|
|
||||||
import "../../../../../components/ha-button";
|
|
||||||
import "../../../../../components/ha-dialog-footer";
|
|
||||||
import "../../../../../components/ha-dialog-header";
|
|
||||||
import "../../../../../components/ha-icon-button";
|
|
||||||
import "../../../../../components/ha-wa-dialog";
|
|
||||||
import { haStyleDialog } from "../../../../../resources/styles";
|
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
|
||||||
import { showToast } from "../../../../../util/toast";
|
|
||||||
import type { ESPHomeEncryptionKeyDialogParams } from "./show-dialog-esphome-encryption-key";
|
|
||||||
|
|
||||||
@customElement("dialog-esphome-encryption-key")
|
|
||||||
class DialogESPHomeEncryptionKey extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@state() private _params?: ESPHomeEncryptionKeyDialogParams;
|
|
||||||
|
|
||||||
public async showDialog(
|
|
||||||
params: ESPHomeEncryptionKeyDialogParams
|
|
||||||
): Promise<void> {
|
|
||||||
this._params = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeDialog(): void {
|
|
||||||
this._params = undefined;
|
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
if (!this._params) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ha-wa-dialog
|
|
||||||
open
|
|
||||||
@closed=${this.closeDialog}
|
|
||||||
header-title=${this.hass.localize(
|
|
||||||
"ui.panel.config.devices.esphome.encryption_key_title"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ha-dialog-header slot="heading">
|
|
||||||
<ha-icon-button
|
|
||||||
slot="navigationIcon"
|
|
||||||
dialogAction="cancel"
|
|
||||||
.label=${this.hass.localize("ui.common.close")}
|
|
||||||
.path=${mdiClose}
|
|
||||||
></ha-icon-button>
|
|
||||||
<span slot="title">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.devices.esphome.encryption_key_title"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</ha-dialog-header>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<p>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.devices.esphome.encryption_key_description"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<div class="key-row">
|
|
||||||
<div class="key-container">
|
|
||||||
<code>${this._params.encryption_key}</code>
|
|
||||||
</div>
|
|
||||||
<ha-icon-button
|
|
||||||
@click=${this._copyToClipboard}
|
|
||||||
.label=${this.hass.localize("ui.common.copy")}
|
|
||||||
.path=${mdiContentCopy}
|
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ha-dialog-footer slot="footer">
|
|
||||||
<ha-button slot="primaryAction" data-dialog="close">
|
|
||||||
${this.hass.localize("ui.common.close")}
|
|
||||||
</ha-button>
|
|
||||||
</ha-dialog-footer>
|
|
||||||
</ha-wa-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _copyToClipboard(): Promise<void> {
|
|
||||||
if (!this._params?.encryption_key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await copyToClipboard(this._params.encryption_key);
|
|
||||||
showToast(this, {
|
|
||||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return [
|
|
||||||
haStyleDialog,
|
|
||||||
css`
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--ha-space-6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.key-row {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--ha-space-2);
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.key-container {
|
|
||||||
flex: 1;
|
|
||||||
border-radius: var(--ha-space-2);
|
|
||||||
border: 1px solid var(--divider-color);
|
|
||||||
background-color: var(
|
|
||||||
--code-editor-background-color,
|
|
||||||
var(--secondary-background-color)
|
|
||||||
);
|
|
||||||
padding: var(--ha-space-3);
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
line-height: var(--ha-line-height-condensed);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"dialog-esphome-encryption-key": DialogESPHomeEncryptionKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
|
||||||
|
|
||||||
export interface ESPHomeEncryptionKeyDialogParams {
|
|
||||||
entry_id: string;
|
|
||||||
encryption_key: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const loadESPHomeEncryptionKeyDialog = () =>
|
|
||||||
import("./dialog-esphome-encryption-key");
|
|
||||||
|
|
||||||
export const showESPHomeEncryptionKeyDialog = (
|
|
||||||
element: HTMLElement,
|
|
||||||
dialogParams: ESPHomeEncryptionKeyDialogParams
|
|
||||||
): void => {
|
|
||||||
fireEvent(element, "show-dialog", {
|
|
||||||
dialogTag: "dialog-esphome-encryption-key",
|
|
||||||
dialogImport: loadESPHomeEncryptionKeyDialog,
|
|
||||||
dialogParams,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -9,10 +9,10 @@ export class MatterAddDevice extends HTMLElement {
|
|||||||
public hass!: HomeAssistant;
|
public hass!: HomeAssistant;
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
navigate("/config/devices/dashboard", {
|
showMatterAddDeviceDialog(this);
|
||||||
|
navigate(`/config/devices`, {
|
||||||
replace: true,
|
replace: true,
|
||||||
});
|
});
|
||||||
showMatterAddDeviceDialog(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import {
|
|||||||
subscribeEntityRegistry,
|
subscribeEntityRegistry,
|
||||||
updateEntityRegistryEntry,
|
updateEntityRegistryEntry,
|
||||||
} from "../../../../../../data/entity_registry";
|
} from "../../../../../../data/entity_registry";
|
||||||
|
import { SubscribeMixin } from "../../../../../../mixins/subscribe-mixin";
|
||||||
import "./zwave-js-add-node-added-insecure";
|
import "./zwave-js-add-node-added-insecure";
|
||||||
import "./zwave-js-add-node-code-input";
|
import "./zwave-js-add-node-code-input";
|
||||||
import "./zwave-js-add-node-configure-device";
|
import "./zwave-js-add-node-configure-device";
|
||||||
@@ -68,7 +69,7 @@ import "./zwave-js-add-node-select-security-strategy";
|
|||||||
const INCLUSION_TIMEOUT_MINUTES = 5;
|
const INCLUSION_TIMEOUT_MINUTES = 5;
|
||||||
|
|
||||||
@customElement("dialog-zwave_js-add-node")
|
@customElement("dialog-zwave_js-add-node")
|
||||||
class DialogZWaveJSAddNode extends LitElement {
|
class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||||
// #region variables
|
// #region variables
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -102,8 +103,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
|
|
||||||
@state() private _securityClasses: SecurityClass[] = [];
|
@state() private _securityClasses: SecurityClass[] = [];
|
||||||
|
|
||||||
@state() private _entities: EntityRegistryEntry[] = [];
|
|
||||||
|
|
||||||
@state() private _codeInput = "";
|
@state() private _codeInput = "";
|
||||||
|
|
||||||
@query("ha-dialog") private _dialog?: HaDialog;
|
@query("ha-dialog") private _dialog?: HaDialog;
|
||||||
@@ -114,14 +113,22 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
|
|
||||||
private _onStop?: () => void;
|
private _onStop?: () => void;
|
||||||
|
|
||||||
private _subscribedAddZwaveNode?: Promise<UnsubscribeFunc | undefined>;
|
private _subscribed?: Promise<UnsubscribeFunc | undefined>;
|
||||||
|
|
||||||
private _newDeviceSubscription?: Promise<UnsubscribeFunc | undefined>;
|
private _newDeviceSubscription?: Promise<UnsubscribeFunc | undefined>;
|
||||||
|
|
||||||
private _subscribedEntityRegistry?: UnsubscribeFunc;
|
@state() private _entities: EntityRegistryEntry[] = [];
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
|
return [
|
||||||
|
subscribeEntityRegistry(this.hass.connection, (entities) => {
|
||||||
|
this._entities = entities;
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this._entryId) {
|
if (!this._entryId) {
|
||||||
return nothing;
|
return nothing;
|
||||||
@@ -432,6 +439,11 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
></zwave-js-add-node-loading>`;
|
></zwave-js-add-node-loading>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public connectedCallback(): void {
|
||||||
|
super.connectedCallback();
|
||||||
|
window.addEventListener("beforeunload", this._onBeforeUnload);
|
||||||
|
}
|
||||||
|
|
||||||
private _onBeforeUnload = (event: BeforeUnloadEvent) => {
|
private _onBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||||
if (this._step && this._shouldPreventClose(this._step)) {
|
if (this._step && this._shouldPreventClose(this._step)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -456,14 +468,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise<void> {
|
public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise<void> {
|
||||||
window.addEventListener("beforeunload", this._onBeforeUnload);
|
|
||||||
this._subscribedEntityRegistry = subscribeEntityRegistry(
|
|
||||||
this.hass.connection,
|
|
||||||
(entities) => {
|
|
||||||
this._entities = entities;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this._step) {
|
if (this._step) {
|
||||||
// already started
|
// already started
|
||||||
return;
|
return;
|
||||||
@@ -558,7 +562,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
this._step = "select_method";
|
this._step = "select_method";
|
||||||
break;
|
break;
|
||||||
case "search_devices":
|
case "search_devices":
|
||||||
this._unsubscribeAddZwaveNode();
|
this._unsubscribe();
|
||||||
if (
|
if (
|
||||||
this._supportsSmartStart &&
|
this._supportsSmartStart &&
|
||||||
this.hass.auth.external?.config.hasBarCodeScanner
|
this.hass.auth.external?.config.hasBarCodeScanner
|
||||||
@@ -600,7 +604,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _searchDevicesShowSecurityOptions() {
|
private _searchDevicesShowSecurityOptions() {
|
||||||
this._unsubscribeAddZwaveNode();
|
this._unsubscribe();
|
||||||
this._step = "choose_security_strategy";
|
this._step = "choose_security_strategy";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,7 +626,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
this._lowSecurity = false;
|
this._lowSecurity = false;
|
||||||
|
|
||||||
const s2Device = qrProvisioningInformation || dsk;
|
const s2Device = qrProvisioningInformation || dsk;
|
||||||
this._subscribedAddZwaveNode = subscribeAddZwaveNode(
|
this._subscribed = subscribeAddZwaveNode(
|
||||||
this.hass,
|
this.hass,
|
||||||
this._entryId!,
|
this._entryId!,
|
||||||
(message) => {
|
(message) => {
|
||||||
@@ -631,7 +635,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
this._step = s2Device ? "search_s2_device" : "search_devices";
|
this._step = s2Device ? "search_s2_device" : "search_devices";
|
||||||
break;
|
break;
|
||||||
case "inclusion failed":
|
case "inclusion failed":
|
||||||
this._unsubscribeAddZwaveNode();
|
this._unsubscribe();
|
||||||
this._step = "failed";
|
this._step = "failed";
|
||||||
break;
|
break;
|
||||||
case "inclusion stopped":
|
case "inclusion stopped":
|
||||||
@@ -673,7 +677,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
this._lowSecurityReason = message.node.low_security_reason;
|
this._lowSecurityReason = message.node.low_security_reason;
|
||||||
break;
|
break;
|
||||||
case "interview completed":
|
case "interview completed":
|
||||||
this._unsubscribeAddZwaveNode();
|
this._unsubscribe();
|
||||||
this._step = "configure_device";
|
this._step = "configure_device";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -690,7 +694,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
});
|
});
|
||||||
this._addNodeTimeoutHandle = window.setTimeout(
|
this._addNodeTimeoutHandle = window.setTimeout(
|
||||||
() => {
|
() => {
|
||||||
this._unsubscribeAddZwaveNode();
|
this._unsubscribe();
|
||||||
this._error = this.hass.localize(
|
this._error = this.hass.localize(
|
||||||
"ui.panel.config.zwave_js.add_node.timeout_error",
|
"ui.panel.config.zwave_js.add_node.timeout_error",
|
||||||
{ minutes: INCLUSION_TIMEOUT_MINUTES }
|
{ minutes: INCLUSION_TIMEOUT_MINUTES }
|
||||||
@@ -1019,10 +1023,10 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _unsubscribeAddZwaveNode(): void {
|
private _unsubscribe(): void {
|
||||||
if (this._subscribedAddZwaveNode) {
|
if (this._subscribed) {
|
||||||
this._subscribedAddZwaveNode.then((unsub) => unsub && unsub());
|
this._subscribed.then((unsub) => unsub && unsub());
|
||||||
this._subscribedAddZwaveNode = undefined;
|
this._subscribed = undefined;
|
||||||
|
|
||||||
if (this._entryId) {
|
if (this._entryId) {
|
||||||
stopZwaveInclusion(this.hass, this._entryId);
|
stopZwaveInclusion(this.hass, this._entryId);
|
||||||
@@ -1056,17 +1060,8 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _unsubscribeDialog() {
|
|
||||||
if (this._subscribedEntityRegistry) {
|
|
||||||
this._subscribedEntityRegistry();
|
|
||||||
this._subscribedEntityRegistry = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _dialogClosed() {
|
private _dialogClosed() {
|
||||||
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
this._unsubscribe();
|
||||||
this._unsubscribeAddZwaveNode();
|
|
||||||
this._unsubscribeDialog();
|
|
||||||
this._open = false;
|
this._open = false;
|
||||||
this._entryId = undefined;
|
this._entryId = undefined;
|
||||||
this._step = undefined;
|
this._step = undefined;
|
||||||
@@ -1105,8 +1100,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
||||||
|
|
||||||
this._unsubscribeAddZwaveNode();
|
this._unsubscribe();
|
||||||
this._unsubscribeDialog();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
|
||||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import "../../../components/ha-button";
|
import "../../../components/ha-button";
|
||||||
@@ -12,7 +11,6 @@ import type { HaSwitch } from "../../../components/ha-switch";
|
|||||||
import "../../../components/ha-switch";
|
import "../../../components/ha-switch";
|
||||||
import type { BackupConfig } from "../../../data/backup";
|
import type { BackupConfig } from "../../../data/backup";
|
||||||
import { fetchBackupConfig } from "../../../data/backup";
|
import { fetchBackupConfig } from "../../../data/backup";
|
||||||
import { getSupervisorUpdateConfig } from "../../../data/supervisor/update";
|
|
||||||
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import type { LabsPreviewFeatureEnableDialogParams } from "./show-dialog-labs-preview-feature-enable";
|
import type { LabsPreviewFeatureEnableDialogParams } from "./show-dialog-labs-preview-feature-enable";
|
||||||
@@ -37,10 +35,7 @@ export class DialogLabsPreviewFeatureEnable
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._createBackup = false;
|
this._createBackup = false;
|
||||||
this._fetchBackupConfig();
|
await this._fetchBackupConfig();
|
||||||
if (isComponentLoaded(this.hass, "hassio")) {
|
|
||||||
this._fetchUpdateBackupConfig();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog(): boolean {
|
public closeDialog(): boolean {
|
||||||
@@ -59,21 +54,15 @@ export class DialogLabsPreviewFeatureEnable
|
|||||||
try {
|
try {
|
||||||
const { config } = await fetchBackupConfig(this.hass);
|
const { config } = await fetchBackupConfig(this.hass);
|
||||||
this._backupConfig = config;
|
this._backupConfig = config;
|
||||||
} catch (err) {
|
|
||||||
// Ignore error, user will get manual backup option
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _fetchUpdateBackupConfig() {
|
// Default to enabled if automatic backups are configured, disabled otherwise
|
||||||
try {
|
this._createBackup =
|
||||||
const config = await getSupervisorUpdateConfig(this.hass);
|
config.automatic_backups_configured &&
|
||||||
this._createBackup = config.core_backup_before_update;
|
!!config.create_backup.password &&
|
||||||
} catch (err) {
|
config.create_backup.agent_ids.length > 0;
|
||||||
// Ignore error, user can still toggle the switch manually
|
} catch {
|
||||||
// eslint-disable-next-line no-console
|
// User will get manual backup option if fetch fails
|
||||||
console.error(err);
|
this._createBackup = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class HaConfigLabs extends SubscribeMixin(LitElement) {
|
|||||||
<hass-subpage
|
<hass-subpage
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
back-path="/config/system"
|
back-path="/config"
|
||||||
.header=${this.hass.localize("ui.panel.config.labs.caption")}
|
.header=${this.hass.localize("ui.panel.config.labs.caption")}
|
||||||
>
|
>
|
||||||
${sortedFeatures.length
|
${sortedFeatures.length
|
||||||
@@ -385,10 +385,6 @@ class HaConfigLabs extends SubscribeMixin(LitElement) {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
a[slot="toolbar-icon"] {
|
|
||||||
color: var(--sidebar-icon-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
|
|
||||||
PANEL_DASHBOARDS.forEach((panel) => {
|
PANEL_DASHBOARDS.forEach((panel) => {
|
||||||
const panelInfo = this.hass.panels[panel];
|
const panelInfo = this.hass.panels[panel];
|
||||||
if (!panelInfo) {
|
if (!panel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const item: DataTableItem = {
|
const item: DataTableItem = {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
|
||||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||||
import "../../../../components/ha-form/ha-form";
|
import "../../../../components/ha-form/ha-form";
|
||||||
import type { AssistPipeline } from "../../../../data/assist_pipeline";
|
import type { AssistPipeline } from "../../../../data/assist_pipeline";
|
||||||
@@ -79,18 +78,6 @@ export class AssistPipelineDetailSTT extends LitElement {
|
|||||||
|
|
||||||
private _supportedLanguagesChanged(ev) {
|
private _supportedLanguagesChanged(ev) {
|
||||||
this._supportedLanguages = ev.detail.value;
|
this._supportedLanguages = ev.detail.value;
|
||||||
|
|
||||||
if (
|
|
||||||
!this.data?.stt_language ||
|
|
||||||
!this._supportedLanguages?.includes(this.data.stt_language)
|
|
||||||
) {
|
|
||||||
// wait for update of conversation_engine
|
|
||||||
setTimeout(() => {
|
|
||||||
const value = { ...this.data };
|
|
||||||
value.stt_language = this._supportedLanguages?.[0] ?? null;
|
|
||||||
fireEvent(this, "value-changed", { value });
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
|
||||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
import "../../../../components/ha-form/ha-form";
|
import "../../../../components/ha-form/ha-form";
|
||||||
@@ -123,18 +122,6 @@ export class AssistPipelineDetailTTS extends LitElement {
|
|||||||
|
|
||||||
private _supportedLanguagesChanged(ev) {
|
private _supportedLanguagesChanged(ev) {
|
||||||
this._supportedLanguages = ev.detail.value;
|
this._supportedLanguages = ev.detail.value;
|
||||||
|
|
||||||
if (
|
|
||||||
!this.data?.tts_language ||
|
|
||||||
!this._supportedLanguages?.includes(this.data?.tts_language)
|
|
||||||
) {
|
|
||||||
// wait for update of conversation_engine
|
|
||||||
setTimeout(() => {
|
|
||||||
const value = { ...this.data };
|
|
||||||
value.tts_language = this._supportedLanguages?.[0] ?? null;
|
|
||||||
fireEvent(this, "value-changed", { value });
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
|
|||||||
@@ -215,62 +215,57 @@ export class AssistPipelineDebug extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<div class="messages">
|
<div class="messages">
|
||||||
${messages.map((content) =>
|
${messages.map((content) =>
|
||||||
content.role === "system"
|
content.role === "system" || content.role === "tool_result"
|
||||||
? content.content
|
? html`
|
||||||
? html`
|
<ha-expansion-panel
|
||||||
<ha-expansion-panel
|
class="content-expansion ${content.role}"
|
||||||
class="content-expansion ${content.role}"
|
>
|
||||||
>
|
<div slot="header">
|
||||||
<div slot="header">System</div>
|
${content.role === "system"
|
||||||
<pre>${content.content}</pre>
|
? "System"
|
||||||
</ha-expansion-panel>
|
: `Result for ${content.tool_name}`}
|
||||||
`
|
</div>
|
||||||
: nothing
|
${content.role === "system"
|
||||||
: content.role === "tool_result"
|
? html`<pre>${content.content}</pre>`
|
||||||
? html`
|
: html`
|
||||||
<ha-expansion-panel
|
<ha-yaml-editor
|
||||||
class="content-expansion ${content.role}"
|
read-only
|
||||||
>
|
auto-update
|
||||||
<div slot="header">
|
.value=${content}
|
||||||
Result for ${content.tool_name}
|
></ha-yaml-editor>
|
||||||
</div>
|
`}
|
||||||
<ha-yaml-editor
|
</ha-expansion-panel>
|
||||||
read-only
|
`
|
||||||
auto-update
|
: html`
|
||||||
.value=${content}
|
${content.content
|
||||||
></ha-yaml-editor>
|
? html`
|
||||||
</ha-expansion-panel>
|
<div class=${`message ${content.role}`}>
|
||||||
`
|
${content.content}
|
||||||
: html`
|
</div>
|
||||||
${content.content
|
`
|
||||||
? html`
|
: nothing}
|
||||||
<div class=${`message ${content.role}`}>
|
${content.role === "assistant" &&
|
||||||
${content.content}
|
content.tool_calls?.length
|
||||||
</div>
|
? html`
|
||||||
`
|
<ha-expansion-panel
|
||||||
: nothing}
|
class="content-expansion assistant"
|
||||||
${content.role === "assistant" &&
|
>
|
||||||
content.tool_calls?.length
|
<span slot="header">
|
||||||
? html`
|
Call
|
||||||
<ha-expansion-panel
|
${content.tool_calls.length === 1
|
||||||
class="content-expansion assistant"
|
? content.tool_calls[0].tool_name
|
||||||
>
|
: `${content.tool_calls.length} tools`}
|
||||||
<span slot="header">
|
</span>
|
||||||
Call
|
|
||||||
${content.tool_calls.length === 1
|
|
||||||
? content.tool_calls[0].tool_name
|
|
||||||
: `${content.tool_calls.length} tools`}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<ha-yaml-editor
|
<ha-yaml-editor
|
||||||
read-only
|
read-only
|
||||||
auto-update
|
auto-update
|
||||||
.value=${content.tool_calls}
|
.value=${content.tool_calls}
|
||||||
></ha-yaml-editor>
|
></ha-yaml-editor>
|
||||||
</ha-expansion-panel>
|
</ha-expansion-panel>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style="clear:both"></div>
|
<div style="clear:both"></div>
|
||||||
|
|||||||
@@ -135,11 +135,6 @@ class HaPanelDevAction extends LitElement {
|
|||||||
? computeObjectId(this._serviceData?.action)
|
? computeObjectId(this._serviceData?.action)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const descriptionPlaceholders =
|
|
||||||
domain && serviceName
|
|
||||||
? this.hass.services[domain]?.[serviceName]?.description_placeholders
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<p>
|
<p>
|
||||||
@@ -312,14 +307,12 @@ class HaPanelDevAction extends LitElement {
|
|||||||
<td><pre>${field.key}</pre></td>
|
<td><pre>${field.key}</pre></td>
|
||||||
<td>
|
<td>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
`component.${domain}.services.${serviceName}.fields.${field.key}.description`,
|
`component.${domain}.services.${serviceName}.fields.${field.key}.description`
|
||||||
descriptionPlaceholders
|
|
||||||
) || field.description}
|
) || field.description}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
`component.${domain}.services.${serviceName}.fields.${field.key}.example`,
|
`component.${domain}.services.${serviceName}.fields.${field.key}.example`
|
||||||
descriptionPlaceholders
|
|
||||||
) || field.example}
|
) || field.example}
|
||||||
</td>
|
</td>
|
||||||
</tr>`
|
</tr>`
|
||||||
@@ -650,11 +643,7 @@ class HaPanelDevAction extends LitElement {
|
|||||||
} catch (_err: any) {
|
} catch (_err: any) {
|
||||||
value =
|
value =
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
`component.${domain}.services.${serviceName}.fields.${field.key}.example`,
|
`component.${domain}.services.${serviceName}.fields.${field.key}.example`
|
||||||
domain && serviceName
|
|
||||||
? this.hass.services[domain][serviceName]
|
|
||||||
.description_placeholders
|
|
||||||
: undefined
|
|
||||||
) || field.example;
|
) || field.example;
|
||||||
}
|
}
|
||||||
example[field.key] = value;
|
example[field.key] = value;
|
||||||
|
|||||||
@@ -1,92 +1,60 @@
|
|||||||
import { mdiDownload } from "@mdi/js";
|
|
||||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
|
import { mdiPencil, mdiDownload } from "@mdi/js";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { navigate } from "../../common/navigate";
|
|
||||||
import "../../components/ha-alert";
|
|
||||||
import "../../components/ha-icon-button-arrow-prev";
|
|
||||||
import "../../components/ha-menu-button";
|
import "../../components/ha-menu-button";
|
||||||
|
import "../../components/ha-icon-button-arrow-prev";
|
||||||
|
import "../../components/ha-list-item";
|
||||||
import "../../components/ha-top-app-bar-fixed";
|
import "../../components/ha-top-app-bar-fixed";
|
||||||
|
import "../../components/ha-alert";
|
||||||
|
import type { LovelaceConfig } from "../../data/lovelace/config/types";
|
||||||
|
import { haStyle } from "../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import "../lovelace/components/hui-energy-period-selector";
|
||||||
|
import type { Lovelace } from "../lovelace/types";
|
||||||
|
import "../lovelace/views/hui-view";
|
||||||
|
import "../lovelace/views/hui-view-container";
|
||||||
|
import { goBack, navigate } from "../../common/navigate";
|
||||||
import type {
|
import type {
|
||||||
BatterySourceTypeEnergyPreference,
|
|
||||||
DeviceConsumptionEnergyPreference,
|
|
||||||
EnergyPreferences,
|
|
||||||
GasSourceTypeEnergyPreference,
|
|
||||||
GridSourceTypeEnergyPreference,
|
GridSourceTypeEnergyPreference,
|
||||||
SolarSourceTypeEnergyPreference,
|
SolarSourceTypeEnergyPreference,
|
||||||
|
BatterySourceTypeEnergyPreference,
|
||||||
|
GasSourceTypeEnergyPreference,
|
||||||
WaterSourceTypeEnergyPreference,
|
WaterSourceTypeEnergyPreference,
|
||||||
|
DeviceConsumptionEnergyPreference,
|
||||||
|
EnergyCollection,
|
||||||
} from "../../data/energy";
|
} from "../../data/energy";
|
||||||
import {
|
import {
|
||||||
computeConsumptionData,
|
computeConsumptionData,
|
||||||
getEnergyDataCollection,
|
getEnergyDataCollection,
|
||||||
getSummedData,
|
getSummedData,
|
||||||
} from "../../data/energy";
|
} from "../../data/energy";
|
||||||
import type { LovelaceConfig } from "../../data/lovelace/config/types";
|
|
||||||
import type { LovelaceViewConfig } from "../../data/lovelace/config/view";
|
|
||||||
import type { StatisticValue } from "../../data/recorder";
|
|
||||||
import { haStyle } from "../../resources/styles";
|
|
||||||
import type { HomeAssistant, PanelInfo } from "../../types";
|
|
||||||
import { fileDownload } from "../../util/file_download";
|
import { fileDownload } from "../../util/file_download";
|
||||||
import "../lovelace/components/hui-energy-period-selector";
|
import type { StatisticValue } from "../../data/recorder";
|
||||||
import "../lovelace/hui-root";
|
|
||||||
import type { ExtraActionItem } from "../lovelace/hui-root";
|
|
||||||
import type { Lovelace } from "../lovelace/types";
|
|
||||||
import "../lovelace/views/hui-view";
|
|
||||||
import "../lovelace/views/hui-view-container";
|
|
||||||
import type { LocalizeKeys } from "../../common/translations/localize";
|
|
||||||
|
|
||||||
export const DEFAULT_ENERGY_COLLECTION_KEY = "energy_dashboard";
|
export const DEFAULT_ENERGY_COLLECTION_KEY = "energy_dashboard";
|
||||||
|
|
||||||
const EMPTY_PREFERENCES: EnergyPreferences = {
|
const ENERGY_LOVELACE_CONFIG: LovelaceConfig = {
|
||||||
energy_sources: [],
|
views: [
|
||||||
device_consumption: [],
|
{
|
||||||
device_consumption_water: [],
|
strategy: {
|
||||||
};
|
type: "energy-overview",
|
||||||
|
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||||
const OVERVIEW_VIEW = {
|
},
|
||||||
path: "overview",
|
},
|
||||||
strategy: {
|
{
|
||||||
type: "energy-overview",
|
strategy: {
|
||||||
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
type: "energy-electricity",
|
||||||
},
|
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||||
} as LovelaceViewConfig;
|
},
|
||||||
|
path: "electricity",
|
||||||
const ENERGY_VIEW = {
|
},
|
||||||
path: "electricity",
|
{
|
||||||
strategy: {
|
type: "panel",
|
||||||
type: "energy",
|
path: "setup",
|
||||||
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
cards: [{ type: "custom:energy-setup-wizard-card" }],
|
||||||
},
|
},
|
||||||
} as LovelaceViewConfig;
|
],
|
||||||
|
|
||||||
const WATER_VIEW = {
|
|
||||||
path: "water",
|
|
||||||
strategy: {
|
|
||||||
type: "water",
|
|
||||||
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
|
||||||
},
|
|
||||||
} as LovelaceViewConfig;
|
|
||||||
|
|
||||||
const GAS_VIEW = {
|
|
||||||
path: "gas",
|
|
||||||
strategy: {
|
|
||||||
type: "gas",
|
|
||||||
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
|
||||||
},
|
|
||||||
} as LovelaceViewConfig;
|
|
||||||
|
|
||||||
const POWER_VIEW = {
|
|
||||||
path: "now",
|
|
||||||
strategy: {
|
|
||||||
type: "power",
|
|
||||||
collection_key: "energy_dashboard_now",
|
|
||||||
},
|
|
||||||
} as LovelaceViewConfig;
|
|
||||||
|
|
||||||
const WIZARD_VIEW = {
|
|
||||||
type: "panel",
|
|
||||||
path: "setup",
|
|
||||||
cards: [{ type: "custom:energy-setup-wizard-card" }],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@customElement("ha-panel-energy")
|
@customElement("ha-panel-energy")
|
||||||
@@ -95,98 +63,176 @@ class PanelEnergy extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||||
|
|
||||||
@property({ attribute: false }) public panel?: PanelInfo;
|
|
||||||
|
|
||||||
@state() private _lovelace?: Lovelace;
|
@state() private _lovelace?: Lovelace;
|
||||||
|
|
||||||
|
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
@state() private _error?: string;
|
||||||
|
|
||||||
@property({ attribute: false }) public route?: {
|
@property({ attribute: false }) public route?: {
|
||||||
path: string;
|
path: string;
|
||||||
prefix: string;
|
prefix: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@state()
|
private _energyCollection?: EnergyCollection;
|
||||||
private _prefs?: EnergyPreferences;
|
|
||||||
|
|
||||||
@state()
|
get _viewPath(): string | undefined {
|
||||||
private _error?: string;
|
const viewPath: string | undefined = this.route!.path.split("/")[1];
|
||||||
|
return viewPath ? decodeURI(viewPath) : undefined;
|
||||||
private get _extraActionItems(): ExtraActionItem[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
icon: mdiDownload,
|
|
||||||
labelKey: "ui.panel.energy.download_data",
|
|
||||||
action: this._dumpCSV,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues) {
|
public connectedCallback() {
|
||||||
super.willUpdate(changedProps);
|
super.connectedCallback();
|
||||||
// Initial setup
|
this._loadPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async willUpdate(changedProps: PropertyValues) {
|
||||||
if (!this.hasUpdated) {
|
if (!this.hasUpdated) {
|
||||||
this.hass.loadFragmentTranslation("lovelace");
|
this.hass.loadFragmentTranslation("lovelace");
|
||||||
this._loadConfig();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!changedProps.has("hass")) {
|
if (!changedProps.has("hass")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldHass = changedProps.get("hass") as this["hass"];
|
const oldHass = changedProps.get("hass") as this["hass"];
|
||||||
if (oldHass && oldHass.localize !== this.hass.localize) {
|
if (oldHass?.locale !== this.hass.locale) {
|
||||||
this._setLovelace();
|
this._setLovelace();
|
||||||
|
} else if (oldHass && oldHass.localize !== this.hass.localize) {
|
||||||
|
this._reloadView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _fetchEnergyPrefs = async (): Promise<
|
private async _loadPrefs() {
|
||||||
EnergyPreferences | undefined
|
if (this._viewPath === "setup") {
|
||||||
> => {
|
await import("./cards/energy-setup-wizard-card");
|
||||||
const collection = getEnergyDataCollection(this.hass, {
|
|
||||||
key: DEFAULT_ENERGY_COLLECTION_KEY,
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
await collection.refresh();
|
|
||||||
} catch (err: any) {
|
|
||||||
if (err.code === "not_found") {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
return collection.prefs;
|
|
||||||
};
|
|
||||||
|
|
||||||
private async _loadConfig() {
|
|
||||||
try {
|
|
||||||
this._error = undefined;
|
|
||||||
const prefs = await this._fetchEnergyPrefs();
|
|
||||||
this._prefs = prefs || EMPTY_PREFERENCES;
|
|
||||||
} catch (err) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error("Failed to load prefs:", err);
|
|
||||||
this._prefs = EMPTY_PREFERENCES;
|
|
||||||
this._error = (err as Error).message || "Unknown error";
|
|
||||||
}
|
|
||||||
await this._setLovelace();
|
|
||||||
|
|
||||||
// Check if current path is valid, navigate to first view if not
|
|
||||||
const views = this._lovelace!.config?.views || [];
|
|
||||||
const validPaths = views.map((view) => view.path);
|
|
||||||
const viewPath: string | undefined = this.route!.path.split("/")[1];
|
|
||||||
if (!viewPath || !validPaths.includes(viewPath)) {
|
|
||||||
navigate(`${this.route!.prefix}/${validPaths[0]}`);
|
|
||||||
} else {
|
} else {
|
||||||
// Force hui-root to re-process the route by creating a new route object
|
this._energyCollection = getEnergyDataCollection(this.hass, {
|
||||||
this.route = { ...this.route! };
|
key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
// Have to manually refresh here as we don't want to subscribe yet
|
||||||
|
await this._energyCollection.refresh();
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.code === "not_found") {
|
||||||
|
navigate("/energy/setup");
|
||||||
|
}
|
||||||
|
this._error = err.message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const prefs = this._energyCollection.prefs!;
|
||||||
|
if (
|
||||||
|
prefs.device_consumption.length === 0 &&
|
||||||
|
prefs.energy_sources.length === 0
|
||||||
|
) {
|
||||||
|
// No energy sources available, start from scratch
|
||||||
|
navigate("/energy/setup");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _setLovelace() {
|
private _back(ev) {
|
||||||
const config = await this._generateLovelaceConfig();
|
ev.stopPropagation();
|
||||||
|
goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._energyCollection?.prefs) {
|
||||||
|
// Still loading
|
||||||
|
return html`<div class="centered">
|
||||||
|
<ha-spinner size="large"></ha-spinner>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
const { prefs } = this._energyCollection;
|
||||||
|
const isSingleView = prefs.energy_sources.every((source) =>
|
||||||
|
["grid", "solar", "battery"].includes(source.type)
|
||||||
|
);
|
||||||
|
let viewPath = this._viewPath;
|
||||||
|
if (isSingleView) {
|
||||||
|
// if only electricity sources, show electricity view directly
|
||||||
|
viewPath = "electricity";
|
||||||
|
}
|
||||||
|
const viewIndex = Math.max(
|
||||||
|
ENERGY_LOVELACE_CONFIG.views.findIndex((view) => view.path === viewPath),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const showBack =
|
||||||
|
this._searchParms.has("historyBack") || (!isSingleView && viewIndex > 0);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="header">
|
||||||
|
<div class="toolbar">
|
||||||
|
${showBack
|
||||||
|
? html`
|
||||||
|
<ha-icon-button-arrow-prev
|
||||||
|
@click=${this._back}
|
||||||
|
slot="navigationIcon"
|
||||||
|
></ha-icon-button-arrow-prev>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<ha-menu-button
|
||||||
|
slot="navigationIcon"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
></ha-menu-button>
|
||||||
|
`}
|
||||||
|
${!this.narrow
|
||||||
|
? html`<div class="main-title">
|
||||||
|
${this.hass.localize("panel.energy")}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
|
<hui-energy-period-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.collectionKey=${DEFAULT_ENERGY_COLLECTION_KEY}
|
||||||
|
>
|
||||||
|
${this.hass.user?.is_admin
|
||||||
|
? html` <ha-list-item
|
||||||
|
slot="overflow-menu"
|
||||||
|
graphic="icon"
|
||||||
|
@request-selected=${this._navigateConfig}
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="graphic" .path=${mdiPencil}> </ha-svg-icon>
|
||||||
|
${this.hass!.localize("ui.panel.energy.configure")}
|
||||||
|
</ha-list-item>`
|
||||||
|
: nothing}
|
||||||
|
<ha-list-item
|
||||||
|
slot="overflow-menu"
|
||||||
|
graphic="icon"
|
||||||
|
@request-selected=${this._dumpCSV}
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="graphic" .path=${mdiDownload}> </ha-svg-icon>
|
||||||
|
${this.hass!.localize("ui.panel.energy.download_data")}
|
||||||
|
</ha-list-item>
|
||||||
|
</hui-energy-period-selector>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hui-view-container
|
||||||
|
.hass=${this.hass}
|
||||||
|
@reload-energy-panel=${this._reloadView}
|
||||||
|
>
|
||||||
|
${this._error
|
||||||
|
? html`<div class="centered">
|
||||||
|
<ha-alert alert-type="error">
|
||||||
|
An error occurred while fetching your energy preferences:
|
||||||
|
${this._error}
|
||||||
|
</ha-alert>
|
||||||
|
</div>`
|
||||||
|
: this._lovelace
|
||||||
|
? html`<hui-view
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
.lovelace=${this._lovelace}
|
||||||
|
.index=${viewIndex}
|
||||||
|
></hui-view>`
|
||||||
|
: nothing}
|
||||||
|
</hui-view-container>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setLovelace() {
|
||||||
this._lovelace = {
|
this._lovelace = {
|
||||||
config: config,
|
config: ENERGY_LOVELACE_CONFIG,
|
||||||
rawConfig: config,
|
rawConfig: ENERGY_LOVELACE_CONFIG,
|
||||||
editMode: false,
|
editMode: false,
|
||||||
urlPath: "energy",
|
urlPath: "energy",
|
||||||
mode: "generated",
|
mode: "generated",
|
||||||
@@ -194,119 +240,19 @@ class PanelEnergy extends LitElement {
|
|||||||
enableFullEditMode: () => undefined,
|
enableFullEditMode: () => undefined,
|
||||||
saveConfig: async () => undefined,
|
saveConfig: async () => undefined,
|
||||||
deleteConfig: async () => undefined,
|
deleteConfig: async () => undefined,
|
||||||
setEditMode: () => this._navigateConfig(),
|
setEditMode: () => undefined,
|
||||||
showToast: () => undefined,
|
showToast: () => undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
private _navigateConfig(ev) {
|
||||||
if (this._error) {
|
ev.stopPropagation();
|
||||||
return html`
|
|
||||||
<div class="centered">
|
|
||||||
<ha-alert alert-type="error">
|
|
||||||
An error occurred loading energy preferences: ${this._error}
|
|
||||||
</ha-alert>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._prefs) {
|
|
||||||
// Still loading
|
|
||||||
return html`
|
|
||||||
<div class="centered">
|
|
||||||
<ha-spinner size="large"></ha-spinner>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._lovelace) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<hui-root
|
|
||||||
.hass=${this.hass}
|
|
||||||
.narrow=${this.narrow}
|
|
||||||
.lovelace=${this._lovelace}
|
|
||||||
.route=${this.route}
|
|
||||||
.panel=${this.panel}
|
|
||||||
.extraActionItems=${this._extraActionItems}
|
|
||||||
@reload-energy-panel=${this._reloadConfig}
|
|
||||||
></hui-root>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _generateLovelaceConfig(): Promise<LovelaceConfig> {
|
|
||||||
if (
|
|
||||||
!this._prefs ||
|
|
||||||
(this._prefs.device_consumption.length === 0 &&
|
|
||||||
this._prefs.energy_sources.length === 0)
|
|
||||||
) {
|
|
||||||
await import("./cards/energy-setup-wizard-card");
|
|
||||||
return {
|
|
||||||
views: [WIZARD_VIEW],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasEnergy = this._prefs.energy_sources.some((source) =>
|
|
||||||
["grid", "solar", "battery"].includes(source.type)
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasPower =
|
|
||||||
this._prefs.energy_sources.some(
|
|
||||||
(source) =>
|
|
||||||
(source.type === "solar" && source.stat_rate) ||
|
|
||||||
(source.type === "battery" && source.stat_rate) ||
|
|
||||||
(source.type === "grid" && source.power?.length)
|
|
||||||
) || this._prefs.device_consumption.some((device) => device.stat_rate);
|
|
||||||
|
|
||||||
const hasWater =
|
|
||||||
this._prefs.energy_sources.some((source) => source.type === "water") ||
|
|
||||||
this._prefs.device_consumption_water?.length > 0;
|
|
||||||
|
|
||||||
const hasGas = this._prefs.energy_sources.some(
|
|
||||||
(source) => source.type === "gas"
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasDeviceConsumption = this._prefs.device_consumption.length > 0;
|
|
||||||
|
|
||||||
const views: LovelaceViewConfig[] = [];
|
|
||||||
if (hasEnergy || hasDeviceConsumption) {
|
|
||||||
views.push(ENERGY_VIEW);
|
|
||||||
}
|
|
||||||
if (hasGas) {
|
|
||||||
views.push(GAS_VIEW);
|
|
||||||
}
|
|
||||||
if (hasWater) {
|
|
||||||
views.push(WATER_VIEW);
|
|
||||||
}
|
|
||||||
if (hasPower) {
|
|
||||||
views.push(POWER_VIEW);
|
|
||||||
}
|
|
||||||
if (views.length > 1) {
|
|
||||||
views.unshift(OVERVIEW_VIEW);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
views: views.map((view) => ({
|
|
||||||
...view,
|
|
||||||
title:
|
|
||||||
view.title ||
|
|
||||||
this.hass.localize(
|
|
||||||
`ui.panel.energy.title.${view.path}` as LocalizeKeys
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private _navigateConfig(ev?: Event) {
|
|
||||||
ev?.stopPropagation();
|
|
||||||
navigate("/config/energy?historyBack=1");
|
navigate("/config/energy?historyBack=1");
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dumpCSV = async () => {
|
private async _dumpCSV(ev) {
|
||||||
const energyData = getEnergyDataCollection(this.hass, {
|
ev.stopPropagation();
|
||||||
key: "energy_dashboard",
|
const energyData = this._energyCollection!;
|
||||||
});
|
|
||||||
|
|
||||||
if (!energyData.prefs || !energyData.state.stats) {
|
if (!energyData.prefs || !energyData.state.stats) {
|
||||||
return;
|
return;
|
||||||
@@ -317,7 +263,6 @@ class PanelEnergy extends LitElement {
|
|||||||
|
|
||||||
const energy_sources = energyData.prefs.energy_sources;
|
const energy_sources = energyData.prefs.energy_sources;
|
||||||
const device_consumption = energyData.prefs.device_consumption;
|
const device_consumption = energyData.prefs.device_consumption;
|
||||||
const device_consumption_water = energyData.prefs.device_consumption_water;
|
|
||||||
const stats = energyData.state.stats;
|
const stats = energyData.state.stats;
|
||||||
|
|
||||||
const timeSet = new Set<number>();
|
const timeSet = new Set<number>();
|
||||||
@@ -503,20 +448,6 @@ class PanelEnergy extends LitElement {
|
|||||||
|
|
||||||
printCategory("device_consumption", devices, electricUnit);
|
printCategory("device_consumption", devices, electricUnit);
|
||||||
|
|
||||||
if (device_consumption_water) {
|
|
||||||
const waterDevices: string[] = [];
|
|
||||||
device_consumption_water.forEach((source) => {
|
|
||||||
source = source as DeviceConsumptionEnergyPreference;
|
|
||||||
waterDevices.push(source.stat_consumption);
|
|
||||||
});
|
|
||||||
|
|
||||||
printCategory(
|
|
||||||
"device_consumption_water",
|
|
||||||
waterDevices,
|
|
||||||
energyData.state.waterUnit
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { summedData, compareSummedData: _ } = getSummedData(
|
const { summedData, compareSummedData: _ } = getSummedData(
|
||||||
energyData.state
|
energyData.state
|
||||||
);
|
);
|
||||||
@@ -615,22 +546,79 @@ class PanelEnergy extends LitElement {
|
|||||||
});
|
});
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
fileDownload(url, "energy.csv");
|
fileDownload(url, "energy.csv");
|
||||||
};
|
}
|
||||||
|
|
||||||
private _reloadConfig() {
|
private _reloadView() {
|
||||||
this._loadConfig();
|
// Force strategy to be re-run by making a copy of the view
|
||||||
|
const config = this._lovelace!.config;
|
||||||
|
this._lovelace = {
|
||||||
|
...this._lovelace!,
|
||||||
|
config: { ...config, views: config.views.map((view) => ({ ...view })) },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
css`
|
css`
|
||||||
|
:host hui-energy-period-selector {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-left: 32px;
|
||||||
|
padding-inline-start: 32px;
|
||||||
|
padding-inline-end: initial;
|
||||||
|
--disabled-text-color: rgba(var(--rgb-text-primary-color), 0.5);
|
||||||
|
direction: var(--direction);
|
||||||
|
--date-range-picker-max-height: calc(100vh - 80px);
|
||||||
|
}
|
||||||
|
:host([narrow]) hui-energy-period-selector {
|
||||||
|
padding-left: 0px;
|
||||||
|
padding-inline-start: 0px;
|
||||||
|
padding-inline-end: initial;
|
||||||
|
}
|
||||||
:host {
|
:host {
|
||||||
--ha-view-sections-column-max-width: 100%;
|
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
}
|
}
|
||||||
|
.header {
|
||||||
|
background-color: var(--app-header-background-color);
|
||||||
|
color: var(--app-header-text-color, white);
|
||||||
|
border-bottom: var(--app-header-border-bottom, none);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: calc(
|
||||||
|
var(--mdc-top-app-bar-width, 100%) - var(
|
||||||
|
--safe-area-inset-right,
|
||||||
|
0px
|
||||||
|
)
|
||||||
|
);
|
||||||
|
padding-top: var(--safe-area-inset-top);
|
||||||
|
z-index: 4;
|
||||||
|
transition: box-shadow 200ms linear;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
-webkit-backdrop-filter: var(--app-header-backdrop-filter, none);
|
||||||
|
backdrop-filter: var(--app-header-backdrop-filter, none);
|
||||||
|
padding-top: var(--safe-area-inset-top);
|
||||||
|
padding-right: var(--safe-area-inset-right);
|
||||||
|
}
|
||||||
|
:host([narrow]) .header {
|
||||||
|
width: calc(
|
||||||
|
var(--mdc-top-app-bar-width, 100%) - var(
|
||||||
|
--safe-area-inset-left,
|
||||||
|
0px
|
||||||
|
) - var(--safe-area-inset-right, 0px)
|
||||||
|
);
|
||||||
|
padding-left: var(--safe-area-inset-left);
|
||||||
|
}
|
||||||
|
:host([scrolled]) .header {
|
||||||
|
box-shadow: var(
|
||||||
|
--mdc-top-app-bar-fixed-box-shadow,
|
||||||
|
0px 2px 4px -1px rgba(0, 0, 0, 0.2),
|
||||||
|
0px 4px 5px 0px rgba(0, 0, 0, 0.14),
|
||||||
|
0px 1px 10px 0px rgba(0, 0, 0, 0.12)
|
||||||
|
);
|
||||||
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
height: var(--header-height);
|
height: var(--header-height);
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -649,6 +637,24 @@ class PanelEnergy extends LitElement {
|
|||||||
line-height: var(--ha-line-height-normal);
|
line-height: var(--ha-line-height-normal);
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
hui-view-container {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: calc(var(--header-height) + var(--safe-area-inset-top));
|
||||||
|
padding-right: var(--safe-area-inset-right);
|
||||||
|
padding-inline-end: var(--safe-area-inset-right);
|
||||||
|
padding-bottom: var(--safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
:host([narrow]) hui-view-container {
|
||||||
|
padding-left: var(--safe-area-inset-left);
|
||||||
|
padding-inline-start: var(--safe-area-inset-left);
|
||||||
|
}
|
||||||
|
hui-view {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
.centered {
|
.centered {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
|||||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
|
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
|
||||||
|
|
||||||
@customElement("energy-view-strategy")
|
@customElement("energy-electricity-view-strategy")
|
||||||
export class EnergyViewStrategy extends ReactiveElement {
|
export class EnergyElectricityViewStrategy extends ReactiveElement {
|
||||||
static async generate(
|
static async generate(
|
||||||
_config: LovelaceStrategyConfig,
|
_config: LovelaceStrategyConfig,
|
||||||
hass: HomeAssistant
|
hass: HomeAssistant
|
||||||
@@ -21,9 +21,6 @@ export class EnergyViewStrategy extends ReactiveElement {
|
|||||||
const energyCollection = getEnergyDataCollection(hass, {
|
const energyCollection = getEnergyDataCollection(hass, {
|
||||||
key: collectionKey,
|
key: collectionKey,
|
||||||
});
|
});
|
||||||
if (!energyCollection.prefs) {
|
|
||||||
await energyCollection.refresh();
|
|
||||||
}
|
|
||||||
const prefs = energyCollection.prefs;
|
const prefs = energyCollection.prefs;
|
||||||
|
|
||||||
// No energy sources available
|
// No energy sources available
|
||||||
@@ -49,19 +46,39 @@ export class EnergyViewStrategy extends ReactiveElement {
|
|||||||
const hasBattery = prefs.energy_sources.some(
|
const hasBattery = prefs.energy_sources.some(
|
||||||
(source) => source.type === "battery"
|
(source) => source.type === "battery"
|
||||||
);
|
);
|
||||||
const showFloorsNAreas = !prefs.device_consumption.some(
|
const hasPowerSources = prefs.energy_sources.find(
|
||||||
(d) => d.included_in_stat
|
(source) =>
|
||||||
|
(source.type === "solar" && source.stat_rate) ||
|
||||||
|
(source.type === "battery" && source.stat_rate) ||
|
||||||
|
(source.type === "grid" && source.power?.length)
|
||||||
|
);
|
||||||
|
const hasPowerDevices = prefs.device_consumption.find(
|
||||||
|
(device) => device.stat_rate
|
||||||
);
|
);
|
||||||
|
|
||||||
view.cards!.push({
|
|
||||||
type: "energy-date-selection",
|
|
||||||
collection_key: collectionKey,
|
|
||||||
});
|
|
||||||
view.cards!.push({
|
view.cards!.push({
|
||||||
type: "energy-compare",
|
type: "energy-compare",
|
||||||
collection_key: "energy_dashboard",
|
collection_key: "energy_dashboard",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (hasPowerSources) {
|
||||||
|
if (hasPowerDevices) {
|
||||||
|
view.cards!.push({
|
||||||
|
title: hass.localize("ui.panel.energy.cards.power_sankey_title"),
|
||||||
|
type: "power-sankey",
|
||||||
|
collection_key: collectionKey,
|
||||||
|
grid_options: {
|
||||||
|
columns: 24,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
view.cards!.push({
|
||||||
|
title: hass.localize("ui.panel.energy.cards.power_sources_graph_title"),
|
||||||
|
type: "power-sources-graph",
|
||||||
|
collection_key: collectionKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Only include if we have a grid or battery.
|
// Only include if we have a grid or battery.
|
||||||
if (hasGrid || hasBattery) {
|
if (hasGrid || hasBattery) {
|
||||||
view.cards!.push({
|
view.cards!.push({
|
||||||
@@ -139,12 +156,15 @@ export class EnergyViewStrategy extends ReactiveElement {
|
|||||||
|
|
||||||
// Only include if we have at least 1 device in the config.
|
// Only include if we have at least 1 device in the config.
|
||||||
if (prefs.device_consumption.length) {
|
if (prefs.device_consumption.length) {
|
||||||
|
const showFloorsNAreas = !prefs.device_consumption.some(
|
||||||
|
(d) => d.included_in_stat
|
||||||
|
);
|
||||||
view.cards!.push({
|
view.cards!.push({
|
||||||
title: hass.localize(
|
title: hass.localize("ui.panel.energy.cards.energy_sankey_title"),
|
||||||
"ui.panel.energy.cards.energy_devices_detail_graph_title"
|
type: "energy-sankey",
|
||||||
),
|
|
||||||
type: "energy-devices-detail-graph",
|
|
||||||
collection_key: "energy_dashboard",
|
collection_key: "energy_dashboard",
|
||||||
|
group_by_floor: showFloorsNAreas,
|
||||||
|
group_by_area: showFloorsNAreas,
|
||||||
});
|
});
|
||||||
view.cards!.push({
|
view.cards!.push({
|
||||||
title: hass.localize(
|
title: hass.localize(
|
||||||
@@ -154,11 +174,11 @@ export class EnergyViewStrategy extends ReactiveElement {
|
|||||||
collection_key: "energy_dashboard",
|
collection_key: "energy_dashboard",
|
||||||
});
|
});
|
||||||
view.cards!.push({
|
view.cards!.push({
|
||||||
title: hass.localize("ui.panel.energy.cards.energy_sankey_title"),
|
title: hass.localize(
|
||||||
type: "energy-sankey",
|
"ui.panel.energy.cards.energy_devices_detail_graph_title"
|
||||||
|
),
|
||||||
|
type: "energy-devices-detail-graph",
|
||||||
collection_key: "energy_dashboard",
|
collection_key: "energy_dashboard",
|
||||||
group_by_floor: showFloorsNAreas,
|
|
||||||
group_by_area: showFloorsNAreas,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +188,6 @@ export class EnergyViewStrategy extends ReactiveElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"energy-view-strategy": EnergyViewStrategy;
|
"energy-electricity-view-strategy": EnergyElectricityViewStrategy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,15 @@ import { ReactiveElement } from "lit";
|
|||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import type { GridSourceTypeEnergyPreference } from "../../../data/energy";
|
import type { GridSourceTypeEnergyPreference } from "../../../data/energy";
|
||||||
import { getEnergyDataCollection } from "../../../data/energy";
|
import { getEnergyDataCollection } from "../../../data/energy";
|
||||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
|
||||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
|
||||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||||
|
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||||
|
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||||
|
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
|
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
|
||||||
|
|
||||||
@customElement("energy-overview-view-strategy")
|
@customElement("energy-overview-view-strategy")
|
||||||
export class EnergyOverviewViewStrategy extends ReactiveElement {
|
export class EnergyViewStrategy extends ReactiveElement {
|
||||||
static async generate(
|
static async generate(
|
||||||
_config: LovelaceStrategyConfig,
|
_config: LovelaceStrategyConfig,
|
||||||
hass: HomeAssistant
|
hass: HomeAssistant
|
||||||
@@ -27,9 +28,6 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
|
|||||||
const energyCollection = getEnergyDataCollection(hass, {
|
const energyCollection = getEnergyDataCollection(hass, {
|
||||||
key: collectionKey,
|
key: collectionKey,
|
||||||
});
|
});
|
||||||
if (!energyCollection.prefs) {
|
|
||||||
await energyCollection.refresh();
|
|
||||||
}
|
|
||||||
const prefs = energyCollection.prefs;
|
const prefs = energyCollection.prefs;
|
||||||
|
|
||||||
// No energy sources available
|
// No energy sources available
|
||||||
@@ -46,94 +44,139 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
|
|||||||
source.type === "grid" &&
|
source.type === "grid" &&
|
||||||
(source.flow_from?.length || source.flow_to?.length)
|
(source.flow_from?.length || source.flow_to?.length)
|
||||||
) as GridSourceTypeEnergyPreference;
|
) as GridSourceTypeEnergyPreference;
|
||||||
|
const hasReturn = hasGrid && hasGrid.flow_to.length > 0;
|
||||||
|
const hasSolar = prefs.energy_sources.some(
|
||||||
|
(source) => source.type === "solar"
|
||||||
|
);
|
||||||
const hasGas = prefs.energy_sources.some((source) => source.type === "gas");
|
const hasGas = prefs.energy_sources.some((source) => source.type === "gas");
|
||||||
const hasBattery = prefs.energy_sources.some(
|
const hasBattery = prefs.energy_sources.some(
|
||||||
(source) => source.type === "battery"
|
(source) => source.type === "battery"
|
||||||
);
|
);
|
||||||
const hasSolar = prefs.energy_sources.some(
|
const hasWater = prefs.energy_sources.some(
|
||||||
(source) => source.type === "solar"
|
|
||||||
);
|
|
||||||
const hasWaterSources = prefs.energy_sources.some(
|
|
||||||
(source) => source.type === "water"
|
(source) => source.type === "water"
|
||||||
);
|
);
|
||||||
const hasWaterDevices = prefs.device_consumption_water?.length;
|
|
||||||
const hasPowerSources = prefs.energy_sources.find(
|
const hasPowerSources = prefs.energy_sources.find(
|
||||||
(source) =>
|
(source) =>
|
||||||
(source.type === "solar" && source.stat_rate) ||
|
(source.type === "solar" && source.stat_rate) ||
|
||||||
(source.type === "battery" && source.stat_rate) ||
|
(source.type === "battery" && source.stat_rate) ||
|
||||||
(source.type === "grid" && source.power?.length)
|
(source.type === "grid" && source.power?.length)
|
||||||
);
|
);
|
||||||
|
const hasPowerDevices = prefs.device_consumption.find(
|
||||||
|
(device) => device.stat_rate
|
||||||
|
);
|
||||||
|
|
||||||
const overviewSection: LovelaceSectionConfig = {
|
const overviewSection: LovelaceSectionConfig = {
|
||||||
type: "grid",
|
type: "grid",
|
||||||
cards: [
|
column_span: 24,
|
||||||
{
|
cards: [],
|
||||||
type: "energy-date-selection",
|
|
||||||
collection_key: collectionKey,
|
|
||||||
allow_compare: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
if (hasGrid || hasBattery || hasSolar) {
|
if (hasPowerSources && hasPowerDevices) {
|
||||||
|
overviewSection.cards!.push({
|
||||||
|
title: hass.localize("ui.panel.energy.cards.power_sankey_title"),
|
||||||
|
type: "power-sankey",
|
||||||
|
collection_key: collectionKey,
|
||||||
|
grid_options: {
|
||||||
|
columns: 24,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Only include if we have a grid or battery.
|
||||||
|
if (hasGrid || hasBattery) {
|
||||||
overviewSection.cards!.push({
|
overviewSection.cards!.push({
|
||||||
title: hass.localize("ui.panel.energy.cards.energy_distribution_title"),
|
title: hass.localize("ui.panel.energy.cards.energy_distribution_title"),
|
||||||
type: "energy-distribution",
|
type: "energy-distribution",
|
||||||
collection_key: collectionKey,
|
collection_key: collectionKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (hasGrid || hasSolar || hasBattery || hasGas || hasWater) {
|
||||||
|
overviewSection.cards!.push({
|
||||||
|
type: "energy-sources-table",
|
||||||
|
collection_key: collectionKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
view.sections!.push(overviewSection);
|
view.sections!.push(overviewSection);
|
||||||
|
|
||||||
if (prefs.energy_sources.length) {
|
const electricitySection: LovelaceSectionConfig = {
|
||||||
view.sections!.push({
|
type: "grid",
|
||||||
type: "grid",
|
cards: [
|
||||||
cards: [
|
{
|
||||||
{
|
type: "heading",
|
||||||
title: hass.localize(
|
heading: hass.localize("ui.panel.energy.overview.electricity"),
|
||||||
"ui.panel.energy.cards.energy_sources_table_title"
|
tap_action: {
|
||||||
),
|
action: "navigate",
|
||||||
type: "energy-sources-table",
|
navigation_path: "/energy/electricity",
|
||||||
collection_key: collectionKey,
|
|
||||||
show_only_totals: true,
|
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
});
|
],
|
||||||
}
|
};
|
||||||
|
|
||||||
if (hasPowerSources) {
|
if (hasPowerSources) {
|
||||||
view.sections!.push({
|
electricitySection.cards!.push({
|
||||||
type: "grid",
|
type: "power-sources-graph",
|
||||||
cards: [
|
collection_key: collectionKey,
|
||||||
{
|
});
|
||||||
title: hass.localize(
|
}
|
||||||
"ui.panel.energy.cards.power_sources_graph_title"
|
if (prefs!.device_consumption.length > 3) {
|
||||||
),
|
electricitySection.cards!.push({
|
||||||
type: "power-sources-graph",
|
title: hass.localize(
|
||||||
|
"ui.panel.energy.cards.energy_top_consumers_title"
|
||||||
|
),
|
||||||
|
type: "energy-devices-graph",
|
||||||
|
collection_key: collectionKey,
|
||||||
|
max_devices: 3,
|
||||||
|
modes: ["bar"],
|
||||||
|
});
|
||||||
|
} else if (hasGrid) {
|
||||||
|
const gauges: LovelaceCardConfig[] = [];
|
||||||
|
// Only include if we have a grid source & return.
|
||||||
|
if (hasReturn) {
|
||||||
|
gauges.push({
|
||||||
|
type: "energy-grid-neutrality-gauge",
|
||||||
|
view_layout: { position: "sidebar" },
|
||||||
|
collection_key: collectionKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
gauges.push({
|
||||||
|
type: "energy-carbon-consumed-gauge",
|
||||||
|
view_layout: { position: "sidebar" },
|
||||||
|
collection_key: collectionKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only include if we have a solar source.
|
||||||
|
if (hasSolar) {
|
||||||
|
if (hasReturn) {
|
||||||
|
gauges.push({
|
||||||
|
type: "energy-solar-consumed-gauge",
|
||||||
|
view_layout: { position: "sidebar" },
|
||||||
collection_key: collectionKey,
|
collection_key: collectionKey,
|
||||||
show_legend: false,
|
});
|
||||||
},
|
}
|
||||||
],
|
gauges.push({
|
||||||
|
type: "energy-self-sufficiency-gauge",
|
||||||
|
view_layout: { position: "sidebar" },
|
||||||
|
collection_key: collectionKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
electricitySection.cards!.push({
|
||||||
|
type: "grid",
|
||||||
|
columns: 2,
|
||||||
|
square: false,
|
||||||
|
cards: gauges,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasGrid || hasBattery) {
|
view.sections!.push(electricitySection);
|
||||||
view.sections!.push({
|
|
||||||
type: "grid",
|
|
||||||
cards: [
|
|
||||||
{
|
|
||||||
title: hass.localize(
|
|
||||||
"ui.panel.energy.cards.energy_usage_graph_title"
|
|
||||||
),
|
|
||||||
type: "energy-usage-graph",
|
|
||||||
collection_key: "energy_dashboard",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasGas) {
|
if (hasGas) {
|
||||||
view.sections!.push({
|
view.sections!.push({
|
||||||
type: "grid",
|
type: "grid",
|
||||||
cards: [
|
cards: [
|
||||||
|
{
|
||||||
|
type: "heading",
|
||||||
|
heading: hass.localize("ui.panel.energy.overview.gas"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: hass.localize(
|
title: hass.localize(
|
||||||
"ui.panel.energy.cards.energy_gas_graph_title"
|
"ui.panel.energy.cards.energy_gas_graph_title"
|
||||||
@@ -145,25 +188,21 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasWaterSources || hasWaterDevices) {
|
if (hasWater) {
|
||||||
view.sections!.push({
|
view.sections!.push({
|
||||||
type: "grid",
|
type: "grid",
|
||||||
cards: [
|
cards: [
|
||||||
hasWaterSources
|
{
|
||||||
? {
|
type: "heading",
|
||||||
title: hass.localize(
|
heading: hass.localize("ui.panel.energy.overview.water"),
|
||||||
"ui.panel.energy.cards.energy_water_graph_title"
|
},
|
||||||
),
|
{
|
||||||
type: "energy-water-graph",
|
title: hass.localize(
|
||||||
collection_key: collectionKey,
|
"ui.panel.energy.cards.energy_water_graph_title"
|
||||||
}
|
),
|
||||||
: {
|
type: "energy-water-graph",
|
||||||
title: hass.localize(
|
collection_key: collectionKey,
|
||||||
"ui.panel.energy.cards.water_sankey_title"
|
},
|
||||||
),
|
|
||||||
type: "water-sankey",
|
|
||||||
collection_key: collectionKey,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -174,6 +213,6 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"energy-overview-view-strategy": EnergyOverviewViewStrategy;
|
"energy-overview-view-strategy": EnergyViewStrategy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
import { ReactiveElement } from "lit";
|
|
||||||
import { customElement } from "lit/decorators";
|
|
||||||
import { getEnergyDataCollection } from "../../../data/energy";
|
|
||||||
import type { HomeAssistant } from "../../../types";
|
|
||||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
|
||||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
|
||||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
|
|
||||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
|
||||||
|
|
||||||
@customElement("gas-view-strategy")
|
|
||||||
export class GasViewStrategy extends ReactiveElement {
|
|
||||||
static async generate(
|
|
||||||
_config: LovelaceStrategyConfig,
|
|
||||||
hass: HomeAssistant
|
|
||||||
): Promise<LovelaceViewConfig> {
|
|
||||||
const view: LovelaceViewConfig = {
|
|
||||||
type: "sections",
|
|
||||||
sections: [{ type: "grid", cards: [] }],
|
|
||||||
};
|
|
||||||
|
|
||||||
const collectionKey =
|
|
||||||
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
|
|
||||||
|
|
||||||
const energyCollection = getEnergyDataCollection(hass, {
|
|
||||||
key: collectionKey,
|
|
||||||
});
|
|
||||||
if (!energyCollection.prefs) {
|
|
||||||
await energyCollection.refresh();
|
|
||||||
}
|
|
||||||
const prefs = energyCollection.prefs;
|
|
||||||
|
|
||||||
const hasGasSources = prefs?.energy_sources.some(
|
|
||||||
(source) => source.type === "gas"
|
|
||||||
);
|
|
||||||
|
|
||||||
// No gas sources available
|
|
||||||
if (!prefs || !hasGasSources) {
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
const section = view.sections![0] as LovelaceSectionConfig;
|
|
||||||
|
|
||||||
section.cards!.push({
|
|
||||||
type: "energy-date-selection",
|
|
||||||
collection_key: collectionKey,
|
|
||||||
});
|
|
||||||
section.cards!.push({
|
|
||||||
type: "energy-compare",
|
|
||||||
collection_key: collectionKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
section.cards!.push({
|
|
||||||
title: hass.localize("ui.panel.energy.cards.energy_gas_graph_title"),
|
|
||||||
type: "energy-gas-graph",
|
|
||||||
collection_key: collectionKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
section.cards!.push({
|
|
||||||
title: hass.localize("ui.panel.energy.cards.energy_sources_table_title"),
|
|
||||||
type: "energy-sources-table",
|
|
||||||
collection_key: collectionKey,
|
|
||||||
types: ["gas"],
|
|
||||||
});
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"gas-view-strategy": GasViewStrategy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user