mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-20 15:07:21 +00:00
Compare commits
234 Commits
helpers-en
...
20251203.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b0bd9d577 | ||
|
|
d839152fd1 | ||
|
|
407cb79805 | ||
|
|
7817ebe983 | ||
|
|
7e58cedd49 | ||
|
|
06334a039c | ||
|
|
6e5853a1c0 | ||
|
|
f4f4520773 | ||
|
|
94453dfba5 | ||
|
|
0ce0247a2c | ||
|
|
ce8cabbad9 | ||
|
|
0802841606 | ||
|
|
cb93e1b741 | ||
|
|
30c383a2fc | ||
|
|
73ee235fef | ||
|
|
31603ea7b2 | ||
|
|
17c1043cfc | ||
|
|
da255dce40 | ||
|
|
0c68072f8f | ||
|
|
d197fd8f76 | ||
|
|
a961a87872 | ||
|
|
cc96c707b9 | ||
|
|
4b73713f2a | ||
|
|
c001102f15 | ||
|
|
c1e5e0bfcb | ||
|
|
a1412e90fd | ||
|
|
f6f40c1679 | ||
|
|
d77bebe96b | ||
|
|
1260af0b45 | ||
|
|
1d37eec411 | ||
|
|
5a52f83358 | ||
|
|
60724eb952 | ||
|
|
de5778079e | ||
|
|
f3710650f2 | ||
|
|
feb35dbc4f | ||
|
|
ee9e101fa6 | ||
|
|
24b16360a6 | ||
|
|
109c81a00d | ||
|
|
eaa1ddbf61 | ||
|
|
b11cb57a1e | ||
|
|
87b5f58779 | ||
|
|
8dac53c672 | ||
|
|
d0966bf35a | ||
|
|
6ba4fc0808 | ||
|
|
bd582ff816 | ||
|
|
d34bf83da0 | ||
|
|
b0cfb31bf3 | ||
|
|
6c39e5d2c5 | ||
|
|
7b51e71092 | ||
|
|
8a82483685 | ||
|
|
bb691fa7a2 | ||
|
|
2232db9c0f | ||
|
|
5375665dc6 | ||
|
|
480122f40a | ||
|
|
ee5c54030a | ||
|
|
b73f50e864 | ||
|
|
b9836073b7 | ||
|
|
a40512e0b5 | ||
|
|
b2122570fb | ||
|
|
885f9333d2 | ||
|
|
f812e7e9fb | ||
|
|
64dad39f6e | ||
|
|
df0fb423ed | ||
|
|
4c3156f290 | ||
|
|
ecdf374902 | ||
|
|
3e924e0cde | ||
|
|
6fb71e12c8 | ||
|
|
6138aa5489 | ||
|
|
61e865d3a6 | ||
|
|
febcbf6242 | ||
|
|
6a2fac6a9e | ||
|
|
b60c5467fc | ||
|
|
ecd563406e | ||
|
|
d5b66315e2 | ||
|
|
5b1719fc6e | ||
|
|
add22cf2e9 | ||
|
|
21509191fa | ||
|
|
1a73cccf0d | ||
|
|
407d68250a | ||
|
|
38b7bd18bb | ||
|
|
a00e944a35 | ||
|
|
481569804e | ||
|
|
a1d7e270ff | ||
|
|
225ccf1d2f | ||
|
|
4a5e1f9f3f | ||
|
|
b27b7210fd | ||
|
|
acd5181449 | ||
|
|
b6b2d03a80 | ||
|
|
7aee2b7cb7 | ||
|
|
df1914cb7a | ||
|
|
6706d5904d | ||
|
|
221aefd764 | ||
|
|
670057e8e6 | ||
|
|
427e46201c | ||
|
|
fd1240f335 | ||
|
|
aa7670cb59 | ||
|
|
468139229c | ||
|
|
39752f0e3f | ||
|
|
4d850d067f | ||
|
|
bcae64df88 | ||
|
|
690fd5a061 | ||
|
|
ac56c6df9a | ||
|
|
e7e4407a09 | ||
|
|
3f0c9538bd | ||
|
|
5c3ccbfdad | ||
|
|
9710142c47 | ||
|
|
57640d17cd | ||
|
|
b5d93e7515 | ||
|
|
ca9b29d82a | ||
|
|
51efa4f61f | ||
|
|
a6c71719d1 | ||
|
|
44aa15809a | ||
|
|
0f99fcd58c | ||
|
|
0ff1b92fde | ||
|
|
98c9e0f2e4 | ||
|
|
493d650e15 | ||
|
|
c4e1ff0276 | ||
|
|
c8f5910d98 | ||
|
|
0ae49c7c3e | ||
|
|
d961aaace4 | ||
|
|
bd6bc25874 | ||
|
|
a45b07ea23 | ||
|
|
f7c446b257 | ||
|
|
71a29aa97d | ||
|
|
ae3a405a7b | ||
|
|
ebbcad812a | ||
|
|
d35a12d7e8 | ||
|
|
67e6dba85c | ||
|
|
fb666a7553 | ||
|
|
d767afb1e1 | ||
|
|
eb6191ab3a | ||
|
|
7fe9ae22f0 | ||
|
|
e3a1e5e0e3 | ||
|
|
01b8a5e01a | ||
|
|
a8ab035793 | ||
|
|
b36795ad3d | ||
|
|
ff0e173cfd | ||
|
|
11883525c2 | ||
|
|
f89f364add | ||
|
|
56391e32f2 | ||
|
|
b7f0f62949 | ||
|
|
b143421886 | ||
|
|
4736a974ec | ||
|
|
2c80f459c2 | ||
|
|
688369f994 | ||
|
|
4e0167c012 | ||
|
|
b3769343ea | ||
|
|
967f4a227c | ||
|
|
4c45b9786c | ||
|
|
23bc874f63 | ||
|
|
14e1bdb586 | ||
|
|
fe50c1212a | ||
|
|
c01fbf5f47 | ||
|
|
5c8da28b61 | ||
|
|
bbb3c0208b | ||
|
|
82a3db39fe | ||
|
|
254857b53f | ||
|
|
1648be6b83 | ||
|
|
c4c774c217 | ||
|
|
6a0aab2088 | ||
|
|
5a5593ec5b | ||
|
|
2d39afdeac | ||
|
|
974ac31277 | ||
|
|
47e98d532d | ||
|
|
2efc513221 | ||
|
|
5d820e3046 | ||
|
|
c3cff3bcd3 | ||
|
|
e65a8a6b66 | ||
|
|
ccc48d158a | ||
|
|
b11e787f09 | ||
|
|
cdb6562de8 | ||
|
|
d8e8c9aa02 | ||
|
|
be392be1e6 | ||
|
|
10dc432445 | ||
|
|
19187f887d | ||
|
|
dc76a42aaa | ||
|
|
1f2b8047a6 | ||
|
|
e8c9ed0528 | ||
|
|
c7ae78c02f | ||
|
|
dc8f1211e6 | ||
|
|
5c25a63ea5 | ||
|
|
c1787ab994 | ||
|
|
6fea535fdc | ||
|
|
e8cee84380 | ||
|
|
b4613edeb7 | ||
|
|
a8b6e5aa3d | ||
|
|
e842193cd6 | ||
|
|
bb0813333d | ||
|
|
ab4c6f80f4 | ||
|
|
89796e425a | ||
|
|
9c42c8bbc4 | ||
|
|
616237caee | ||
|
|
2d36a0d37f | ||
|
|
1ec432a20f | ||
|
|
afd91b2261 | ||
|
|
cdfb7f914f | ||
|
|
33b0897522 | ||
|
|
5f0cf1b522 | ||
|
|
afb2ad95a4 | ||
|
|
27beab3133 | ||
|
|
435c82489b | ||
|
|
3ba6bf272e | ||
|
|
eec99b2fa3 | ||
|
|
d23e45e410 | ||
|
|
3c82d12609 | ||
|
|
15d67997e7 | ||
|
|
a6dfcb3100 | ||
|
|
26c2369228 | ||
|
|
2eed446492 | ||
|
|
7ebdeab6b2 | ||
|
|
0c35278f51 | ||
|
|
561122f03d | ||
|
|
95311be034 | ||
|
|
1eda44a102 | ||
|
|
d76781eb91 | ||
|
|
82d44e051f | ||
|
|
fdc9f5a3b7 | ||
|
|
ee6c82aba9 | ||
|
|
11d3f5c2ba | ||
|
|
feb68ce373 | ||
|
|
7f9a9de157 | ||
|
|
8e1b6a3d3b | ||
|
|
6e6e5a53e2 | ||
|
|
0408734ec5 | ||
|
|
317519fc08 | ||
|
|
843d79eab4 | ||
|
|
165a757f06 | ||
|
|
ea8b730142 | ||
|
|
e88c97d625 | ||
|
|
7560988b76 | ||
|
|
eecd8077b6 | ||
|
|
cbab5c3f7b | ||
|
|
a5d27c8bb8 | ||
|
|
a6a340b5db |
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.11.0.cjs
|
yarnPath: .yarn/releases/yarn-4.12.0.cjs
|
||||||
|
|||||||
@@ -156,7 +156,9 @@ 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,18 +44,24 @@ 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",
|
||||||
@@ -63,25 +69,46 @@ 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",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -154,6 +154,38 @@ 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",
|
||||||
@@ -171,6 +203,15 @@ 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",
|
||||||
@@ -225,4 +266,58 @@ 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,17 +17,15 @@ 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 = lastVal + delta;
|
const mean = delta;
|
||||||
statistics.push({
|
statistics.push({
|
||||||
start: currentDate.getTime(),
|
start: currentDate.getTime(),
|
||||||
end: currentDate.getTime(),
|
end: currentDate.getTime(),
|
||||||
@@ -38,7 +36,6 @@ const generateMeanStatistics = (
|
|||||||
state: mean,
|
state: mean,
|
||||||
sum: null,
|
sum: null,
|
||||||
});
|
});
|
||||||
lastVal = mean;
|
|
||||||
currentDate =
|
currentDate =
|
||||||
period === "day"
|
period === "day"
|
||||||
? addDays(currentDate, 1)
|
? addDays(currentDate, 1)
|
||||||
@@ -336,7 +333,6 @@ 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,7 +18,6 @@ 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";
|
||||||
@@ -38,11 +37,6 @@ 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,10 +381,6 @@ 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>
|
||||||
|
|||||||
50
package.json
50
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.19.1",
|
"@codemirror/autocomplete": "6.20.0",
|
||||||
"@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.6",
|
"@codemirror/view": "6.38.8",
|
||||||
"@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",
|
"@home-assistant/webawesome": "3.0.0-ha.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.6",
|
"barcode-detector": "3.0.8",
|
||||||
"color-name": "2.0.2",
|
"color-name": "2.1.0",
|
||||||
"comlink": "4.4.2",
|
"comlink": "4.4.2",
|
||||||
"core-js": "3.46.0",
|
"core-js": "3.47.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,8 +111,8 @@
|
|||||||
"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.14",
|
"hls.js": "1.6.15",
|
||||||
"home-assistant-js-websocket": "9.5.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",
|
||||||
"js-yaml": "4.1.1",
|
"js-yaml": "4.1.1",
|
||||||
@@ -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.0",
|
"marked": "17.0.1",
|
||||||
"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.3.0",
|
"workbox-cacheable-response": "7.4.0",
|
||||||
"workbox-core": "7.3.0",
|
"workbox-core": "7.4.0",
|
||||||
"workbox-expiration": "7.3.0",
|
"workbox-expiration": "7.4.0",
|
||||||
"workbox-precaching": "7.3.0",
|
"workbox-precaching": "7.4.0",
|
||||||
"workbox-routing": "7.3.0",
|
"workbox-routing": "7.4.0",
|
||||||
"workbox-strategies": "7.3.0",
|
"workbox-strategies": "7.4.0",
|
||||||
"xss": "1.0.15"
|
"xss": "1.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -153,12 +153,12 @@
|
|||||||
"@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.3.1",
|
"@lokalise/node-api": "15.4.0",
|
||||||
"@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",
|
||||||
"@rsdoctor/rspack-plugin": "1.3.8",
|
"@rsdoctor/rspack-plugin": "1.3.11",
|
||||||
"@rspack/core": "1.6.1",
|
"@rspack/core": "1.6.4",
|
||||||
"@rspack/dev-server": "1.1.4",
|
"@rspack/dev-server": "1.1.4",
|
||||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||||
"@types/chromecast-caf-receiver": "6.0.22",
|
"@types/chromecast-caf-receiver": "6.0.22",
|
||||||
@@ -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.8",
|
"@vitest/coverage-v8": "4.0.13",
|
||||||
"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.1.0",
|
"jsdom": "27.2.0",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lint-staged": "16.2.6",
|
"lint-staged": "16.2.7",
|
||||||
"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.1.0",
|
"rspack-manifest-plugin": "5.2.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.46.3",
|
"typescript-eslint": "8.47.0",
|
||||||
"vite-tsconfig-paths": "5.1.4",
|
"vite-tsconfig-paths": "5.1.4",
|
||||||
"vitest": "4.0.8",
|
"vitest": "4.0.13",
|
||||||
"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.11.0",
|
"packageManager": "yarn@4.12.0",
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "22.21.1"
|
"node": "22.21.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_3969_57097)">
|
|
||||||
<path d="M0 4C0 1.79086 1.79086 0 4 0H16C18.2091 0 20 1.79086 20 4V4C20 6.20914 18.2091 8 16 8H4C1.79086 8 0 6.20914 0 4V4Z" fill="white" fill-opacity="0.48"/>
|
|
||||||
<path d="M0 20C0 15.5817 3.58172 12 8 12H68C72.4183 12 76 15.5817 76 20V36C76 40.4183 72.4183 44 68 44H8C3.58172 44 0 40.4183 0 36V20Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M8 12.5H68C72.1421 12.5 75.5 15.8579 75.5 20V36C75.5 40.1421 72.1421 43.5 68 43.5H8C3.85786 43.5 0.5 40.1421 0.5 36V20C0.5 15.8579 3.85786 12.5 8 12.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<path d="M32.9844 27.0156C32.9844 26.0781 32.7031 25.2656 32.1406 24.5781C31.5781 23.8594 30.8594 23.375 29.9844 23.125V22C29.9844 21.4375 30.125 20.9375 30.4062 20.5C30.6875 20.0312 31.0469 19.6719 31.4844 19.4219C31.9531 19.1406 32.4531 19 32.9844 19H43.0156C43.5469 19 44.0312 19.1406 44.4688 19.4219C44.9375 19.6719 45.3125 20.0312 45.5938 20.5C45.875 20.9375 46.0156 21.4375 46.0156 22V23.125C45.1406 23.375 44.4219 23.8594 43.8594 24.5781C43.2969 25.2656 43.0156 26.0781 43.0156 27.0156V28.9844H32.9844V27.0156ZM47 25C47.5625 25 48.0312 25.2031 48.4062 25.6094C48.8125 25.9844 49.0156 26.4531 49.0156 27.0156V31.9844C49.0156 32.5469 48.875 33.0625 48.5938 33.5312C48.3125 33.9688 47.9375 34.3281 47.4688 34.6094C47.0312 34.8594 46.5469 34.9844 46.0156 34.9844V36.0156C46.0156 36.2656 45.9062 36.5 45.6875 36.7188C45.5 36.9062 45.2656 37 44.9844 37C44.7344 37 44.5 36.9062 44.2812 36.7188C44.0938 36.5 44 36.2656 44 36.0156V34.9844H32V36.0156C32 36.2656 31.8906 36.5 31.6719 36.7188C31.4844 36.9062 31.2656 37 31.0156 37C30.7344 37 30.4844 36.9062 30.2656 36.7188C30.0781 36.5 29.9844 36.2656 29.9844 36.0156V34.9844C29.4531 34.9844 28.9531 34.8594 28.4844 34.6094C28.0469 34.3281 27.6875 33.9688 27.4062 33.5312C27.125 33.0625 26.9844 32.5469 26.9844 31.9844V27.0156C26.9844 26.4531 27.1719 25.9844 27.5469 25.6094C27.9531 25.2031 28.4375 25 29 25C29.5625 25 30.0312 25.2031 30.4062 25.6094C30.8125 25.9844 31.0156 26.4531 31.0156 27.0156V31H44.9844V27.0156C44.9844 26.4531 45.1719 25.9844 45.5469 25.6094C45.9531 25.2031 46.4375 25 47 25Z" fill="#03A9F4"/>
|
|
||||||
<path d="M0 56C0 51.5817 3.58172 48 8 48H68C72.4183 48 76 51.5817 76 56V72C76 76.4183 72.4183 80 68 80H8C3.58172 80 0 76.4183 0 72V56Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M8 48.5H68C72.1421 48.5 75.5 51.8579 75.5 56V72C75.5 76.1421 72.1421 79.5 68 79.5H8C3.85786 79.5 0.5 76.1421 0.5 72V56C0.5 51.8579 3.85786 48.5 8 48.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<path d="M44 61.9844H47.9844V64H46.0156V72.0156H29.9844V64H28.0156V61.9844H32C31.4375 61.9844 30.9531 61.7969 30.5469 61.4219C30.1719 61.0156 29.9844 60.5469 29.9844 60.0156V55.9844H35.9844V60.0156C35.9844 60.5469 35.7812 61.0156 35.375 61.4219C35 61.7969 34.5469 61.9844 34.0156 61.9844H41.9844V58.9844C41.9844 58.7344 41.8906 58.5156 41.7031 58.3281C41.5156 58.1094 41.2812 58 41 58C40.7188 58 40.4844 58.1094 40.2969 58.3281C40.1094 58.5156 40.0156 58.7344 40.0156 58.9844H38C38 58.4531 38.125 57.9688 38.375 57.5312C38.6562 57.0625 39.0156 56.6875 39.4531 56.4062C39.9219 56.125 40.4375 55.9844 41 55.9844C41.5625 55.9844 42.0625 56.125 42.5 56.4062C42.9688 56.6875 43.3281 57.0625 43.5781 57.5312C43.8594 57.9688 44 58.4531 44 58.9844V61.9844ZM38.9844 70V64H37.0156V70H38.9844Z" fill="#03A9F4"/>
|
|
||||||
<path d="M0 92C0 87.5817 3.58172 84 8 84H68C72.4183 84 76 87.5817 76 92V108C76 112.418 72.4183 116 68 116H8C3.58172 116 0 112.418 0 108V92Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M8 84.5H68C72.1421 84.5 75.5 87.8579 75.5 92V108C75.5 112.142 72.1421 115.5 68 115.5H8C3.85786 115.5 0.5 112.142 0.5 108V92C0.5 87.8579 3.85786 84.5 8 84.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<path d="M44.9844 94.9844C46.0781 94.9844 47.0156 95.3906 47.7969 96.2031C48.6094 96.9844 49.0156 97.9219 49.0156 99.0156V108.016H47V105.016H29V108.016H26.9844V93.0156H29V102.016H37.0156V94.9844H44.9844ZM35.0938 100.094C34.5 100.688 33.7969 100.984 32.9844 100.984C32.1719 100.984 31.4688 100.688 30.875 100.094C30.2812 99.5 29.9844 98.7969 29.9844 97.9844C29.9844 97.1719 30.2812 96.4688 30.875 95.875C31.4688 95.2812 32.1719 94.9844 32.9844 94.9844C33.7969 94.9844 34.5 95.2812 35.0938 95.875C35.6875 96.4688 35.9844 97.1719 35.9844 97.9844C35.9844 98.7969 35.6875 99.5 35.0938 100.094Z" fill="#03A9F4"/>
|
|
||||||
<path d="M0 128C0 123.582 3.58172 120 8 120H68C72.4183 120 76 123.582 76 128V144C76 148.418 72.4183 152 68 152H8C3.58172 152 0 148.418 0 144V128Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M8 120.5H68C72.1421 120.5 75.5 123.858 75.5 128V144C75.5 148.142 72.1421 151.5 68 151.5H8C3.85786 151.5 0.5 148.142 0.5 144V128C0.5 123.858 3.85786 120.5 8 120.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<path d="M46.0156 136.984H47.9844V142.984C47.9844 143.516 47.7812 143.984 47.375 144.391C47 144.797 46.5469 145 46.0156 145C46.0156 145.281 45.9062 145.516 45.6875 145.703C45.5 145.891 45.2656 145.984 44.9844 145.984H31.0156C30.7344 145.984 30.4844 145.891 30.2656 145.703C30.0781 145.516 29.9844 145.281 29.9844 145C29.4531 145 28.9844 144.797 28.5781 144.391C28.2031 143.984 28.0156 143.516 28.0156 142.984V136.984H31.0156V136.234C31.0156 135.641 31.2344 135.125 31.6719 134.688C32.1406 134.219 32.6719 133.984 33.2656 133.984C33.8906 133.984 34.4531 134.234 34.9531 134.734L36.3125 136.281C36.5 136.5 36.7812 136.734 37.1562 136.984H44V128.828C44 128.609 43.9219 128.422 43.7656 128.266C43.6094 128.078 43.4062 127.984 43.1562 127.984C42.9375 127.984 42.75 128.062 42.5938 128.219L41.3281 129.484C41.3906 129.734 41.4219 129.906 41.4219 130C41.4219 130.344 41.3125 130.703 41.0938 131.078L38.3281 128.312C38.7031 128.094 39.0625 127.984 39.4062 127.984C39.5625 127.984 39.7344 128.016 39.9219 128.078L41.1875 126.812C41.7188 126.281 42.375 126.016 43.1562 126.016C43.9375 126.016 44.6094 126.297 45.1719 126.859C45.7344 127.391 46.0156 128.047 46.0156 128.828V136.984ZM31.5781 132.438C31.2031 132.031 31.0156 131.547 31.0156 130.984C31.0156 130.422 31.2031 129.953 31.5781 129.578C31.9531 129.203 32.4219 129.016 32.9844 129.016C33.5469 129.016 34.0156 129.203 34.3906 129.578C34.7969 129.953 35 130.422 35 130.984C35 131.547 34.7969 132.031 34.3906 132.438C34.0156 132.812 33.5469 133 32.9844 133C32.4219 133 31.9531 132.812 31.5781 132.438Z" fill="#03A9F4"/>
|
|
||||||
<path d="M84 4C84 1.79086 85.7909 0 88 0H100C102.209 0 104 1.79086 104 4V4C104 6.20914 102.209 8 100 8H88C85.7909 8 84 6.20914 84 4V4Z" fill="white" fill-opacity="0.48"/>
|
|
||||||
<path d="M84 20C84 15.5817 87.5817 12 92 12H152C156.418 12 160 15.5817 160 20V36C160 40.4183 156.418 44 152 44H92C87.5817 44 84 40.4183 84 36V20Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M92 12.5H152C156.142 12.5 159.5 15.8579 159.5 20V36C159.5 40.1421 156.142 43.5 152 43.5H92C87.8579 43.5 84.5 40.1421 84.5 36V20C84.5 15.8579 87.8579 12.5 92 12.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<path d="M131.984 30.0156C131.984 30.7656 131.797 31.4531 131.422 32.0781C131.047 32.6719 130.562 33.1406 129.969 33.4844C129.844 34.2031 129.5 34.8125 128.938 35.3125C128.406 35.7812 127.766 36.0156 127.016 36.0156C126.359 36.0156 125.766 35.8281 125.234 35.4531C124.734 35.0781 124.391 34.5938 124.203 34H119.797C119.609 34.5938 119.25 35.0781 118.719 35.4531C118.219 35.8281 117.641 36.0156 116.984 36.0156C116.234 36.0156 115.578 35.7812 115.016 35.3125C114.484 34.8125 114.156 34.2031 114.031 33.4844C113.438 33.1406 112.953 32.6719 112.578 32.0781C112.203 31.4531 112.016 30.7656 112.016 30.0156C112.016 29.1094 112.266 28.3125 112.766 27.625C113.297 26.9375 113.969 26.4688 114.781 26.2188L113 24.3906L112.719 24.7188C112.5 24.9062 112.25 25 111.969 25C111.719 25 111.5 24.9062 111.312 24.7188C111.094 24.5312 110.984 24.2969 110.984 24.0156C110.984 23.7344 111.094 23.5 111.312 23.3125L113.281 21.2969C113.469 21.1094 113.703 21.0156 113.984 21.0156C114.266 21.0156 114.5 21.1094 114.688 21.2969C114.906 21.4844 115.016 21.7188 115.016 22C115.016 22.2812 114.906 22.5156 114.688 22.7031L114.406 22.9844L115.812 24.3906L116.609 22.0469C116.797 21.4219 117.156 20.9219 117.688 20.5469C118.219 20.1719 118.797 19.9844 119.422 19.9844H124.578C125.203 19.9844 125.781 20.1719 126.312 20.5469C126.844 20.9219 127.203 21.4219 127.391 22.0469L128.75 26.0781C129.375 26.2031 129.922 26.4531 130.391 26.8281C130.891 27.2031 131.281 27.6719 131.562 28.2344C131.844 28.7656 131.984 29.3594 131.984 30.0156ZM116.984 34C117.266 34 117.5 33.9062 117.688 33.7188C117.906 33.5 118.016 33.2656 118.016 33.0156C118.016 32.7344 117.906 32.5 117.688 32.3125C117.5 32.0938 117.266 31.9844 116.984 31.9844C116.734 31.9844 116.5 32.0938 116.281 32.3125C116.094 32.5 116 32.7344 116 33.0156C116 33.2656 116.094 33.5 116.281 33.7188C116.5 33.9062 116.734 34 116.984 34ZM121.016 25.9844V22H119.422C118.953 22 118.641 22.2344 118.484 22.7031L117.406 25.9844H121.016ZM122.984 22V25.9844H126.594L125.516 22.7031C125.359 22.2344 125.047 22 124.578 22H122.984ZM127.016 34C127.266 34 127.484 33.9062 127.672 33.7188C127.891 33.5 128 33.2656 128 33.0156C128 32.7344 127.891 32.5 127.672 32.3125C127.484 32.0938 127.266 31.9844 127.016 31.9844C126.734 31.9844 126.484 32.0938 126.266 32.3125C126.078 32.5 125.984 32.7344 125.984 33.0156C125.984 33.2656 126.078 33.5 126.266 33.7188C126.484 33.9062 126.734 34 127.016 34Z" fill="#03A9F4"/>
|
|
||||||
<path d="M84 56C84 51.5817 87.5817 48 92 48H152C156.418 48 160 51.5817 160 56V72C160 76.4183 156.418 80 152 80H92C87.5817 80 84 76.4183 84 72V56Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M92 48.5H152C156.142 48.5 159.5 51.8579 159.5 56V72C159.5 76.1421 156.142 79.5 152 79.5H92C87.8579 79.5 84.5 76.1421 84.5 72V56C84.5 51.8579 87.8579 48.5 92 48.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<path d="M128.984 58.9844C130.078 58.9844 131.016 59.3906 131.797 60.2031C132.609 60.9844 133.016 61.9219 133.016 63.0156V72.0156H131V69.0156H113V72.0156H110.984V57.0156H113V66.0156H121.016V58.9844H128.984ZM119.094 64.0938C118.5 64.6875 117.797 64.9844 116.984 64.9844C116.172 64.9844 115.469 64.6875 114.875 64.0938C114.281 63.5 113.984 62.7969 113.984 61.9844C113.984 61.1719 114.281 60.4688 114.875 59.875C115.469 59.2812 116.172 58.9844 116.984 58.9844C117.797 58.9844 118.5 59.2812 119.094 59.875C119.688 60.4688 119.984 61.1719 119.984 61.9844C119.984 62.7969 119.688 63.5 119.094 64.0938Z" fill="#03A9F4"/>
|
|
||||||
<path d="M84 92C84 87.5817 87.5817 84 92 84H152C156.418 84 160 87.5817 160 92V108C160 112.418 156.418 116 152 116H92C87.5817 116 84 112.418 84 108V92Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M92 84.5H152C156.142 84.5 159.5 87.8579 159.5 92V108C159.5 112.142 156.142 115.5 152 115.5H92C87.8579 115.5 84.5 112.142 84.5 108V92C84.5 87.8579 87.8579 84.5 92 84.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<path d="M112.016 94H131.984V106H130.016V103.984H125.984V106H124.016V96.0156H113.984V106H112.016V94ZM130.016 96.0156H125.984V97.9844H130.016V96.0156ZM125.984 102.016H130.016V100H125.984V102.016Z" fill="#03A9F4"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_3969_57097">
|
|
||||||
<rect width="160" height="160" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 11 KiB |
@@ -1,76 +0,0 @@
|
|||||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_4744_40067)">
|
|
||||||
<path d="M0 6C0 2.68629 2.68629 0 6 0H28C31.3137 0 34 2.68629 34 6C34 9.31371 31.3137 12 28 12H6C2.68629 12 0 9.31371 0 6Z" fill="white" fill-opacity="0.48"/>
|
|
||||||
<path d="M0 28C0 23.5817 3.58172 20 8 20H42.6667C47.0849 20 50.6667 23.5817 50.6667 28V36C50.6667 40.4183 47.0849 44 42.6667 44H8.00001C3.58173 44 0 40.4183 0 36V28Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M8 20.5H42.667C46.809 20.5002 50.167 23.858 50.167 28V36C50.167 40.142 46.809 43.4998 42.667 43.5H8C3.85787 43.5 0.5 40.1421 0.5 36V28C0.5 23.8579 3.85786 20.5 8 20.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<path d="M6 32C6 28.6863 8.68629 26 12 26C15.3137 26 18 28.6863 18 32C18 35.3137 15.3137 38 12 38C8.68629 38 6 35.3137 6 32Z" fill="white" fill-opacity="0.24"/>
|
|
||||||
<path d="M24 31C24 29.3431 25.3431 28 27 28H39.6667C41.3235 28 42.6667 29.3431 42.6667 31V33C42.6667 34.6569 41.3235 36 39.6667 36H27C25.3431 36 24 34.6569 24 33V31Z" fill="white" fill-opacity="0.24"/>
|
|
||||||
<path d="M54.6666 28C54.6666 23.5817 58.2483 20 62.6666 20H97.3333C101.752 20 105.333 23.5817 105.333 28V36C105.333 40.4183 101.752 44 97.3333 44H62.6666C58.2484 44 54.6666 40.4183 54.6666 36V28Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M62.6666 20.5H97.3336C101.476 20.5002 104.834 23.858 104.834 28V36C104.834 40.142 101.476 43.4998 97.3336 43.5H62.6666C58.5245 43.5 55.1666 40.1421 55.1666 36V28C55.1666 23.8579 58.5245 20.5 62.6666 20.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<path d="M60.6666 32C60.6666 28.6863 63.3529 26 66.6666 26C69.9803 26 72.6666 28.6863 72.6666 32C72.6666 35.3137 69.9803 38 66.6666 38C63.3529 38 60.6666 35.3137 60.6666 32Z" fill="white" fill-opacity="0.24"/>
|
|
||||||
<path d="M78.6666 31C78.6666 29.3431 80.0098 28 81.6666 28H94.3333C95.9901 28 97.3333 29.3431 97.3333 31V33C97.3333 34.6569 95.9901 36 94.3333 36H81.6666C80.0098 36 78.6666 34.6569 78.6666 33V31Z" fill="white" fill-opacity="0.24"/>
|
|
||||||
<path d="M109.333 28C109.333 23.5817 112.915 20 117.333 20H152C156.418 20 160 23.5817 160 28V36C160 40.4183 156.418 44 152 44H117.333C112.915 44 109.333 40.4183 109.333 36V28Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M117.333 20.5H152C156.142 20.5002 159.5 23.858 159.5 28V36C159.5 40.142 156.142 43.4998 152 43.5H117.333C113.191 43.5 109.833 40.1421 109.833 36V28C109.833 23.8579 113.191 20.5 117.333 20.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<path d="M115.333 32C115.333 28.6863 118.02 26 121.333 26C124.647 26 127.333 28.6863 127.333 32C127.333 35.3137 124.647 38 121.333 38C118.02 38 115.333 35.3137 115.333 32Z" fill="white" fill-opacity="0.24"/>
|
|
||||||
<path d="M133.333 31C133.333 29.3431 134.677 28 136.333 28H149C150.657 28 152 29.3431 152 31V33C152 34.6569 150.657 36 149 36H136.333C134.677 36 133.333 34.6569 133.333 33V31Z" fill="white" fill-opacity="0.24"/>
|
|
||||||
<path d="M0 56C0 53.7909 1.79086 52 4 52H29C31.2091 52 33 53.7909 33 56C33 58.2091 31.2091 60 29 60H4C1.79086 60 0 58.2091 0 56Z" fill="white" fill-opacity="0.48"/>
|
|
||||||
<path d="M0 72C0 67.5817 3.58172 64 8 64H29C33.4183 64 37 67.5817 37 72V96C37 100.418 33.4183 104 29 104H8C3.58172 104 0 100.418 0 96V72Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M8 64.5H29C33.1421 64.5 36.5 67.8579 36.5 72V96C36.5 100.142 33.1421 103.5 29 103.5H8C3.85786 103.5 0.5 100.142 0.5 96V72C0.5 67.8579 3.85786 64.5 8 64.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<mask id="mask0_4744_40067" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="6" y="72" width="25" height="24">
|
|
||||||
<path d="M18.5 74C16.6435 74 14.863 74.7375 13.5503 76.0503C12.2375 77.363 11.5 79.1435 11.5 81C11.5 83.38 12.69 85.47 14.5 86.74V89C14.5 89.2652 14.6054 89.5196 14.7929 89.7071C14.9804 89.8946 15.2348 90 15.5 90H21.5C21.7652 90 22.0196 89.8946 22.2071 89.7071C22.3946 89.5196 22.5 89.2652 22.5 89V86.74C24.31 85.47 25.5 83.38 25.5 81C25.5 79.1435 24.7625 77.363 23.4497 76.0503C22.137 74.7375 20.3565 74 18.5 74ZM15.5 93C15.5 93.2652 15.6054 93.5196 15.7929 93.7071C15.9804 93.8946 16.2348 94 16.5 94H20.5C20.7652 94 21.0196 93.8946 21.2071 93.7071C21.3946 93.5196 21.5 93.2652 21.5 93V92H15.5V93Z" fill="black"/>
|
|
||||||
</mask>
|
|
||||||
<g mask="url(#mask0_4744_40067)">
|
|
||||||
<rect x="6.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
|
||||||
</g>
|
|
||||||
<path d="M41 72C41 67.5817 44.5817 64 49 64H70C74.4183 64 78 67.5817 78 72V96C78 100.418 74.4183 104 70 104H49C44.5817 104 41 100.418 41 96V72Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M49 64.5H70C74.1421 64.5 77.5 67.8579 77.5 72V96C77.5 100.142 74.1421 103.5 70 103.5H49C44.8579 103.5 41.5 100.142 41.5 96V72C41.5 67.8579 44.8579 64.5 49 64.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<mask id="mask1_4744_40067" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="47" y="72" width="25" height="24">
|
|
||||||
<path d="M66.5 80C67.61 80 68.5 80.9 68.5 82V88.76C69.11 89.31 69.5 90.11 69.5 91C69.5 92.66 68.16 94 66.5 94C64.84 94 63.5 92.66 63.5 91C63.5 90.11 63.89 89.31 64.5 88.76V82C64.5 80.9 65.4 80 66.5 80ZM66.5 81C65.95 81 65.5 81.45 65.5 82V83H67.5V82C67.5 81.45 67.05 81 66.5 81ZM52.5 92V84H49.5L59.5 75L63.9 78.96C63.04 79.69 62.5 80.78 62.5 82V88C61.87 88.83 61.5 89.87 61.5 91L61.6 92H52.5Z" fill="black"/>
|
|
||||||
</mask>
|
|
||||||
<g mask="url(#mask1_4744_40067)">
|
|
||||||
<rect x="47.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
|
||||||
</g>
|
|
||||||
<path d="M82 72C82 67.5817 85.5817 64 90 64H111C115.418 64 119 67.5817 119 72V96C119 100.418 115.418 104 111 104H90C85.5817 104 82 100.418 82 96V72Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M90 64.5H111C115.142 64.5 118.5 67.8579 118.5 72V96C118.5 100.142 115.142 103.5 111 103.5H90C85.8579 103.5 82.5 100.142 82.5 96V72C82.5 67.8579 85.8579 64.5 90 64.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<mask id="mask2_4744_40067" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="88" y="72" width="25" height="24">
|
|
||||||
<path d="M100.5 84H107.5C106.97 88.11 104.22 91.78 100.5 92.92V84H93.5V78.3L100.5 75.19M100.5 73L91.5 77V83C91.5 88.55 95.34 93.73 100.5 95C105.66 93.73 109.5 88.55 109.5 83V77L100.5 73Z" fill="black"/>
|
|
||||||
</mask>
|
|
||||||
<g mask="url(#mask2_4744_40067)">
|
|
||||||
<rect x="88.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
|
||||||
</g>
|
|
||||||
<path d="M123 72C123 67.5817 126.582 64 131 64H152C156.418 64 160 67.5817 160 72V96C160 100.418 156.418 104 152 104H131C126.582 104 123 100.418 123 96V72Z" fill="#1C1C1C"/>
|
|
||||||
<path d="M131 64.5H152C156.142 64.5 159.5 67.8579 159.5 72V96C159.5 100.142 156.142 103.5 152 103.5H131C126.858 103.5 123.5 100.142 123.5 96V72C123.5 67.8579 126.858 64.5 131 64.5Z" stroke="white" stroke-opacity="0.24"/>
|
|
||||||
<mask id="mask3_4744_40067" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="129" y="72" width="25" height="24">
|
|
||||||
<path d="M145.5 84C145.5 83.4696 145.711 82.9609 146.086 82.5858C146.461 82.2107 146.97 82 147.5 82C148.03 82 148.539 82.2107 148.914 82.5858C149.289 82.9609 149.5 83.4696 149.5 84C149.5 84.5304 149.289 85.0391 148.914 85.4142C148.539 85.7893 148.03 86 147.5 86C146.97 86 146.461 85.7893 146.086 85.4142C145.711 85.0391 145.5 84.5304 145.5 84ZM139.5 84C139.5 83.4696 139.711 82.9609 140.086 82.5858C140.461 82.2107 140.97 82 141.5 82C142.03 82 142.539 82.2107 142.914 82.5858C143.289 82.9609 143.5 83.4696 143.5 84C143.5 84.5304 143.289 85.0391 142.914 85.4142C142.539 85.7893 142.03 86 141.5 86C140.97 86 140.461 85.7893 140.086 85.4142C139.711 85.0391 139.5 84.5304 139.5 84ZM133.5 84C133.5 83.4696 133.711 82.9609 134.086 82.5858C134.461 82.2107 134.97 82 135.5 82C136.03 82 136.539 82.2107 136.914 82.5858C137.289 82.9609 137.5 83.4696 137.5 84C137.5 84.5304 137.289 85.0391 136.914 85.4142C136.539 85.7893 136.03 86 135.5 86C134.97 86 134.461 85.7893 134.086 85.4142C133.711 85.0391 133.5 84.5304 133.5 84Z" fill="black"/>
|
|
||||||
</mask>
|
|
||||||
<g mask="url(#mask3_4744_40067)">
|
|
||||||
<rect x="129.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
|
||||||
</g>
|
|
||||||
<path d="M0 116C0 113.791 1.79086 112 4 112H29C31.2091 112 33 113.791 33 116C33 118.209 31.2091 120 29 120H4C1.79086 120 0 118.209 0 116Z" fill="white" fill-opacity="0.48"/>
|
|
||||||
<path d="M0 132C0 127.582 3.58172 124 8 124H70C74.4183 124 78 127.582 78 132V160H0V132Z" fill="url(#paint0_linear_4744_40067)"/>
|
|
||||||
<path d="M8 124.5H70C74.1421 124.5 77.5 127.858 77.5 132V159.5H0.5V132C0.5 127.858 3.85786 124.5 8 124.5Z" stroke="url(#paint1_linear_4744_40067)" stroke-opacity="0.12"/>
|
|
||||||
<path d="M82 132C82 127.582 85.5817 124 90 124H152C156.418 124 160 127.582 160 132V160H82V132Z" fill="url(#paint2_linear_4744_40067)"/>
|
|
||||||
<path d="M90 124.5H152C156.142 124.5 159.5 127.858 159.5 132V159.5H82.5V132C82.5 127.858 85.8579 124.5 90 124.5Z" stroke="url(#paint3_linear_4744_40067)" stroke-opacity="0.12"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="paint0_linear_4744_40067" x1="39" y1="124" x2="39" y2="160" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop offset="0.5" stop-color="#1C1C1C"/>
|
|
||||||
<stop offset="1" stop-color="#1C1C1C" stop-opacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="paint1_linear_4744_40067" x1="39" y1="124" x2="39" y2="160" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop offset="0.5" stop-color="white" stop-opacity="0.24"/>
|
|
||||||
<stop offset="1" stop-opacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="paint2_linear_4744_40067" x1="121" y1="124" x2="121" y2="160" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop offset="0.5" stop-color="#1C1C1C"/>
|
|
||||||
<stop offset="1" stop-color="#1C1C1C" stop-opacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="paint3_linear_4744_40067" x1="121" y1="124" x2="121" y2="160" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop offset="0.5" stop-color="white" stop-opacity="0.24"/>
|
|
||||||
<stop offset="1" stop-opacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
<clipPath id="clip0_4744_40067">
|
|
||||||
<rect width="160" height="160" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 9.3 KiB |
@@ -1,32 +0,0 @@
|
|||||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_3969_50764)">
|
|
||||||
<path d="M0 4C0 1.79086 1.79086 0 4 0H16C18.2091 0 20 1.79086 20 4V4C20 6.20914 18.2091 8 16 8H4C1.79086 8 0 6.20914 0 4V4Z" fill="black" fill-opacity="0.32"/>
|
|
||||||
<path d="M0 20C0 15.5817 3.58172 12 8 12H68C72.4183 12 76 15.5817 76 20V36C76 40.4183 72.4183 44 68 44H8C3.58172 44 0 40.4183 0 36V20Z" fill="white"/>
|
|
||||||
<path d="M8 12.5H68C72.1421 12.5 75.5 15.8579 75.5 20V36C75.5 40.1421 72.1421 43.5 68 43.5H8C3.85786 43.5 0.5 40.1421 0.5 36V20C0.5 15.8579 3.85786 12.5 8 12.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<path d="M32.9844 27.0156C32.9844 26.0781 32.7031 25.2656 32.1406 24.5781C31.5781 23.8594 30.8594 23.375 29.9844 23.125V22C29.9844 21.4375 30.125 20.9375 30.4062 20.5C30.6875 20.0312 31.0469 19.6719 31.4844 19.4219C31.9531 19.1406 32.4531 19 32.9844 19H43.0156C43.5469 19 44.0312 19.1406 44.4688 19.4219C44.9375 19.6719 45.3125 20.0312 45.5938 20.5C45.875 20.9375 46.0156 21.4375 46.0156 22V23.125C45.1406 23.375 44.4219 23.8594 43.8594 24.5781C43.2969 25.2656 43.0156 26.0781 43.0156 27.0156V28.9844H32.9844V27.0156ZM47 25C47.5625 25 48.0312 25.2031 48.4062 25.6094C48.8125 25.9844 49.0156 26.4531 49.0156 27.0156V31.9844C49.0156 32.5469 48.875 33.0625 48.5938 33.5312C48.3125 33.9688 47.9375 34.3281 47.4688 34.6094C47.0312 34.8594 46.5469 34.9844 46.0156 34.9844V36.0156C46.0156 36.2656 45.9062 36.5 45.6875 36.7188C45.5 36.9062 45.2656 37 44.9844 37C44.7344 37 44.5 36.9062 44.2812 36.7188C44.0938 36.5 44 36.2656 44 36.0156V34.9844H32V36.0156C32 36.2656 31.8906 36.5 31.6719 36.7188C31.4844 36.9062 31.2656 37 31.0156 37C30.7344 37 30.4844 36.9062 30.2656 36.7188C30.0781 36.5 29.9844 36.2656 29.9844 36.0156V34.9844C29.4531 34.9844 28.9531 34.8594 28.4844 34.6094C28.0469 34.3281 27.6875 33.9688 27.4062 33.5312C27.125 33.0625 26.9844 32.5469 26.9844 31.9844V27.0156C26.9844 26.4531 27.1719 25.9844 27.5469 25.6094C27.9531 25.2031 28.4375 25 29 25C29.5625 25 30.0312 25.2031 30.4062 25.6094C30.8125 25.9844 31.0156 26.4531 31.0156 27.0156V31H44.9844V27.0156C44.9844 26.4531 45.1719 25.9844 45.5469 25.6094C45.9531 25.2031 46.4375 25 47 25Z" fill="#03A9F4"/>
|
|
||||||
<path d="M0 56C0 51.5817 3.58172 48 8 48H68C72.4183 48 76 51.5817 76 56V72C76 76.4183 72.4183 80 68 80H8C3.58172 80 0 76.4183 0 72V56Z" fill="white"/>
|
|
||||||
<path d="M8 48.5H68C72.1421 48.5 75.5 51.8579 75.5 56V72C75.5 76.1421 72.1421 79.5 68 79.5H8C3.85786 79.5 0.5 76.1421 0.5 72V56C0.5 51.8579 3.85786 48.5 8 48.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<path d="M44 61.9844H47.9844V64H46.0156V72.0156H29.9844V64H28.0156V61.9844H32C31.4375 61.9844 30.9531 61.7969 30.5469 61.4219C30.1719 61.0156 29.9844 60.5469 29.9844 60.0156V55.9844H35.9844V60.0156C35.9844 60.5469 35.7812 61.0156 35.375 61.4219C35 61.7969 34.5469 61.9844 34.0156 61.9844H41.9844V58.9844C41.9844 58.7344 41.8906 58.5156 41.7031 58.3281C41.5156 58.1094 41.2812 58 41 58C40.7188 58 40.4844 58.1094 40.2969 58.3281C40.1094 58.5156 40.0156 58.7344 40.0156 58.9844H38C38 58.4531 38.125 57.9688 38.375 57.5312C38.6562 57.0625 39.0156 56.6875 39.4531 56.4062C39.9219 56.125 40.4375 55.9844 41 55.9844C41.5625 55.9844 42.0625 56.125 42.5 56.4062C42.9688 56.6875 43.3281 57.0625 43.5781 57.5312C43.8594 57.9688 44 58.4531 44 58.9844V61.9844ZM38.9844 70V64H37.0156V70H38.9844Z" fill="#03A9F4"/>
|
|
||||||
<path d="M0 92C0 87.5817 3.58172 84 8 84H68C72.4183 84 76 87.5817 76 92V108C76 112.418 72.4183 116 68 116H8C3.58172 116 0 112.418 0 108V92Z" fill="white"/>
|
|
||||||
<path d="M8 84.5H68C72.1421 84.5 75.5 87.8579 75.5 92V108C75.5 112.142 72.1421 115.5 68 115.5H8C3.85786 115.5 0.5 112.142 0.5 108V92C0.5 87.8579 3.85786 84.5 8 84.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<path d="M44.9844 94.9844C46.0781 94.9844 47.0156 95.3906 47.7969 96.2031C48.6094 96.9844 49.0156 97.9219 49.0156 99.0156V108.016H47V105.016H29V108.016H26.9844V93.0156H29V102.016H37.0156V94.9844H44.9844ZM35.0938 100.094C34.5 100.688 33.7969 100.984 32.9844 100.984C32.1719 100.984 31.4688 100.688 30.875 100.094C30.2812 99.5 29.9844 98.7969 29.9844 97.9844C29.9844 97.1719 30.2812 96.4688 30.875 95.875C31.4688 95.2812 32.1719 94.9844 32.9844 94.9844C33.7969 94.9844 34.5 95.2812 35.0938 95.875C35.6875 96.4688 35.9844 97.1719 35.9844 97.9844C35.9844 98.7969 35.6875 99.5 35.0938 100.094Z" fill="#03A9F4"/>
|
|
||||||
<path d="M0 128C0 123.582 3.58172 120 8 120H68C72.4183 120 76 123.582 76 128V144C76 148.418 72.4183 152 68 152H8C3.58172 152 0 148.418 0 144V128Z" fill="white"/>
|
|
||||||
<path d="M8 120.5H68C72.1421 120.5 75.5 123.858 75.5 128V144C75.5 148.142 72.1421 151.5 68 151.5H8C3.85786 151.5 0.5 148.142 0.5 144V128C0.5 123.858 3.85786 120.5 8 120.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<path d="M46.0156 136.984H47.9844V142.984C47.9844 143.516 47.7812 143.984 47.375 144.391C47 144.797 46.5469 145 46.0156 145C46.0156 145.281 45.9062 145.516 45.6875 145.703C45.5 145.891 45.2656 145.984 44.9844 145.984H31.0156C30.7344 145.984 30.4844 145.891 30.2656 145.703C30.0781 145.516 29.9844 145.281 29.9844 145C29.4531 145 28.9844 144.797 28.5781 144.391C28.2031 143.984 28.0156 143.516 28.0156 142.984V136.984H31.0156V136.234C31.0156 135.641 31.2344 135.125 31.6719 134.688C32.1406 134.219 32.6719 133.984 33.2656 133.984C33.8906 133.984 34.4531 134.234 34.9531 134.734L36.3125 136.281C36.5 136.5 36.7812 136.734 37.1562 136.984H44V128.828C44 128.609 43.9219 128.422 43.7656 128.266C43.6094 128.078 43.4062 127.984 43.1562 127.984C42.9375 127.984 42.75 128.062 42.5938 128.219L41.3281 129.484C41.3906 129.734 41.4219 129.906 41.4219 130C41.4219 130.344 41.3125 130.703 41.0938 131.078L38.3281 128.312C38.7031 128.094 39.0625 127.984 39.4062 127.984C39.5625 127.984 39.7344 128.016 39.9219 128.078L41.1875 126.812C41.7188 126.281 42.375 126.016 43.1562 126.016C43.9375 126.016 44.6094 126.297 45.1719 126.859C45.7344 127.391 46.0156 128.047 46.0156 128.828V136.984ZM31.5781 132.438C31.2031 132.031 31.0156 131.547 31.0156 130.984C31.0156 130.422 31.2031 129.953 31.5781 129.578C31.9531 129.203 32.4219 129.016 32.9844 129.016C33.5469 129.016 34.0156 129.203 34.3906 129.578C34.7969 129.953 35 130.422 35 130.984C35 131.547 34.7969 132.031 34.3906 132.438C34.0156 132.812 33.5469 133 32.9844 133C32.4219 133 31.9531 132.812 31.5781 132.438Z" fill="#03A9F4"/>
|
|
||||||
<path d="M84 4C84 1.79086 85.7909 0 88 0H100C102.209 0 104 1.79086 104 4V4C104 6.20914 102.209 8 100 8H88C85.7909 8 84 6.20914 84 4V4Z" fill="black" fill-opacity="0.32"/>
|
|
||||||
<path d="M84 20C84 15.5817 87.5817 12 92 12H152C156.418 12 160 15.5817 160 20V36C160 40.4183 156.418 44 152 44H92C87.5817 44 84 40.4183 84 36V20Z" fill="white"/>
|
|
||||||
<path d="M92 12.5H152C156.142 12.5 159.5 15.8579 159.5 20V36C159.5 40.1421 156.142 43.5 152 43.5H92C87.8579 43.5 84.5 40.1421 84.5 36V20C84.5 15.8579 87.8579 12.5 92 12.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<path d="M131.984 30.0156C131.984 30.7656 131.797 31.4531 131.422 32.0781C131.047 32.6719 130.562 33.1406 129.969 33.4844C129.844 34.2031 129.5 34.8125 128.938 35.3125C128.406 35.7812 127.766 36.0156 127.016 36.0156C126.359 36.0156 125.766 35.8281 125.234 35.4531C124.734 35.0781 124.391 34.5938 124.203 34H119.797C119.609 34.5938 119.25 35.0781 118.719 35.4531C118.219 35.8281 117.641 36.0156 116.984 36.0156C116.234 36.0156 115.578 35.7812 115.016 35.3125C114.484 34.8125 114.156 34.2031 114.031 33.4844C113.438 33.1406 112.953 32.6719 112.578 32.0781C112.203 31.4531 112.016 30.7656 112.016 30.0156C112.016 29.1094 112.266 28.3125 112.766 27.625C113.297 26.9375 113.969 26.4688 114.781 26.2188L113 24.3906L112.719 24.7188C112.5 24.9062 112.25 25 111.969 25C111.719 25 111.5 24.9062 111.312 24.7188C111.094 24.5312 110.984 24.2969 110.984 24.0156C110.984 23.7344 111.094 23.5 111.312 23.3125L113.281 21.2969C113.469 21.1094 113.703 21.0156 113.984 21.0156C114.266 21.0156 114.5 21.1094 114.688 21.2969C114.906 21.4844 115.016 21.7188 115.016 22C115.016 22.2812 114.906 22.5156 114.688 22.7031L114.406 22.9844L115.812 24.3906L116.609 22.0469C116.797 21.4219 117.156 20.9219 117.688 20.5469C118.219 20.1719 118.797 19.9844 119.422 19.9844H124.578C125.203 19.9844 125.781 20.1719 126.312 20.5469C126.844 20.9219 127.203 21.4219 127.391 22.0469L128.75 26.0781C129.375 26.2031 129.922 26.4531 130.391 26.8281C130.891 27.2031 131.281 27.6719 131.562 28.2344C131.844 28.7656 131.984 29.3594 131.984 30.0156ZM116.984 34C117.266 34 117.5 33.9062 117.688 33.7188C117.906 33.5 118.016 33.2656 118.016 33.0156C118.016 32.7344 117.906 32.5 117.688 32.3125C117.5 32.0938 117.266 31.9844 116.984 31.9844C116.734 31.9844 116.5 32.0938 116.281 32.3125C116.094 32.5 116 32.7344 116 33.0156C116 33.2656 116.094 33.5 116.281 33.7188C116.5 33.9062 116.734 34 116.984 34ZM121.016 25.9844V22H119.422C118.953 22 118.641 22.2344 118.484 22.7031L117.406 25.9844H121.016ZM122.984 22V25.9844H126.594L125.516 22.7031C125.359 22.2344 125.047 22 124.578 22H122.984ZM127.016 34C127.266 34 127.484 33.9062 127.672 33.7188C127.891 33.5 128 33.2656 128 33.0156C128 32.7344 127.891 32.5 127.672 32.3125C127.484 32.0938 127.266 31.9844 127.016 31.9844C126.734 31.9844 126.484 32.0938 126.266 32.3125C126.078 32.5 125.984 32.7344 125.984 33.0156C125.984 33.2656 126.078 33.5 126.266 33.7188C126.484 33.9062 126.734 34 127.016 34Z" fill="#03A9F4"/>
|
|
||||||
<path d="M84 56C84 51.5817 87.5817 48 92 48H152C156.418 48 160 51.5817 160 56V72C160 76.4183 156.418 80 152 80H92C87.5817 80 84 76.4183 84 72V56Z" fill="white"/>
|
|
||||||
<path d="M92 48.5H152C156.142 48.5 159.5 51.8579 159.5 56V72C159.5 76.1421 156.142 79.5 152 79.5H92C87.8579 79.5 84.5 76.1421 84.5 72V56C84.5 51.8579 87.8579 48.5 92 48.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<path d="M128.984 58.9844C130.078 58.9844 131.016 59.3906 131.797 60.2031C132.609 60.9844 133.016 61.9219 133.016 63.0156V72.0156H131V69.0156H113V72.0156H110.984V57.0156H113V66.0156H121.016V58.9844H128.984ZM119.094 64.0938C118.5 64.6875 117.797 64.9844 116.984 64.9844C116.172 64.9844 115.469 64.6875 114.875 64.0938C114.281 63.5 113.984 62.7969 113.984 61.9844C113.984 61.1719 114.281 60.4688 114.875 59.875C115.469 59.2812 116.172 58.9844 116.984 58.9844C117.797 58.9844 118.5 59.2812 119.094 59.875C119.688 60.4688 119.984 61.1719 119.984 61.9844C119.984 62.7969 119.688 63.5 119.094 64.0938Z" fill="#03A9F4"/>
|
|
||||||
<path d="M84 92C84 87.5817 87.5817 84 92 84H152C156.418 84 160 87.5817 160 92V108C160 112.418 156.418 116 152 116H92C87.5817 116 84 112.418 84 108V92Z" fill="white"/>
|
|
||||||
<path d="M92 84.5H152C156.142 84.5 159.5 87.8579 159.5 92V108C159.5 112.142 156.142 115.5 152 115.5H92C87.8579 115.5 84.5 112.142 84.5 108V92C84.5 87.8579 87.8579 84.5 92 84.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<path d="M112.016 94H131.984V106H130.016V103.984H125.984V106H124.016V96.0156H113.984V106H112.016V94ZM130.016 96.0156H125.984V97.9844H130.016V96.0156ZM125.984 102.016H130.016V100H125.984V102.016Z" fill="#03A9F4"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_3969_50764">
|
|
||||||
<rect width="160" height="160" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 11 KiB |
@@ -1,76 +0,0 @@
|
|||||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_4744_39984)">
|
|
||||||
<path d="M0 6C0 2.68629 2.68629 0 6 0H28C31.3137 0 34 2.68629 34 6C34 9.31371 31.3137 12 28 12H6C2.68629 12 0 9.31371 0 6Z" fill="black" fill-opacity="0.32"/>
|
|
||||||
<path d="M0 28C0 23.5817 3.58172 20 8 20H42.6667C47.0849 20 50.6667 23.5817 50.6667 28V36C50.6667 40.4183 47.0849 44 42.6667 44H8.00001C3.58173 44 0 40.4183 0 36V28Z" fill="white"/>
|
|
||||||
<path d="M8 20.5H42.667C46.809 20.5002 50.167 23.858 50.167 28V36C50.167 40.142 46.809 43.4998 42.667 43.5H8C3.85787 43.5 0.5 40.1421 0.5 36V28C0.5 23.8579 3.85786 20.5 8 20.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<rect x="6" y="26" width="12" height="12" rx="6" fill="black" fill-opacity="0.12"/>
|
|
||||||
<path d="M24 31C24 29.3431 25.3431 28 27 28H39.6667C41.3235 28 42.6667 29.3431 42.6667 31V33C42.6667 34.6569 41.3235 36 39.6667 36H27C25.3431 36 24 34.6569 24 33V31Z" fill="black" fill-opacity="0.12"/>
|
|
||||||
<path d="M54.6667 28C54.6667 23.5817 58.2484 20 62.6667 20H97.3333C101.752 20 105.333 23.5817 105.333 28V36C105.333 40.4183 101.752 44 97.3334 44H62.6667C58.2484 44 54.6667 40.4183 54.6667 36V28Z" fill="white"/>
|
|
||||||
<path d="M62.6667 20.5H97.3337C101.476 20.5002 104.834 23.858 104.834 28V36C104.834 40.142 101.476 43.4998 97.3337 43.5H62.6667C58.5246 43.5 55.1667 40.1421 55.1667 36V28C55.1667 23.8579 58.5246 20.5 62.6667 20.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<rect x="60.6667" y="26" width="12" height="12" rx="6" fill="black" fill-opacity="0.12"/>
|
|
||||||
<path d="M78.6667 31C78.6667 29.3431 80.0098 28 81.6667 28H94.3334C95.9902 28 97.3334 29.3431 97.3334 31V33C97.3334 34.6569 95.9902 36 94.3334 36H81.6667C80.0098 36 78.6667 34.6569 78.6667 33V31Z" fill="black" fill-opacity="0.12"/>
|
|
||||||
<path d="M109.333 28C109.333 23.5817 112.915 20 117.333 20H152C156.418 20 160 23.5817 160 28V36C160 40.4183 156.418 44 152 44H117.333C112.915 44 109.333 40.4183 109.333 36V28Z" fill="white"/>
|
|
||||||
<path d="M117.333 20.5H152C156.142 20.5002 159.5 23.858 159.5 28V36C159.5 40.142 156.142 43.4998 152 43.5H117.333C113.191 43.5 109.833 40.1421 109.833 36V28C109.833 23.8579 113.191 20.5 117.333 20.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<rect x="115.333" y="26" width="12" height="12" rx="6" fill="black" fill-opacity="0.12"/>
|
|
||||||
<path d="M133.333 31C133.333 29.3431 134.676 28 136.333 28H149C150.657 28 152 29.3431 152 31V33C152 34.6569 150.657 36 149 36H136.333C134.676 36 133.333 34.6569 133.333 33V31Z" fill="black" fill-opacity="0.12"/>
|
|
||||||
<path d="M0 56C0 53.7909 1.79086 52 4 52H29C31.2091 52 33 53.7909 33 56C33 58.2091 31.2091 60 29 60H4C1.79086 60 0 58.2091 0 56Z" fill="black" fill-opacity="0.32"/>
|
|
||||||
<path d="M0 72C0 67.5817 3.58172 64 8 64H29C33.4183 64 37 67.5817 37 72V96C37 100.418 33.4183 104 29 104H8C3.58172 104 0 100.418 0 96V72Z" fill="white"/>
|
|
||||||
<path d="M8 64.5H29C33.1421 64.5 36.5 67.8579 36.5 72V96C36.5 100.142 33.1421 103.5 29 103.5H8C3.85786 103.5 0.5 100.142 0.5 96V72C0.5 67.8579 3.85786 64.5 8 64.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<mask id="mask0_4744_39984" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="6" y="72" width="25" height="24">
|
|
||||||
<path d="M18.5 74C16.6435 74 14.863 74.7375 13.5503 76.0503C12.2375 77.363 11.5 79.1435 11.5 81C11.5 83.38 12.69 85.47 14.5 86.74V89C14.5 89.2652 14.6054 89.5196 14.7929 89.7071C14.9804 89.8946 15.2348 90 15.5 90H21.5C21.7652 90 22.0196 89.8946 22.2071 89.7071C22.3946 89.5196 22.5 89.2652 22.5 89V86.74C24.31 85.47 25.5 83.38 25.5 81C25.5 79.1435 24.7625 77.363 23.4497 76.0503C22.137 74.7375 20.3565 74 18.5 74ZM15.5 93C15.5 93.2652 15.6054 93.5196 15.7929 93.7071C15.9804 93.8946 16.2348 94 16.5 94H20.5C20.7652 94 21.0196 93.8946 21.2071 93.7071C21.3946 93.5196 21.5 93.2652 21.5 93V92H15.5V93Z" fill="black"/>
|
|
||||||
</mask>
|
|
||||||
<g mask="url(#mask0_4744_39984)">
|
|
||||||
<rect x="6.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
|
||||||
</g>
|
|
||||||
<path d="M41 72C41 67.5817 44.5817 64 49 64H70C74.4183 64 78 67.5817 78 72V96C78 100.418 74.4183 104 70 104H49C44.5817 104 41 100.418 41 96V72Z" fill="white"/>
|
|
||||||
<path d="M49 64.5H70C74.1421 64.5 77.5 67.8579 77.5 72V96C77.5 100.142 74.1421 103.5 70 103.5H49C44.8579 103.5 41.5 100.142 41.5 96V72C41.5 67.8579 44.8579 64.5 49 64.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<mask id="mask1_4744_39984" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="47" y="72" width="25" height="24">
|
|
||||||
<path d="M66.5 80C67.61 80 68.5 80.9 68.5 82V88.76C69.11 89.31 69.5 90.11 69.5 91C69.5 92.66 68.16 94 66.5 94C64.84 94 63.5 92.66 63.5 91C63.5 90.11 63.89 89.31 64.5 88.76V82C64.5 80.9 65.4 80 66.5 80ZM66.5 81C65.95 81 65.5 81.45 65.5 82V83H67.5V82C67.5 81.45 67.05 81 66.5 81ZM52.5 92V84H49.5L59.5 75L63.9 78.96C63.04 79.69 62.5 80.78 62.5 82V88C61.87 88.83 61.5 89.87 61.5 91L61.6 92H52.5Z" fill="black"/>
|
|
||||||
</mask>
|
|
||||||
<g mask="url(#mask1_4744_39984)">
|
|
||||||
<rect x="47.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
|
||||||
</g>
|
|
||||||
<path d="M82 72C82 67.5817 85.5817 64 90 64H111C115.418 64 119 67.5817 119 72V96C119 100.418 115.418 104 111 104H90C85.5817 104 82 100.418 82 96V72Z" fill="white"/>
|
|
||||||
<path d="M90 64.5H111C115.142 64.5 118.5 67.8579 118.5 72V96C118.5 100.142 115.142 103.5 111 103.5H90C85.8579 103.5 82.5 100.142 82.5 96V72C82.5 67.8579 85.8579 64.5 90 64.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<mask id="mask2_4744_39984" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="88" y="72" width="25" height="24">
|
|
||||||
<path d="M100.5 84H107.5C106.97 88.11 104.22 91.78 100.5 92.92V84H93.5V78.3L100.5 75.19M100.5 73L91.5 77V83C91.5 88.55 95.34 93.73 100.5 95C105.66 93.73 109.5 88.55 109.5 83V77L100.5 73Z" fill="black"/>
|
|
||||||
</mask>
|
|
||||||
<g mask="url(#mask2_4744_39984)">
|
|
||||||
<rect x="88.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
|
||||||
</g>
|
|
||||||
<path d="M123 72C123 67.5817 126.582 64 131 64H152C156.418 64 160 67.5817 160 72V96C160 100.418 156.418 104 152 104H131C126.582 104 123 100.418 123 96V72Z" fill="white"/>
|
|
||||||
<path d="M131 64.5H152C156.142 64.5 159.5 67.8579 159.5 72V96C159.5 100.142 156.142 103.5 152 103.5H131C126.858 103.5 123.5 100.142 123.5 96V72C123.5 67.8579 126.858 64.5 131 64.5Z" stroke="black" stroke-opacity="0.12"/>
|
|
||||||
<mask id="mask3_4744_39984" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="129" y="72" width="25" height="24">
|
|
||||||
<path d="M145.5 84C145.5 83.4696 145.711 82.9609 146.086 82.5858C146.461 82.2107 146.97 82 147.5 82C148.03 82 148.539 82.2107 148.914 82.5858C149.289 82.9609 149.5 83.4696 149.5 84C149.5 84.5304 149.289 85.0391 148.914 85.4142C148.539 85.7893 148.03 86 147.5 86C146.97 86 146.461 85.7893 146.086 85.4142C145.711 85.0391 145.5 84.5304 145.5 84ZM139.5 84C139.5 83.4696 139.711 82.9609 140.086 82.5858C140.461 82.2107 140.97 82 141.5 82C142.03 82 142.539 82.2107 142.914 82.5858C143.289 82.9609 143.5 83.4696 143.5 84C143.5 84.5304 143.289 85.0391 142.914 85.4142C142.539 85.7893 142.03 86 141.5 86C140.97 86 140.461 85.7893 140.086 85.4142C139.711 85.0391 139.5 84.5304 139.5 84ZM133.5 84C133.5 83.4696 133.711 82.9609 134.086 82.5858C134.461 82.2107 134.97 82 135.5 82C136.03 82 136.539 82.2107 136.914 82.5858C137.289 82.9609 137.5 83.4696 137.5 84C137.5 84.5304 137.289 85.0391 136.914 85.4142C136.539 85.7893 136.03 86 135.5 86C134.97 86 134.461 85.7893 134.086 85.4142C133.711 85.0391 133.5 84.5304 133.5 84Z" fill="black"/>
|
|
||||||
</mask>
|
|
||||||
<g mask="url(#mask3_4744_39984)">
|
|
||||||
<rect x="129.5" y="72" width="24" height="24" fill="#18BCF2"/>
|
|
||||||
</g>
|
|
||||||
<path d="M0 116C0 113.791 1.79086 112 4 112H29C31.2091 112 33 113.791 33 116C33 118.209 31.2091 120 29 120H4C1.79086 120 0 118.209 0 116Z" fill="black" fill-opacity="0.32"/>
|
|
||||||
<path d="M0 132C0 127.582 3.58172 124 8 124H70C74.4183 124 78 127.582 78 132V160H0V132Z" fill="url(#paint0_linear_4744_39984)"/>
|
|
||||||
<path d="M8 124.5H70C74.1421 124.5 77.5 127.858 77.5 132V159.5H0.5V132C0.5 127.858 3.85786 124.5 8 124.5Z" stroke="url(#paint1_linear_4744_39984)" stroke-opacity="0.12"/>
|
|
||||||
<path d="M82 132C82 127.582 85.5817 124 90 124H152C156.418 124 160 127.582 160 132V160H82V132Z" fill="url(#paint2_linear_4744_39984)"/>
|
|
||||||
<path d="M90 124.5H152C156.142 124.5 159.5 127.858 159.5 132V159.5H82.5V132C82.5 127.858 85.8579 124.5 90 124.5Z" stroke="url(#paint3_linear_4744_39984)" stroke-opacity="0.12"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="paint0_linear_4744_39984" x1="39" y1="124" x2="39" y2="160" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop offset="0.5" stop-color="white"/>
|
|
||||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="paint1_linear_4744_39984" x1="39" y1="124" x2="39" y2="160" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop offset="0.5" stop-opacity="0.12"/>
|
|
||||||
<stop offset="1" stop-opacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="paint2_linear_4744_39984" x1="121" y1="124" x2="121" y2="160" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop offset="0.5" stop-color="white"/>
|
|
||||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="paint3_linear_4744_39984" x1="121" y1="124" x2="121" y2="160" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop offset="0.5" stop-opacity="0.12"/>
|
|
||||||
<stop offset="1" stop-opacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
<clipPath id="clip0_4744_39984">
|
|
||||||
<rect width="160" height="160" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 8.9 KiB |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20251029.0"
|
version = "20251203.3"
|
||||||
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,5 +1,6 @@
|
|||||||
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 {
|
||||||
@@ -30,7 +31,11 @@ export function askWrite() {
|
|||||||
export function saveTokens(tokens: AuthData | null) {
|
export function saveTokens(tokens: AuthData | null) {
|
||||||
tokenCache.tokens = tokens;
|
tokenCache.tokens = tokens;
|
||||||
|
|
||||||
if (!tokenCache.writeEnabled && extractSearchParam("storeToken") === "true") {
|
if (
|
||||||
|
!tokenCache.writeEnabled &&
|
||||||
|
(extractSearchParam("storeToken") === "true" ||
|
||||||
|
hassUrl !== `${location.protocol}//${location.host}`)
|
||||||
|
) {
|
||||||
tokenCache.writeEnabled = true;
|
tokenCache.writeEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,64 +1,16 @@
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { theme2hex } from "./convert-color";
|
import { theme2hex } from "./convert-color";
|
||||||
|
|
||||||
export const COLORS = [
|
// Total number of colors defined in CSS variables (--color-1 through --color-54)
|
||||||
"#4269d0",
|
export const COLORS_COUNT = 54;
|
||||||
"#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(index: number) {
|
export function getColorByIndex(
|
||||||
return COLORS[index % COLORS.length];
|
index: number,
|
||||||
|
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(
|
||||||
@@ -68,15 +20,19 @@ 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);
|
getColorByIndex(index, style);
|
||||||
return theme2hex(themeColor);
|
return theme2hex(themeColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAllGraphColors = memoizeOne(
|
export const getAllGraphColors = memoizeOne(
|
||||||
(style: CSSStyleDeclaration) =>
|
(style: CSSStyleDeclaration) =>
|
||||||
COLORS.map((_color, index) => getGraphColorByIndex(index, style)),
|
Array.from({ length: COLORS_COUNT }, (_, index) =>
|
||||||
|
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,6 +593,7 @@ 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,6 +167,7 @@ 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)
|
||||||
@@ -279,6 +280,7 @@ 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,6 +373,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||||||
itemName: 3,
|
itemName: 3,
|
||||||
},
|
},
|
||||||
renderItem: this._renderItem,
|
renderItem: this._renderItem,
|
||||||
|
progressive: 0,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ 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";
|
||||||
@@ -37,11 +36,7 @@ export class HaAreasDisplayEditor extends LitElement {
|
|||||||
public showNavigationButton = false;
|
public showNavigationButton = false;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const compare = areaCompare(this.hass.areas);
|
const areas = Object.values(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,7 +7,6 @@ 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";
|
||||||
@@ -131,11 +130,8 @@ 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 compare = areaCompare(hassAreas);
|
const areas = Object.values(hassAreas);
|
||||||
|
|
||||||
const areas = Object.values(hassAreas).sort((areaA, areaB) =>
|
|
||||||
compare(areaA.area_id, areaB.area_id)
|
|
||||||
);
|
|
||||||
const groupedItems: Record<string, DisplayItem[]> = areas.reduce(
|
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,6 +659,7 @@ 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,7 +21,8 @@ export class HaBottomSheet extends LitElement {
|
|||||||
|
|
||||||
private _isDragging = false;
|
private _isDragging = false;
|
||||||
|
|
||||||
private _handleAfterHide() {
|
private _handleAfterHide(afterHideEvent: Event) {
|
||||||
|
afterHideEvent.stopPropagation();
|
||||||
this.open = false;
|
this.open = false;
|
||||||
const ev = new Event("closed", {
|
const ev = new Event("closed", {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
|
|||||||
@@ -651,6 +651,18 @@ export class HaCodeEditor extends ReactiveElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Properties that should never suggest entities
|
||||||
|
const negativeProperties = ["action"];
|
||||||
|
|
||||||
|
// Create regex pattern for negative properties
|
||||||
|
const negativePropertyPattern = negativeProperties.join("|");
|
||||||
|
const negativeEntityFieldRegex = new RegExp(
|
||||||
|
`^\\s*(-\\s+)?(${negativePropertyPattern}):\\s*`
|
||||||
|
);
|
||||||
|
if (lineText.match(negativeEntityFieldRegex)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Original entity completion logic for non-YAML or when not in entity_id field
|
// Original entity completion logic for non-YAML or when not in entity_id field
|
||||||
|
|||||||
84
src/components/ha-condition-icon.ts
Normal file
84
src/components/ha-condition-icon.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import {
|
||||||
|
mdiAmpersand,
|
||||||
|
mdiClockOutline,
|
||||||
|
mdiCodeBraces,
|
||||||
|
mdiDevices,
|
||||||
|
mdiGateOr,
|
||||||
|
mdiIdentifier,
|
||||||
|
mdiMapMarkerRadius,
|
||||||
|
mdiNotEqualVariant,
|
||||||
|
mdiNumeric,
|
||||||
|
mdiStateMachine,
|
||||||
|
mdiWeatherSunny,
|
||||||
|
} from "@mdi/js";
|
||||||
|
import { html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { until } from "lit/directives/until";
|
||||||
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
|
import { conditionIcon, FALLBACK_DOMAIN_ICONS } from "../data/icons";
|
||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
import "./ha-icon";
|
||||||
|
import "./ha-svg-icon";
|
||||||
|
|
||||||
|
export const CONDITION_ICONS = {
|
||||||
|
device: mdiDevices,
|
||||||
|
and: mdiAmpersand,
|
||||||
|
or: mdiGateOr,
|
||||||
|
not: mdiNotEqualVariant,
|
||||||
|
state: mdiStateMachine,
|
||||||
|
numeric_state: mdiNumeric,
|
||||||
|
sun: mdiWeatherSunny,
|
||||||
|
template: mdiCodeBraces,
|
||||||
|
time: mdiClockOutline,
|
||||||
|
trigger: mdiIdentifier,
|
||||||
|
zone: mdiMapMarkerRadius,
|
||||||
|
};
|
||||||
|
|
||||||
|
@customElement("ha-condition-icon")
|
||||||
|
export class HaConditionIcon extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public condition?: string;
|
||||||
|
|
||||||
|
@property() public icon?: string;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (this.icon) {
|
||||||
|
return html`<ha-icon .icon=${this.icon}></ha-icon>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.condition) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.hass) {
|
||||||
|
return this._renderFallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
const icon = conditionIcon(this.hass, this.condition).then((icn) => {
|
||||||
|
if (icn) {
|
||||||
|
return html`<ha-icon .icon=${icn}></ha-icon>`;
|
||||||
|
}
|
||||||
|
return this._renderFallback();
|
||||||
|
});
|
||||||
|
|
||||||
|
return html`${until(icon)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderFallback() {
|
||||||
|
const domain = computeDomain(this.condition!);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${CONDITION_ICONS[this.condition!] ||
|
||||||
|
FALLBACK_DOMAIN_ICONS[domain]}
|
||||||
|
></ha-svg-icon>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-condition-icon": HaConditionIcon;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -202,6 +202,7 @@ 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);
|
||||||
@@ -211,7 +212,6 @@ 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;
|
||||||
|
|||||||
@@ -75,11 +75,15 @@ export class HaDialogHeader extends LitElement {
|
|||||||
font-size: var(--ha-font-size-xl);
|
font-size: var(--ha-font-size-xl);
|
||||||
line-height: var(--ha-line-height-condensed);
|
line-height: var(--ha-line-height-condensed);
|
||||||
font-weight: var(--ha-font-weight-medium);
|
font-weight: var(--ha-font-weight-medium);
|
||||||
|
color: var(--ha-dialog-header-title-color, var(--primary-text-color));
|
||||||
}
|
}
|
||||||
.header-subtitle {
|
.header-subtitle {
|
||||||
font-size: var(--ha-font-size-m);
|
font-size: var(--ha-font-size-m);
|
||||||
line-height: var(--ha-line-height-normal);
|
line-height: var(--ha-line-height-normal);
|
||||||
color: var(--secondary-text-color);
|
color: var(
|
||||||
|
--ha-dialog-header-subtitle-color,
|
||||||
|
var(--secondary-text-color)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@media all and (min-width: 450px) and (min-height: 500px) {
|
@media all and (min-width: 450px) and (min-height: 500px) {
|
||||||
.header-bar {
|
.header-bar {
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ export class HaExpansionPanel extends LitElement {
|
|||||||
::slotted([slot="header"]) {
|
::slotted([slot="header"]) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
|
|||||||
@@ -167,30 +167,33 @@ 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;
|
||||||
value.push(labelId);
|
if (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: value.length ? value : undefined,
|
||||||
items: undefined,
|
items: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
mdiHomeFloor3,
|
mdiHomeFloor3,
|
||||||
mdiHomeFloorNegative1,
|
mdiHomeFloorNegative1,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { LitElement, html } from "lit";
|
import { LitElement, html, nothing } 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,6 +56,9 @@ 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) {
|
private _hidePicker(ev: Event) {
|
||||||
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,7 +344,10 @@ 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: max(var(--body-width), 250px);
|
max-width: var(
|
||||||
|
--ha-generic-picker-max-width,
|
||||||
|
max(var(--body-width), 250px)
|
||||||
|
);
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ export class HaIconOverflowMenu extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
cursor: initial;
|
||||||
}
|
}
|
||||||
div[role="separator"] {
|
div[role="separator"] {
|
||||||
border-right: 1px solid var(--divider-color);
|
border-right: 1px solid var(--divider-color);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface DisplayItem {
|
|||||||
label: string;
|
label: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
disableSorting?: boolean;
|
disableSorting?: boolean;
|
||||||
|
disableHiding?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DisplayValue {
|
export interface DisplayValue {
|
||||||
@@ -101,6 +102,7 @@ export class HaItemDisplayEditor extends LitElement {
|
|||||||
icon,
|
icon,
|
||||||
iconPath,
|
iconPath,
|
||||||
disableSorting,
|
disableSorting,
|
||||||
|
disableHiding,
|
||||||
} = item;
|
} = item;
|
||||||
return html`
|
return html`
|
||||||
<ha-md-list-item
|
<ha-md-list-item
|
||||||
@@ -155,7 +157,8 @@ export class HaItemDisplayEditor extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
<ha-icon-button
|
${!isVisible || !disableHiding
|
||||||
|
? html`<ha-icon-button
|
||||||
.path=${isVisible ? mdiEye : mdiEyeOff}
|
.path=${isVisible ? mdiEye : mdiEyeOff}
|
||||||
slot="end"
|
slot="end"
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -166,7 +169,9 @@ export class HaItemDisplayEditor extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.value=${value}
|
.value=${value}
|
||||||
@click=${this._toggle}
|
@click=${this._toggle}
|
||||||
></ha-icon-button>
|
.disabled=${disableHiding || false}
|
||||||
|
></ha-icon-button>`
|
||||||
|
: nothing}
|
||||||
${isVisible && !disableSorting
|
${isVisible && !disableSorting
|
||||||
? html`
|
? html`
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
|
|||||||
@@ -154,7 +154,10 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this._getLabelsMemoized(
|
return this._getLabelsMemoized(
|
||||||
this.hass,
|
this.hass.states,
|
||||||
|
this.hass.areas,
|
||||||
|
this.hass.devices,
|
||||||
|
this.hass.entities,
|
||||||
this._labels,
|
this._labels,
|
||||||
this.includeDomains,
|
this.includeDomains,
|
||||||
this.excludeDomains,
|
this.excludeDomains,
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ 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,6 +73,8 @@ 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;
|
||||||
|
|
||||||
@@ -135,6 +137,7 @@ 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(markdownCache.get(key)!, this.renderRoot);
|
render(h(unsafeHTML(markdownCache.get(key))), this.renderRoot);
|
||||||
this._resize();
|
this._resize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,10 +99,7 @@ class HaMarkdownElement extends ReactiveElement {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
render(
|
render(h(unsafeHTML(elements.join(""))), this.renderRoot);
|
||||||
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,14 +71,9 @@ 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: rgba(10, 10, 10, 0.15);
|
background-color: var(--markdown-image-background-color);
|
||||||
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;
|
||||||
@@ -86,10 +81,8 @@ export class HaMarkdown extends LitElement {
|
|||||||
p:first-child > img:last-child {
|
p:first-child > img:last-child {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
ol,
|
ha-markdown-element > :is(ol, ul) {
|
||||||
ul {
|
padding-inline-start: var(--markdown-list-indent, revert);
|
||||||
list-style-position: inside;
|
|
||||||
padding-inline-start: 0;
|
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
&:has(input[type="checkbox"]) {
|
&:has(input[type="checkbox"]) {
|
||||||
@@ -139,18 +132,34 @@ 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: collapse;
|
border-collapse: var(--markdown-table-border-collapse, collapse);
|
||||||
display: block;
|
}
|
||||||
overflow-x: auto;
|
div:has(> table) {
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
th {
|
th {
|
||||||
text-align: start;
|
text-align: var(--markdown-table-text-align, start);
|
||||||
}
|
}
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
border: 1px solid var(--markdown-table-border-color, transparent);
|
border-width: var(--markdown-table-border-width, 1px);
|
||||||
padding: 0.25em 0.5em;
|
border-style: var(--markdown-table-border-style, solid);
|
||||||
|
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);
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ export class HaMdMenuItem extends MenuItemEl {
|
|||||||
::slotted([slot="headline"]) {
|
::slotted([slot="headline"]) {
|
||||||
text-wrap: nowrap;
|
text-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
:host([disabled]) {
|
||||||
|
opacity: 1;
|
||||||
|
--md-menu-item-label-text-color: var(--disabled-text-color);
|
||||||
|
--md-menu-item-leading-icon-color: var(--disabled-text-color);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
|||||||
import { titleCase } from "../common/string/title-case";
|
import { titleCase } from "../common/string/title-case";
|
||||||
import { fetchConfig } from "../data/lovelace/config/types";
|
import { fetchConfig } from "../data/lovelace/config/types";
|
||||||
import type { LovelaceViewRawConfig } from "../data/lovelace/config/view";
|
import type { LovelaceViewRawConfig } from "../data/lovelace/config/view";
|
||||||
import { getDefaultPanelUrlPath } from "../data/panel";
|
import { getPanelIcon, getPanelTitle } from "../data/panel";
|
||||||
import type { HomeAssistant, PanelInfo, ValueChangedEvent } from "../types";
|
import type { HomeAssistant, PanelInfo, ValueChangedEvent } from "../types";
|
||||||
import "./ha-combo-box";
|
import "./ha-combo-box";
|
||||||
import type { HaComboBox } from "./ha-combo-box";
|
import type { HaComboBox } from "./ha-combo-box";
|
||||||
@@ -43,13 +43,8 @@ const createViewNavigationItem = (
|
|||||||
|
|
||||||
const createPanelNavigationItem = (hass: HomeAssistant, panel: PanelInfo) => ({
|
const createPanelNavigationItem = (hass: HomeAssistant, panel: PanelInfo) => ({
|
||||||
path: `/${panel.url_path}`,
|
path: `/${panel.url_path}`,
|
||||||
icon: panel.icon ?? "mdi:view-dashboard",
|
icon: getPanelIcon(panel) || "mdi:view-dashboard",
|
||||||
title:
|
title: getPanelTitle(hass, panel) || "",
|
||||||
panel.url_path === getDefaultPanelUrlPath(hass)
|
|
||||||
? hass.localize("panel.states")
|
|
||||||
: hass.localize(`panel.${panel.title}`) ||
|
|
||||||
panel.title ||
|
|
||||||
(panel.url_path ? titleCase(panel.url_path) : ""),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@customElement("ha-navigation-picker")
|
@customElement("ha-navigation-picker")
|
||||||
|
|||||||
28
src/components/ha-section-title.ts
Normal file
28
src/components/ha-section-title.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -465,10 +465,16 @@ 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;
|
||||||
|
|
||||||
@@ -537,7 +543,8 @@ 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
|
||||||
@@ -575,7 +582,8 @@ 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}
|
||||||
@@ -611,7 +619,10 @@ 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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,6 +669,10 @@ 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 ||
|
||||||
@@ -679,7 +694,8 @@ 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
|
||||||
@@ -689,7 +705,8 @@ 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,8 +92,14 @@ class HaServicePicker extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const descriptionPlaceholders =
|
||||||
|
this.hass.services[domain][service].description_placeholders;
|
||||||
|
|
||||||
const serviceName =
|
const serviceName =
|
||||||
localize(`component.${domain}.services.${service}.name`) ||
|
localize(
|
||||||
|
`component.${domain}.services.${service}.name`,
|
||||||
|
descriptionPlaceholders
|
||||||
|
) ||
|
||||||
services[domain][service].name ||
|
services[domain][service].name ||
|
||||||
service;
|
service;
|
||||||
|
|
||||||
@@ -163,16 +169,21 @@ 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 ||
|
||||||
"";
|
"";
|
||||||
|
|||||||
@@ -1,22 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
mdiBell,
|
mdiBell,
|
||||||
mdiCalendar,
|
|
||||||
mdiCellphoneCog,
|
mdiCellphoneCog,
|
||||||
mdiChartBox,
|
|
||||||
mdiClipboardList,
|
|
||||||
mdiCog,
|
mdiCog,
|
||||||
mdiFormatListBulletedType,
|
|
||||||
mdiHammer,
|
|
||||||
mdiLightningBolt,
|
|
||||||
mdiMenu,
|
mdiMenu,
|
||||||
mdiMenuOpen,
|
mdiMenuOpen,
|
||||||
mdiPlayBoxMultiple,
|
|
||||||
mdiTooltipAccount,
|
|
||||||
mdiViewDashboard,
|
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import {
|
import {
|
||||||
customElement,
|
customElement,
|
||||||
eventOptions,
|
eventOptions,
|
||||||
@@ -33,7 +24,14 @@ import { computeRTL } from "../common/util/compute_rtl";
|
|||||||
import { throttle } from "../common/util/throttle";
|
import { throttle } from "../common/util/throttle";
|
||||||
import { subscribeFrontendUserData } from "../data/frontend";
|
import { subscribeFrontendUserData } from "../data/frontend";
|
||||||
import type { ActionHandlerDetail } from "../data/lovelace/action_handler";
|
import type { ActionHandlerDetail } from "../data/lovelace/action_handler";
|
||||||
import { getDefaultPanelUrlPath } from "../data/panel";
|
import {
|
||||||
|
FIXED_PANELS,
|
||||||
|
getDefaultPanelUrlPath,
|
||||||
|
getPanelIcon,
|
||||||
|
getPanelIconPath,
|
||||||
|
getPanelTitle,
|
||||||
|
SHOW_AFTER_SPACER_PANELS,
|
||||||
|
} from "../data/panel";
|
||||||
import type { PersistentNotification } from "../data/persistent_notification";
|
import type { PersistentNotification } from "../data/persistent_notification";
|
||||||
import { subscribeNotifications } from "../data/persistent_notification";
|
import { subscribeNotifications } from "../data/persistent_notification";
|
||||||
import { subscribeRepairsIssueRegistry } from "../data/repairs";
|
import { subscribeRepairsIssueRegistry } from "../data/repairs";
|
||||||
@@ -54,8 +52,6 @@ import "./ha-spinner";
|
|||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
import "./user/ha-user-badge";
|
import "./user/ha-user-badge";
|
||||||
|
|
||||||
const SHOW_AFTER_SPACER = ["config", "developer-tools"];
|
|
||||||
|
|
||||||
const SUPPORT_SCROLL_IF_NEEDED = "scrollIntoViewIfNeeded" in document.body;
|
const SUPPORT_SCROLL_IF_NEEDED = "scrollIntoViewIfNeeded" in document.body;
|
||||||
|
|
||||||
const SORT_VALUE_URL_PATHS = {
|
const SORT_VALUE_URL_PATHS = {
|
||||||
@@ -67,18 +63,6 @@ const SORT_VALUE_URL_PATHS = {
|
|||||||
config: 11,
|
config: 11,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PANEL_ICONS = {
|
|
||||||
calendar: mdiCalendar,
|
|
||||||
"developer-tools": mdiHammer,
|
|
||||||
energy: mdiLightningBolt,
|
|
||||||
history: mdiChartBox,
|
|
||||||
logbook: mdiFormatListBulletedType,
|
|
||||||
lovelace: mdiViewDashboard,
|
|
||||||
map: mdiTooltipAccount,
|
|
||||||
"media-browser": mdiPlayBoxMultiple,
|
|
||||||
todo: mdiClipboardList,
|
|
||||||
};
|
|
||||||
|
|
||||||
const panelSorter = (
|
const panelSorter = (
|
||||||
reverseSort: string[],
|
reverseSort: string[],
|
||||||
defaultPanel: string,
|
defaultPanel: string,
|
||||||
@@ -155,16 +139,23 @@ export const computePanels = memoizeOne(
|
|||||||
const beforeSpacer: PanelInfo[] = [];
|
const beforeSpacer: PanelInfo[] = [];
|
||||||
const afterSpacer: PanelInfo[] = [];
|
const afterSpacer: PanelInfo[] = [];
|
||||||
|
|
||||||
Object.values(panels).forEach((panel) => {
|
const allPanels = Object.values(panels).filter(
|
||||||
|
(panel) => !FIXED_PANELS.includes(panel.url_path)
|
||||||
|
);
|
||||||
|
|
||||||
|
allPanels.forEach((panel) => {
|
||||||
|
const isDefaultPanel = panel.url_path === defaultPanel;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
!isDefaultPanel &&
|
||||||
|
(!panel.title ||
|
||||||
hiddenPanels.includes(panel.url_path) ||
|
hiddenPanels.includes(panel.url_path) ||
|
||||||
(!panel.title && panel.url_path !== defaultPanel) ||
|
|
||||||
(panel.default_visible === false &&
|
(panel.default_visible === false &&
|
||||||
!panelsOrder.includes(panel.url_path))
|
!panelsOrder.includes(panel.url_path)))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
(SHOW_AFTER_SPACER.includes(panel.url_path)
|
(SHOW_AFTER_SPACER_PANELS.includes(panel.url_path)
|
||||||
? afterSpacer
|
? afterSpacer
|
||||||
: beforeSpacer
|
: beforeSpacer
|
||||||
).push(panel);
|
).push(panel);
|
||||||
@@ -206,6 +197,8 @@ 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;
|
||||||
@@ -246,15 +239,24 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the supervisor as being part of configuration
|
const selectedPanel = this.hass.panelUrl;
|
||||||
const selectedPanel = this.route.path?.startsWith("/hassio/")
|
|
||||||
? "config"
|
|
||||||
: this.hass.panelUrl;
|
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return html`
|
return html`
|
||||||
@@ -397,9 +399,9 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
private _renderAllPanels(selectedPanel: string) {
|
private _renderAllPanels(selectedPanel: string) {
|
||||||
if (!this._panelOrder || !this._hiddenPanels) {
|
if (!this._panelOrder || !this._hiddenPanels) {
|
||||||
return html`
|
return html`
|
||||||
<ha-fade-in .delay=${500}
|
<ha-fade-in .delay=${500}>
|
||||||
><ha-spinner size="small"></ha-spinner
|
<ha-spinner size="small"></ha-spinner>
|
||||||
></ha-fade-in>
|
</ha-fade-in>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,61 +415,42 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
this.hass.locale
|
this.hass.locale
|
||||||
);
|
);
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
return html`
|
return html`
|
||||||
<ha-md-list
|
<ha-md-list
|
||||||
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}
|
||||||
>
|
>
|
||||||
${this._renderPanels(beforeSpacer, selectedPanel, defaultPanel)}
|
${this._renderPanels(beforeSpacer, selectedPanel)}
|
||||||
${this._renderSpacer()}
|
${this._renderSpacer()}
|
||||||
${this._renderPanels(afterSpacer, selectedPanel, defaultPanel)}
|
${this._renderPanels(afterSpacer, selectedPanel)}
|
||||||
${this._renderExternalConfiguration()}
|
${this.hass.user?.is_admin
|
||||||
|
? this._renderConfiguration(selectedPanel)
|
||||||
|
: this._renderExternalConfiguration()}
|
||||||
</ha-md-list>
|
</ha-md-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderPanels(
|
private _renderPanels(panels: PanelInfo[], selectedPanel: string) {
|
||||||
panels: PanelInfo[],
|
|
||||||
selectedPanel: string,
|
|
||||||
defaultPanel: string
|
|
||||||
) {
|
|
||||||
return panels.map((panel) =>
|
return panels.map((panel) =>
|
||||||
this._renderPanel(
|
this._renderPanel(panel, panel.url_path === selectedPanel)
|
||||||
panel.url_path,
|
|
||||||
panel.url_path === defaultPanel
|
|
||||||
? panel.title || this.hass.localize("panel.states")
|
|
||||||
: this.hass.localize(`panel.${panel.title}`) || panel.title,
|
|
||||||
panel.icon,
|
|
||||||
panel.url_path === defaultPanel && !panel.icon
|
|
||||||
? PANEL_ICONS.lovelace
|
|
||||||
: panel.url_path in PANEL_ICONS
|
|
||||||
? PANEL_ICONS[panel.url_path]
|
|
||||||
: undefined,
|
|
||||||
selectedPanel
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderPanel(
|
private _renderPanel(panel: PanelInfo, isSelected: boolean) {
|
||||||
urlPath: string,
|
const title = getPanelTitle(this.hass, panel);
|
||||||
title: string | null,
|
const urlPath = panel.url_path;
|
||||||
icon: string | null | undefined,
|
const icon = getPanelIcon(panel);
|
||||||
iconPath: string | null | undefined,
|
const iconPath = getPanelIconPath(panel);
|
||||||
selectedPanel: string
|
|
||||||
) {
|
return html`
|
||||||
return urlPath === "config"
|
|
||||||
? this._renderConfiguration(title, selectedPanel)
|
|
||||||
: html`
|
|
||||||
<ha-md-list-item
|
<ha-md-list-item
|
||||||
.href=${`/${urlPath}`}
|
.href=${`/${urlPath}`}
|
||||||
type="link"
|
type="link"
|
||||||
class=${classMap({
|
class=${classMap({ selected: isSelected })}
|
||||||
selected: selectedPanel === urlPath,
|
|
||||||
})}
|
|
||||||
@mouseenter=${this._itemMouseEnter}
|
@mouseenter=${this._itemMouseEnter}
|
||||||
@mouseleave=${this._itemMouseLeave}
|
@mouseleave=${this._itemMouseLeave}
|
||||||
>
|
>
|
||||||
@@ -487,10 +470,15 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
return html`<div class="spacer" disabled></div>`;
|
return html`<div class="spacer" disabled></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderConfiguration(title: string | null, selectedPanel: string) {
|
private _renderConfiguration(selectedPanel: string) {
|
||||||
|
if (!this.hass.user?.is_admin) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const isSelected =
|
||||||
|
selectedPanel === "config" || this.route.path?.startsWith("/hassio/");
|
||||||
return html`
|
return html`
|
||||||
<ha-md-list-item
|
<ha-md-list-item
|
||||||
class="configuration${selectedPanel === "config" ? " selected" : ""}"
|
class="configuration ${classMap({ selected: isSelected })}"
|
||||||
type="button"
|
type="button"
|
||||||
href="/config"
|
href="/config"
|
||||||
@mouseenter=${this._itemMouseEnter}
|
@mouseenter=${this._itemMouseEnter}
|
||||||
@@ -504,15 +492,17 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
${this._updatesCount + this._issuesCount}
|
${this._updatesCount + this._issuesCount}
|
||||||
</span>
|
</span>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
<span class="item-text" slot="headline">${title}</span>
|
<span class="item-text" slot="headline"
|
||||||
|
>${this.hass.localize("panel.config")}</span
|
||||||
|
>
|
||||||
${this.alwaysExpand && (this._updatesCount > 0 || this._issuesCount > 0)
|
${this.alwaysExpand && (this._updatesCount > 0 || this._issuesCount > 0)
|
||||||
? html`
|
? html`
|
||||||
<span class="badge" slot="end"
|
<span class="badge" slot="end"
|
||||||
>${this._updatesCount + this._issuesCount}</span
|
>${this._updatesCount + this._issuesCount}</span
|
||||||
>
|
>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -535,19 +525,20 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
? html`
|
? html`
|
||||||
<span class="badge" slot="start"> ${notificationCount} </span>
|
<span class="badge" slot="start"> ${notificationCount} </span>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
<span class="item-text" slot="headline"
|
<span class="item-text" slot="headline"
|
||||||
>${this.hass.localize("ui.notification_drawer.title")}</span
|
>${this.hass.localize("ui.notification_drawer.title")}</span
|
||||||
>
|
>
|
||||||
${this.alwaysExpand && notificationCount > 0
|
${this.alwaysExpand && notificationCount > 0
|
||||||
? html`<span class="badge" slot="end">${notificationCount}</span>`
|
? html`<span class="badge" slot="end">${notificationCount}</span>`
|
||||||
: ""}
|
: nothing}
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderUserItem(selectedPanel: string) {
|
private _renderUserItem(selectedPanel: string) {
|
||||||
const isRTL = computeRTL(this.hass);
|
const isRTL = computeRTL(this.hass);
|
||||||
|
const isSelected = selectedPanel === "profile";
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-md-list-item
|
<ha-md-list-item
|
||||||
@@ -555,7 +546,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
type="link"
|
type="link"
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
user: true,
|
user: true,
|
||||||
selected: selectedPanel === "profile",
|
selected: isSelected,
|
||||||
rtl: isRTL,
|
rtl: isRTL,
|
||||||
})}
|
})}
|
||||||
@mouseenter=${this._itemMouseEnter}
|
@mouseenter=${this._itemMouseEnter}
|
||||||
@@ -566,18 +557,18 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
.user=${this.hass.user}
|
.user=${this.hass.user}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
></ha-user-badge>
|
></ha-user-badge>
|
||||||
|
<span class="item-text" slot="headline">
|
||||||
<span class="item-text" slot="headline"
|
${this.hass.user ? this.hass.user.name : ""}
|
||||||
>${this.hass.user ? this.hass.user.name : ""}</span
|
</span>
|
||||||
>
|
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderExternalConfiguration() {
|
private _renderExternalConfiguration() {
|
||||||
return html`${!this.hass.user?.is_admin &&
|
if (!this.hass.auth.external?.config.hasSettingsScreen) {
|
||||||
this.hass.auth.external?.config.hasSettingsScreen
|
return nothing;
|
||||||
? html`
|
}
|
||||||
|
return html`
|
||||||
<ha-md-list-item
|
<ha-md-list-item
|
||||||
@click=${this._handleExternalAppConfiguration}
|
@click=${this._handleExternalAppConfiguration}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -589,8 +580,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
${this.hass.localize("ui.sidebar.external_app_configuration")}
|
${this.hass.localize("ui.sidebar.external_app_configuration")}
|
||||||
</span>
|
</span>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
`
|
`;
|
||||||
: ""}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleExternalAppConfiguration(ev: Event) {
|
private _handleExternalAppConfiguration(ev: Event) {
|
||||||
@@ -645,6 +635,14 @@ 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,
|
||||||
})
|
})
|
||||||
|
|||||||
184
src/components/ha-snowflakes.ts
Normal file
184
src/components/ha-snowflakes.ts
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
import { subscribeLabFeature } from "../data/labs";
|
||||||
|
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||||
|
|
||||||
|
interface Snowflake {
|
||||||
|
id: number;
|
||||||
|
left: number;
|
||||||
|
size: number;
|
||||||
|
duration: number;
|
||||||
|
delay: number;
|
||||||
|
rotation: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-snowflakes")
|
||||||
|
export class HaSnowflakes extends SubscribeMixin(LitElement) {
|
||||||
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
|
@state() private _enabled = false;
|
||||||
|
|
||||||
|
@state() private _snowflakes: Snowflake[] = [];
|
||||||
|
|
||||||
|
private _maxSnowflakes = 50;
|
||||||
|
|
||||||
|
public hassSubscribe() {
|
||||||
|
return [
|
||||||
|
subscribeLabFeature(
|
||||||
|
this.hass!.connection,
|
||||||
|
"frontend",
|
||||||
|
"winter_mode",
|
||||||
|
(feature) => {
|
||||||
|
this._enabled = feature.enabled;
|
||||||
|
}
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _generateSnowflakes() {
|
||||||
|
if (!this._enabled) {
|
||||||
|
this._snowflakes = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const snowflakes: Snowflake[] = [];
|
||||||
|
for (let i = 0; i < this._maxSnowflakes; i++) {
|
||||||
|
snowflakes.push({
|
||||||
|
id: i,
|
||||||
|
left: Math.random() * 100, // Random position from 0-100%
|
||||||
|
size: Math.random() * 12 + 8, // Random size between 8-20px
|
||||||
|
duration: Math.random() * 8 + 8, // Random duration between 8-16s
|
||||||
|
delay: Math.random() * 8, // Random delay between 0-8s
|
||||||
|
rotation: Math.random() * 720 - 360, // Random starting rotation -360 to 360deg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._snowflakes = snowflakes;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(changedProps: Map<string, unknown>) {
|
||||||
|
super.willUpdate(changedProps);
|
||||||
|
if (changedProps.has("_enabled")) {
|
||||||
|
this._generateSnowflakes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._enabled) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDark = this.hass?.themes.darkMode ?? false;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="snowflakes ${isDark ? "dark" : "light"}" aria-hidden="true">
|
||||||
|
${this._snowflakes.map(
|
||||||
|
(flake) => html`
|
||||||
|
<svg
|
||||||
|
class="snowflake ${this.narrow && flake.id >= 30
|
||||||
|
? "hide-narrow"
|
||||||
|
: ""}"
|
||||||
|
style="
|
||||||
|
left: ${flake.left}%;
|
||||||
|
width: ${flake.size}px;
|
||||||
|
height: ${flake.size}px;
|
||||||
|
animation-duration: ${flake.duration}s;
|
||||||
|
animation-delay: ${flake.delay}s;
|
||||||
|
--rotation: ${flake.rotation}deg;
|
||||||
|
"
|
||||||
|
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"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 9999;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snowflakes {
|
||||||
|
position: absolute;
|
||||||
|
top: -10%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 110%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snowflake {
|
||||||
|
position: absolute;
|
||||||
|
top: -10%;
|
||||||
|
opacity: 0.7;
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: fall linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .snowflake {
|
||||||
|
color: #00bcd4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .snowflake {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snowflake.hide-narrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fall {
|
||||||
|
0% {
|
||||||
|
transform: translateY(-10vh) translateX(0) rotate(var(--rotation));
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translateY(30vh) translateX(10px)
|
||||||
|
rotate(calc(var(--rotation) + 25deg));
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(60vh) translateX(-10px)
|
||||||
|
rotate(calc(var(--rotation) + 50deg));
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translateY(85vh) translateX(10px)
|
||||||
|
rotate(calc(var(--rotation) + 75deg));
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(120vh) translateX(0)
|
||||||
|
rotate(calc(var(--rotation) + 100deg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.snowflake {
|
||||||
|
animation: none;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-snowflakes": HaSnowflakes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
areaMeetsFilter,
|
areaMeetsFilter,
|
||||||
deviceMeetsFilter,
|
deviceMeetsFilter,
|
||||||
entityRegMeetsFilter,
|
entityRegMeetsFilter,
|
||||||
|
getTargetComboBoxItemType,
|
||||||
type TargetType,
|
type TargetType,
|
||||||
type TargetTypeFloorless,
|
type TargetTypeFloorless,
|
||||||
} from "../data/target";
|
} from "../data/target";
|
||||||
@@ -47,7 +48,6 @@ 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,35 +634,6 @@ 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,
|
||||||
@@ -686,7 +657,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = this._getRowType(firstItem as PickerComboBoxItem);
|
const type = getTargetComboBoxItemType(firstItem as PickerComboBoxItem);
|
||||||
const translationType:
|
const translationType:
|
||||||
| "areas"
|
| "areas"
|
||||||
| "entities"
|
| "entities"
|
||||||
@@ -858,7 +829,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
if (!filterType || filterType === "label") {
|
if (!filterType || filterType === "label") {
|
||||||
let labels = this._getLabelsMemoized(
|
let labels = this._getLabelsMemoized(
|
||||||
this.hass,
|
this.hass.states,
|
||||||
|
this.hass.areas,
|
||||||
|
this.hass.devices,
|
||||||
|
this.hass.entities,
|
||||||
this._labelRegistry,
|
this._labelRegistry,
|
||||||
includeDomains,
|
includeDomains,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -974,14 +948,11 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = this._getRowType(item);
|
const type = getTargetComboBoxItemType(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,6 +13,7 @@ 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,7 +6,6 @@ import {
|
|||||||
mdiDevices,
|
mdiDevices,
|
||||||
mdiFormatListBulleted,
|
mdiFormatListBulleted,
|
||||||
mdiGestureDoubleTap,
|
mdiGestureDoubleTap,
|
||||||
mdiHomeAssistant,
|
|
||||||
mdiMapMarker,
|
mdiMapMarker,
|
||||||
mdiMapMarkerRadius,
|
mdiMapMarkerRadius,
|
||||||
mdiMessageAlert,
|
mdiMessageAlert,
|
||||||
@@ -23,6 +22,7 @@ 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,3 +1,5 @@
|
|||||||
|
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,
|
||||||
@@ -7,8 +9,6 @@ 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,6 +84,9 @@ 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";
|
||||||
|
|
||||||
@@ -172,7 +175,9 @@ 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 = () => {
|
||||||
@@ -198,18 +203,7 @@ export class HaWaDialog extends LitElement {
|
|||||||
haStyleScrollbar,
|
haStyleScrollbar,
|
||||||
css`
|
css`
|
||||||
wa-dialog {
|
wa-dialog {
|
||||||
--full-width: var(
|
--full-width: var(--ha-dialog-width-full, min(95vw, var(--safe-width)));
|
||||||
--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);
|
||||||
@@ -226,8 +220,7 @@ 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, 100vw);
|
max-width: var(--ha-dialog-max-width, var(--safe-width));
|
||||||
max-width: var(--ha-dialog-max-width, 100svw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([width="small"]) wa-dialog {
|
:host([width="small"]) wa-dialog {
|
||||||
@@ -235,7 +228,7 @@ export class HaWaDialog extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host([width="large"]) wa-dialog {
|
:host([width="large"]) wa-dialog {
|
||||||
--width: min(var(--ha-dialog-width-lg, 720px), var(--full-width));
|
--width: min(var(--ha-dialog-width-lg, 1024px), var(--full-width));
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([width="full"]) wa-dialog {
|
:host([width="full"]) wa-dialog {
|
||||||
@@ -247,34 +240,56 @@ 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(100% - var(--ha-space-20))
|
calc(var(--safe-height) - 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 {
|
:host([type="standard"]) {
|
||||||
--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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export interface AnalyticsPreferences {
|
|||||||
diagnostics?: boolean;
|
diagnostics?: boolean;
|
||||||
usage?: boolean;
|
usage?: boolean;
|
||||||
statistics?: boolean;
|
statistics?: boolean;
|
||||||
|
snapshots?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Analytics {
|
export interface Analytics {
|
||||||
|
|||||||
@@ -21,11 +21,52 @@ 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"],
|
||||||
@@ -40,7 +81,43 @@ 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);
|
||||||
@@ -146,6 +223,7 @@ export const getAreasAndFloors = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
let outputAreas = areas;
|
let outputAreas = areas;
|
||||||
|
let outputFloors = floors;
|
||||||
|
|
||||||
let areaIds: string[] | undefined;
|
let areaIds: string[] | undefined;
|
||||||
|
|
||||||
@@ -177,11 +255,35 @@ export const getAreasAndFloors = (
|
|||||||
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)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hierarchy = getAreasFloorHierarchy(floors, outputAreas);
|
if (
|
||||||
|
entityFilter ||
|
||||||
|
deviceFilter ||
|
||||||
|
includeDomains ||
|
||||||
|
excludeDomains ||
|
||||||
|
includeDeviceClasses
|
||||||
|
) {
|
||||||
|
// Ensure we only include floors that have areas with the filtered entities/devices
|
||||||
|
const validFloorIds = new Set(
|
||||||
|
outputAreas.map((area) => area.floor_id).filter((id) => id)
|
||||||
|
);
|
||||||
|
outputFloors = outputFloors.filter((floor) =>
|
||||||
|
validFloorIds.has(floor.floor_id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const items: FloorComboBoxItem[] = [];
|
const hierarchy = getAreasFloorHierarchy(outputFloors, outputAreas);
|
||||||
|
|
||||||
|
const items: (
|
||||||
|
| FloorComboBoxItem
|
||||||
|
| FloorNestedComboBoxItem
|
||||||
|
| UnassignedAreasFloorComboBoxItem
|
||||||
|
)[] = [];
|
||||||
|
|
||||||
hierarchy.floors.forEach((f) => {
|
hierarchy.floors.forEach((f) => {
|
||||||
const floor = haFloors[f.id];
|
const floor = haFloors[f.id];
|
||||||
@@ -196,7 +298,7 @@ export const getAreasAndFloors = (
|
|||||||
})
|
})
|
||||||
.flat();
|
.flat();
|
||||||
|
|
||||||
items.push({
|
const floorItem: FloorComboBoxItem | FloorNestedComboBoxItem = {
|
||||||
id: formatId({ id: floor.floor_id, type: "floor" }),
|
id: formatId({ id: floor.floor_id, type: "floor" }),
|
||||||
type: "floor",
|
type: "floor",
|
||||||
primary: floorName,
|
primary: floorName,
|
||||||
@@ -208,10 +310,11 @@ export const getAreasAndFloors = (
|
|||||||
...floor.aliases,
|
...floor.aliases,
|
||||||
...areaSearchLabels,
|
...areaSearchLabels,
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
|
|
||||||
items.push(
|
items.push(floorItem);
|
||||||
...floorAreas.map((area) => {
|
|
||||||
|
const floorAreasItems = floorAreas.map((area) => {
|
||||||
const areaName = computeAreaName(area);
|
const areaName = computeAreaName(area);
|
||||||
return {
|
return {
|
||||||
id: formatId({ id: area.area_id, type: "area" }),
|
id: formatId({ id: area.area_id, type: "area" }),
|
||||||
@@ -225,12 +328,16 @@ export const getAreasAndFloors = (
|
|||||||
...area.aliases,
|
...area.aliases,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
items.push(
|
if (nested && floor) {
|
||||||
...hierarchy.areas.map((areaId) => {
|
(floorItem as unknown as FloorNestedComboBoxItem).areas = floorAreasItems;
|
||||||
|
} else {
|
||||||
|
items.push(...floorAreasItems);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const unassignedAreaItems = hierarchy.areas.map((areaId) => {
|
||||||
const area = haAreas[areaId];
|
const area = haAreas[areaId];
|
||||||
const areaName = computeAreaName(area) || area.area_id;
|
const areaName = computeAreaName(area) || area.area_id;
|
||||||
return {
|
return {
|
||||||
@@ -241,8 +348,15 @@ export const getAreasAndFloors = (
|
|||||||
icon: area.icon || undefined,
|
icon: area.icon || undefined,
|
||||||
search_labels: [area.area_id, areaName, ...area.aliases],
|
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,7 +1,9 @@
|
|||||||
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 { EntityRegistryEntry } from "./entity_registry";
|
import type {
|
||||||
|
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";
|
||||||
@@ -18,7 +20,10 @@ export interface AreaRegistryEntry extends RegistryEntry {
|
|||||||
temperature_entity_id: string | null;
|
temperature_entity_id: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AreaEntityLookup = Record<string, EntityRegistryEntry[]>;
|
export type AreaEntityLookup = Record<
|
||||||
|
string,
|
||||||
|
(EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||||
|
>;
|
||||||
|
|
||||||
export type AreaDeviceLookup = Record<string, DeviceRegistryEntry[]>;
|
export type AreaDeviceLookup = Record<string, DeviceRegistryEntry[]>;
|
||||||
|
|
||||||
@@ -69,7 +74,7 @@ export const reorderAreaRegistryEntries = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getAreaEntityLookup = (
|
export const getAreaEntityLookup = (
|
||||||
entities: EntityRegistryEntry[]
|
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||||
): AreaEntityLookup => {
|
): AreaEntityLookup => {
|
||||||
const areaEntityLookup: AreaEntityLookup = {};
|
const areaEntityLookup: AreaEntityLookup = {};
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
@@ -99,22 +104,3 @@ 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;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -214,6 +214,8 @@ export interface PipelineRun {
|
|||||||
stage: "ready" | "wake_word" | "stt" | "intent" | "tts" | "done" | "error";
|
stage: "ready" | "wake_word" | "stt" | "intent" | "tts" | "done" | "error";
|
||||||
run: PipelineRunStartEvent["data"];
|
run: PipelineRunStartEvent["data"];
|
||||||
error?: PipelineErrorEvent["data"];
|
error?: PipelineErrorEvent["data"];
|
||||||
|
started: Date;
|
||||||
|
finished?: Date;
|
||||||
wake_word?: PipelineWakeWordStartEvent["data"] &
|
wake_word?: PipelineWakeWordStartEvent["data"] &
|
||||||
Partial<PipelineWakeWordEndEvent["data"]> & { done: boolean };
|
Partial<PipelineWakeWordEndEvent["data"]> & { done: boolean };
|
||||||
stt?: PipelineSTTStartEvent["data"] &
|
stt?: PipelineSTTStartEvent["data"] &
|
||||||
@@ -235,6 +237,7 @@ export const processEvent = (
|
|||||||
stage: "ready",
|
stage: "ready",
|
||||||
run: event.data,
|
run: event.data,
|
||||||
events: [event],
|
events: [event],
|
||||||
|
started: new Date(event.timestamp),
|
||||||
};
|
};
|
||||||
return run;
|
return run;
|
||||||
}
|
}
|
||||||
@@ -290,9 +293,14 @@ export const processEvent = (
|
|||||||
tts: { ...run.tts!, ...event.data, done: true },
|
tts: { ...run.tts!, ...event.data, done: true },
|
||||||
};
|
};
|
||||||
} else if (event.type === "run-end") {
|
} else if (event.type === "run-end") {
|
||||||
run = { ...run, stage: "done" };
|
run = { ...run, finished: new Date(event.timestamp), stage: "done" };
|
||||||
} else if (event.type === "error") {
|
} else if (event.type === "error") {
|
||||||
run = { ...run, stage: "error", error: event.data };
|
run = {
|
||||||
|
...run,
|
||||||
|
finished: new Date(event.timestamp),
|
||||||
|
stage: "error",
|
||||||
|
error: event.data,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
run = { ...run };
|
run = { ...run };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type { LocalizeKeys } from "../common/translations/localize";
|
|||||||
import { createSearchParam } from "../common/url/search-params";
|
import { createSearchParam } from "../common/url/search-params";
|
||||||
import type { Context, HomeAssistant } from "../types";
|
import type { Context, HomeAssistant } from "../types";
|
||||||
import type { BlueprintInput } from "./blueprint";
|
import type { BlueprintInput } from "./blueprint";
|
||||||
|
import type { ConditionDescription } from "./condition";
|
||||||
import { CONDITION_BUILDING_BLOCKS } from "./condition";
|
import { CONDITION_BUILDING_BLOCKS } from "./condition";
|
||||||
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||||
import type { Action, Field, MODES } from "./script";
|
import type { Action, Field, MODES } from "./script";
|
||||||
@@ -113,12 +114,6 @@ 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;
|
||||||
@@ -126,6 +121,12 @@ 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";
|
||||||
@@ -236,6 +237,12 @@ interface BaseCondition {
|
|||||||
condition: string;
|
condition: string;
|
||||||
alias?: string;
|
alias?: string;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
options?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformCondition extends BaseCondition {
|
||||||
|
condition: Exclude<string, LegacyCondition["condition"]>;
|
||||||
|
target?: HassServiceTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LogicalCondition extends BaseCondition {
|
export interface LogicalCondition extends BaseCondition {
|
||||||
@@ -320,7 +327,7 @@ export type AutomationElementGroup = Record<
|
|||||||
{ icon?: string; members?: AutomationElementGroup }
|
{ icon?: string; members?: AutomationElementGroup }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type Condition =
|
export type LegacyCondition =
|
||||||
| StateCondition
|
| StateCondition
|
||||||
| NumericStateCondition
|
| NumericStateCondition
|
||||||
| SunCondition
|
| SunCondition
|
||||||
@@ -331,6 +338,8 @@ export type Condition =
|
|||||||
| LogicalCondition
|
| LogicalCondition
|
||||||
| TriggerCondition;
|
| TriggerCondition;
|
||||||
|
|
||||||
|
export type Condition = LegacyCondition | PlatformCondition;
|
||||||
|
|
||||||
export type ConditionWithShorthand =
|
export type ConditionWithShorthand =
|
||||||
| Condition
|
| Condition
|
||||||
| ShorthandAndConditionList
|
| ShorthandAndConditionList
|
||||||
@@ -608,6 +617,7 @@ export interface ConditionSidebarConfig extends BaseSidebarConfig {
|
|||||||
insertAfter: (value: Condition | Condition[]) => boolean;
|
insertAfter: (value: Condition | Condition[]) => boolean;
|
||||||
toggleYamlMode: () => void;
|
toggleYamlMode: () => void;
|
||||||
config: Condition;
|
config: Condition;
|
||||||
|
description?: ConditionDescription;
|
||||||
yamlMode: boolean;
|
yamlMode: boolean;
|
||||||
uiSupported: boolean;
|
uiSupported: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,14 @@ import {
|
|||||||
} from "../common/string/format-list";
|
} from "../common/string/format-list";
|
||||||
import { hasTemplate } from "../common/string/has-template";
|
import { hasTemplate } from "../common/string/has-template";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import type { Condition, ForDict, LegacyTrigger, Trigger } from "./automation";
|
import type {
|
||||||
|
Condition,
|
||||||
|
ForDict,
|
||||||
|
LegacyCondition,
|
||||||
|
LegacyTrigger,
|
||||||
|
Trigger,
|
||||||
|
} from "./automation";
|
||||||
|
import { getConditionDomain, getConditionObjectId } from "./condition";
|
||||||
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||||
import {
|
import {
|
||||||
localizeDeviceAutomationCondition,
|
localizeDeviceAutomationCondition,
|
||||||
@@ -137,9 +144,7 @@ const tryDescribeTrigger = (
|
|||||||
const type = getTriggerObjectId(trigger.trigger);
|
const type = getTriggerObjectId(trigger.trigger);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
hass.localize(
|
hass.localize(`component.${domain}.triggers.${type}.name`) ||
|
||||||
`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`
|
||||||
) ||
|
) ||
|
||||||
@@ -896,6 +901,37 @@ const tryDescribeCondition = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const description = describeLegacyCondition(
|
||||||
|
condition as LegacyCondition,
|
||||||
|
hass,
|
||||||
|
entityRegistry
|
||||||
|
);
|
||||||
|
|
||||||
|
if (description) {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionType = condition.condition;
|
||||||
|
|
||||||
|
const domain = getConditionDomain(condition.condition);
|
||||||
|
const type = getConditionObjectId(condition.condition);
|
||||||
|
|
||||||
|
return (
|
||||||
|
hass.localize(`component.${domain}.conditions.${type}.name`) ||
|
||||||
|
hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.conditions.type.${conditionType as LegacyCondition["condition"]}.label`
|
||||||
|
) ||
|
||||||
|
hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.conditions.unknown_condition`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const describeLegacyCondition = (
|
||||||
|
condition: LegacyCondition,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entityRegistry: EntityRegistryEntry[]
|
||||||
|
) => {
|
||||||
if (condition.condition === "or") {
|
if (condition.condition === "or") {
|
||||||
const conditions = ensureArray(condition.conditions);
|
const conditions = ensureArray(condition.conditions);
|
||||||
|
|
||||||
@@ -1287,12 +1323,5 @@ const tryDescribeCondition = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return undefined;
|
||||||
hass.localize(
|
|
||||||
`ui.panel.config.automation.editor.conditions.type.${condition.condition}.label`
|
|
||||||
) ||
|
|
||||||
hass.localize(
|
|
||||||
`ui.panel.config.automation.editor.conditions.unknown_condition`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -137,8 +137,12 @@ const getCalendarDate = (dateObj: any): string | undefined => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCalendars = (hass: HomeAssistant): Calendar[] =>
|
export const getCalendars = (
|
||||||
Object.keys(hass.states)
|
hass: HomeAssistant,
|
||||||
|
element: Element
|
||||||
|
): Calendar[] => {
|
||||||
|
const computedStyles = getComputedStyle(element);
|
||||||
|
return Object.keys(hass.states)
|
||||||
.filter(
|
.filter(
|
||||||
(eid) =>
|
(eid) =>
|
||||||
computeDomain(eid) === "calendar" &&
|
computeDomain(eid) === "calendar" &&
|
||||||
@@ -149,8 +153,9 @@ export const getCalendars = (hass: HomeAssistant): Calendar[] =>
|
|||||||
.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),
|
backgroundColor: getColorByIndex(idx, computedStyles),
|
||||||
}));
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
export const createCalendarEvent = (
|
export const createCalendarEvent = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
|||||||
228
src/data/chat_log.ts
Normal file
228
src/data/chat_log.ts
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export const enum ChatLogEventType {
|
||||||
|
INITIAL_STATE = "initial_state",
|
||||||
|
CREATED = "created",
|
||||||
|
UPDATED = "updated",
|
||||||
|
DELETED = "deleted",
|
||||||
|
CONTENT_ADDED = "content_added",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatLogAttachment {
|
||||||
|
media_content_id: string;
|
||||||
|
mime_type: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatLogSystemContent {
|
||||||
|
role: "system";
|
||||||
|
content: string;
|
||||||
|
created: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatLogUserContent {
|
||||||
|
role: "user";
|
||||||
|
content: string;
|
||||||
|
created: Date;
|
||||||
|
attachments?: ChatLogAttachment[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatLogAssistantContent {
|
||||||
|
role: "assistant";
|
||||||
|
agent_id: string;
|
||||||
|
created: Date;
|
||||||
|
content?: string;
|
||||||
|
thinking_content?: string;
|
||||||
|
tool_calls?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatLogToolResultContent {
|
||||||
|
role: "tool_result";
|
||||||
|
agent_id: string;
|
||||||
|
tool_call_id: string;
|
||||||
|
tool_name: string;
|
||||||
|
tool_result: any;
|
||||||
|
created: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChatLogContent =
|
||||||
|
| ChatLogSystemContent
|
||||||
|
| ChatLogUserContent
|
||||||
|
| ChatLogAssistantContent
|
||||||
|
| ChatLogToolResultContent;
|
||||||
|
|
||||||
|
export interface ChatLog {
|
||||||
|
conversation_id: string;
|
||||||
|
continue_conversation: boolean;
|
||||||
|
content: ChatLogContent[];
|
||||||
|
created: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal wire format types (not exported)
|
||||||
|
interface ChatLogSystemContentWire {
|
||||||
|
role: "system";
|
||||||
|
content: string;
|
||||||
|
created: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChatLogUserContentWire {
|
||||||
|
role: "user";
|
||||||
|
content: string;
|
||||||
|
created: string;
|
||||||
|
attachments?: ChatLogAttachment[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChatLogAssistantContentWire {
|
||||||
|
role: "assistant";
|
||||||
|
agent_id: string;
|
||||||
|
created: string;
|
||||||
|
content?: string;
|
||||||
|
thinking_content?: string;
|
||||||
|
tool_calls?: {
|
||||||
|
tool_name: string;
|
||||||
|
tool_args: Record<string, any>;
|
||||||
|
id: string;
|
||||||
|
external: boolean;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChatLogToolResultContentWire {
|
||||||
|
role: "tool_result";
|
||||||
|
agent_id: string;
|
||||||
|
tool_call_id: string;
|
||||||
|
tool_name: string;
|
||||||
|
tool_result: any;
|
||||||
|
created: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatLogContentWire =
|
||||||
|
| ChatLogSystemContentWire
|
||||||
|
| ChatLogUserContentWire
|
||||||
|
| ChatLogAssistantContentWire
|
||||||
|
| ChatLogToolResultContentWire;
|
||||||
|
|
||||||
|
interface ChatLogWire {
|
||||||
|
conversation_id: string;
|
||||||
|
continue_conversation: boolean;
|
||||||
|
content: ChatLogContentWire[];
|
||||||
|
created: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const processContent = (content: ChatLogContentWire): ChatLogContent => ({
|
||||||
|
...content,
|
||||||
|
created: new Date(content.created),
|
||||||
|
});
|
||||||
|
|
||||||
|
const processChatLog = (chatLog: ChatLogWire): ChatLog => ({
|
||||||
|
...chatLog,
|
||||||
|
created: new Date(chatLog.created),
|
||||||
|
content: chatLog.content.map(processContent),
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ChatLogInitialStateEvent {
|
||||||
|
event_type: ChatLogEventType.INITIAL_STATE;
|
||||||
|
data: ChatLogWire;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChatLogIndexInitialStateEvent {
|
||||||
|
event_type: ChatLogEventType.INITIAL_STATE;
|
||||||
|
data: ChatLogWire[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChatLogCreatedEvent {
|
||||||
|
conversation_id: string;
|
||||||
|
event_type: ChatLogEventType.CREATED;
|
||||||
|
data: ChatLogWire;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChatLogUpdatedEvent {
|
||||||
|
conversation_id: string;
|
||||||
|
event_type: ChatLogEventType.UPDATED;
|
||||||
|
data: { chat_log: ChatLogWire };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChatLogDeletedEvent {
|
||||||
|
conversation_id: string;
|
||||||
|
event_type: ChatLogEventType.DELETED;
|
||||||
|
data: ChatLogWire;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChatLogContentAddedEvent {
|
||||||
|
conversation_id: string;
|
||||||
|
event_type: ChatLogEventType.CONTENT_ADDED;
|
||||||
|
data: { content: ChatLogContentWire };
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatLogSubscriptionEvent =
|
||||||
|
| ChatLogInitialStateEvent
|
||||||
|
| ChatLogUpdatedEvent
|
||||||
|
| ChatLogDeletedEvent
|
||||||
|
| ChatLogContentAddedEvent;
|
||||||
|
|
||||||
|
type ChatLogIndexSubscriptionEvent =
|
||||||
|
| ChatLogIndexInitialStateEvent
|
||||||
|
| ChatLogCreatedEvent
|
||||||
|
| ChatLogDeletedEvent;
|
||||||
|
|
||||||
|
export const subscribeChatLog = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
conversationId: string,
|
||||||
|
callback: (chatLog: ChatLog | null) => void
|
||||||
|
): Promise<UnsubscribeFunc> => {
|
||||||
|
let chatLog: ChatLog | null = null;
|
||||||
|
|
||||||
|
return hass.connection.subscribeMessage<ChatLogSubscriptionEvent>(
|
||||||
|
(event) => {
|
||||||
|
if (event.event_type === ChatLogEventType.INITIAL_STATE) {
|
||||||
|
chatLog = processChatLog(event.data);
|
||||||
|
callback(chatLog);
|
||||||
|
} else if (event.event_type === ChatLogEventType.CONTENT_ADDED) {
|
||||||
|
if (chatLog) {
|
||||||
|
chatLog = {
|
||||||
|
...chatLog,
|
||||||
|
content: [...chatLog.content, processContent(event.data.content)],
|
||||||
|
};
|
||||||
|
callback(chatLog);
|
||||||
|
}
|
||||||
|
} else if (event.event_type === ChatLogEventType.UPDATED) {
|
||||||
|
chatLog = processChatLog(event.data.chat_log);
|
||||||
|
callback(chatLog);
|
||||||
|
} else if (event.event_type === ChatLogEventType.DELETED) {
|
||||||
|
chatLog = null;
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "conversation/chat_log/subscribe",
|
||||||
|
conversation_id: conversationId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const subscribeChatLogIndex = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
callback: (chatLogs: ChatLog[]) => void
|
||||||
|
): Promise<UnsubscribeFunc> => {
|
||||||
|
let chatLogs: ChatLog[] = [];
|
||||||
|
|
||||||
|
return hass.connection.subscribeMessage<ChatLogIndexSubscriptionEvent>(
|
||||||
|
(event) => {
|
||||||
|
if (event.event_type === ChatLogEventType.INITIAL_STATE) {
|
||||||
|
chatLogs = event.data.map(processChatLog);
|
||||||
|
callback(chatLogs);
|
||||||
|
} else if (event.event_type === ChatLogEventType.CREATED) {
|
||||||
|
chatLogs = [...chatLogs, processChatLog(event.data)];
|
||||||
|
callback(chatLogs);
|
||||||
|
} else if (event.event_type === ChatLogEventType.DELETED) {
|
||||||
|
chatLogs = chatLogs.filter(
|
||||||
|
(chatLog) => chatLog.conversation_id !== event.conversation_id
|
||||||
|
);
|
||||||
|
callback(chatLogs);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "conversation/chat_log/subscribe_index",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,38 +1,15 @@
|
|||||||
import {
|
import { mdiMapClock, mdiShape } from "@mdi/js";
|
||||||
mdiAmpersand,
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
mdiClockOutline,
|
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||||
mdiCodeBraces,
|
import type { HomeAssistant } from "../types";
|
||||||
mdiDevices,
|
|
||||||
mdiGateOr,
|
|
||||||
mdiIdentifier,
|
|
||||||
mdiMapClock,
|
|
||||||
mdiMapMarkerRadius,
|
|
||||||
mdiNotEqualVariant,
|
|
||||||
mdiNumeric,
|
|
||||||
mdiShape,
|
|
||||||
mdiStateMachine,
|
|
||||||
mdiWeatherSunny,
|
|
||||||
} from "@mdi/js";
|
|
||||||
import type { AutomationElementGroupCollection } from "./automation";
|
import type { AutomationElementGroupCollection } from "./automation";
|
||||||
|
import type { Selector, TargetSelector } from "./selector";
|
||||||
export const CONDITION_ICONS = {
|
|
||||||
device: mdiDevices,
|
|
||||||
and: mdiAmpersand,
|
|
||||||
or: mdiGateOr,
|
|
||||||
not: mdiNotEqualVariant,
|
|
||||||
state: mdiStateMachine,
|
|
||||||
numeric_state: mdiNumeric,
|
|
||||||
sun: mdiWeatherSunny,
|
|
||||||
template: mdiCodeBraces,
|
|
||||||
time: mdiClockOutline,
|
|
||||||
trigger: mdiIdentifier,
|
|
||||||
zone: mdiMapMarkerRadius,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CONDITION_COLLECTIONS: AutomationElementGroupCollection[] = [
|
export const CONDITION_COLLECTIONS: AutomationElementGroupCollection[] = [
|
||||||
{
|
{
|
||||||
groups: {
|
groups: {
|
||||||
device: {},
|
device: {},
|
||||||
|
dynamicGroups: {},
|
||||||
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
||||||
time_location: {
|
time_location: {
|
||||||
icon: mdiMapClock,
|
icon: mdiMapClock,
|
||||||
@@ -40,11 +17,19 @@ 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;
|
||||||
@@ -62,3 +47,33 @@ export const COLLAPSIBLE_CONDITION_ELEMENTS = [
|
|||||||
"ha-automation-condition-not",
|
"ha-automation-condition-not",
|
||||||
"ha-automation-condition-or",
|
"ha-automation-condition-or",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export interface ConditionDescription {
|
||||||
|
target?: TargetSelector["target"];
|
||||||
|
fields: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
example?: string | boolean | number;
|
||||||
|
default?: unknown;
|
||||||
|
required?: boolean;
|
||||||
|
selector?: Selector;
|
||||||
|
context?: Record<string, string>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConditionDescriptions = Record<string, ConditionDescription>;
|
||||||
|
|
||||||
|
export const subscribeConditions = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
callback: (conditions: ConditionDescriptions) => void
|
||||||
|
) =>
|
||||||
|
hass.connection.subscribeMessage<ConditionDescriptions>(callback, {
|
||||||
|
type: "condition_platforms/subscribe",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getConditionDomain = (condition: string) =>
|
||||||
|
condition.includes(".") ? computeDomain(condition) : condition;
|
||||||
|
|
||||||
|
export const getConditionObjectId = (condition: string) =>
|
||||||
|
condition.includes(".") ? computeObjectId(condition) : "_";
|
||||||
|
|||||||
@@ -50,7 +50,11 @@ export type DeviceEntityDisplayLookup = Record<
|
|||||||
EntityRegistryDisplayEntry[]
|
EntityRegistryDisplayEntry[]
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type DeviceEntityLookup = Record<string, EntityRegistryEntry[]>;
|
export type DeviceEntityLookup<
|
||||||
|
T extends EntityRegistryEntry | EntityRegistryDisplayEntry =
|
||||||
|
| EntityRegistryEntry
|
||||||
|
| EntityRegistryDisplayEntry,
|
||||||
|
> = Record<string, T[]>;
|
||||||
|
|
||||||
export interface DeviceRegistryEntryMutableParams {
|
export interface DeviceRegistryEntryMutableParams {
|
||||||
area_id?: string | null;
|
area_id?: string | null;
|
||||||
@@ -107,7 +111,7 @@ export const sortDeviceRegistryByName = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const getDeviceEntityLookup = (
|
export const getDeviceEntityLookup = (
|
||||||
entities: EntityRegistryEntry[]
|
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||||
): 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 } from "home-assistant-js-websocket";
|
import type { Collection, HassEntity } 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 {
|
||||||
@@ -200,6 +200,7 @@ export type EnergySource =
|
|||||||
export interface EnergyPreferences {
|
export interface EnergyPreferences {
|
||||||
energy_sources: EnergySource[];
|
energy_sources: EnergySource[];
|
||||||
device_consumption: DeviceConsumptionEnergyPreference[];
|
device_consumption: DeviceConsumptionEnergyPreference[];
|
||||||
|
device_consumption_water: DeviceConsumptionEnergyPreference[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnergyInfo {
|
export interface EnergyInfo {
|
||||||
@@ -216,6 +217,7 @@ export interface EnergyValidationIssue {
|
|||||||
export interface EnergyPreferencesValidation {
|
export interface EnergyPreferencesValidation {
|
||||||
energy_sources: EnergyValidationIssue[][];
|
energy_sources: EnergyValidationIssue[][];
|
||||||
device_consumption: EnergyValidationIssue[][];
|
device_consumption: EnergyValidationIssue[][];
|
||||||
|
device_consumption_water: EnergyValidationIssue[][];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getEnergyInfo = (hass: HomeAssistant) =>
|
export const getEnergyInfo = (hass: HomeAssistant) =>
|
||||||
@@ -356,6 +358,11 @@ export const getReferencedStatisticIds = (
|
|||||||
if (!(includeTypes && !includeTypes.includes("device"))) {
|
if (!(includeTypes && !includeTypes.includes("device"))) {
|
||||||
statIDs.push(...prefs.device_consumption.map((d) => d.stat_consumption));
|
statIDs.push(...prefs.device_consumption.map((d) => d.stat_consumption));
|
||||||
}
|
}
|
||||||
|
if (!(includeTypes && !includeTypes.includes("water"))) {
|
||||||
|
statIDs.push(
|
||||||
|
...prefs.device_consumption_water.map((d) => d.stat_consumption)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return statIDs;
|
return statIDs;
|
||||||
};
|
};
|
||||||
@@ -775,6 +782,7 @@ export const getEnergyDataCollection = (
|
|||||||
hass.locale,
|
hass.locale,
|
||||||
hass.config
|
hass.config
|
||||||
);
|
);
|
||||||
|
collection.refresh();
|
||||||
scheduleUpdatePeriod();
|
scheduleUpdatePeriod();
|
||||||
},
|
},
|
||||||
addHours(
|
addHours(
|
||||||
@@ -1353,3 +1361,37 @@ 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
14
src/data/esphome.ts
Normal file
14
src/data/esphome.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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,4 +1,3 @@
|
|||||||
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";
|
||||||
@@ -75,27 +74,3 @@ 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;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ export interface CoreFrontendUserData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SidebarFrontendUserData {
|
export interface SidebarFrontendUserData {
|
||||||
panelOrder: string[];
|
panelOrder?: string[];
|
||||||
hiddenPanels: string[];
|
hiddenPanels?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CoreFrontendSystemData {
|
export interface CoreFrontendSystemData {
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ import type {
|
|||||||
|
|
||||||
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
||||||
import { getTriggerDomain, getTriggerObjectId } from "./trigger";
|
import { getTriggerDomain, getTriggerObjectId } from "./trigger";
|
||||||
|
import { getConditionDomain, getConditionObjectId } from "./condition";
|
||||||
|
|
||||||
/** Icon to use when no icon specified for service. */
|
/** Icon to use when no icon specified for service. */
|
||||||
export const DEFAULT_SERVICE_ICON = mdiRoomService;
|
export const DEFAULT_SERVICE_ICON = mdiRoomService;
|
||||||
@@ -138,15 +139,25 @@ const resources: {
|
|||||||
all?: Promise<Record<string, TriggerIcons>>;
|
all?: Promise<Record<string, TriggerIcons>>;
|
||||||
domains: Record<string, TriggerIcons | Promise<TriggerIcons>>;
|
domains: Record<string, TriggerIcons | Promise<TriggerIcons>>;
|
||||||
};
|
};
|
||||||
|
conditions: {
|
||||||
|
all?: Promise<Record<string, ConditionIcons>>;
|
||||||
|
domains: Record<string, ConditionIcons | Promise<ConditionIcons>>;
|
||||||
|
};
|
||||||
} = {
|
} = {
|
||||||
entity: {},
|
entity: {},
|
||||||
entity_component: {},
|
entity_component: {},
|
||||||
services: { domains: {} },
|
services: { domains: {} },
|
||||||
triggers: { domains: {} },
|
triggers: { domains: {} },
|
||||||
|
conditions: { domains: {} },
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IconResources<
|
interface IconResources<
|
||||||
T extends ComponentIcons | PlatformIcons | ServiceIcons | TriggerIcons,
|
T extends
|
||||||
|
| ComponentIcons
|
||||||
|
| PlatformIcons
|
||||||
|
| ServiceIcons
|
||||||
|
| TriggerIcons
|
||||||
|
| ConditionIcons,
|
||||||
> {
|
> {
|
||||||
resources: Record<string, T>;
|
resources: Record<string, T>;
|
||||||
}
|
}
|
||||||
@@ -195,17 +206,24 @@ type TriggerIcons = Record<
|
|||||||
{ trigger: string; sections?: Record<string, string> }
|
{ trigger: string; sections?: Record<string, string> }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
type ConditionIcons = Record<
|
||||||
|
string,
|
||||||
|
{ condition: string; sections?: Record<string, string> }
|
||||||
|
>;
|
||||||
|
|
||||||
export type IconCategory =
|
export type IconCategory =
|
||||||
| "entity"
|
| "entity"
|
||||||
| "entity_component"
|
| "entity_component"
|
||||||
| "services"
|
| "services"
|
||||||
| "triggers";
|
| "triggers"
|
||||||
|
| "conditions";
|
||||||
|
|
||||||
interface CategoryType {
|
interface CategoryType {
|
||||||
entity: PlatformIcons;
|
entity: PlatformIcons;
|
||||||
entity_component: ComponentIcons;
|
entity_component: ComponentIcons;
|
||||||
services: ServiceIcons;
|
services: ServiceIcons;
|
||||||
triggers: TriggerIcons;
|
triggers: TriggerIcons;
|
||||||
|
conditions: ConditionIcons;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getHassIcons = async <T extends IconCategory>(
|
export const getHassIcons = async <T extends IconCategory>(
|
||||||
@@ -327,6 +345,13 @@ export const getTriggerIcons = async (
|
|||||||
): Promise<TriggerIcons | Record<string, TriggerIcons> | undefined> =>
|
): Promise<TriggerIcons | Record<string, TriggerIcons> | undefined> =>
|
||||||
getCategoryIcons(hass, "triggers", domain, force);
|
getCategoryIcons(hass, "triggers", domain, force);
|
||||||
|
|
||||||
|
export const getConditionIcons = async (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
domain?: string,
|
||||||
|
force = false
|
||||||
|
): Promise<ConditionIcons | Record<string, ConditionIcons> | undefined> =>
|
||||||
|
getCategoryIcons(hass, "conditions", domain, force);
|
||||||
|
|
||||||
// Cache for sorted range keys
|
// Cache for sorted range keys
|
||||||
const sortedRangeCache = new WeakMap<Record<string, string>, number[]>();
|
const sortedRangeCache = new WeakMap<Record<string, string>, number[]>();
|
||||||
|
|
||||||
@@ -526,6 +551,25 @@ export const triggerIcon = async (
|
|||||||
return icon;
|
return icon;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const conditionIcon = async (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
condition: string
|
||||||
|
): Promise<string | undefined> => {
|
||||||
|
let icon: string | undefined;
|
||||||
|
|
||||||
|
const domain = getConditionDomain(condition);
|
||||||
|
const conditionIcons = await getConditionIcons(hass, domain);
|
||||||
|
if (conditionIcons) {
|
||||||
|
const conditionName = getConditionObjectId(condition);
|
||||||
|
const condIcon = conditionIcons[conditionName] as ConditionIcons[string];
|
||||||
|
icon = condIcon?.condition;
|
||||||
|
}
|
||||||
|
if (!icon) {
|
||||||
|
icon = await domainIcon(hass, domain);
|
||||||
|
}
|
||||||
|
return icon;
|
||||||
|
};
|
||||||
|
|
||||||
export const serviceIcon = async (
|
export const serviceIcon = async (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
service: string
|
service: string
|
||||||
|
|||||||
@@ -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 type { HomeAssistant } from "../types";
|
|
||||||
import { debounce } from "../common/util/debounce";
|
import { debounce } from "../common/util/debounce";
|
||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
|
||||||
export const integrationsWithPanel = {
|
export const integrationsWithPanel = {
|
||||||
bluetooth: "config/bluetooth",
|
bluetooth: "config/bluetooth",
|
||||||
@@ -25,6 +25,8 @@ 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,7 +101,10 @@ export const deleteLabelRegistryEntry = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getLabels = (
|
export const getLabels = (
|
||||||
hass: HomeAssistant,
|
hassStates: HomeAssistant["states"],
|
||||||
|
hassAreas: HomeAssistant["areas"],
|
||||||
|
hassDevices: HomeAssistant["devices"],
|
||||||
|
hassEntities: HomeAssistant["entities"],
|
||||||
labels?: LabelRegistryEntry[],
|
labels?: LabelRegistryEntry[],
|
||||||
includeDomains?: string[],
|
includeDomains?: string[],
|
||||||
excludeDomains?: string[],
|
excludeDomains?: string[],
|
||||||
@@ -115,8 +118,8 @@ export const getLabels = (
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const devices = Object.values(hass.devices);
|
const devices = Object.values(hassDevices);
|
||||||
const entities = Object.values(hass.entities);
|
const entities = Object.values(hassEntities);
|
||||||
|
|
||||||
let deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
let deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
||||||
let inputDevices: DeviceRegistryEntry[] | undefined;
|
let inputDevices: DeviceRegistryEntry[] | undefined;
|
||||||
@@ -170,7 +173,7 @@ export const getLabels = (
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return deviceEntityLookup[device.id].some((entity) => {
|
return deviceEntityLookup[device.id].some((entity) => {
|
||||||
const stateObj = hass.states[entity.entity_id];
|
const stateObj = hassStates[entity.entity_id];
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -181,8 +184,9 @@ export const getLabels = (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
inputEntities = inputEntities!.filter((entity) => {
|
inputEntities = inputEntities!.filter((entity) => {
|
||||||
const stateObj = hass.states[entity.entity_id];
|
const stateObj = hassStates[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)
|
||||||
);
|
);
|
||||||
@@ -200,7 +204,7 @@ export const getLabels = (
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return deviceEntityLookup[device.id].some((entity) => {
|
return deviceEntityLookup[device.id].some((entity) => {
|
||||||
const stateObj = hass.states[entity.entity_id];
|
const stateObj = hassStates[entity.entity_id];
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -208,7 +212,7 @@ export const getLabels = (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
inputEntities = inputEntities!.filter((entity) => {
|
inputEntities = inputEntities!.filter((entity) => {
|
||||||
const stateObj = hass.states[entity.entity_id];
|
const stateObj = hassStates[entity.entity_id];
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -245,8 +249,8 @@ export const getLabels = (
|
|||||||
|
|
||||||
if (areaIds) {
|
if (areaIds) {
|
||||||
areaIds.forEach((areaId) => {
|
areaIds.forEach((areaId) => {
|
||||||
const area = hass.areas[areaId];
|
const area = hassAreas[areaId];
|
||||||
area.labels.forEach((label) => usedLabels.add(label));
|
area?.labels.forEach((label) => usedLabels.add(label));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ 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[]> => {
|
||||||
@@ -27,6 +32,15 @@ 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,
|
||||||
@@ -65,6 +79,12 @@ 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
|
||||||
@@ -76,3 +96,23 @@ 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,7 +1,10 @@
|
|||||||
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 { LovelaceSectionRawConfig } from "./section";
|
import type {
|
||||||
|
LovelaceSectionConfig,
|
||||||
|
LovelaceSectionRawConfig,
|
||||||
|
} from "./section";
|
||||||
import type { LovelaceStrategyConfig } from "./strategy";
|
import type { LovelaceStrategyConfig } from "./strategy";
|
||||||
|
|
||||||
export interface ShowViewConfig {
|
export interface ShowViewConfig {
|
||||||
@@ -33,6 +36,12 @@ 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;
|
||||||
@@ -56,6 +65,8 @@ 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 {
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
import {
|
||||||
|
mdiAccount,
|
||||||
|
mdiCalendar,
|
||||||
|
mdiChartBox,
|
||||||
|
mdiClipboardList,
|
||||||
|
mdiFormatListBulletedType,
|
||||||
|
mdiHammer,
|
||||||
|
mdiLightningBolt,
|
||||||
|
mdiPlayBoxMultiple,
|
||||||
|
mdiTooltipAccount,
|
||||||
|
mdiViewDashboard,
|
||||||
|
} from "@mdi/js";
|
||||||
import type { HomeAssistant, PanelInfo } from "../types";
|
import type { HomeAssistant, PanelInfo } from "../types";
|
||||||
|
|
||||||
/** Panel to show when no panel is picked. */
|
/** Panel to show when no panel is picked. */
|
||||||
@@ -60,7 +72,7 @@ export const getPanelTitleFromUrlPath = (
|
|||||||
return getPanelTitle(hass, panel);
|
return getPanelTitle(hass, panel);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPanelIcon = (panel: PanelInfo): string | null => {
|
export const getPanelIcon = (panel: PanelInfo): string | undefined => {
|
||||||
if (!panel.icon) {
|
if (!panel.icon) {
|
||||||
switch (panel.component_name) {
|
switch (panel.component_name) {
|
||||||
case "profile":
|
case "profile":
|
||||||
@@ -70,5 +82,24 @@ export const getPanelIcon = (panel: PanelInfo): string | null => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return panel.icon;
|
return panel.icon || undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PANEL_ICON_PATHS = {
|
||||||
|
calendar: mdiCalendar,
|
||||||
|
"developer-tools": mdiHammer,
|
||||||
|
energy: mdiLightningBolt,
|
||||||
|
history: mdiChartBox,
|
||||||
|
logbook: mdiFormatListBulletedType,
|
||||||
|
lovelace: mdiViewDashboard,
|
||||||
|
profile: mdiAccount,
|
||||||
|
map: mdiTooltipAccount,
|
||||||
|
"media-browser": mdiPlayBoxMultiple,
|
||||||
|
todo: mdiClipboardList,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPanelIconPath = (panel: PanelInfo): string | undefined =>
|
||||||
|
PANEL_ICON_PATHS[panel.url_path];
|
||||||
|
|
||||||
|
export const FIXED_PANELS = ["profile", "config"];
|
||||||
|
export const SHOW_AFTER_SPACER_PANELS = ["developer-tools"];
|
||||||
|
|||||||
@@ -219,9 +219,13 @@ 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(`component.${domain}.services.${serviceName}.name`) ||
|
hass.localize(
|
||||||
hass.services[domain][serviceName]?.name;
|
`component.${domain}.services.${serviceName}.name`,
|
||||||
|
descriptionPlaceholders
|
||||||
|
) || hass.services[domain]?.[serviceName]?.name;
|
||||||
|
|
||||||
if (config.metadata) {
|
if (config.metadata) {
|
||||||
return hass.localize(
|
return hass.localize(
|
||||||
|
|||||||
@@ -1,15 +1,30 @@
|
|||||||
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 { DeviceRegistryEntry } from "./device_registry";
|
import type { DevicePickerItem, DeviceRegistryEntry } from "./device_registry";
|
||||||
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
||||||
import type { EntityRegistryDisplayEntry } from "./entity_registry";
|
import type {
|
||||||
|
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[];
|
||||||
@@ -35,6 +50,39 @@ 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"],
|
||||||
@@ -162,3 +210,32 @@ 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";
|
||||||
|
};
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ export type TranslationCategory =
|
|||||||
| "preview_features"
|
| "preview_features"
|
||||||
| "selector"
|
| "selector"
|
||||||
| "services"
|
| "services"
|
||||||
| "triggers";
|
| "triggers"
|
||||||
|
| "conditions";
|
||||||
|
|
||||||
export const subscribeTranslationPreferences = (
|
export const subscribeTranslationPreferences = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
|||||||
@@ -28,18 +28,24 @@ 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,15 +1,16 @@
|
|||||||
import { mdiAlertOutline } from "@mdi/js";
|
import { mdiAlertOutline, mdiClose } 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";
|
||||||
|
|
||||||
@@ -19,12 +20,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;
|
||||||
@@ -34,6 +35,7 @@ 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 {
|
||||||
@@ -60,16 +62,30 @@ 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-md-dialog
|
<ha-wa-dialog
|
||||||
open
|
.hass=${this.hass}
|
||||||
.disableCancelAction=${confirmPrompt}
|
.open=${this._open}
|
||||||
|
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"
|
||||||
>
|
>
|
||||||
<div slot="headline">
|
<ha-dialog-header slot="header">
|
||||||
<span .title=${dialogTitle} id="dialog-box-title">
|
${!confirmPrompt
|
||||||
|
? 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}
|
||||||
@@ -78,13 +94,13 @@ class DialogBox extends LitElement {
|
|||||||
: nothing}
|
: nothing}
|
||||||
${dialogTitle}
|
${dialogTitle}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</ha-dialog-header>
|
||||||
<div slot="content" id="dialog-box-description">
|
<div 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
|
||||||
dialogInitialFocus
|
autofocus
|
||||||
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
|
||||||
@@ -99,10 +115,11 @@ class DialogBox extends LitElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
<div slot="actions">
|
<ha-dialog-footer slot="footer">
|
||||||
${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"
|
||||||
@@ -114,6 +131,7 @@ 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"}
|
||||||
@@ -122,8 +140,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>
|
||||||
</div>
|
</ha-dialog-footer>
|
||||||
</ha-md-dialog>
|
</ha-wa-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,20 +166,20 @@ class DialogBox extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _closeDialog() {
|
private _closeDialog() {
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
this._open = false;
|
||||||
this._dialog?.close();
|
|
||||||
this._closePromise = new Promise((resolve) => {
|
this._closePromise = new Promise((resolve) => {
|
||||||
this._closeResolve = resolve;
|
this._closeResolve = resolve;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dialogClosed() {
|
private _dialogClosed() {
|
||||||
if (!this._closeState) {
|
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
if (!this._closeState) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -187,6 +205,14 @@ 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 type { HASSDomEvent, ValidHassDomEvent } from "../common/dom/fire_event";
|
|
||||||
import { mainWindow } from "../common/dom/get_main_window";
|
|
||||||
import type { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
|
|
||||||
import { ancestorsWithProperty } from "../common/dom/ancestors-with-property";
|
import { ancestorsWithProperty } from "../common/dom/ancestors-with-property";
|
||||||
import { deepActiveElement } from "../common/dom/deep-active-element";
|
import { deepActiveElement } from "../common/dom/deep-active-element";
|
||||||
|
import type { HASSDomEvent, ValidHassDomEvent } from "../common/dom/fire_event";
|
||||||
|
import { mainWindow } from "../common/dom/get_main_window";
|
||||||
import { nextRender } from "../common/util/render-status";
|
import { nextRender } from "../common/util/render-status";
|
||||||
|
import type { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
|
||||||
|
|
||||||
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?: () => boolean;
|
closeDialog?: (historyState?: any) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ShowDialogParams<T> {
|
interface ShowDialogParams<T> {
|
||||||
@@ -143,27 +143,32 @@ export const showDialog = async (
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const closeDialog = async (dialogTag: string): Promise<boolean> => {
|
export const closeDialog = async (
|
||||||
|
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() !== false;
|
return dialogElement.closeDialog(historyState) !== false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// called on back()
|
// called on back()
|
||||||
export const closeLastDialog = async () => {
|
export const closeLastDialog = async (historyState?: any) => {
|
||||||
if (OPEN_DIALOG_STACK.length) {
|
if (OPEN_DIALOG_STACK.length) {
|
||||||
const lastDialog = OPEN_DIALOG_STACK.pop();
|
const lastDialog = OPEN_DIALOG_STACK.pop() as DialogState;
|
||||||
const closed = await closeDialog(lastDialog!.dialogTag);
|
const closed = await closeDialog(lastDialog.dialogTag, historyState);
|
||||||
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 (
|
||||||
if (OPEN_DIALOG_STACK.length && mainWindow.history.state?.opensDialog) {
|
OPEN_DIALOG_STACK.length &&
|
||||||
|
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 },
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ import { LitElement, css, html, nothing } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../components/ha-alert";
|
import "../../components/ha-alert";
|
||||||
import "../../components/ha-icon";
|
import "../../components/ha-icon";
|
||||||
import "../../components/ha-list-item";
|
import "../../components/ha-md-list-item";
|
||||||
import "../../components/ha-spinner";
|
import "../../components/ha-spinner";
|
||||||
import type {
|
import type {
|
||||||
ExternalEntityAddToActions,
|
|
||||||
ExternalEntityAddToAction,
|
ExternalEntityAddToAction,
|
||||||
|
ExternalEntityAddToActions,
|
||||||
} from "../../external_app/external_messaging";
|
} from "../../external_app/external_messaging";
|
||||||
import { showToast } from "../../util/toast";
|
import { showToast } from "../../util/toast";
|
||||||
|
|
||||||
import type { HomeAssistant } from "../../types";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
@customElement("ha-more-info-add-to")
|
@customElement("ha-more-info-add-to")
|
||||||
export class HaMoreInfoAddTo extends LitElement {
|
export class HaMoreInfoAddTo extends LitElement {
|
||||||
@@ -93,19 +93,18 @@ export class HaMoreInfoAddTo extends LitElement {
|
|||||||
<div class="actions-list">
|
<div class="actions-list">
|
||||||
${this._externalActions.actions.map(
|
${this._externalActions.actions.map(
|
||||||
(action) => html`
|
(action) => html`
|
||||||
<ha-list-item
|
<ha-md-list-item
|
||||||
graphic="icon"
|
type="button"
|
||||||
.disabled=${!action.enabled}
|
.disabled=${!action.enabled}
|
||||||
.action=${action}
|
.action=${action}
|
||||||
.twoline=${!!action.details}
|
|
||||||
@click=${this._actionSelected}
|
@click=${this._actionSelected}
|
||||||
>
|
>
|
||||||
|
<ha-icon slot="start" .icon=${action.mdi_icon}></ha-icon>
|
||||||
<span>${action.name}</span>
|
<span>${action.name}</span>
|
||||||
${action.details
|
${action.details
|
||||||
? html`<span slot="secondary">${action.details}</span>`
|
? html`<span slot="supporting-text">${action.details}</span>`
|
||||||
: nothing}
|
: nothing}
|
||||||
<ha-icon slot="graphic" .icon=${action.mdi_icon}></ha-icon>
|
</ha-md-list-item>
|
||||||
</ha-list-item>
|
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -131,15 +130,6 @@ export class HaMoreInfoAddTo extends LitElement {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-list-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-list-item[disabled] {
|
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-icon {
|
ha-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -302,7 +302,9 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _goToAddEntityTo(ev) {
|
private _goToAddEntityTo(ev) {
|
||||||
if (!shouldHandleRequestSelectedEvent(ev)) return;
|
// Only check for request-selected events (from menu items), not regular clicks (from icon button)
|
||||||
|
if (ev.type === "request-selected" && !shouldHandleRequestSelectedEvent(ev))
|
||||||
|
return;
|
||||||
this._setView("add_to");
|
this._setView("add_to");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,6 +552,17 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
: nothing}
|
: nothing}
|
||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
`
|
`
|
||||||
|
: !__DEMO__ && this._shouldShowAddEntityTo()
|
||||||
|
? 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}
|
: nothing}
|
||||||
`
|
`
|
||||||
: isSpecificInitialView
|
: isSpecificInitialView
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import "@material/mwc-linear-progress/mwc-linear-progress";
|
import "@material/mwc-linear-progress/mwc-linear-progress";
|
||||||
import { mdiClose } from "@mdi/js";
|
import { mdiClose, mdiDotsVertical, mdiRestart } from "@mdi/js";
|
||||||
import { css, html, LitElement, nothing, type TemplateResult } from "lit";
|
import { css, html, LitElement, nothing, type TemplateResult } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@@ -9,18 +9,30 @@ import "../../components/ha-dialog-header";
|
|||||||
import "../../components/ha-fade-in";
|
import "../../components/ha-fade-in";
|
||||||
import "../../components/ha-icon-button";
|
import "../../components/ha-icon-button";
|
||||||
import "../../components/ha-items-display-editor";
|
import "../../components/ha-items-display-editor";
|
||||||
import type { DisplayValue } from "../../components/ha-items-display-editor";
|
import type {
|
||||||
|
DisplayItem,
|
||||||
|
DisplayValue,
|
||||||
|
} from "../../components/ha-items-display-editor";
|
||||||
|
import "../../components/ha-md-button-menu";
|
||||||
import "../../components/ha-md-dialog";
|
import "../../components/ha-md-dialog";
|
||||||
import type { HaMdDialog } from "../../components/ha-md-dialog";
|
import type { HaMdDialog } from "../../components/ha-md-dialog";
|
||||||
import { computePanels, PANEL_ICONS } from "../../components/ha-sidebar";
|
import "../../components/ha-md-menu-item";
|
||||||
|
import { computePanels } from "../../components/ha-sidebar";
|
||||||
import "../../components/ha-spinner";
|
import "../../components/ha-spinner";
|
||||||
|
import "../../components/ha-svg-icon";
|
||||||
import {
|
import {
|
||||||
fetchFrontendUserData,
|
fetchFrontendUserData,
|
||||||
saveFrontendUserData,
|
saveFrontendUserData,
|
||||||
} from "../../data/frontend";
|
} from "../../data/frontend";
|
||||||
|
import {
|
||||||
|
getDefaultPanelUrlPath,
|
||||||
|
getPanelIcon,
|
||||||
|
getPanelIconPath,
|
||||||
|
getPanelTitle,
|
||||||
|
SHOW_AFTER_SPACER_PANELS,
|
||||||
|
} from "../../data/panel";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||||
import { getDefaultPanelUrlPath } from "../../data/panel";
|
|
||||||
|
|
||||||
@customElement("dialog-edit-sidebar")
|
@customElement("dialog-edit-sidebar")
|
||||||
class DialogEditSidebar extends LitElement {
|
class DialogEditSidebar extends LitElement {
|
||||||
@@ -105,48 +117,53 @@ class DialogEditSidebar extends LitElement {
|
|||||||
this.hass.locale
|
this.hass.locale
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add default hidden panels that are missing in hidden
|
const orderSet = new Set(this._order);
|
||||||
|
const hiddenSet = new Set(this._hidden);
|
||||||
|
|
||||||
for (const panel of panels) {
|
for (const panel of panels) {
|
||||||
if (
|
if (
|
||||||
panel.default_visible === false &&
|
panel.default_visible === false &&
|
||||||
!this._order.includes(panel.url_path) &&
|
!orderSet.has(panel.url_path) &&
|
||||||
!this._hidden.includes(panel.url_path)
|
!hiddenSet.has(panel.url_path)
|
||||||
) {
|
) {
|
||||||
this._hidden.push(panel.url_path);
|
hiddenSet.add(panel.url_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hiddenSet.has(defaultPanel)) {
|
||||||
|
hiddenSet.delete(defaultPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hiddenPanels = Array.from(hiddenSet);
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
...beforeSpacer,
|
...beforeSpacer,
|
||||||
...panels.filter((panel) => this._hidden!.includes(panel.url_path)),
|
...panels.filter((panel) => hiddenPanels.includes(panel.url_path)),
|
||||||
...afterSpacer.filter((panel) => panel.url_path !== "config"),
|
...afterSpacer,
|
||||||
].map((panel) => ({
|
].map<DisplayItem>((panel) => ({
|
||||||
value: panel.url_path,
|
value: panel.url_path,
|
||||||
label:
|
label:
|
||||||
panel.url_path === defaultPanel
|
(getPanelTitle(this.hass, panel) || panel.url_path) +
|
||||||
? panel.title || this.hass.localize("panel.states")
|
`${defaultPanel === panel.url_path ? " (default)" : ""}`,
|
||||||
: this.hass.localize(`panel.${panel.title}`) || panel.title || "?",
|
icon: getPanelIcon(panel),
|
||||||
icon: panel.icon || undefined,
|
iconPath: getPanelIconPath(panel),
|
||||||
iconPath:
|
disableSorting: SHOW_AFTER_SPACER_PANELS.includes(panel.url_path),
|
||||||
panel.url_path === defaultPanel && !panel.icon
|
disableHiding: panel.url_path === defaultPanel,
|
||||||
? PANEL_ICONS.lovelace
|
|
||||||
: panel.url_path in PANEL_ICONS
|
|
||||||
? PANEL_ICONS[panel.url_path]
|
|
||||||
: undefined,
|
|
||||||
disableSorting: panel.url_path === "developer-tools",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return html`<ha-items-display-editor
|
return html`
|
||||||
|
<ha-items-display-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${{
|
.value=${{
|
||||||
order: this._order,
|
order: this._order,
|
||||||
hidden: this._hidden,
|
hidden: hiddenPanels,
|
||||||
}}
|
}}
|
||||||
.items=${items}
|
.items=${items}
|
||||||
@value-changed=${this._changed}
|
@value-changed=${this._changed}
|
||||||
dont-sort-visible
|
dont-sort-visible
|
||||||
>
|
>
|
||||||
</ha-items-display-editor>`;
|
</ha-items-display-editor>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -171,6 +188,22 @@ class DialogEditSidebar extends LitElement {
|
|||||||
>${this.hass.localize("ui.sidebar.edit_subtitle")}</span
|
>${this.hass.localize("ui.sidebar.edit_subtitle")}</span
|
||||||
>`
|
>`
|
||||||
: nothing}
|
: nothing}
|
||||||
|
<ha-md-button-menu
|
||||||
|
slot="actionItems"
|
||||||
|
positioning="popover"
|
||||||
|
anchor-corner="end-end"
|
||||||
|
menu-corner="start-end"
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
slot="trigger"
|
||||||
|
.label=${this.hass.localize("ui.common.menu")}
|
||||||
|
.path=${mdiDotsVertical}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-md-menu-item .clickAction=${this._resetToDefaults}>
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiRestart}></ha-svg-icon>
|
||||||
|
${this.hass.localize("ui.sidebar.reset_to_defaults")}
|
||||||
|
</ha-md-menu-item>
|
||||||
|
</ha-md-button-menu>
|
||||||
</ha-dialog-header>
|
</ha-dialog-header>
|
||||||
<div slot="content" class="content">${this._renderContent()}</div>
|
<div slot="content" class="content">${this._renderContent()}</div>
|
||||||
<div slot="actions">
|
<div slot="actions">
|
||||||
@@ -194,6 +227,26 @@ class DialogEditSidebar extends LitElement {
|
|||||||
this._hidden = [...hidden];
|
this._hidden = [...hidden];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _resetToDefaults = async () => {
|
||||||
|
const confirmation = await showConfirmationDialog(this, {
|
||||||
|
text: this.hass.localize("ui.sidebar.reset_confirmation"),
|
||||||
|
confirmText: this.hass.localize("ui.common.reset"),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._order = [];
|
||||||
|
this._hidden = [];
|
||||||
|
try {
|
||||||
|
await saveFrontendUserData(this.hass.connection, "sidebar", {});
|
||||||
|
} catch (err: any) {
|
||||||
|
this._error = err.message || err;
|
||||||
|
}
|
||||||
|
this.closeDialog();
|
||||||
|
};
|
||||||
|
|
||||||
private async _save() {
|
private async _save() {
|
||||||
if (this._migrateToUserData) {
|
if (this._migrateToUserData) {
|
||||||
const confirmation = await showConfirmationDialog(this, {
|
const confirmation = await showConfirmationDialog(this, {
|
||||||
|
|||||||
@@ -143,7 +143,6 @@ 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 {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { listenMediaQuery } from "../common/dom/media_query";
|
|||||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||||
import "../components/ha-drawer";
|
import "../components/ha-drawer";
|
||||||
|
import "../components/ha-snowflakes";
|
||||||
import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer";
|
import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer";
|
||||||
import type { HomeAssistant, Route } from "../types";
|
import type { HomeAssistant, Route } from "../types";
|
||||||
import "./partial-panel-resolver";
|
import "./partial-panel-resolver";
|
||||||
@@ -50,6 +51,7 @@ export class HomeAssistantMain extends LitElement {
|
|||||||
this.hass.panels && this.hass.userData && this.hass.systemData;
|
this.hass.panels && this.hass.userData && this.hass.systemData;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
|
<ha-snowflakes .hass=${this.hass} .narrow=${this.narrow}></ha-snowflakes>
|
||||||
<ha-drawer
|
<ha-drawer
|
||||||
.type=${sidebarNarrow ? "modal" : ""}
|
.type=${sidebarNarrow ? "modal" : ""}
|
||||||
.open=${sidebarNarrow ? this._drawerOpen : false}
|
.open=${sidebarNarrow ? this._drawerOpen : false}
|
||||||
|
|||||||
187
src/mixins/scrollable-fade-mixin.ts
Normal file
187
src/mixins/scrollable-fade-mixin.ts
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||||
|
import { css, html } from "lit";
|
||||||
|
import type {
|
||||||
|
CSSResultGroup,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import { state } from "lit/decorators";
|
||||||
|
import type { Constructor } from "../types";
|
||||||
|
|
||||||
|
const stylesArray = (styles?: CSSResultGroup | CSSResultGroup[]) =>
|
||||||
|
styles === undefined ? [] : Array.isArray(styles) ? styles : [styles];
|
||||||
|
|
||||||
|
export const ScrollableFadeMixin = <T extends Constructor<LitElement>>(
|
||||||
|
superClass: T
|
||||||
|
) => {
|
||||||
|
class ScrollableFadeClass extends superClass {
|
||||||
|
@state() protected _contentScrolled = false;
|
||||||
|
|
||||||
|
@state() protected _contentScrollable = false;
|
||||||
|
|
||||||
|
private _scrollTarget?: HTMLElement | null;
|
||||||
|
|
||||||
|
private _onScroll = (ev: Event) => {
|
||||||
|
const target = ev.currentTarget as HTMLElement;
|
||||||
|
this._contentScrolled = (target.scrollTop ?? 0) > 0;
|
||||||
|
this._updateScrollableState(target);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _resize = new ResizeController(this, {
|
||||||
|
target: null,
|
||||||
|
callback: (entries) => {
|
||||||
|
const target = entries[0]?.target as HTMLElement | undefined;
|
||||||
|
if (target) {
|
||||||
|
this._updateScrollableState(target);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
private static readonly DEFAULT_SAFE_AREA_PADDING = 16;
|
||||||
|
|
||||||
|
private static readonly DEFAULT_SCROLLABLE_ELEMENT: HTMLElement | null =
|
||||||
|
null;
|
||||||
|
|
||||||
|
protected get scrollFadeSafeAreaPadding() {
|
||||||
|
return ScrollableFadeClass.DEFAULT_SAFE_AREA_PADDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get scrollableElement(): HTMLElement | null {
|
||||||
|
return ScrollableFadeClass.DEFAULT_SCROLLABLE_ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProperties: PropertyValues) {
|
||||||
|
super.firstUpdated?.(changedProperties);
|
||||||
|
this._attachScrollableElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProperties: PropertyValues) {
|
||||||
|
super.updated?.(changedProperties);
|
||||||
|
this._attachScrollableElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
this._detachScrollableElement();
|
||||||
|
super.disconnectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderScrollableFades(rounded = false): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class=${classMap({
|
||||||
|
"fade-top": true,
|
||||||
|
rounded,
|
||||||
|
visible: this._contentScrolled,
|
||||||
|
})}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class=${classMap({
|
||||||
|
"fade-bottom": true,
|
||||||
|
rounded,
|
||||||
|
visible: this._contentScrollable,
|
||||||
|
})}
|
||||||
|
></div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
const superCtor = Object.getPrototypeOf(this) as
|
||||||
|
| typeof LitElement
|
||||||
|
| undefined;
|
||||||
|
const inheritedStyles = stylesArray(
|
||||||
|
(superCtor?.styles ?? []) as CSSResultGroup | CSSResultGroup[]
|
||||||
|
);
|
||||||
|
return [
|
||||||
|
...inheritedStyles,
|
||||||
|
css`
|
||||||
|
.fade-top,
|
||||||
|
.fade-bottom {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha-space-0);
|
||||||
|
right: var(--ha-space-0);
|
||||||
|
height: var(--ha-space-4);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 180ms ease-in-out;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
var(--shadow-color),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
border-radius: var(--ha-border-radius-square);
|
||||||
|
z-index: 100;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.fade-top {
|
||||||
|
top: var(--ha-space-0);
|
||||||
|
}
|
||||||
|
.fade-bottom {
|
||||||
|
bottom: var(--ha-space-0);
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-top.visible,
|
||||||
|
.fade-bottom.visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-top.rounded,
|
||||||
|
.fade-bottom.rounded {
|
||||||
|
border-radius: var(
|
||||||
|
--ha-card-border-radius,
|
||||||
|
var(--ha-border-radius-lg)
|
||||||
|
);
|
||||||
|
border-bottom-left-radius: var(--ha-border-radius-square);
|
||||||
|
border-bottom-right-radius: var(--ha-border-radius-square);
|
||||||
|
}
|
||||||
|
.fade-top.rounded {
|
||||||
|
border-top-left-radius: var(--ha-border-radius-square);
|
||||||
|
border-top-right-radius: var(--ha-border-radius-square);
|
||||||
|
}
|
||||||
|
.fade-bottom.rounded {
|
||||||
|
border-bottom-left-radius: var(--ha-border-radius-square);
|
||||||
|
border-bottom-right-radius: var(--ha-border-radius-square);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _attachScrollableElement() {
|
||||||
|
const element = this.scrollableElement;
|
||||||
|
if (element === this._scrollTarget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._detachScrollableElement();
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._scrollTarget = element;
|
||||||
|
element.addEventListener("scroll", this._onScroll, { passive: true });
|
||||||
|
this._resize.observe(element);
|
||||||
|
this._updateScrollableState(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _detachScrollableElement() {
|
||||||
|
if (!this._scrollTarget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._scrollTarget.removeEventListener("scroll", this._onScroll);
|
||||||
|
this._resize.unobserve?.(this._scrollTarget);
|
||||||
|
this._scrollTarget = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateScrollableState(element: HTMLElement) {
|
||||||
|
const safeAreaInsetBottom =
|
||||||
|
parseFloat(
|
||||||
|
getComputedStyle(element).getPropertyValue("--safe-area-inset-bottom")
|
||||||
|
) || 0;
|
||||||
|
const { scrollHeight = 0, clientHeight = 0, scrollTop = 0 } = element;
|
||||||
|
this._contentScrollable =
|
||||||
|
scrollHeight - clientHeight >
|
||||||
|
scrollTop + safeAreaInsetBottom + this.scrollFadeSafeAreaPadding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScrollableFadeClass;
|
||||||
|
};
|
||||||
@@ -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._calendars = getCalendars(this.hass, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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._calendars = getCalendars(this.hass, this);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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: {
|
||||||
@@ -115,6 +116,7 @@ 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}
|
||||||
|
|||||||
496
src/panels/config/areas/dialog-areas-floors-order.ts
Normal file
496
src/panels/config/areas/dialog-areas-floors-order.ts
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
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,6 +144,10 @@ 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,7 +21,6 @@ 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";
|
||||||
@@ -42,7 +41,6 @@ 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 {
|
||||||
@@ -58,6 +56,7 @@ 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__";
|
||||||
@@ -84,6 +83,8 @@ 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;
|
||||||
@@ -167,7 +168,9 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.isWide=${this.isWide}
|
.isWide=${this.isWide}
|
||||||
back-path="/config"
|
.backPath=${this._searchParms.has("historyBack")
|
||||||
|
? undefined
|
||||||
|
: "/config"}
|
||||||
.tabs=${configSections.areas}
|
.tabs=${configSections.areas}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
has-fab
|
has-fab
|
||||||
@@ -179,14 +182,6 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
@click=${this._showHelp}
|
@click=${this._showHelp}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<ha-sortable
|
|
||||||
handle-selector=".handle"
|
|
||||||
draggable-selector=".floor"
|
|
||||||
@item-moved=${this._floorMoved}
|
|
||||||
.options=${SORT_OPTIONS}
|
|
||||||
group="floors"
|
|
||||||
invert-swap
|
|
||||||
>
|
|
||||||
<div class="floors">
|
<div class="floors">
|
||||||
${this._hierarchy.floors.map(({ areas, id }) => {
|
${this._hierarchy.floors.map(({ areas, id }) => {
|
||||||
const floor = this.hass.floors[id];
|
const floor = this.hass.floors[id];
|
||||||
@@ -201,10 +196,6 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
${floor.name}
|
${floor.name}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<ha-svg-icon
|
|
||||||
class="handle"
|
|
||||||
.path=${mdiDragHorizontalVariant}
|
|
||||||
></ha-svg-icon>
|
|
||||||
<ha-button-menu
|
<ha-button-menu
|
||||||
.floor=${floor}
|
.floor=${floor}
|
||||||
@action=${this._handleFloorAction}
|
@action=${this._handleFloorAction}
|
||||||
@@ -213,6 +204,16 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
slot="trigger"
|
slot="trigger"
|
||||||
.path=${mdiDotsVertical}
|
.path=${mdiDotsVertical}
|
||||||
></ha-icon-button>
|
></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
|
||||||
|
>
|
||||||
|
<li divider role="separator"></li>
|
||||||
<ha-list-item graphic="icon"
|
<ha-list-item graphic="icon"
|
||||||
><ha-svg-icon
|
><ha-svg-icon
|
||||||
.path=${mdiPencil}
|
.path=${mdiPencil}
|
||||||
@@ -259,7 +260,6 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</ha-sortable>
|
|
||||||
|
|
||||||
${this._hierarchy.areas.length
|
${this._hierarchy.areas.length
|
||||||
? html`
|
? html`
|
||||||
@@ -267,9 +267,30 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<h2>
|
<h2>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.areas.picker.unassigned_areas"
|
this._hierarchy.floors.length
|
||||||
|
? "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"
|
||||||
@@ -391,51 +412,6 @@ 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) {
|
||||||
@@ -561,14 +537,23 @@ 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._editFloor(floor);
|
this._showReorderDialog();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
|
this._editFloor(floor);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
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();
|
||||||
}
|
}
|
||||||
@@ -598,6 +583,10 @@ 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"),
|
||||||
|
|||||||
17
src/panels/config/areas/show-dialog-areas-floors-order.ts
Normal file
17
src/panels/config/areas/show-dialog-areas-floors-order.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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,5 +1,6 @@
|
|||||||
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";
|
||||||
@@ -213,7 +214,7 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addAction = (action: string) => {
|
private _addAction = (action: string, target?: HassServiceTarget) => {
|
||||||
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));
|
||||||
@@ -223,6 +224,7 @@ 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(
|
||||||
|
|||||||
@@ -1,19 +1,29 @@
|
|||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import { stringCompare } from "../../../../../common/string/compare";
|
|
||||||
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
||||||
|
import { stringCompare } from "../../../../../common/string/compare";
|
||||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||||
|
import { CONDITION_ICONS } from "../../../../../components/ha-condition-icon";
|
||||||
import "../../../../../components/ha-list-item";
|
import "../../../../../components/ha-list-item";
|
||||||
import "../../../../../components/ha-select";
|
import "../../../../../components/ha-select";
|
||||||
import type { HaSelect } from "../../../../../components/ha-select";
|
import type { HaSelect } from "../../../../../components/ha-select";
|
||||||
import type { Condition } from "../../../../../data/automation";
|
import {
|
||||||
|
DYNAMIC_PREFIX,
|
||||||
|
getValueFromDynamic,
|
||||||
|
isDynamic,
|
||||||
|
type Condition,
|
||||||
|
} from "../../../../../data/automation";
|
||||||
|
import type { ConditionDescriptions } from "../../../../../data/condition";
|
||||||
import {
|
import {
|
||||||
CONDITION_BUILDING_BLOCKS,
|
CONDITION_BUILDING_BLOCKS,
|
||||||
CONDITION_ICONS,
|
getConditionDomain,
|
||||||
|
getConditionObjectId,
|
||||||
|
subscribeConditions,
|
||||||
} from "../../../../../data/condition";
|
} from "../../../../../data/condition";
|
||||||
import type { Entries, HomeAssistant } from "../../../../../types";
|
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
|
||||||
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../../condition/ha-automation-condition-editor";
|
import "../../condition/ha-automation-condition-editor";
|
||||||
import type HaAutomationConditionEditor from "../../condition/ha-automation-condition-editor";
|
import type HaAutomationConditionEditor from "../../condition/ha-automation-condition-editor";
|
||||||
import "../../condition/types/ha-automation-condition-and";
|
import "../../condition/types/ha-automation-condition-and";
|
||||||
@@ -30,7 +40,10 @@ import "../../condition/types/ha-automation-condition-zone";
|
|||||||
import type { ActionElement } from "../ha-automation-action-row";
|
import type { ActionElement } from "../ha-automation-action-row";
|
||||||
|
|
||||||
@customElement("ha-automation-action-condition")
|
@customElement("ha-automation-action-condition")
|
||||||
export class HaConditionAction extends LitElement implements ActionElement {
|
export class HaConditionAction
|
||||||
|
extends SubscribeMixin(LitElement)
|
||||||
|
implements ActionElement
|
||||||
|
{
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
@@ -43,6 +56,8 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
@property({ type: Boolean, attribute: "indent" }) public indent = false;
|
@property({ type: Boolean, attribute: "indent" }) public indent = false;
|
||||||
|
|
||||||
|
@state() private _conditionDescriptions: ConditionDescriptions = {};
|
||||||
|
|
||||||
@query("ha-automation-condition-editor")
|
@query("ha-automation-condition-editor")
|
||||||
private _conditionEditor?: HaAutomationConditionEditor;
|
private _conditionEditor?: HaAutomationConditionEditor;
|
||||||
|
|
||||||
@@ -50,6 +65,21 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
return { condition: "state" };
|
return { condition: "state" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected hassSubscribe() {
|
||||||
|
return [
|
||||||
|
subscribeConditions(this.hass, (conditions) =>
|
||||||
|
this._addConditions(conditions)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addConditions(conditions: ConditionDescriptions) {
|
||||||
|
this._conditionDescriptions = {
|
||||||
|
...this._conditionDescriptions,
|
||||||
|
...conditions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const buildingBlock = CONDITION_BUILDING_BLOCKS.includes(
|
const buildingBlock = CONDITION_BUILDING_BLOCKS.includes(
|
||||||
this.action.condition
|
this.action.condition
|
||||||
@@ -64,19 +94,25 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
"ui.panel.config.automation.editor.conditions.type_select"
|
"ui.panel.config.automation.editor.conditions.type_select"
|
||||||
)}
|
)}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.value=${this.action.condition}
|
.value=${this.action.condition in this._conditionDescriptions
|
||||||
|
? `${DYNAMIC_PREFIX}${this.action.condition}`
|
||||||
|
: this.action.condition}
|
||||||
naturalMenuWidth
|
naturalMenuWidth
|
||||||
@selected=${this._typeChanged}
|
@selected=${this._typeChanged}
|
||||||
@closed=${stopPropagation}
|
@closed=${stopPropagation}
|
||||||
>
|
>
|
||||||
${this._processedTypes(this.hass.localize).map(
|
${this._processedTypes(
|
||||||
([opt, label, icon]) => html`
|
this._conditionDescriptions,
|
||||||
|
this.hass.localize
|
||||||
|
).map(
|
||||||
|
([opt, label, condition]) => html`
|
||||||
<ha-list-item .value=${opt} graphic="icon">
|
<ha-list-item .value=${opt} graphic="icon">
|
||||||
${label}<ha-svg-icon
|
${label}
|
||||||
|
<ha-condition-icon
|
||||||
slot="graphic"
|
slot="graphic"
|
||||||
.path=${icon}
|
.condition=${condition}
|
||||||
></ha-svg-icon
|
></ha-condition-icon>
|
||||||
></ha-list-item>
|
</ha-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</ha-select>
|
</ha-select>
|
||||||
@@ -88,11 +124,14 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
? html`
|
? html`
|
||||||
<ha-automation-condition-editor
|
<ha-automation-condition-editor
|
||||||
.condition=${this.action}
|
.condition=${this.action}
|
||||||
|
.description=${this._conditionDescriptions[this.action.condition]}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@value-changed=${this._conditionChanged}
|
@value-changed=${this._conditionChanged}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.uiSupported=${this._uiSupported(this.action.condition)}
|
.uiSupported=${this._uiSupported(
|
||||||
|
this._getType(this.action, this._conditionDescriptions)
|
||||||
|
)}
|
||||||
.indent=${this.indent}
|
.indent=${this.indent}
|
||||||
action
|
action
|
||||||
></ha-automation-condition-editor>
|
></ha-automation-condition-editor>
|
||||||
@@ -102,19 +141,46 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _processedTypes = memoizeOne(
|
private _processedTypes = memoizeOne(
|
||||||
(localize: LocalizeFunc): [string, string, string][] =>
|
(
|
||||||
(Object.entries(CONDITION_ICONS) as Entries<typeof CONDITION_ICONS>)
|
conditionDescriptions: ConditionDescriptions,
|
||||||
.map(
|
localize: LocalizeFunc
|
||||||
([condition, icon]) =>
|
): [string, string, string][] => {
|
||||||
|
const legacy = (
|
||||||
|
Object.keys(CONDITION_ICONS) as (keyof typeof CONDITION_ICONS)[]
|
||||||
|
).map(
|
||||||
|
(condition) =>
|
||||||
[
|
[
|
||||||
condition,
|
condition,
|
||||||
localize(
|
localize(
|
||||||
`ui.panel.config.automation.editor.conditions.type.${condition}.label`
|
`ui.panel.config.automation.editor.conditions.type.${condition}.label`
|
||||||
),
|
),
|
||||||
icon,
|
condition,
|
||||||
] as [string, string, string]
|
] as [string, string, string]
|
||||||
)
|
);
|
||||||
.sort((a, b) => stringCompare(a[1], b[1], this.hass.locale.language))
|
const platform = Object.keys(conditionDescriptions).map((condition) => {
|
||||||
|
const domain = getConditionDomain(condition);
|
||||||
|
const conditionObjId = getConditionObjectId(condition);
|
||||||
|
return [
|
||||||
|
`${DYNAMIC_PREFIX}${condition}`,
|
||||||
|
localize(`component.${domain}.conditions.${conditionObjId}.name`) ||
|
||||||
|
condition,
|
||||||
|
condition,
|
||||||
|
] as [string, string, string];
|
||||||
|
});
|
||||||
|
return [...legacy, ...platform].sort((a, b) =>
|
||||||
|
stringCompare(a[1], b[1], this.hass.locale.language)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _getType = memoizeOne(
|
||||||
|
(condition: Condition, conditionDescriptions: ConditionDescriptions) => {
|
||||||
|
if (condition.condition in conditionDescriptions) {
|
||||||
|
return "platform";
|
||||||
|
}
|
||||||
|
|
||||||
|
return condition.condition;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
private _conditionChanged(ev: CustomEvent) {
|
private _conditionChanged(ev: CustomEvent) {
|
||||||
@@ -132,6 +198,18 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isDynamic(type)) {
|
||||||
|
const value = getValueFromDynamic(type);
|
||||||
|
if (value !== this.action.condition) {
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
condition: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const elClass = customElements.get(
|
const elClass = customElements.get(
|
||||||
`ha-automation-condition-${type}`
|
`ha-automation-condition-${type}`
|
||||||
) as CustomElementConstructor & {
|
) as CustomElementConstructor & {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,391 @@
|
|||||||
|
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
@@ -8,11 +8,13 @@ import "../../../../components/ha-yaml-editor";
|
|||||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||||
import type { Condition } from "../../../../data/automation";
|
import type { Condition } from "../../../../data/automation";
|
||||||
import { expandConditionWithShorthand } from "../../../../data/automation";
|
import { expandConditionWithShorthand } from "../../../../data/automation";
|
||||||
|
import type { ConditionDescription } from "../../../../data/condition";
|
||||||
import { COLLAPSIBLE_CONDITION_ELEMENTS } from "../../../../data/condition";
|
import { COLLAPSIBLE_CONDITION_ELEMENTS } from "../../../../data/condition";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import "../ha-automation-editor-warning";
|
import "../ha-automation-editor-warning";
|
||||||
import { editorStyles, indentStyle } from "../styles";
|
import { editorStyles, indentStyle } from "../styles";
|
||||||
import type { ConditionElement } from "./ha-automation-condition-row";
|
import type { ConditionElement } from "./ha-automation-condition-row";
|
||||||
|
import "./types/ha-automation-condition-platform";
|
||||||
|
|
||||||
@customElement("ha-automation-condition-editor")
|
@customElement("ha-automation-condition-editor")
|
||||||
export default class HaAutomationConditionEditor extends LitElement {
|
export default class HaAutomationConditionEditor extends LitElement {
|
||||||
@@ -35,6 +37,8 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||||||
@property({ type: Boolean, attribute: "supported" }) public uiSupported =
|
@property({ type: Boolean, attribute: "supported" }) public uiSupported =
|
||||||
false;
|
false;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public description?: ConditionDescription;
|
||||||
|
|
||||||
@query("ha-yaml-editor") public yamlEditor?: HaYamlEditor;
|
@query("ha-yaml-editor") public yamlEditor?: HaYamlEditor;
|
||||||
|
|
||||||
@query(COLLAPSIBLE_CONDITION_ELEMENTS.join(", "))
|
@query(COLLAPSIBLE_CONDITION_ELEMENTS.join(", "))
|
||||||
@@ -83,7 +87,14 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<div @value-changed=${this._onUiChanged}>
|
<div @value-changed=${this._onUiChanged}>
|
||||||
${dynamicElement(
|
${this.description
|
||||||
|
? html`<ha-automation-condition-platform
|
||||||
|
.hass=${this.hass}
|
||||||
|
.condition=${this.condition}
|
||||||
|
.description=${this.description}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
></ha-automation-condition-platform>`
|
||||||
|
: dynamicElement(
|
||||||
`ha-automation-condition-${condition.condition}`,
|
`ha-automation-condition-${condition.condition}`,
|
||||||
{
|
{
|
||||||
hass: this.hass,
|
hass: this.hass,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
|||||||
import "../../../../components/ha-automation-row";
|
import "../../../../components/ha-automation-row";
|
||||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
|
import "../../../../components/ha-condition-icon";
|
||||||
import "../../../../components/ha-expansion-panel";
|
import "../../../../components/ha-expansion-panel";
|
||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
import "../../../../components/ha-md-button-menu";
|
import "../../../../components/ha-md-button-menu";
|
||||||
@@ -44,10 +45,8 @@ import type {
|
|||||||
} from "../../../../data/automation";
|
} from "../../../../data/automation";
|
||||||
import { isCondition, testCondition } from "../../../../data/automation";
|
import { isCondition, testCondition } from "../../../../data/automation";
|
||||||
import { describeCondition } from "../../../../data/automation_i18n";
|
import { describeCondition } from "../../../../data/automation_i18n";
|
||||||
import {
|
import type { ConditionDescriptions } from "../../../../data/condition";
|
||||||
CONDITION_BUILDING_BLOCKS,
|
import { CONDITION_BUILDING_BLOCKS } from "../../../../data/condition";
|
||||||
CONDITION_ICONS,
|
|
||||||
} from "../../../../data/condition";
|
|
||||||
import { validateConfig } from "../../../../data/config";
|
import { validateConfig } from "../../../../data/config";
|
||||||
import { fullEntitiesContext } from "../../../../data/context";
|
import { fullEntitiesContext } from "../../../../data/context";
|
||||||
import type { EntityRegistryEntry } from "../../../../data/entity_registry";
|
import type { EntityRegistryEntry } from "../../../../data/entity_registry";
|
||||||
@@ -130,6 +129,9 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
|
|
||||||
@state() private _warnings?: string[];
|
@state() private _warnings?: string[];
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public conditionDescriptions: ConditionDescriptions = {};
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "sidebar" })
|
@property({ type: Boolean, attribute: "sidebar" })
|
||||||
public optionsInSidebar = false;
|
public optionsInSidebar = false;
|
||||||
|
|
||||||
@@ -179,11 +181,11 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
|
|
||||||
private _renderRow() {
|
private _renderRow() {
|
||||||
return html`
|
return html`
|
||||||
<ha-svg-icon
|
<ha-condition-icon
|
||||||
slot="leading-icon"
|
slot="leading-icon"
|
||||||
class="condition-icon"
|
.hass=${this.hass}
|
||||||
.path=${CONDITION_ICONS[this.condition.condition]}
|
.condition=${this.condition.condition}
|
||||||
></ha-svg-icon>
|
></ha-condition-icon>
|
||||||
<h3 slot="header">
|
<h3 slot="header">
|
||||||
${capitalizeFirstLetter(
|
${capitalizeFirstLetter(
|
||||||
describeCondition(this.condition, this.hass, this._entityReg)
|
describeCondition(this.condition, this.hass, this._entityReg)
|
||||||
@@ -395,9 +397,14 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
<ha-automation-condition-editor
|
<ha-automation-condition-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.condition=${this.condition}
|
.condition=${this.condition}
|
||||||
|
.description=${this.conditionDescriptions[
|
||||||
|
this.condition.condition
|
||||||
|
]}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.yamlMode=${this._yamlMode}
|
.yamlMode=${this._yamlMode}
|
||||||
.uiSupported=${this._uiSupported(this.condition.condition)}
|
.uiSupported=${this._uiSupported(
|
||||||
|
this._getType(this.condition, this.conditionDescriptions)
|
||||||
|
)}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||||
></ha-automation-condition-editor>`
|
></ha-automation-condition-editor>`
|
||||||
@@ -476,7 +483,9 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.condition=${this.condition}
|
.condition=${this.condition}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.uiSupported=${this._uiSupported(this.condition.condition)}
|
.uiSupported=${this._uiSupported(
|
||||||
|
this._getType(this.condition, this.conditionDescriptions)
|
||||||
|
)}
|
||||||
indent
|
indent
|
||||||
.selected=${this._selected}
|
.selected=${this._selected}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
@@ -786,7 +795,10 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
cut: this._cutCondition,
|
cut: this._cutCondition,
|
||||||
test: this._testCondition,
|
test: this._testCondition,
|
||||||
config: sidebarCondition,
|
config: sidebarCondition,
|
||||||
uiSupported: this._uiSupported(sidebarCondition.condition),
|
uiSupported: this._uiSupported(
|
||||||
|
this._getType(sidebarCondition, this.conditionDescriptions)
|
||||||
|
),
|
||||||
|
description: this.conditionDescriptions[sidebarCondition.condition],
|
||||||
yamlMode: this._yamlMode,
|
yamlMode: this._yamlMode,
|
||||||
} satisfies ConditionSidebarConfig);
|
} satisfies ConditionSidebarConfig);
|
||||||
this._selected = true;
|
this._selected = true;
|
||||||
@@ -802,6 +814,16 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getType = memoizeOne(
|
||||||
|
(condition: Condition, conditionDescriptions: ConditionDescriptions) => {
|
||||||
|
if (condition.condition in conditionDescriptions) {
|
||||||
|
return "platform";
|
||||||
|
}
|
||||||
|
|
||||||
|
return condition.condition;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
private _uiSupported = memoizeOne(
|
private _uiSupported = memoizeOne(
|
||||||
(type: string) =>
|
(type: string) =>
|
||||||
customElements.get(`ha-automation-condition-${type}`) !== undefined
|
customElements.get(`ha-automation-condition-${type}`) !== undefined
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
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";
|
||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import { ensureArray } from "../../../../common/array/ensure-array";
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||||
@@ -12,11 +17,19 @@ import "../../../../components/ha-button";
|
|||||||
import "../../../../components/ha-button-menu";
|
import "../../../../components/ha-button-menu";
|
||||||
import "../../../../components/ha-sortable";
|
import "../../../../components/ha-sortable";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import type {
|
import {
|
||||||
AutomationClipboard,
|
getValueFromDynamic,
|
||||||
Condition,
|
isDynamic,
|
||||||
|
type AutomationClipboard,
|
||||||
|
type Condition,
|
||||||
} from "../../../../data/automation";
|
} from "../../../../data/automation";
|
||||||
import { CONDITION_BUILDING_BLOCKS } from "../../../../data/condition";
|
import type { ConditionDescriptions } from "../../../../data/condition";
|
||||||
|
import {
|
||||||
|
CONDITION_BUILDING_BLOCKS,
|
||||||
|
subscribeConditions,
|
||||||
|
} from "../../../../data/condition";
|
||||||
|
import { subscribeLabFeature } from "../../../../data/labs";
|
||||||
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import {
|
import {
|
||||||
PASTE_VALUE,
|
PASTE_VALUE,
|
||||||
@@ -25,10 +38,9 @@ import {
|
|||||||
import { automationRowsStyles } from "../styles";
|
import { automationRowsStyles } from "../styles";
|
||||||
import "./ha-automation-condition-row";
|
import "./ha-automation-condition-row";
|
||||||
import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
||||||
import { ensureArray } from "../../../../common/array/ensure-array";
|
|
||||||
|
|
||||||
@customElement("ha-automation-condition")
|
@customElement("ha-automation-condition")
|
||||||
export default class HaAutomationCondition extends LitElement {
|
export default class HaAutomationCondition extends SubscribeMixin(LitElement) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public conditions!: Condition[];
|
@property({ attribute: false }) public conditions!: Condition[];
|
||||||
@@ -46,6 +58,8 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
|
|
||||||
@state() private _rowSortSelected?: number;
|
@state() private _rowSortSelected?: number;
|
||||||
|
|
||||||
|
@state() private _conditionDescriptions: ConditionDescriptions = {};
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "automationClipboard",
|
key: "automationClipboard",
|
||||||
@@ -64,6 +78,59 @@ export default class HaAutomationCondition extends 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() {
|
||||||
|
return [
|
||||||
|
subscribeLabFeature(
|
||||||
|
this.hass!.connection,
|
||||||
|
"automation",
|
||||||
|
"new_triggers_conditions",
|
||||||
|
(feature) => {
|
||||||
|
this._newTriggersAndConditions = feature.enabled;
|
||||||
|
}
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _subscribeDescriptions() {
|
||||||
|
this._unsubscribe();
|
||||||
|
this._conditionDescriptions = {};
|
||||||
|
this._unsub = subscribeConditions(this.hass, (descriptions) => {
|
||||||
|
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) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this.hass.loadBackendTranslation("conditions");
|
||||||
|
}
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues) {
|
protected updated(changedProperties: PropertyValues) {
|
||||||
if (!changedProperties.has("conditions")) {
|
if (!changedProperties.has("conditions")) {
|
||||||
return;
|
return;
|
||||||
@@ -168,6 +235,7 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
.last=${idx === this.conditions.length - 1}
|
.last=${idx === this.conditions.length - 1}
|
||||||
.totalConditions=${this.conditions.length}
|
.totalConditions=${this.conditions.length}
|
||||||
.condition=${cond}
|
.condition=${cond}
|
||||||
|
.conditionDescriptions=${this._conditionDescriptions}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
@duplicate=${this._duplicateCondition}
|
@duplicate=${this._duplicateCondition}
|
||||||
@@ -231,12 +299,17 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addCondition = (value) => {
|
private _addCondition = (value: string, target?: HassServiceTarget) => {
|
||||||
let conditions: Condition[];
|
let conditions: Condition[];
|
||||||
if (value === PASTE_VALUE) {
|
if (value === PASTE_VALUE) {
|
||||||
conditions = this.conditions.concat(
|
conditions = this.conditions.concat(
|
||||||
deepClone(this._clipboard!.condition)
|
deepClone(this._clipboard!.condition)
|
||||||
);
|
);
|
||||||
|
} else if (isDynamic(value)) {
|
||||||
|
conditions = this.conditions.concat({
|
||||||
|
condition: getValueFromDynamic(value),
|
||||||
|
target,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const condition = value as Condition["condition"];
|
const condition = value as Condition["condition"];
|
||||||
const elClass = customElements.get(
|
const elClass = customElements.get(
|
||||||
|
|||||||
@@ -0,0 +1,459 @@
|
|||||||
|
import { mdiHelpCircle } from "@mdi/js";
|
||||||
|
import type { PropertyValues } from "lit";
|
||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { computeDomain } from "../../../../../common/entity/compute_domain";
|
||||||
|
import "../../../../../components/ha-checkbox";
|
||||||
|
import "../../../../../components/ha-selector/ha-selector";
|
||||||
|
import "../../../../../components/ha-settings-row";
|
||||||
|
import type { PlatformCondition } from "../../../../../data/automation";
|
||||||
|
import {
|
||||||
|
getConditionDomain,
|
||||||
|
getConditionObjectId,
|
||||||
|
type ConditionDescription,
|
||||||
|
} from "../../../../../data/condition";
|
||||||
|
import type { IntegrationManifest } from "../../../../../data/integration";
|
||||||
|
import { fetchIntegrationManifest } from "../../../../../data/integration";
|
||||||
|
import type { TargetSelector } from "../../../../../data/selector";
|
||||||
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
import { documentationUrl } from "../../../../../util/documentation-url";
|
||||||
|
|
||||||
|
const showOptionalToggle = (field: ConditionDescription["fields"][string]) =>
|
||||||
|
field.selector &&
|
||||||
|
!field.required &&
|
||||||
|
!("boolean" in field.selector && field.default);
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition-platform")
|
||||||
|
export class HaPlatformCondition extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public condition!: PlatformCondition;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public description?: ConditionDescription;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@state() private _checkedKeys = new Set();
|
||||||
|
|
||||||
|
@state() private _manifest?: IntegrationManifest;
|
||||||
|
|
||||||
|
public static get defaultConfig(): PlatformCondition {
|
||||||
|
return { condition: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(changedProperties: PropertyValues<this>) {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
if (!this.hasUpdated) {
|
||||||
|
this.hass.loadBackendTranslation("conditions");
|
||||||
|
this.hass.loadBackendTranslation("selector");
|
||||||
|
}
|
||||||
|
if (!changedProperties.has("condition")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const oldValue = changedProperties.get("condition") as
|
||||||
|
| undefined
|
||||||
|
| this["condition"];
|
||||||
|
|
||||||
|
// Fetch the manifest if we have a condition selected and the condition domain changed.
|
||||||
|
// If no condition is selected, clear the manifest.
|
||||||
|
if (this.condition?.condition) {
|
||||||
|
const domain = getConditionDomain(this.condition.condition);
|
||||||
|
|
||||||
|
const oldDomain = getConditionDomain(oldValue?.condition || "");
|
||||||
|
|
||||||
|
if (domain !== oldDomain) {
|
||||||
|
this._fetchManifest(domain);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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() {
|
||||||
|
const domain = getConditionDomain(this.condition.condition);
|
||||||
|
const conditionName = getConditionObjectId(this.condition.condition);
|
||||||
|
|
||||||
|
const description = this.hass.localize(
|
||||||
|
`component.${domain}.conditions.${conditionName}.description`
|
||||||
|
);
|
||||||
|
|
||||||
|
const conditionDesc = this.description;
|
||||||
|
|
||||||
|
const shouldRenderDataYaml = !conditionDesc?.fields;
|
||||||
|
|
||||||
|
const hasOptional = Boolean(
|
||||||
|
conditionDesc?.fields &&
|
||||||
|
Object.values(conditionDesc.fields).some((field) =>
|
||||||
|
showOptionalToggle(field)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="description">
|
||||||
|
${description ? html`<p>${description}</p>` : nothing}
|
||||||
|
${this._manifest
|
||||||
|
? html`<a
|
||||||
|
href=${this._manifest.is_built_in
|
||||||
|
? documentationUrl(
|
||||||
|
this.hass,
|
||||||
|
`/integrations/${this._manifest.domain}`
|
||||||
|
)
|
||||||
|
: this._manifest.documentation}
|
||||||
|
title=${this.hass.localize(
|
||||||
|
"ui.components.service-control.integration_doc"
|
||||||
|
)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
.path=${mdiHelpCircle}
|
||||||
|
class="help-icon"
|
||||||
|
></ha-icon-button>
|
||||||
|
</a>`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
${conditionDesc && "target" in conditionDesc
|
||||||
|
? html`<ha-settings-row narrow>
|
||||||
|
${hasOptional
|
||||||
|
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
||||||
|
: nothing}
|
||||||
|
<span slot="heading"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.components.service-control.target"
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<span slot="description"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.components.service-control.target_secondary"
|
||||||
|
)}</span
|
||||||
|
><ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${this._targetSelector(conditionDesc.target)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@value-changed=${this._targetChanged}
|
||||||
|
.value=${this.condition?.target}
|
||||||
|
></ha-selector
|
||||||
|
></ha-settings-row>`
|
||||||
|
: nothing}
|
||||||
|
${shouldRenderDataYaml
|
||||||
|
? html`<ha-yaml-editor
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.components.service-control.action_data"
|
||||||
|
)}
|
||||||
|
.name=${"data"}
|
||||||
|
.readOnly=${this.disabled}
|
||||||
|
.defaultValue=${this.condition?.options}
|
||||||
|
@value-changed=${this._dataChanged}
|
||||||
|
></ha-yaml-editor>`
|
||||||
|
: Object.entries(conditionDesc.fields).map(([fieldName, dataField]) =>
|
||||||
|
this._renderField(
|
||||||
|
fieldName,
|
||||||
|
dataField,
|
||||||
|
hasOptional,
|
||||||
|
domain,
|
||||||
|
conditionName
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _targetSelector = memoizeOne(
|
||||||
|
(targetSelector: TargetSelector["target"] | null | undefined) =>
|
||||||
|
targetSelector ? { target: { ...targetSelector } } : { target: {} }
|
||||||
|
);
|
||||||
|
|
||||||
|
private _renderField = (
|
||||||
|
fieldName: string,
|
||||||
|
dataField: ConditionDescription["fields"][string],
|
||||||
|
hasOptional: boolean,
|
||||||
|
domain: string | undefined,
|
||||||
|
conditionName: string | undefined
|
||||||
|
) => {
|
||||||
|
const selector = dataField?.selector ?? { text: null };
|
||||||
|
|
||||||
|
const showOptional = showOptionalToggle(dataField);
|
||||||
|
|
||||||
|
return dataField.selector
|
||||||
|
? html`<ha-settings-row narrow>
|
||||||
|
${!showOptional
|
||||||
|
? hasOptional
|
||||||
|
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
||||||
|
: nothing
|
||||||
|
: html`<ha-checkbox
|
||||||
|
.key=${fieldName}
|
||||||
|
.checked=${this._checkedKeys.has(fieldName) ||
|
||||||
|
(this.condition?.options &&
|
||||||
|
this.condition.options[fieldName] !== undefined)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@change=${this._checkboxChanged}
|
||||||
|
slot="prefix"
|
||||||
|
></ha-checkbox>`}
|
||||||
|
<span slot="heading"
|
||||||
|
>${this.hass.localize(
|
||||||
|
`component.${domain}.conditions.${conditionName}.fields.${fieldName}.name`
|
||||||
|
) || conditionName}</span
|
||||||
|
>
|
||||||
|
<span slot="description"
|
||||||
|
>${this.hass.localize(
|
||||||
|
`component.${domain}.conditions.${conditionName}.fields.${fieldName}.description`
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<ha-selector
|
||||||
|
.disabled=${this.disabled ||
|
||||||
|
(showOptional &&
|
||||||
|
!this._checkedKeys.has(fieldName) &&
|
||||||
|
(!this.condition?.options ||
|
||||||
|
this.condition.options[fieldName] === undefined))}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${selector}
|
||||||
|
.context=${this._generateContext(dataField)}
|
||||||
|
.key=${fieldName}
|
||||||
|
@value-changed=${this._dataChanged}
|
||||||
|
.value=${this.condition?.options
|
||||||
|
? this.condition.options[fieldName]
|
||||||
|
: undefined}
|
||||||
|
.placeholder=${dataField.default}
|
||||||
|
.localizeValue=${this._localizeValueCallback}
|
||||||
|
></ha-selector>
|
||||||
|
</ha-settings-row>`
|
||||||
|
: nothing;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _generateContext(
|
||||||
|
field: ConditionDescription["fields"][string]
|
||||||
|
): Record<string, any> | undefined {
|
||||||
|
if (!field.context) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = {};
|
||||||
|
for (const [context_key, data_key] of Object.entries(field.context)) {
|
||||||
|
context[context_key] =
|
||||||
|
data_key === "target"
|
||||||
|
? this.condition.target
|
||||||
|
: this.condition.options?.[data_key];
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _dataChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (ev.detail.isValid === false) {
|
||||||
|
// Don't clear an object selector that returns invalid YAML
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const key = (ev.currentTarget as any).key;
|
||||||
|
const value = ev.detail.value;
|
||||||
|
if (
|
||||||
|
this.condition?.options?.[key] === value ||
|
||||||
|
((!this.condition?.options || !(key in this.condition.options)) &&
|
||||||
|
(value === "" || value === undefined))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = { ...this.condition?.options, [key]: value };
|
||||||
|
|
||||||
|
if (
|
||||||
|
value === "" ||
|
||||||
|
value === undefined ||
|
||||||
|
(typeof value === "object" && !Object.keys(value).length)
|
||||||
|
) {
|
||||||
|
delete options[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.condition,
|
||||||
|
options,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _targetChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.condition,
|
||||||
|
target: ev.detail.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _checkboxChanged(ev) {
|
||||||
|
const checked = ev.currentTarget.checked;
|
||||||
|
const key = ev.currentTarget.key;
|
||||||
|
let options;
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
this._checkedKeys.add(key);
|
||||||
|
const field =
|
||||||
|
this.description &&
|
||||||
|
Object.entries(this.description).find(([k, _value]) => k === key)?.[1];
|
||||||
|
let defaultValue = field?.default;
|
||||||
|
|
||||||
|
if (
|
||||||
|
defaultValue == null &&
|
||||||
|
field?.selector &&
|
||||||
|
"constant" in field.selector
|
||||||
|
) {
|
||||||
|
defaultValue = field.selector.constant?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
defaultValue == null &&
|
||||||
|
field?.selector &&
|
||||||
|
"boolean" in field.selector
|
||||||
|
) {
|
||||||
|
defaultValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultValue != null) {
|
||||||
|
options = {
|
||||||
|
...this.condition?.options,
|
||||||
|
[key]: defaultValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._checkedKeys.delete(key);
|
||||||
|
options = { ...this.condition?.options };
|
||||||
|
delete options[key];
|
||||||
|
}
|
||||||
|
if (options) {
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.condition,
|
||||||
|
options,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.requestUpdate("_checkedKeys");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _localizeValueCallback = (key: string) => {
|
||||||
|
if (!this.condition?.condition) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.hass.localize(
|
||||||
|
`component.${computeDomain(this.condition.condition)}.selector.${key}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private async _fetchManifest(integration: string) {
|
||||||
|
this._manifest = undefined;
|
||||||
|
try {
|
||||||
|
this._manifest = await fetchIntegrationManifest(this.hass, integration);
|
||||||
|
} catch (_err: any) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Unable to fetch integration manifest for ${integration}`);
|
||||||
|
// Ignore if loading manifest fails. Probably bad JSON in manifest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
margin: 0px calc(-1 * var(--ha-space-4));
|
||||||
|
}
|
||||||
|
ha-settings-row {
|
||||||
|
padding: 0 var(--ha-space-4);
|
||||||
|
}
|
||||||
|
ha-settings-row[narrow] {
|
||||||
|
padding-bottom: var(--ha-space-2);
|
||||||
|
}
|
||||||
|
ha-settings-row {
|
||||||
|
--settings-row-content-width: 100%;
|
||||||
|
--settings-row-prefix-display: contents;
|
||||||
|
border-top: var(
|
||||||
|
--service-control-items-border-top,
|
||||||
|
1px solid var(--divider-color)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ha-service-picker,
|
||||||
|
ha-entity-picker,
|
||||||
|
ha-yaml-editor {
|
||||||
|
display: block;
|
||||||
|
margin: 0 var(--ha-space-4);
|
||||||
|
}
|
||||||
|
ha-yaml-editor {
|
||||||
|
padding: var(--ha-space-4) 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0 var(--ha-space-4);
|
||||||
|
padding: var(--ha-space-4) 0;
|
||||||
|
}
|
||||||
|
:host([hide-picker]) p {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
.checkbox-spacer {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
ha-checkbox {
|
||||||
|
margin-left: calc(var(--ha-space-4) * -1);
|
||||||
|
margin-inline-start: calc(var(--ha-space-4) * -1);
|
||||||
|
margin-inline-end: initial;
|
||||||
|
}
|
||||||
|
.help-icon {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
justify-content: space-between;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 2px;
|
||||||
|
padding-inline-end: 2px;
|
||||||
|
padding-inline-start: initial;
|
||||||
|
}
|
||||||
|
.description p {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-condition-platform": HaPlatformCondition;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -299,7 +299,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
},
|
},
|
||||||
area: {
|
area: {
|
||||||
title: localize("ui.panel.config.automation.picker.headers.area"),
|
title: localize("ui.panel.config.automation.picker.headers.area"),
|
||||||
defaultHidden: true,
|
|
||||||
groupable: true,
|
groupable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
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) => void;
|
add: (key: string, target?: HassServiceTarget) => void;
|
||||||
clipboardItem: string | undefined;
|
clipboardItem: string | undefined;
|
||||||
}
|
}
|
||||||
const loadDialog = () => import("./add-automation-element-dialog");
|
const loadDialog = () => import("./add-automation-element-dialog");
|
||||||
|
|||||||
@@ -93,9 +93,13 @@ export default class HaAutomationSidebarAction extends LitElement {
|
|||||||
".",
|
".",
|
||||||
2
|
2
|
||||||
);
|
);
|
||||||
|
|
||||||
title = `${domainToName(this.hass.localize, domain)}: ${
|
title = `${domainToName(this.hass.localize, domain)}: ${
|
||||||
this.hass.localize(`component.${domain}.services.${service}.name`) ||
|
this.hass.localize(
|
||||||
this.hass.services[domain][service]?.name ||
|
`component.${domain}.services.${service}.name`,
|
||||||
|
this.hass.services[domain]?.[service]?.description_placeholders
|
||||||
|
) ||
|
||||||
|
this.hass.services[domain]?.[service]?.name ||
|
||||||
title
|
title
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user