mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-17 08:59:43 +00:00
Compare commits
136 Commits
fix-automa
...
20240207.1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b3766cbc62 | ||
![]() |
3469013f1a | ||
![]() |
03486e4125 | ||
![]() |
20560fb847 | ||
![]() |
bad18da658 | ||
![]() |
e26c7c491a | ||
![]() |
b0c8ae0c94 | ||
![]() |
cc1658cbab | ||
![]() |
4b768f0635 | ||
![]() |
989057d947 | ||
![]() |
6671d24fa6 | ||
![]() |
297c721229 | ||
![]() |
70c502bb45 | ||
![]() |
add0b55657 | ||
![]() |
d61fc9ec6c | ||
![]() |
d1592bf262 | ||
![]() |
3c744c09f1 | ||
![]() |
3ef61aaf02 | ||
![]() |
c738127c09 | ||
![]() |
6e62f568fc | ||
![]() |
2ba3a991a9 | ||
![]() |
50e559487d | ||
![]() |
4cd02c81bb | ||
![]() |
55c6d3a7c4 | ||
![]() |
242f3813bc | ||
![]() |
aa93cb17a7 | ||
![]() |
4692d885d1 | ||
![]() |
b39ac984f9 | ||
![]() |
9894d83e22 | ||
![]() |
113083a241 | ||
![]() |
32971cc875 | ||
![]() |
137f59feb1 | ||
![]() |
6675121b85 | ||
![]() |
aed0a35c9c | ||
![]() |
65a8518d99 | ||
![]() |
cb690e9d4e | ||
![]() |
5da67de95f | ||
![]() |
b9609f2154 | ||
![]() |
aaabb6e1fb | ||
![]() |
6561de34f0 | ||
![]() |
016ff74483 | ||
![]() |
f5e9839b42 | ||
![]() |
d044f4d34e | ||
![]() |
696717dd90 | ||
![]() |
eb3b168975 | ||
![]() |
aa400ce6ab | ||
![]() |
682f9a0f04 | ||
![]() |
e478038206 | ||
![]() |
259a9a4f58 | ||
![]() |
b08d1ae7e9 | ||
![]() |
3970fdd070 | ||
![]() |
946445b2df | ||
![]() |
17b090af58 | ||
![]() |
6690a0e4b1 | ||
![]() |
c291448ffa | ||
![]() |
6f831699be | ||
![]() |
fb73bfb964 | ||
![]() |
28a0d216f9 | ||
![]() |
69f2566526 | ||
![]() |
7b3797502a | ||
![]() |
cf960be07e | ||
![]() |
8a410d6c82 | ||
![]() |
22c3132638 | ||
![]() |
ce32de6e23 | ||
![]() |
b6bc88e460 | ||
![]() |
6e00806f1a | ||
![]() |
d9fa148c49 | ||
![]() |
939b3a8092 | ||
![]() |
95920ba710 | ||
![]() |
462ac79890 | ||
![]() |
601a165b2a | ||
![]() |
62bb9b1a87 | ||
![]() |
b60ba35a9f | ||
![]() |
c97c3f2fc4 | ||
![]() |
ed888200f9 | ||
![]() |
f4859320eb | ||
![]() |
b159f4c074 | ||
![]() |
b700e08d52 | ||
![]() |
c1bdd679ff | ||
![]() |
b728b9efc4 | ||
![]() |
8acae63939 | ||
![]() |
374f5ee1be | ||
![]() |
528533a2dd | ||
![]() |
2b18db8525 | ||
![]() |
6cd8ee9253 | ||
![]() |
e45709fffc | ||
![]() |
28c21b1041 | ||
![]() |
0919f0e89e | ||
![]() |
7ce9a937b1 | ||
![]() |
456c011f3e | ||
![]() |
a31b9f1b4d | ||
![]() |
a1cf18468b | ||
![]() |
f147a5e909 | ||
![]() |
8d541595b8 | ||
![]() |
32fd8270d7 | ||
![]() |
efddbfcfa0 | ||
![]() |
0b20725f5f | ||
![]() |
030566c1e8 | ||
![]() |
fef2c44cb8 | ||
![]() |
b99b13251f | ||
![]() |
288d173a4d | ||
![]() |
2c69fe8c53 | ||
![]() |
62dafac72b | ||
![]() |
22929672a0 | ||
![]() |
ae0eac3415 | ||
![]() |
4ea7d826bc | ||
![]() |
336214d97f | ||
![]() |
c9a0ae6e2d | ||
![]() |
0bc69fb9b3 | ||
![]() |
6e7366bf69 | ||
![]() |
8ee4aa9e63 | ||
![]() |
386c3ea1ca | ||
![]() |
c2f3e43ee5 | ||
![]() |
7354988ec9 | ||
![]() |
53dedc6c65 | ||
![]() |
de3b9a5bb2 | ||
![]() |
8d496e1511 | ||
![]() |
01bd88ce10 | ||
![]() |
18b5fd59a6 | ||
![]() |
750c1d5013 | ||
![]() |
f2226cdec2 | ||
![]() |
c125ec087a | ||
![]() |
f099f66065 | ||
![]() |
4fcf99faa7 | ||
![]() |
2add88ccc2 | ||
![]() |
aa94ec7949 | ||
![]() |
7b4ecfd30a | ||
![]() |
9d9e789f4b | ||
![]() |
6ce613acd2 | ||
![]() |
aa38e2d409 | ||
![]() |
fce4e5e382 | ||
![]() |
eb5e7ba3f3 | ||
![]() |
ae2e8e7402 | ||
![]() |
b854d23431 | ||
![]() |
ef735d65cf | ||
![]() |
2803e6aa95 |
2
.github/workflows/release-drafter.yaml
vendored
2
.github/workflows/release-drafter.yaml
vendored
@@ -18,6 +18,6 @@ jobs:
|
||||
pull-requests: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
- uses: release-drafter/release-drafter@v6.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
@@ -1,39 +0,0 @@
|
||||
diff --git a/modular/sortable.complete.esm.js b/modular/sortable.complete.esm.js
|
||||
index 02e9f2d6bebeb430fe6e7c1cc3f9c3c9df051f14..bb8268b0844a1faa4108cc92c0be2a3dbaf23f83 100644
|
||||
--- a/modular/sortable.complete.esm.js
|
||||
+++ b/modular/sortable.complete.esm.js
|
||||
@@ -1657,7 +1657,7 @@ Sortable.prototype =
|
||||
target = parent; // store last element
|
||||
}
|
||||
/* jshint boss:true */
|
||||
- while (parent = parent.parentNode);
|
||||
+ while (parent = parent.parentNode || parent.getRootNode().host);
|
||||
}
|
||||
|
||||
_unhideGhostForTarget();
|
||||
diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js
|
||||
index b04c8b4634f7c6b4ef1aadbb48afe6564306dea9..39a107163c8c336ebd669b5ea8a936af87e1c1e7 100644
|
||||
--- a/modular/sortable.core.esm.js
|
||||
+++ b/modular/sortable.core.esm.js
|
||||
@@ -1657,7 +1657,7 @@ Sortable.prototype =
|
||||
target = parent; // store last element
|
||||
}
|
||||
/* jshint boss:true */
|
||||
- while (parent = parent.parentNode);
|
||||
+ while (parent = parent.parentNode || parent.getRootNode().host);
|
||||
}
|
||||
|
||||
_unhideGhostForTarget();
|
||||
diff --git a/modular/sortable.esm.js b/modular/sortable.esm.js
|
||||
index 6ec7ed1bb557e21c2578200161e989c65d23150b..0a05475a22904472fac6c13f524c674da76584b0 100644
|
||||
--- a/modular/sortable.esm.js
|
||||
+++ b/modular/sortable.esm.js
|
||||
@@ -1657,7 +1657,7 @@ Sortable.prototype =
|
||||
target = parent; // store last element
|
||||
}
|
||||
/* jshint boss:true */
|
||||
- while (parent = parent.parentNode);
|
||||
+ while (parent = parent.parentNode || parent.getRootNode().host);
|
||||
}
|
||||
|
||||
_unhideGhostForTarget();
|
73
.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch
Normal file
73
.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch
Normal file
@@ -0,0 +1,73 @@
|
||||
diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js
|
||||
index 93ba17509e2e8583ab241fea6845fbe714c584a2..de0651ddb5dced30d36f7d764da0dd0b441f523f 100644
|
||||
--- a/modular/sortable.core.esm.js
|
||||
+++ b/modular/sortable.core.esm.js
|
||||
@@ -1461,7 +1461,7 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
}
|
||||
target = parent; // store last element
|
||||
}
|
||||
- /* jshint boss:true */ while (parent = parent.parentNode);
|
||||
+ /* jshint boss:true */ while (parent = parent.parentNode || parent.getRootNode().host);
|
||||
}
|
||||
_unhideGhostForTarget();
|
||||
}
|
||||
@@ -1781,11 +1781,16 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
}
|
||||
if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) {
|
||||
capture();
|
||||
- if (elLastChild && elLastChild.nextSibling) {
|
||||
- // the last draggable element is not the last node
|
||||
- el.insertBefore(dragEl, elLastChild.nextSibling);
|
||||
- } else {
|
||||
- el.appendChild(dragEl);
|
||||
+ try {
|
||||
+ if (elLastChild && elLastChild.nextSibling) {
|
||||
+ // the last draggable element is not the last node
|
||||
+ el.insertBefore(dragEl, elLastChild.nextSibling);
|
||||
+ } else {
|
||||
+ el.appendChild(dragEl);
|
||||
+ }
|
||||
+ }
|
||||
+ catch(err) {
|
||||
+ return completed(false);
|
||||
}
|
||||
parentEl = el; // actualization
|
||||
|
||||
@@ -1802,7 +1807,13 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
targetRect = getRect(target);
|
||||
if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, false) !== false) {
|
||||
capture();
|
||||
- el.insertBefore(dragEl, firstChild);
|
||||
+ try {
|
||||
+ el.insertBefore(dragEl, firstChild);
|
||||
+ }
|
||||
+ catch(err) {
|
||||
+ return completed(false);
|
||||
+ }
|
||||
+
|
||||
parentEl = el; // actualization
|
||||
|
||||
changed();
|
||||
@@ -1849,12 +1860,17 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
_silent = true;
|
||||
setTimeout(_unsilent, 30);
|
||||
capture();
|
||||
- if (after && !nextSibling) {
|
||||
- el.appendChild(dragEl);
|
||||
- } else {
|
||||
- target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
|
||||
- }
|
||||
|
||||
+ try {
|
||||
+ if (after && !nextSibling) {
|
||||
+ el.appendChild(dragEl);
|
||||
+ } else {
|
||||
+ target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
|
||||
+ }
|
||||
+ }
|
||||
+ catch(err) {
|
||||
+ return completed(false);
|
||||
+ }
|
||||
// Undo chrome's scroll adjustment (has no effect on other browsers)
|
||||
if (scrolledPastTop) {
|
||||
scrollBy(scrolledPastTop, 0, scrollBefore - scrolledPastTop.scrollTop);
|
File diff suppressed because one or more lines are too long
@@ -6,4 +6,4 @@ enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.0.2.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.1.0.cjs
|
||||
|
@@ -28,7 +28,6 @@ class HcLaunchScreen extends LitElement {
|
||||
:host {
|
||||
display: block;
|
||||
height: 100vh;
|
||||
padding-top: 64px;
|
||||
background-color: white;
|
||||
font-size: 24px;
|
||||
}
|
||||
@@ -36,12 +35,13 @@ class HcLaunchScreen extends LitElement {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
img {
|
||||
width: 717px;
|
||||
height: 376px;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 80%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.status {
|
||||
padding-right: 54px;
|
||||
|
@@ -17,7 +17,7 @@ class HcLovelace extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public lovelaceConfig!: LovelaceConfig;
|
||||
|
||||
@property() public viewPath?: string | number;
|
||||
@property() public viewPath?: string | number | null;
|
||||
|
||||
@property() public urlPath: string | null = null;
|
||||
|
||||
@@ -93,6 +93,9 @@ class HcLovelace extends LitElement {
|
||||
}
|
||||
|
||||
private get _viewIndex() {
|
||||
if (this.viewPath === null) {
|
||||
return 0;
|
||||
}
|
||||
const selectedView = this.viewPath;
|
||||
const selectedViewInt = parseInt(selectedView as string, 10);
|
||||
for (let i = 0; i < this.lovelaceConfig.views.length; i++) {
|
||||
|
@@ -51,10 +51,10 @@ export class HcMain extends HassElement {
|
||||
|
||||
@state() private _lovelacePath: string | number | null = null;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _urlPath?: string | null;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
private _hassUUID?: string;
|
||||
|
||||
private _unsubLovelace?: UnsubscribeFunc;
|
||||
@@ -81,7 +81,7 @@ export class HcMain extends HassElement {
|
||||
|
||||
if (
|
||||
!this._lovelaceConfig ||
|
||||
this._lovelacePath === null ||
|
||||
this._urlPath === undefined ||
|
||||
// Guard against part of HA not being loaded yet.
|
||||
!this.hass ||
|
||||
!this.hass.states ||
|
||||
@@ -99,8 +99,8 @@ export class HcMain extends HassElement {
|
||||
<hc-lovelace
|
||||
.hass=${this.hass}
|
||||
.lovelaceConfig=${this._lovelaceConfig}
|
||||
.viewPath=${this._lovelacePath}
|
||||
.urlPath=${this._urlPath}
|
||||
.viewPath=${this._lovelacePath}
|
||||
@config-refresh=${this._generateDefaultLovelaceConfig}
|
||||
></hc-lovelace>
|
||||
`;
|
||||
@@ -226,9 +226,9 @@ export class HcMain extends HassElement {
|
||||
this.initializeHass(auth, connection);
|
||||
if (this._hassUUID !== msg.hassUUID) {
|
||||
this._hassUUID = msg.hassUUID;
|
||||
this._lovelacePath = null;
|
||||
this._urlPath = undefined;
|
||||
this._lovelaceConfig = undefined;
|
||||
this._urlPath = undefined;
|
||||
this._lovelacePath = null;
|
||||
if (this._unsubLovelace) {
|
||||
this._unsubLovelace();
|
||||
this._unsubLovelace = undefined;
|
||||
@@ -285,7 +285,7 @@ export class HcMain extends HassElement {
|
||||
],
|
||||
};
|
||||
this._urlPath = "energy";
|
||||
this._lovelacePath = 0;
|
||||
this._lovelacePath = null;
|
||||
this._sendStatus();
|
||||
return;
|
||||
}
|
||||
|
@@ -17,12 +17,14 @@ import { energyEntities } from "./stubs/entities";
|
||||
import { mockEntityRegistry } from "./stubs/entity_registry";
|
||||
import { mockEvents } from "./stubs/events";
|
||||
import { mockFrontend } from "./stubs/frontend";
|
||||
import { mockIcons } from "./stubs/icons";
|
||||
import { mockHistory } from "./stubs/history";
|
||||
import { mockLovelace } from "./stubs/lovelace";
|
||||
import { mockMediaPlayer } from "./stubs/media_player";
|
||||
import { mockPersistentNotification } from "./stubs/persistent_notification";
|
||||
import { mockRecorder } from "./stubs/recorder";
|
||||
import { mockTodo } from "./stubs/todo";
|
||||
import { mockSensor } from "./stubs/sensor";
|
||||
import { mockSystemLog } from "./stubs/system_log";
|
||||
import { mockTemplate } from "./stubs/template";
|
||||
import { mockTranslations } from "./stubs/translations";
|
||||
@@ -50,11 +52,13 @@ export class HaDemo extends HomeAssistantAppEl {
|
||||
mockHistory(hass);
|
||||
mockRecorder(hass);
|
||||
mockTodo(hass);
|
||||
mockSensor(hass);
|
||||
mockSystemLog(hass);
|
||||
mockTemplate(hass);
|
||||
mockEvents(hass);
|
||||
mockMediaPlayer(hass);
|
||||
mockFrontend(hass);
|
||||
mockIcons(hass);
|
||||
mockEnergy(hass);
|
||||
mockPersistentNotification(hass);
|
||||
mockConfigEntries(hass);
|
||||
|
33
demo/src/stubs/icons.ts
Normal file
33
demo/src/stubs/icons.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { IconCategory } from "../../../src/data/icons";
|
||||
import { ENTITY_COMPONENT_ICONS } from "../../../src/fake_data/entity_component_icons";
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockIcons = (hass: MockHomeAssistant) => {
|
||||
hass.mockWS(
|
||||
"frontend/get_icons",
|
||||
async ({
|
||||
category,
|
||||
integration,
|
||||
}: {
|
||||
category: IconCategory;
|
||||
integration?: string;
|
||||
}) => {
|
||||
if (integration) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://raw.githubusercontent.com/home-assistant/core/dev/homeassistant/components/${integration}/icons.json`
|
||||
).then((resp) => resp.json());
|
||||
return { resources: { [integration]: response[category] || {} } };
|
||||
} catch {
|
||||
return { resources: {} };
|
||||
}
|
||||
}
|
||||
if (category === "entity_component") {
|
||||
return {
|
||||
resources: ENTITY_COMPONENT_ICONS,
|
||||
};
|
||||
}
|
||||
return { resources: {} };
|
||||
}
|
||||
);
|
||||
};
|
58
demo/src/stubs/sensor.ts
Normal file
58
demo/src/stubs/sensor.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockSensor = (hass: MockHomeAssistant) => {
|
||||
hass.mockWS("sensor/numeric_device_classes", () => [
|
||||
{
|
||||
numeric_device_classes: [
|
||||
"volume_storage",
|
||||
"gas",
|
||||
"data_size",
|
||||
"irradiance",
|
||||
"wind_speed",
|
||||
"volatile_organic_compounds",
|
||||
"volatile_organic_compounds_parts",
|
||||
"voltage",
|
||||
"frequency",
|
||||
"precipitation_intensity",
|
||||
"volume",
|
||||
"precipitation",
|
||||
"battery",
|
||||
"nitrogen_dioxide",
|
||||
"speed",
|
||||
"signal_strength",
|
||||
"pm1",
|
||||
"nitrous_oxide",
|
||||
"atmospheric_pressure",
|
||||
"data_rate",
|
||||
"temperature",
|
||||
"power_factor",
|
||||
"aqi",
|
||||
"current",
|
||||
"volume_flow_rate",
|
||||
"humidity",
|
||||
"duration",
|
||||
"ozone",
|
||||
"distance",
|
||||
"pressure",
|
||||
"pm25",
|
||||
"weight",
|
||||
"energy",
|
||||
"carbon_monoxide",
|
||||
"apparent_power",
|
||||
"illuminance",
|
||||
"energy_storage",
|
||||
"moisture",
|
||||
"power",
|
||||
"water",
|
||||
"carbon_dioxide",
|
||||
"ph",
|
||||
"reactive_power",
|
||||
"monetary",
|
||||
"nitrogen_monoxide",
|
||||
"pm10",
|
||||
"sound_pressure",
|
||||
"sulphur_dioxide",
|
||||
],
|
||||
},
|
||||
]);
|
||||
};
|
@@ -21,4 +21,5 @@ export const mockTodo = (hass: MockHomeAssistant) => {
|
||||
},
|
||||
] as TodoItem[],
|
||||
}));
|
||||
hass.mockWS("todo/item/subscribe", (_msg, _hass) => () => {});
|
||||
};
|
||||
|
@@ -3,7 +3,6 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/trace/hat-script-graph";
|
||||
import "../../../../src/components/trace/hat-trace-timeline";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
|
@@ -10,6 +10,7 @@ import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervis
|
||||
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
@@ -97,22 +98,25 @@ const DEVICES = [
|
||||
},
|
||||
];
|
||||
|
||||
const AREAS = [
|
||||
const AREAS: AreaRegistryEntry[] = [
|
||||
{
|
||||
area_id: "backyard",
|
||||
name: "Backyard",
|
||||
icon: null,
|
||||
picture: null,
|
||||
aliases: [],
|
||||
},
|
||||
{
|
||||
area_id: "bedroom",
|
||||
name: "Bedroom",
|
||||
icon: "mdi:bed",
|
||||
picture: null,
|
||||
aliases: [],
|
||||
},
|
||||
{
|
||||
area_id: "livingroom",
|
||||
name: "Livingroom",
|
||||
icon: "mdi:sofa",
|
||||
picture: null,
|
||||
aliases: [],
|
||||
},
|
||||
|
@@ -9,6 +9,7 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import "../../../../src/components/ha-selector/ha-selector";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
|
||||
import { BlueprintInput } from "../../../../src/data/blueprint";
|
||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
@@ -93,22 +94,25 @@ const DEVICES = [
|
||||
},
|
||||
];
|
||||
|
||||
const AREAS = [
|
||||
const AREAS: AreaRegistryEntry[] = [
|
||||
{
|
||||
area_id: "backyard",
|
||||
name: "Backyard",
|
||||
icon: null,
|
||||
picture: null,
|
||||
aliases: [],
|
||||
},
|
||||
{
|
||||
area_id: "bedroom",
|
||||
name: "Bedroom",
|
||||
icon: "mdi:bed",
|
||||
picture: null,
|
||||
aliases: [],
|
||||
},
|
||||
{
|
||||
area_id: "livingroom",
|
||||
name: "Livingroom",
|
||||
icon: "mdi:sofa",
|
||||
picture: null,
|
||||
aliases: [],
|
||||
},
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||
@@ -84,6 +85,7 @@ class DemoAlarmPanelEntity extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
@@ -146,6 +147,7 @@ class DemoArea extends LitElement {
|
||||
entity_id: "binary_sensor.kitchen_door",
|
||||
},
|
||||
]);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "controller_1", "on", {
|
||||
@@ -66,6 +67,7 @@ class DemoConditional extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
@@ -323,6 +324,7 @@ class DemoEntities extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
@@ -82,6 +83,7 @@ class DemoButtonEntity extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("device_tracker", "demo_paulus", "work", {
|
||||
@@ -123,6 +124,7 @@ class DemoEntityFilter extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("sensor", "brightness", "12", {}),
|
||||
@@ -128,6 +129,7 @@ class DemoGaugeEntity extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("device_tracker", "demo_paulus", "home", {
|
||||
@@ -238,6 +239,7 @@ class DemoGlanceEntity extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import { mockHistory } from "../../../../demo/src/stubs/history";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "kitchen_lights", "on", {
|
||||
@@ -214,6 +215,7 @@ class DemoStack extends LitElement {
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockHistory(hass);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
@@ -76,6 +77,7 @@ class DemoLightEntity extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
@@ -138,6 +139,7 @@ class DemoPictureElements extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "kitchen_lights", "on", {
|
||||
@@ -93,6 +94,7 @@ class DemoPictureEntity extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("switch", "decorative_lights", "on", {
|
||||
@@ -134,6 +135,7 @@ class DemoPictureGlance extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { createPlantEntities } from "../../data/plants";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
@@ -43,6 +44,7 @@ export class DemoPlantEntity extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(createPlantEntities());
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("climate", "ecobee", "auto", {
|
||||
@@ -116,6 +117,7 @@ class DemoThermostatEntity extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,6 +6,7 @@ import { VacuumEntityFeature } from "../../../../src/data/vacuum";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("switch", "tv_outlet", "on", {
|
||||
@@ -79,6 +80,18 @@ const CONFIGS = [
|
||||
color: pink
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Whole tile tap action",
|
||||
config: `
|
||||
- type: tile
|
||||
entity: switch.tv_outlet
|
||||
color: pink
|
||||
tap_action:
|
||||
action: toggle
|
||||
icon_tap_action:
|
||||
action: none
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Unknown entity",
|
||||
config: `
|
||||
@@ -172,6 +185,7 @@ class DemoTile extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { mockTodo } from "../../../../demo/src/stubs/todo";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("todo", "shopping_list", "2", {
|
||||
@@ -47,6 +48,7 @@ class DemoTodoListEntity extends LitElement {
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
|
||||
mockTodo(hass);
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ import "../../../../src/components/data-table/ha-data-table";
|
||||
import type { DataTableColumnContainer } from "../../../../src/components/data-table/ha-data-table";
|
||||
import "../../../../src/components/entity/state-badge";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
const SENSOR_DEVICE_CLASSES = [
|
||||
@@ -291,6 +292,7 @@ const ENTITIES: HassEntity[] = [
|
||||
createEntity("water_heater.high_demand", "high_demand"),
|
||||
createEntity("water_heater.heat_pump", "heat_pump"),
|
||||
createEntity("water_heater.gas", "gas"),
|
||||
createEntity("select.speed", "ridiculous_speed"),
|
||||
];
|
||||
|
||||
function createEntity(
|
||||
@@ -397,6 +399,16 @@ export class DemoEntityState extends LitElement {
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
mockIcons(hass);
|
||||
hass.updateHass({
|
||||
entities: {
|
||||
"select.speed": {
|
||||
entity_id: "select.speed",
|
||||
translation_key: "speed",
|
||||
platform: "demo",
|
||||
},
|
||||
},
|
||||
});
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
}
|
||||
|
28
package.json
28
package.json
@@ -29,7 +29,7 @@
|
||||
"@braintree/sanitize-url": "7.0.0",
|
||||
"@codemirror/autocomplete": "6.12.0",
|
||||
"@codemirror/commands": "6.3.3",
|
||||
"@codemirror/language": "6.10.0",
|
||||
"@codemirror/language": "6.10.1",
|
||||
"@codemirror/legacy-modes": "6.3.3",
|
||||
"@codemirror/search": "6.5.5",
|
||||
"@codemirror/state": "6.4.0",
|
||||
@@ -51,7 +51,7 @@
|
||||
"@fullcalendar/timegrid": "6.1.10",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.6",
|
||||
"@lit-labs/motion": "1.0.7",
|
||||
"@lit-labs/observers": "2.0.2",
|
||||
"@lit-labs/virtualizer": "2.0.12",
|
||||
"@lrnwebcomponents/simple-tooltip": "8.0.0",
|
||||
@@ -89,8 +89,8 @@
|
||||
"@polymer/paper-toast": "3.0.1",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.3.4",
|
||||
"@vaadin/vaadin-themable-mixin": "24.3.4",
|
||||
"@vaadin/combo-box": "24.3.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.3.5",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||
@@ -109,7 +109,7 @@
|
||||
"element-internals-polyfill": "1.3.10",
|
||||
"fuse.js": "7.0.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"hls.js": "1.5.2",
|
||||
"hls.js": "1.5.3",
|
||||
"home-assistant-js-websocket": "9.1.0",
|
||||
"idb-keyval": "6.2.1",
|
||||
"intl-messageformat": "10.5.11",
|
||||
@@ -183,8 +183,8 @@
|
||||
"@types/tar": "6.1.11",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "6.19.1",
|
||||
"@typescript-eslint/parser": "6.19.1",
|
||||
"@typescript-eslint/eslint-plugin": "6.20.0",
|
||||
"@typescript-eslint/parser": "6.20.0",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"@web/dev-server-rollup": "0.4.1",
|
||||
"babel-loader": "9.1.3",
|
||||
@@ -199,7 +199,7 @@
|
||||
"eslint-plugin-disable": "2.0.3",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-lit": "1.11.0",
|
||||
"eslint-plugin-lit-a11y": "4.1.1",
|
||||
"eslint-plugin-lit-a11y": "4.1.2",
|
||||
"eslint-plugin-unused-imports": "3.0.0",
|
||||
"eslint-plugin-wc": "2.0.4",
|
||||
"fancy-log": "2.0.0",
|
||||
@@ -212,13 +212,13 @@
|
||||
"gulp-rename": "2.0.0",
|
||||
"gulp-zopfli-green": "6.0.1",
|
||||
"html-minifier-terser": "7.2.0",
|
||||
"husky": "9.0.6",
|
||||
"husky": "9.0.10",
|
||||
"instant-mocha": "1.5.2",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "15.2.0",
|
||||
"lint-staged": "15.2.1",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.template": "4.5.0",
|
||||
"magic-string": "0.30.5",
|
||||
"magic-string": "0.30.6",
|
||||
"map-stream": "0.0.7",
|
||||
"mocha": "10.2.0",
|
||||
"object-hash": "3.0.0",
|
||||
@@ -240,7 +240,7 @@
|
||||
"typescript": "5.3.3",
|
||||
"vinyl-buffer": "1.0.1",
|
||||
"vinyl-source-stream": "2.0.0",
|
||||
"webpack": "5.90.0",
|
||||
"webpack": "5.90.1",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "4.15.1",
|
||||
"webpack-manifest-plugin": "5.0.0",
|
||||
@@ -255,8 +255,8 @@
|
||||
"lit": "2.8.0",
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "1.6.3",
|
||||
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch",
|
||||
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
|
||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
},
|
||||
"packageManager": "yarn@4.0.2"
|
||||
"packageManager": "yarn@4.1.0"
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20240125.0"
|
||||
version = "20240207.1"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@@ -38,4 +38,8 @@ export function setDirectionStyles(direction: string, element: LitElement) {
|
||||
"--margin-title",
|
||||
direction === "ltr" ? "var(--margin-title-ltr)" : "var(--margin-title-rtl)"
|
||||
);
|
||||
element.style.setProperty(
|
||||
"--scale-direction",
|
||||
direction === "ltr" ? "1" : "-1"
|
||||
);
|
||||
}
|
||||
|
@@ -10,7 +10,6 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { clamp } from "../../common/number/clamp";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
|
||||
@@ -212,12 +211,10 @@ export class HaChartBase extends LitElement {
|
||||
height: `${
|
||||
this.height ?? this._chartHeight ?? this.clientWidth / 2
|
||||
}px`,
|
||||
"padding-left": `${
|
||||
computeRTL(this.hass) ? 0 : this._paddingYAxisInternal
|
||||
}px`,
|
||||
"padding-right": `${
|
||||
computeRTL(this.hass) ? this._paddingYAxisInternal : 0
|
||||
}px`,
|
||||
"padding-left": `${this._paddingYAxisInternal}`,
|
||||
"padding-right": 0,
|
||||
"padding-inline-start": `${this._paddingYAxisInternal}`,
|
||||
"padding-inline-end": 0,
|
||||
})}
|
||||
>
|
||||
<canvas></canvas>
|
||||
@@ -433,14 +430,6 @@ export class HaChartBase extends LitElement {
|
||||
.chartTooltip .bullet {
|
||||
align-self: baseline;
|
||||
}
|
||||
:host([rtl]) .chartLegend .bullet,
|
||||
:host([rtl]) .chartTooltip .bullet {
|
||||
margin-right: inherit;
|
||||
margin-left: 6px;
|
||||
margin-inline-end: inherit;
|
||||
margin-inline-start: 6px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.chartTooltip {
|
||||
padding: 8px;
|
||||
font-size: 90%;
|
||||
@@ -449,12 +438,13 @@ export class HaChartBase extends LitElement {
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
z-index: 1;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
width: 200px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:host([rtl]) .chartTooltip {
|
||||
direction: rtl;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.chartLegend ul,
|
||||
.chartTooltip ul {
|
||||
|
@@ -47,6 +47,12 @@ export class StateHistoryChartLine extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public logarithmicScale = false;
|
||||
|
||||
@property({ type: Number }) public minYAxis?: number;
|
||||
|
||||
@property({ type: Number }) public maxYAxis?: number;
|
||||
|
||||
@property({ type: Boolean }) public fitYData = false;
|
||||
|
||||
@state() private _chartData?: ChartData<"line">;
|
||||
|
||||
@state() private _entityIds: string[] = [];
|
||||
@@ -84,7 +90,10 @@ export class StateHistoryChartLine extends LitElement {
|
||||
changedProps.has("startTime") ||
|
||||
changedProps.has("endTime") ||
|
||||
changedProps.has("unit") ||
|
||||
changedProps.has("logarithmicScale")
|
||||
changedProps.has("logarithmicScale") ||
|
||||
changedProps.has("minYAxis") ||
|
||||
changedProps.has("maxYAxis") ||
|
||||
changedProps.has("fitYData")
|
||||
) {
|
||||
this._chartOptions = {
|
||||
parsing: false,
|
||||
@@ -121,6 +130,10 @@ export class StateHistoryChartLine extends LitElement {
|
||||
},
|
||||
},
|
||||
y: {
|
||||
suggestedMin: this.fitYData ? this.minYAxis : null,
|
||||
suggestedMax: this.fitYData ? this.maxYAxis : null,
|
||||
min: this.fitYData ? null : this.minYAxis,
|
||||
max: this.fitYData ? null : this.maxYAxis,
|
||||
ticks: {
|
||||
maxTicksLimit: 7,
|
||||
},
|
||||
@@ -207,7 +220,12 @@ export class StateHistoryChartLine extends LitElement {
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
onClick: (e: any) => {
|
||||
if (!this.clickForMoreInfo) {
|
||||
if (
|
||||
!this.clickForMoreInfo ||
|
||||
!(e.native instanceof MouseEvent) ||
|
||||
(e.native instanceof PointerEvent &&
|
||||
e.native.pointerType !== "mouse")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -224,7 +224,11 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
onClick: (e: any) => {
|
||||
if (!this.clickForMoreInfo) {
|
||||
if (
|
||||
!this.clickForMoreInfo ||
|
||||
!(e.native instanceof MouseEvent) ||
|
||||
(e.native instanceof PointerEvent && e.native.pointerType !== "mouse")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -74,6 +74,12 @@ export class StateHistoryCharts extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public logarithmicScale = false;
|
||||
|
||||
@property({ type: Number }) public minYAxis?: number;
|
||||
|
||||
@property({ type: Number }) public maxYAxis?: number;
|
||||
|
||||
@property({ type: Boolean }) public fitYData = false;
|
||||
|
||||
private _computedStartTime!: Date;
|
||||
|
||||
private _computedEndTime!: Date;
|
||||
@@ -161,6 +167,9 @@ export class StateHistoryCharts extends LitElement {
|
||||
.chartIndex=${index}
|
||||
.clickForMoreInfo=${this.clickForMoreInfo}
|
||||
.logarithmicScale=${this.logarithmicScale}
|
||||
.minYAxis=${this.minYAxis}
|
||||
.maxYAxis=${this.maxYAxis}
|
||||
.fitYData=${this.fitYData}
|
||||
@y-width-changed=${this._yWidthChanged}
|
||||
></state-history-chart-line>
|
||||
</div> `;
|
||||
|
@@ -688,15 +688,12 @@ export class HaDataTable extends LitElement {
|
||||
padding-left: 16px;
|
||||
/* @noflip */
|
||||
padding-right: 0;
|
||||
/* @noflip */
|
||||
padding-inline-start: 16px;
|
||||
/* @noflip */
|
||||
padding-inline-end: initial;
|
||||
width: 60px;
|
||||
}
|
||||
:host([dir="rtl"]) .mdc-data-table__header-cell--checkbox,
|
||||
:host([dir="rtl"]) .mdc-data-table__cell--checkbox {
|
||||
/* @noflip */
|
||||
padding-left: 0;
|
||||
/* @noflip */
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.mdc-data-table__table {
|
||||
height: 100%;
|
||||
@@ -723,11 +720,7 @@ export class HaDataTable extends LitElement {
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--numeric {
|
||||
text-align: right;
|
||||
}
|
||||
:host([dir="rtl"]) .mdc-data-table__cell--numeric {
|
||||
/* @noflip */
|
||||
text-align: left;
|
||||
text-align: var(--float-end);
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--icon {
|
||||
@@ -753,15 +746,7 @@ export class HaDataTable extends LitElement {
|
||||
.mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:not(
|
||||
.not-sorted
|
||||
) {
|
||||
text-align: left;
|
||||
}
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:hover,
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:not(
|
||||
.not-sorted
|
||||
) {
|
||||
text-align: right;
|
||||
text-align: var(--float-start);
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--icon:first-child img,
|
||||
@@ -771,27 +756,14 @@ export class HaDataTable extends LitElement {
|
||||
.mdc-data-table__cell--icon:first-child ha-domain-icon,
|
||||
.mdc-data-table__cell--icon:first-child ha-service-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
:host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-icon,
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__cell--icon:first-child
|
||||
ha-state-icon,
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__cell--icon:first-child
|
||||
ha-svg-icon
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__cell--icon:first-child
|
||||
img {
|
||||
margin-left: auto;
|
||||
margin-right: 8px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--icon:first-child state-badge {
|
||||
margin-right: -8px;
|
||||
}
|
||||
:host([dir="rtl"]) .mdc-data-table__cell--icon:first-child state-badge {
|
||||
margin-right: auto;
|
||||
margin-left: -8px;
|
||||
margin-inline-end: -8px;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--overflow-menu,
|
||||
@@ -824,15 +796,8 @@ export class HaDataTable extends LitElement {
|
||||
.mdc-data-table__header-cell--icon-button:first-child,
|
||||
.mdc-data-table__cell--icon-button:first-child {
|
||||
padding-left: 16px;
|
||||
}
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__header-cell--overflow-menu:first-child,
|
||||
:host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:first-child,
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__header-cell--overflow-menu:first-child,
|
||||
:host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:first-child {
|
||||
padding-left: 8px;
|
||||
padding-right: 16px;
|
||||
padding-inline-start: 16px;
|
||||
padding-inline-end: initial;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--overflow-menu:last-child,
|
||||
@@ -840,14 +805,8 @@ export class HaDataTable extends LitElement {
|
||||
.mdc-data-table__header-cell--icon-button:last-child,
|
||||
.mdc-data-table__cell--icon-button:last-child {
|
||||
padding-right: 16px;
|
||||
}
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__header-cell--overflow-menu:last-child,
|
||||
:host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:last-child,
|
||||
:host([dir="rtl"]) .mdc-data-table__header-cell--icon-button:last-child,
|
||||
:host([dir="rtl"]) .mdc-data-table__cell--icon-button:last-child {
|
||||
padding-right: 8px;
|
||||
padding-left: 16px;
|
||||
padding-inline-end: 16px;
|
||||
padding-inline-start: initial;
|
||||
}
|
||||
.mdc-data-table__cell--overflow-menu,
|
||||
.mdc-data-table__header-cell--overflow-menu {
|
||||
@@ -867,28 +826,15 @@ export class HaDataTable extends LitElement {
|
||||
letter-spacing: 0.0071428571em;
|
||||
text-decoration: inherit;
|
||||
text-transform: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
:host([dir="rtl"]) .mdc-data-table__header-cell {
|
||||
/* @noflip */
|
||||
text-align: right;
|
||||
text-align: var(--float-start);
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell--numeric {
|
||||
text-align: right;
|
||||
text-align: var(--float-end);
|
||||
}
|
||||
.mdc-data-table__header-cell--numeric.sortable:hover,
|
||||
.mdc-data-table__header-cell--numeric.sortable:not(.not-sorted) {
|
||||
text-align: left;
|
||||
}
|
||||
:host([dir="rtl"]) .mdc-data-table__header-cell--numeric {
|
||||
/* @noflip */
|
||||
text-align: left;
|
||||
}
|
||||
:host([dir="rtl"]) .mdc-data-table__header-cell--numeric.sortable:hover,
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__header-cell--numeric.sortable:not(.not-sorted) {
|
||||
text-align: right;
|
||||
text-align: var(--float-start);
|
||||
}
|
||||
|
||||
/* custom from here */
|
||||
@@ -909,20 +855,15 @@ export class HaDataTable extends LitElement {
|
||||
.mdc-data-table__header-cell span {
|
||||
position: relative;
|
||||
left: 0px;
|
||||
}
|
||||
:host([dir="rtl"]) .mdc-data-table__header-cell span {
|
||||
left: auto;
|
||||
right: 0px;
|
||||
inset-inline-start: 0px;
|
||||
inset-inline-end: initial;
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell.sortable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.mdc-data-table__header-cell > * {
|
||||
transition: left 0.2s ease;
|
||||
}
|
||||
:host([dir="rtl"]) .mdc-data-table__header-cell > * {
|
||||
transition: right 0.2s ease;
|
||||
transition: var(--float-start) 0.2s ease;
|
||||
}
|
||||
.mdc-data-table__header-cell ha-svg-icon {
|
||||
top: -3px;
|
||||
@@ -930,35 +871,20 @@ export class HaDataTable extends LitElement {
|
||||
}
|
||||
.mdc-data-table__header-cell.not-sorted ha-svg-icon {
|
||||
left: -20px;
|
||||
}
|
||||
:host([dir="rtl"]) .mdc-data-table__header-cell.not-sorted ha-svg-icon {
|
||||
right: -20px;
|
||||
inset-inline-start: -20px;
|
||||
inset-inline-end: initial;
|
||||
}
|
||||
.mdc-data-table__header-cell.sortable:not(.not-sorted) span,
|
||||
.mdc-data-table__header-cell.sortable.not-sorted:hover span {
|
||||
left: 24px;
|
||||
}
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__header-cell.sortable:not(.not-sorted)
|
||||
span,
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__header-cell.sortable.not-sorted:hover
|
||||
span {
|
||||
left: auto;
|
||||
right: 24px;
|
||||
inset-inline-start: 24px;
|
||||
inset-inline-end: initial;
|
||||
}
|
||||
.mdc-data-table__header-cell.sortable:not(.not-sorted) ha-svg-icon,
|
||||
.mdc-data-table__header-cell.sortable:hover.not-sorted ha-svg-icon {
|
||||
left: 12px;
|
||||
}
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__header-cell.sortable:not(.not-sorted)
|
||||
ha-svg-icon,
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__header-cell.sortable:hover.not-sorted
|
||||
ha-svg-icon {
|
||||
left: auto;
|
||||
right: 12px;
|
||||
inset-inline-start: 12px;
|
||||
inset-inline-end: initial;
|
||||
}
|
||||
.table-header {
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
@@ -966,6 +892,8 @@ export class HaDataTable extends LitElement {
|
||||
search-input {
|
||||
display: block;
|
||||
flex: 1;
|
||||
--mdc-text-field-fill-color: var(--sidebar-background-color);
|
||||
--mdc-text-field-idle-line-color: transparent;
|
||||
}
|
||||
slot[name="header"] {
|
||||
display: block;
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
localizeWeekdays,
|
||||
localizeMonths,
|
||||
} from "../common/datetime/localize_date";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
|
||||
// Set the current date to the left picker instead of the right picker because the right is hidden
|
||||
const CustomDateRangePicker = Vue.extend({
|
||||
@@ -157,7 +158,7 @@ class DateRangePickerElement extends WrappedElement {
|
||||
min-width: initial !important;
|
||||
max-height: var(--date-range-picker-max-height);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
.daterangepicker:before {
|
||||
display: none;
|
||||
}
|
||||
@@ -267,15 +268,37 @@ class DateRangePickerElement extends WrappedElement {
|
||||
.calendar-table {
|
||||
padding: 0 !important;
|
||||
}
|
||||
.daterangepicker.ltr {
|
||||
.calendar-time {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
.daterangepicker.ltr {
|
||||
direction: var(--direction);
|
||||
text-align: var(--float-start);
|
||||
}
|
||||
.vue-daterange-picker{
|
||||
min-width: unset !important;
|
||||
display: block !important;
|
||||
}
|
||||
`;
|
||||
if (mainWindow.document.dir === "rtl") {
|
||||
style.innerHTML += `
|
||||
.daterangepicker .calendar-table .next span {
|
||||
transform: rotate(135deg);
|
||||
-webkit-transform: rotate(135deg);
|
||||
}
|
||||
.daterangepicker .calendar-table .prev span {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg);
|
||||
}
|
||||
.daterangepicker td.start-date {
|
||||
border-radius: 0 50% 50% 0;
|
||||
}
|
||||
.daterangepicker td.end-date {
|
||||
border-radius: 50% 0 0 50%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
const shadowRoot = this.shadowRoot!;
|
||||
shadowRoot.appendChild(style);
|
||||
// Stop click events from reaching the document, otherwise it will close the picker immediately.
|
||||
|
@@ -133,9 +133,9 @@ export class HaStateLabelBadge extends LitElement {
|
||||
entityState,
|
||||
this._timerTimeRemaining
|
||||
)}
|
||||
.description=${this.showName === false
|
||||
? undefined
|
||||
: this.name ?? computeStateName(entityState)}
|
||||
.description=${this.showName
|
||||
? this.name ?? computeStateName(entityState)
|
||||
: undefined}
|
||||
>
|
||||
${!image && showIcon
|
||||
? html`<ha-state-icon
|
||||
|
@@ -3,7 +3,6 @@ import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-relative-time";
|
||||
import "./state-badge";
|
||||
@@ -16,9 +15,6 @@ class StateInfo extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public inDialog = false;
|
||||
|
||||
// property used only in CSS
|
||||
@property({ type: Boolean, reflect: true }) public rtl = false;
|
||||
|
||||
@property() public color?: string;
|
||||
|
||||
protected render() {
|
||||
@@ -79,18 +75,6 @@ class StateInfo extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
if (!changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.locale !== this.hass.locale) {
|
||||
this.rtl = computeRTL(this.hass);
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
@@ -106,17 +90,14 @@ class StateInfo extends LitElement {
|
||||
|
||||
.info {
|
||||
margin-left: 8px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:host([rtl]) .info {
|
||||
margin-right: 8px;
|
||||
margin-left: 0;
|
||||
text-align: right;
|
||||
text-align: var(--float-start);
|
||||
}
|
||||
|
||||
.name {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { html, LitElement, nothing, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -36,8 +36,12 @@ type ScorableAreaRegistryEntry = ScorableTextItem & AreaRegistryEntry;
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (item) =>
|
||||
html`<ha-list-item
|
||||
graphic="icon"
|
||||
class=${classMap({ "add-new": item.area_id === "add_new" })}
|
||||
>
|
||||
${item.icon
|
||||
? html`<ha-icon slot="graphic" .icon=${item.icon}></ha-icon>`
|
||||
: nothing}
|
||||
${item.name}
|
||||
</ha-list-item>`;
|
||||
|
||||
@@ -135,6 +139,7 @@ export class HaAreaPicker extends LitElement {
|
||||
area_id: "no_areas",
|
||||
name: this.hass.localize("ui.components.area-picker.no_areas"),
|
||||
picture: null,
|
||||
icon: null,
|
||||
aliases: [],
|
||||
},
|
||||
];
|
||||
@@ -262,7 +267,9 @@ export class HaAreaPicker extends LitElement {
|
||||
}
|
||||
|
||||
if (areaIds) {
|
||||
outputAreas = areas.filter((area) => areaIds!.includes(area.area_id));
|
||||
outputAreas = outputAreas.filter((area) =>
|
||||
areaIds!.includes(area.area_id)
|
||||
);
|
||||
}
|
||||
|
||||
if (excludeAreas) {
|
||||
@@ -277,6 +284,7 @@ export class HaAreaPicker extends LitElement {
|
||||
area_id: "no_areas",
|
||||
name: this.hass.localize("ui.components.area-picker.no_match"),
|
||||
picture: null,
|
||||
icon: null,
|
||||
aliases: [],
|
||||
},
|
||||
];
|
||||
@@ -290,6 +298,7 @@ export class HaAreaPicker extends LitElement {
|
||||
area_id: "add_new",
|
||||
name: this.hass.localize("ui.components.area-picker.add_new"),
|
||||
picture: null,
|
||||
icon: "mdi:plus",
|
||||
aliases: [],
|
||||
},
|
||||
];
|
||||
|
@@ -69,6 +69,7 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
display: flex;
|
||||
--mdc-icon-button-size: var(--button-toggle-size, 36px);
|
||||
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
|
||||
direction: ltr;
|
||||
}
|
||||
mwc-button {
|
||||
--mdc-shape-small: 0;
|
||||
@@ -119,19 +120,6 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
--mdc-shape-small: 4px;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
:host([dir="rtl"]) ha-icon-button:first-child,
|
||||
:host([dir="rtl"]) mwc-button:first-child {
|
||||
border-radius: 0 4px 4px 0;
|
||||
border-right-width: 1px;
|
||||
--mdc-shape-small: 0 4px 4px 0;
|
||||
--mdc-button-outline-width: 1px;
|
||||
}
|
||||
:host([dir="rtl"]) ha-icon-button:last-child,
|
||||
:host([dir="rtl"]) mwc-button:last-child {
|
||||
--mdc-shape-small: 4px 0 0 4px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -32,7 +32,6 @@ import { firstWeekdayIndex } from "../common/datetime/first_weekday";
|
||||
import { formatDate } from "../common/datetime/format_date";
|
||||
import { formatDateTime } from "../common/datetime/format_date_time";
|
||||
import { useAmPm } from "../common/datetime/use_am_pm";
|
||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./date-range-picker";
|
||||
import "./ha-icon-button";
|
||||
@@ -65,8 +64,6 @@ export class HaDateRangePicker extends LitElement {
|
||||
|
||||
@state() private _hour24format = false;
|
||||
|
||||
@state() private _rtlDirection = "ltr";
|
||||
|
||||
@property({ type: Boolean }) public extendedPresets = false;
|
||||
|
||||
@property() public openingDirection?: "right" | "left" | "center" | "inline";
|
||||
@@ -236,7 +233,6 @@ export class HaDateRangePicker extends LitElement {
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.locale !== this.hass.locale) {
|
||||
this._hour24format = !useAmPm(this.hass.locale);
|
||||
this._rtlDirection = computeRTLDirection(this.hass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,11 +302,7 @@ export class HaDateRangePicker extends LitElement {
|
||||
></ha-icon-button>`}
|
||||
</div>
|
||||
${this.ranges !== false && (this.ranges || this._ranges)
|
||||
? html`<div
|
||||
slot="ranges"
|
||||
class="date-range-ranges"
|
||||
.dir=${this._rtlDirection}
|
||||
>
|
||||
? html`<div slot="ranges" class="date-range-ranges">
|
||||
<mwc-list @action=${this._setDateRange} activatable>
|
||||
${Object.keys(this.ranges || this._ranges!).map(
|
||||
(name) => html`<mwc-list-item>${name}</mwc-list-item>`
|
||||
|
@@ -3,6 +3,7 @@ import { styles } from "@material/mwc-drawer/mwc-drawer.css";
|
||||
import { css, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
|
||||
const blockingElements = (document as any).$blockingElements;
|
||||
|
||||
@@ -12,6 +13,8 @@ export class HaDrawer extends DrawerBase {
|
||||
|
||||
private _mc?: HammerManager;
|
||||
|
||||
private _rtlStyle?: HTMLElement;
|
||||
|
||||
protected createAdapter() {
|
||||
return {
|
||||
...super.createAdapter(),
|
||||
@@ -31,8 +34,26 @@ export class HaDrawer extends DrawerBase {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("direction")) {
|
||||
this.mdcRoot.dir = this.direction;
|
||||
if (mainWindow.document.dir === "rtl") {
|
||||
this._rtlStyle = document.createElement("style");
|
||||
this._rtlStyle.innerHTML = `
|
||||
.mdc-drawer--animate {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
.mdc-drawer--opening {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.mdc-drawer--closing {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
`;
|
||||
|
||||
this.shadowRoot!.appendChild(this._rtlStyle);
|
||||
} else if (this._rtlStyle) {
|
||||
this.shadowRoot!.removeChild(this._rtlStyle);
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProps.has("open") && this.open && this.type === "modal") {
|
||||
this._setupSwipe();
|
||||
} else if (this._mc) {
|
||||
@@ -66,6 +87,8 @@ export class HaDrawer extends DrawerBase {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
border-color: var(--divider-color, rgba(0, 0, 0, 0.12));
|
||||
inset-inline-start: 0 !important;
|
||||
inset-inline-end: initial !important;
|
||||
}
|
||||
.mdc-drawer.mdc-drawer--modal.mdc-drawer--open {
|
||||
z-index: 200;
|
||||
|
@@ -2,6 +2,7 @@ import { LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import QRCode from "qrcode";
|
||||
import "./ha-alert";
|
||||
import { rgb2hex } from "../common/color/convert-color";
|
||||
|
||||
@customElement("ha-qr-code")
|
||||
export class HaQrCode extends LitElement {
|
||||
@@ -65,16 +66,37 @@ export class HaQrCode extends LitElement {
|
||||
changedProperties.has("centerImage"))
|
||||
) {
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const textRgb = computedStyles.getPropertyValue(
|
||||
"--rgb-primary-text-color"
|
||||
);
|
||||
const backgroundRgb = computedStyles.getPropertyValue(
|
||||
"--rgb-card-background-color"
|
||||
);
|
||||
const textHex = rgb2hex(
|
||||
textRgb.split(",").map((a) => parseInt(a, 10)) as [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
]
|
||||
);
|
||||
const backgroundHex = rgb2hex(
|
||||
backgroundRgb.split(",").map((a) => parseInt(a, 10)) as [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
]
|
||||
);
|
||||
|
||||
QRCode.toCanvas(canvas, this.data, {
|
||||
errorCorrectionLevel: this.errorCorrectionLevel,
|
||||
errorCorrectionLevel:
|
||||
this.errorCorrectionLevel || (this.centerImage ? "Q" : "M"),
|
||||
width: this.width,
|
||||
scale: this.scale,
|
||||
margin: this.margin,
|
||||
maskPattern: this.maskPattern,
|
||||
color: {
|
||||
light: computedStyles.getPropertyValue("--card-background-color"),
|
||||
dark: computedStyles.getPropertyValue("--primary-text-color"),
|
||||
light: backgroundHex,
|
||||
dark: textHex,
|
||||
},
|
||||
}).catch((err) => {
|
||||
this._error = err.message;
|
||||
|
30
src/components/ha-selector/ha-selector-qr-code.ts
Normal file
30
src/components/ha-selector/ha-selector-qr-code.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { QRCodeSelector } from "../../data/selector";
|
||||
import "../ha-qr-code";
|
||||
|
||||
@customElement("ha-selector-qr_code")
|
||||
export class HaSelectorQRCode extends LitElement {
|
||||
@property({ attribute: false }) public selector!: QRCodeSelector;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-qr-code
|
||||
.data=${this.selector.qr_code?.data}
|
||||
.scale=${this.selector.qr_code?.scale}
|
||||
.errorCorrectionLevel=${this.selector.qr_code?.error_correction_level}
|
||||
.centerImage=${this.selector.qr_code?.center_image}
|
||||
></ha-qr-code>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-qr-code {
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-qr_code": HaSelectorQRCode;
|
||||
}
|
||||
}
|
@@ -34,6 +34,7 @@ const LOAD_ELEMENTS = {
|
||||
navigation: () => import("./ha-selector-navigation"),
|
||||
number: () => import("./ha-selector-number"),
|
||||
object: () => import("./ha-selector-object"),
|
||||
qr_code: () => import("./ha-selector-qr-code"),
|
||||
select: () => import("./ha-selector-select"),
|
||||
selector: () => import("./ha-selector-selector"),
|
||||
state: () => import("./ha-selector-state"),
|
||||
|
@@ -38,7 +38,6 @@ import { storage } from "../common/decorators/storage";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import { throttle } from "../common/util/throttle";
|
||||
import { ActionHandlerDetail } from "../data/lovelace/action_handler";
|
||||
import {
|
||||
@@ -307,16 +306,12 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.locale !== this.hass.locale) {
|
||||
toggleAttribute(this, "rtl", computeRTL(this.hass));
|
||||
}
|
||||
|
||||
this._calculateCounts();
|
||||
|
||||
if (!SUPPORT_SCROLL_IF_NEEDED) {
|
||||
return;
|
||||
}
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.panelUrl !== this.hass.panelUrl) {
|
||||
const selectedEl = this.shadowRoot!.querySelector(".iron-selected");
|
||||
if (selectedEl) {
|
||||
@@ -851,29 +846,22 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
font-size: 20px;
|
||||
align-items: center;
|
||||
padding-left: calc(4px + env(safe-area-inset-left));
|
||||
}
|
||||
:host([rtl]) .menu {
|
||||
padding-left: 4px;
|
||||
padding-right: calc(4px + env(safe-area-inset-right));
|
||||
padding-inline-start: calc(4px + env(safe-area-inset-left));
|
||||
padding-inline-end: initial;
|
||||
}
|
||||
:host([expanded]) .menu {
|
||||
width: calc(256px + env(safe-area-inset-left));
|
||||
}
|
||||
:host([rtl][expanded]) .menu {
|
||||
width: calc(256px + env(safe-area-inset-right));
|
||||
}
|
||||
.menu ha-icon-button {
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
.title {
|
||||
margin-left: 19px;
|
||||
margin-inline-start: 19px;
|
||||
margin-inline-end: initial;
|
||||
width: 100%;
|
||||
display: none;
|
||||
}
|
||||
:host([rtl]) .title {
|
||||
margin-left: 0;
|
||||
margin-right: 19px;
|
||||
}
|
||||
:host([narrow]) .title {
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
@@ -904,11 +892,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
overflow-x: hidden;
|
||||
background: none;
|
||||
margin-left: env(safe-area-inset-left);
|
||||
}
|
||||
|
||||
:host([rtl]) paper-listbox {
|
||||
margin-left: initial;
|
||||
margin-right: env(safe-area-inset-right);
|
||||
margin-inline-start: env(safe-area-inset-left);
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -925,6 +910,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
box-sizing: border-box;
|
||||
margin: 4px;
|
||||
padding-left: 12px;
|
||||
padding-inline-start: 12px;
|
||||
padding-inline-end: initial;
|
||||
border-radius: 4px;
|
||||
--paper-item-min-height: 40px;
|
||||
width: 48px;
|
||||
@@ -932,10 +919,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
:host([expanded]) paper-icon-item {
|
||||
width: 248px;
|
||||
}
|
||||
:host([rtl]) paper-icon-item {
|
||||
padding-left: auto;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
ha-icon[slot="item-icon"],
|
||||
ha-svg-icon[slot="item-icon"] {
|
||||
@@ -1010,11 +993,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
.configuration-container {
|
||||
display: flex;
|
||||
margin-left: env(safe-area-inset-left);
|
||||
}
|
||||
:host([rtl]) .notifications-container,
|
||||
:host([rtl]) .configuration-container {
|
||||
margin-left: initial;
|
||||
margin-right: env(safe-area-inset-right);
|
||||
margin-inline-start: env(safe-area-inset-left);
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
.notifications {
|
||||
cursor: pointer;
|
||||
@@ -1025,23 +1005,18 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
.profile {
|
||||
margin-left: env(safe-area-inset-left);
|
||||
}
|
||||
:host([rtl]) .profile {
|
||||
margin-left: initial;
|
||||
margin-right: env(safe-area-inset-right);
|
||||
margin-inline-start: env(safe-area-inset-left);
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
.profile paper-icon-item {
|
||||
padding-left: 4px;
|
||||
}
|
||||
:host([rtl]) .profile paper-icon-item {
|
||||
padding-left: auto;
|
||||
padding-right: 4px;
|
||||
margin-inline-start: 4px;
|
||||
margin-inline-end: auto;
|
||||
}
|
||||
.profile .item-text {
|
||||
margin-left: 8px;
|
||||
}
|
||||
:host([rtl]) .profile .item-text {
|
||||
margin-right: 8px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
|
||||
.notification-badge,
|
||||
@@ -1106,9 +1081,9 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:host([rtl]) .menu ha-icon-button {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
.menu ha-icon-button {
|
||||
-webkit-transform: scaleX(var(--scale-direction));
|
||||
transform: scaleX(var(--scale-direction));
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -2,9 +2,15 @@ import { customElement } from "lit/decorators";
|
||||
import "element-internals-polyfill";
|
||||
import { MdSlider } from "@material/web/slider/slider";
|
||||
import { CSSResult, css } from "lit";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
|
||||
@customElement("ha-slider")
|
||||
export class HaSlider extends MdSlider {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.dir = mainWindow.document.dir;
|
||||
}
|
||||
|
||||
static override styles: CSSResult[] = [
|
||||
...MdSlider.styles,
|
||||
css`
|
||||
|
@@ -39,6 +39,12 @@ export class HaSortable extends LitElement {
|
||||
@property({ type: String, attribute: "group" })
|
||||
public group?: string;
|
||||
|
||||
@property({ type: Number, attribute: "swap-threshold" })
|
||||
public swapThreshold?: number;
|
||||
|
||||
@property({ type: Boolean, attribute: "invert-swap" })
|
||||
public invertSwap?: boolean;
|
||||
|
||||
protected updated(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has("disabled")) {
|
||||
if (this.disabled) {
|
||||
@@ -81,7 +87,7 @@ export class HaSortable extends LitElement {
|
||||
}
|
||||
|
||||
.sortable-ghost {
|
||||
border: 2px solid var(--primary-color);
|
||||
box-shadow: 0 0 0 2px var(--primary-color);
|
||||
background: rgba(var(--rgb-primary-color), 0.25);
|
||||
border-radius: 4px;
|
||||
opacity: 0.4;
|
||||
@@ -108,7 +114,7 @@ export class HaSortable extends LitElement {
|
||||
|
||||
const options: SortableInstance.Options = {
|
||||
animation: 150,
|
||||
swapThreshold: 0.75,
|
||||
swapThreshold: 1,
|
||||
onChoose: this._handleChoose,
|
||||
onEnd: this._handleEnd,
|
||||
};
|
||||
@@ -116,6 +122,13 @@ export class HaSortable extends LitElement {
|
||||
if (this.draggableSelector) {
|
||||
options.draggable = this.draggableSelector;
|
||||
}
|
||||
|
||||
if (this.swapThreshold !== undefined) {
|
||||
options.swapThreshold = this.swapThreshold;
|
||||
}
|
||||
if (this.invertSwap !== undefined) {
|
||||
options.invertSwap = this.invertSwap;
|
||||
}
|
||||
if (this.handleSelector) {
|
||||
options.handle = this.handleSelector;
|
||||
}
|
||||
|
@@ -98,6 +98,7 @@ export class HaTargetPicker extends LitElement {
|
||||
area_id,
|
||||
area?.name || area_id,
|
||||
undefined,
|
||||
area?.icon,
|
||||
mdiSofa
|
||||
);
|
||||
})
|
||||
@@ -110,6 +111,7 @@ export class HaTargetPicker extends LitElement {
|
||||
device_id,
|
||||
device ? computeDeviceName(device, this.hass) : device_id,
|
||||
undefined,
|
||||
undefined,
|
||||
mdiDevices
|
||||
);
|
||||
})
|
||||
@@ -209,7 +211,8 @@ export class HaTargetPicker extends LitElement {
|
||||
id: string,
|
||||
name: string,
|
||||
entityState?: HassEntity,
|
||||
iconPath?: string
|
||||
icon?: string | null,
|
||||
fallbackIconPath?: string
|
||||
) {
|
||||
return html`
|
||||
<div
|
||||
@@ -217,12 +220,17 @@ export class HaTargetPicker extends LitElement {
|
||||
[type]: true,
|
||||
})}"
|
||||
>
|
||||
${iconPath
|
||||
? html`<ha-svg-icon
|
||||
${icon
|
||||
? html`<ha-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${iconPath}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
.icon=${icon}
|
||||
></ha-icon>`
|
||||
: fallbackIconPath
|
||||
? html`<ha-svg-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${fallbackIconPath}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
${entityState
|
||||
? html`<ha-state-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
|
@@ -3,18 +3,11 @@ import { styles as textfieldStyles } from "@material/mwc-textfield/mwc-textfield
|
||||
import { styles as textareaStyles } from "@material/mwc-textarea/mwc-textarea.css";
|
||||
import { css, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
|
||||
@customElement("ha-textarea")
|
||||
export class HaTextArea extends TextAreaBase {
|
||||
@property({ type: Boolean, reflect: true }) autogrow = false;
|
||||
|
||||
firstUpdated() {
|
||||
super.firstUpdated();
|
||||
|
||||
this.setAttribute("dir", mainWindow.document.dir);
|
||||
}
|
||||
|
||||
updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (this.autogrow && changedProperties.has("value")) {
|
||||
@@ -54,9 +47,10 @@ export class HaTextArea extends TextAreaBase {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
:host([dir="rtl"]) .mdc-floating-label {
|
||||
right: 16px;
|
||||
left: initial;
|
||||
.mdc-floating-label {
|
||||
inset-inline-start: 16px !important;
|
||||
inset-inline-end: initial !important;
|
||||
transform-origin: var(--float-start) top;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -133,7 +133,7 @@ export class HaLocationsEditor extends LitElement {
|
||||
.layers=${this._getLayers(this._circles, this._locationMarkers)}
|
||||
.zoom=${this.zoom}
|
||||
.autoFit=${this.autoFit}
|
||||
.darkMode=${this.darkMode}
|
||||
?darkMode=${this.darkMode}
|
||||
></ha-map>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
|
@@ -8,12 +8,18 @@ import type {
|
||||
Marker,
|
||||
Polyline,
|
||||
} from "leaflet";
|
||||
import { isToday } from "date-fns";
|
||||
import { css, CSSResultGroup, PropertyValues, ReactiveElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import {
|
||||
LeafletModuleType,
|
||||
setupLeafletMap,
|
||||
} from "../../common/dom/setup-leaflet-map";
|
||||
import {
|
||||
formatTimeWithSeconds,
|
||||
formatTimeWeekday,
|
||||
} from "../../common/datetime/format_time";
|
||||
import { formatDateTime } from "../../common/datetime/format_date_time";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { loadPolyfillIfNeeded } from "../../resources/resize-observer.polyfill";
|
||||
@@ -27,12 +33,14 @@ const getEntityId = (entity: string | HaMapEntity): string =>
|
||||
|
||||
export interface HaMapPathPoint {
|
||||
point: LatLngTuple;
|
||||
tooltip: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
export interface HaMapPaths {
|
||||
points: HaMapPathPoint[];
|
||||
color?: string;
|
||||
name?: string;
|
||||
gradualOpacity?: number;
|
||||
fullDatetime?: boolean;
|
||||
}
|
||||
|
||||
export interface HaMapEntity {
|
||||
@@ -156,9 +164,9 @@ export class HaMap extends ReactiveElement {
|
||||
}
|
||||
|
||||
private _updateMapStyle(): void {
|
||||
const darkMode = this.darkMode ?? this.hass.themes.darkMode ?? false;
|
||||
const forcedDark = this.darkMode ?? false;
|
||||
const map = this.shadowRoot!.getElementById("map");
|
||||
const darkMode = this.darkMode || (this.hass.themes.darkMode ?? false);
|
||||
const forcedDark = this.darkMode;
|
||||
const map = this.renderRoot.querySelector("#map");
|
||||
map!.classList.toggle("dark", darkMode);
|
||||
map!.classList.toggle("forced-dark", forcedDark);
|
||||
}
|
||||
@@ -242,6 +250,30 @@ export class HaMap extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _computePathTooltip(path: HaMapPaths, point: HaMapPathPoint): string {
|
||||
let formattedTime: string;
|
||||
if (path.fullDatetime) {
|
||||
formattedTime = formatDateTime(
|
||||
point.timestamp,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
);
|
||||
} else if (isToday(point.timestamp)) {
|
||||
formattedTime = formatTimeWithSeconds(
|
||||
point.timestamp,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
);
|
||||
} else {
|
||||
formattedTime = formatTimeWeekday(
|
||||
point.timestamp,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
);
|
||||
}
|
||||
return `${path.name}<br>${formattedTime}`;
|
||||
}
|
||||
|
||||
private _drawPaths(): void {
|
||||
const hass = this.hass;
|
||||
const map = this.leafletMap;
|
||||
@@ -289,7 +321,10 @@ export class HaMap extends ReactiveElement {
|
||||
fillOpacity: opacity,
|
||||
interactive: true,
|
||||
})
|
||||
.bindTooltip(path.points[pointIndex].tooltip, { direction: "top" })
|
||||
.bindTooltip(
|
||||
this._computePathTooltip(path, path.points[pointIndex]),
|
||||
{ direction: "top" }
|
||||
)
|
||||
);
|
||||
|
||||
// DRAW line between this and next point
|
||||
@@ -319,7 +354,10 @@ export class HaMap extends ReactiveElement {
|
||||
fillOpacity: opacity,
|
||||
interactive: true,
|
||||
})
|
||||
.bindTooltip(path.points[pointIndex].tooltip, { direction: "top" })
|
||||
.bindTooltip(
|
||||
this._computePathTooltip(path, path.points[pointIndex]),
|
||||
{ direction: "top" }
|
||||
)
|
||||
);
|
||||
}
|
||||
this._mapPaths.forEach((marker) => map.addLayer(marker));
|
||||
@@ -361,7 +399,7 @@ export class HaMap extends ReactiveElement {
|
||||
);
|
||||
|
||||
const className =
|
||||
this.darkMode ?? this.hass.themes.darkMode ? "dark" : "light";
|
||||
this.darkMode || this.hass.themes.darkMode ? "dark" : "light";
|
||||
|
||||
for (const entity of this.entities) {
|
||||
const stateObj = hass.states[getEntityId(entity)];
|
||||
@@ -556,6 +594,7 @@ export class HaMap extends ReactiveElement {
|
||||
color: white !important;
|
||||
border-radius: 4px;
|
||||
box-shadow: none !important;
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -25,7 +25,6 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { until } from "lit/directives/until";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { isUnavailableState } from "../../data/entity";
|
||||
import type { MediaPlayerItem } from "../../data/media-player";
|
||||
@@ -539,7 +538,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
.graphic=${mediaClass.show_list_images
|
||||
? "medium"
|
||||
: "avatar"}
|
||||
dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<span class="title">
|
||||
${this.hass.localize(
|
||||
@@ -637,7 +635,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
@click=${this._childClicked}
|
||||
.item=${child}
|
||||
.graphic=${mediaClass.show_list_images ? "medium" : "avatar"}
|
||||
dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
${backgroundImage === "none" && !child.can_play
|
||||
? html`<ha-svg-icon
|
||||
@@ -1198,10 +1195,8 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
|
||||
mwc-list-item .title {
|
||||
margin-left: 16px;
|
||||
}
|
||||
mwc-list-item[dir="rtl"] .title {
|
||||
margin-right: 16px;
|
||||
margin-left: 0;
|
||||
margin-inline-start: 16px;
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
|
||||
/* ============= Narrow ============= */
|
||||
@@ -1332,6 +1327,10 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
lit-virtualizer.not_shown {
|
||||
height: calc(100% - 36px);
|
||||
}
|
||||
|
||||
ha-browse-media-tts {
|
||||
direction: var(--direction);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import {
|
||||
html,
|
||||
TemplateResult,
|
||||
svg,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { NODE_SIZE, SPACING } from "./hat-graph-const";
|
||||
@@ -51,7 +52,7 @@ export class HatGraphNode extends LitElement {
|
||||
: Math.ceil((NODE_SIZE + SPACING * 2) / 2)} ${width} ${height}"
|
||||
>
|
||||
${this.graphStart
|
||||
? ``
|
||||
? nothing
|
||||
: svg`
|
||||
<path
|
||||
class="connector"
|
||||
@@ -64,7 +65,6 @@ export class HatGraphNode extends LitElement {
|
||||
`}
|
||||
<g class="node">
|
||||
<circle cx="0" cy="0" r=${NODE_SIZE / 2} />
|
||||
}
|
||||
${this.badge
|
||||
? svg`
|
||||
<g class="number">
|
||||
@@ -81,9 +81,11 @@ export class HatGraphNode extends LitElement {
|
||||
>${this.badge > 9 ? "9+" : this.badge}</text>
|
||||
</g>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
<g style="pointer-events: none" transform="translate(${-12} ${-12})">
|
||||
${this.iconPath ? svg`<path class="icon" d=${this.iconPath}/>` : ""}
|
||||
${this.iconPath
|
||||
? svg`<path class="icon" d=${this.iconPath}/>`
|
||||
: svg`<foreignObject><span class="icon"><slot name="icon"></slot></span></foreignObject>`}
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -152,6 +154,13 @@ export class HatGraphNode extends LitElement {
|
||||
path.icon {
|
||||
fill: var(--icon-clr);
|
||||
}
|
||||
foreignObject {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.icon {
|
||||
color: var(--icon-clr);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -17,11 +17,10 @@ import {
|
||||
mdiRoomService,
|
||||
mdiShuffleDisabled,
|
||||
} from "@mdi/js";
|
||||
import { LitElement, PropertyValues, css, html } from "lit";
|
||||
import { LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { ACTION_ICONS } from "../../data/action";
|
||||
import { Condition, Trigger } from "../../data/automation";
|
||||
import {
|
||||
Action,
|
||||
@@ -41,11 +40,14 @@ import {
|
||||
IfActionTraceStep,
|
||||
TraceExtended,
|
||||
} from "../../data/trace";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-service-icon";
|
||||
import "./hat-graph-branch";
|
||||
import { BRANCH_HEIGHT, NODE_SIZE, SPACING } from "./hat-graph-const";
|
||||
import "./hat-graph-node";
|
||||
import "./hat-graph-spacer";
|
||||
import { ACTION_ICONS } from "../../data/action";
|
||||
|
||||
export interface NodeInfo {
|
||||
path: string;
|
||||
@@ -64,6 +66,8 @@ export class HatScriptGraph extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public selected?: string;
|
||||
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
public renderedNodes: Record<string, NodeInfo> = {};
|
||||
|
||||
public trackedNodes: Record<string, NodeInfo> = {};
|
||||
@@ -415,13 +419,21 @@ export class HatScriptGraph extends LitElement {
|
||||
return html`
|
||||
<hat-graph-node
|
||||
.graphStart=${graphStart}
|
||||
.iconPath=${mdiRoomService}
|
||||
.iconPath=${node.service ? undefined : mdiRoomService}
|
||||
@focus=${this.selectNode(node, path)}
|
||||
?track=${path in this.trace.trace}
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${disabled || node.enabled === false}
|
||||
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
|
||||
></hat-graph-node>
|
||||
>
|
||||
${node.service
|
||||
? html`<ha-service-icon
|
||||
slot="icon"
|
||||
.hass=${this.hass}
|
||||
.service=${node.service}
|
||||
></ha-service-icon>`
|
||||
: nothing}
|
||||
</hat-graph-node>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -667,8 +679,6 @@ export class HatScriptGraph extends LitElement {
|
||||
}
|
||||
.parent {
|
||||
margin-left: 8px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.error {
|
||||
|
@@ -24,6 +24,8 @@ class HaUsersPickerLight extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public users?: User[];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
if (this.users === undefined) {
|
||||
@@ -57,6 +59,7 @@ class HaUsersPickerLight extends LitElement {
|
||||
this.users,
|
||||
notSelectedUsers
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._userChanged}
|
||||
></ha-user-picker>
|
||||
<ha-icon-button
|
||||
@@ -78,7 +81,7 @@ class HaUsersPickerLight extends LitElement {
|
||||
this.hass!.localize("ui.components.user-picker.add_user")}
|
||||
.hass=${this.hass}
|
||||
.users=${notSelectedUsers}
|
||||
.disabled=${!notSelectedUsers?.length}
|
||||
.disabled=${this.disabled || !notSelectedUsers?.length}
|
||||
@value-changed=${this._addUser}
|
||||
></ha-user-picker>
|
||||
`;
|
||||
|
@@ -9,6 +9,7 @@ export interface AreaRegistryEntry {
|
||||
area_id: string;
|
||||
name: string;
|
||||
picture: string | null;
|
||||
icon: string | null;
|
||||
aliases: string[];
|
||||
}
|
||||
|
||||
@@ -23,6 +24,7 @@ export interface AreaDeviceLookup {
|
||||
export interface AreaRegistryEntryMutableParams {
|
||||
name: string;
|
||||
picture?: string | null;
|
||||
icon?: string | null;
|
||||
aliases?: string[];
|
||||
}
|
||||
|
||||
|
@@ -74,8 +74,8 @@ export interface StateTrigger extends BaseTrigger {
|
||||
platform: "state";
|
||||
entity_id: string | string[];
|
||||
attribute?: string;
|
||||
from?: string | number;
|
||||
to?: string | string[] | number;
|
||||
from?: string | string[];
|
||||
to?: string | string[];
|
||||
for?: string | number | ForDict;
|
||||
}
|
||||
|
||||
|
@@ -199,57 +199,46 @@ const tryDescribeTrigger = (
|
||||
|
||||
// State Trigger
|
||||
if (trigger.platform === "state") {
|
||||
let base = "When";
|
||||
const entities: string[] = [];
|
||||
const states = hass.states;
|
||||
|
||||
let attribute = "";
|
||||
if (trigger.attribute) {
|
||||
const stateObj = Array.isArray(trigger.entity_id)
|
||||
? hass.states[trigger.entity_id[0]]
|
||||
: hass.states[trigger.entity_id];
|
||||
base += ` ${computeAttributeNameDisplay(
|
||||
attribute = computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
trigger.attribute
|
||||
)} of`;
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(trigger.entity_id)) {
|
||||
for (const entity of trigger.entity_id.values()) {
|
||||
const entityArray: string[] = ensureArray(trigger.entity_id);
|
||||
if (entityArray) {
|
||||
for (const entity of entityArray) {
|
||||
if (states[entity]) {
|
||||
entities.push(computeStateName(states[entity]) || entity);
|
||||
}
|
||||
}
|
||||
} else if (trigger.entity_id) {
|
||||
entities.push(
|
||||
states[trigger.entity_id]
|
||||
? computeStateName(states[trigger.entity_id])
|
||||
: trigger.entity_id
|
||||
);
|
||||
}
|
||||
|
||||
if (entities.length === 0) {
|
||||
// no entity_id or empty array
|
||||
entities.push("something");
|
||||
}
|
||||
const stateObj = hass.states[entityArray[0]];
|
||||
|
||||
base += ` ${entities} changes`;
|
||||
|
||||
const stateObj =
|
||||
hass.states[
|
||||
Array.isArray(trigger.entity_id)
|
||||
? trigger.entity_id[0]
|
||||
: trigger.entity_id
|
||||
];
|
||||
let fromChoice = "other";
|
||||
let fromString = "";
|
||||
if (trigger.from !== undefined) {
|
||||
let fromArray: string[] = [];
|
||||
if (trigger.from === null) {
|
||||
if (!trigger.attribute) {
|
||||
base += " from any state";
|
||||
fromChoice = "null";
|
||||
}
|
||||
} else if (Array.isArray(trigger.from)) {
|
||||
} else {
|
||||
fromArray = ensureArray(trigger.from);
|
||||
|
||||
const from: string[] = [];
|
||||
for (const state of trigger.from.values()) {
|
||||
for (const state of fromArray) {
|
||||
from.push(
|
||||
trigger.attribute
|
||||
? hass
|
||||
@@ -263,34 +252,25 @@ const tryDescribeTrigger = (
|
||||
);
|
||||
}
|
||||
if (from.length !== 0) {
|
||||
const fromString = formatListWithOrs(hass.locale, from);
|
||||
base += ` from ${fromString}`;
|
||||
fromString = formatListWithOrs(hass.locale, from);
|
||||
fromChoice = "fromUsed";
|
||||
}
|
||||
} else {
|
||||
base += ` from ${
|
||||
trigger.attribute
|
||||
? hass
|
||||
.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
trigger.attribute,
|
||||
trigger.from
|
||||
)
|
||||
.toString()
|
||||
: hass
|
||||
.formatEntityState(stateObj, trigger.from.toString())
|
||||
.toString()
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
let toChoice = "other";
|
||||
let toString = "";
|
||||
if (trigger.to !== undefined) {
|
||||
let toArray: string[] = [];
|
||||
if (trigger.to === null) {
|
||||
if (!trigger.attribute) {
|
||||
base += " to any state";
|
||||
toChoice = "null";
|
||||
}
|
||||
} else if (Array.isArray(trigger.to)) {
|
||||
} else {
|
||||
toArray = ensureArray(trigger.to);
|
||||
|
||||
const to: string[] = [];
|
||||
for (const state of trigger.to.values()) {
|
||||
for (const state of toArray) {
|
||||
to.push(
|
||||
trigger.attribute
|
||||
? hass
|
||||
@@ -304,21 +284,9 @@ const tryDescribeTrigger = (
|
||||
);
|
||||
}
|
||||
if (to.length !== 0) {
|
||||
const toString = formatListWithOrs(hass.locale, to);
|
||||
base += ` to ${toString}`;
|
||||
toString = formatListWithOrs(hass.locale, to);
|
||||
toChoice = "toUsed";
|
||||
}
|
||||
} else {
|
||||
base += ` to ${
|
||||
trigger.attribute
|
||||
? hass
|
||||
.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
trigger.attribute,
|
||||
trigger.to
|
||||
)
|
||||
.toString()
|
||||
: hass.formatEntityState(stateObj, trigger.to.toString())
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,17 +295,29 @@ const tryDescribeTrigger = (
|
||||
trigger.from === undefined &&
|
||||
trigger.to === undefined
|
||||
) {
|
||||
base += " state or any attributes";
|
||||
toChoice = "special";
|
||||
}
|
||||
|
||||
let duration = "";
|
||||
if (trigger.for) {
|
||||
const duration = describeDuration(hass.locale, trigger.for);
|
||||
if (duration) {
|
||||
base += ` for ${duration}`;
|
||||
}
|
||||
duration = describeDuration(hass.locale, trigger.for) ?? "";
|
||||
}
|
||||
|
||||
return base;
|
||||
return hass.localize(
|
||||
`${triggerTranslationBaseKey}.state.description.full`,
|
||||
{
|
||||
hasAttribute: attribute !== "" ? "true" : "false",
|
||||
attribute: attribute,
|
||||
hasEntity: entities.length !== 0 ? "true" : "false",
|
||||
entity: formatListWithOrs(hass.locale, entities),
|
||||
fromChoice: fromChoice,
|
||||
fromString: fromString,
|
||||
toChoice: toChoice,
|
||||
toString: toString,
|
||||
hasDuration: duration !== "" ? "true" : "false",
|
||||
duration: duration,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Sun Trigger
|
||||
|
@@ -72,6 +72,8 @@ export const enum ClimateEntityFeature {
|
||||
PRESET_MODE = 16,
|
||||
SWING_MODE = 32,
|
||||
AUX_HEAT = 64,
|
||||
TURN_OFF = 128,
|
||||
TURN_ON = 256,
|
||||
}
|
||||
|
||||
const hvacModeOrdering = HVAC_MODES.reduce(
|
||||
|
@@ -470,9 +470,15 @@ export const computeHistory = (
|
||||
}[domain];
|
||||
}
|
||||
|
||||
const deviceClass: string | undefined = (
|
||||
currentState?.attributes || numericStateFromHistory?.a
|
||||
)?.device_class;
|
||||
const specialDomainClasses = {
|
||||
climate: "temperature",
|
||||
humidifier: "humidity",
|
||||
water_heater: "temperature",
|
||||
};
|
||||
|
||||
const deviceClass: string | undefined =
|
||||
specialDomainClasses[domain] ||
|
||||
(currentState?.attributes || numericStateFromHistory?.a)?.device_class;
|
||||
|
||||
const key = computeGroupKey(unit, deviceClass, splitDeviceClasses);
|
||||
|
||||
|
@@ -8,37 +8,49 @@ import {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "./entity_registry";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { atLeastVersion } from "../common/config/version";
|
||||
|
||||
const resources: Record<IconCategory, any> = {
|
||||
const resources: {
|
||||
entity: Record<string, Promise<PlatformIcons>>;
|
||||
entity_component: {
|
||||
domains?: string[];
|
||||
resources?: Promise<Record<string, ComponentIcons>>;
|
||||
};
|
||||
services: {
|
||||
all?: Promise<Record<string, ServiceIcons>>;
|
||||
domains: { [domain: string]: ServiceIcons | Promise<ServiceIcons> };
|
||||
};
|
||||
} = {
|
||||
entity: {},
|
||||
entity_component: undefined,
|
||||
services: {},
|
||||
entity_component: {},
|
||||
services: { domains: {} },
|
||||
};
|
||||
|
||||
interface IconResources {
|
||||
resources: Record<string, string | Record<string, string>>;
|
||||
interface IconResources<
|
||||
T extends ComponentIcons | PlatformIcons | ServiceIcons,
|
||||
> {
|
||||
resources: Record<string, T>;
|
||||
}
|
||||
|
||||
interface PlatformIcons {
|
||||
[domain: string]: {
|
||||
[translation_key: string]: {
|
||||
state: Record<string, string>;
|
||||
state_attributes: Record<
|
||||
string,
|
||||
{
|
||||
state: Record<string, string>;
|
||||
default: string;
|
||||
}
|
||||
>;
|
||||
default: string;
|
||||
};
|
||||
[translation_key: string]: {
|
||||
state: Record<string, string>;
|
||||
state_attributes: Record<
|
||||
string,
|
||||
{
|
||||
state: Record<string, string>;
|
||||
default: string;
|
||||
}
|
||||
>;
|
||||
default: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ComponentIcons {
|
||||
export interface ComponentIcons {
|
||||
[device_class: string]: {
|
||||
state: Record<string, string>;
|
||||
state_attributes: Record<
|
||||
state?: Record<string, string>;
|
||||
state_attributes?: Record<
|
||||
string,
|
||||
{
|
||||
state: Record<string, string>;
|
||||
@@ -55,12 +67,18 @@ interface ServiceIcons {
|
||||
|
||||
export type IconCategory = "entity" | "entity_component" | "services";
|
||||
|
||||
export const getHassIcons = async (
|
||||
type CategoryType = {
|
||||
entity: PlatformIcons;
|
||||
entity_component: ComponentIcons;
|
||||
services: ServiceIcons;
|
||||
};
|
||||
|
||||
export const getHassIcons = async <T extends IconCategory>(
|
||||
hass: HomeAssistant,
|
||||
category: IconCategory,
|
||||
category: T,
|
||||
integration?: string
|
||||
): Promise<IconResources> =>
|
||||
hass.callWS<{ resources: Record<string, string> }>({
|
||||
) =>
|
||||
hass.callWS<IconResources<CategoryType[T]>>({
|
||||
type: "frontend/get_icons",
|
||||
category,
|
||||
integration,
|
||||
@@ -70,14 +88,20 @@ export const getPlatformIcons = async (
|
||||
hass: HomeAssistant,
|
||||
integration: string,
|
||||
force = false
|
||||
): Promise<PlatformIcons> => {
|
||||
): Promise<PlatformIcons | undefined> => {
|
||||
if (!force && integration in resources.entity) {
|
||||
return resources.entity[integration];
|
||||
}
|
||||
const result = getHassIcons(hass, "entity", integration);
|
||||
resources.entity[integration] = result.then(
|
||||
if (
|
||||
!isComponentLoaded(hass, integration) ||
|
||||
!atLeastVersion(hass.connection.haVersion, 2024, 2)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
const result = getHassIcons(hass, "entity", integration).then(
|
||||
(res) => res?.resources[integration]
|
||||
);
|
||||
resources.entity[integration] = result;
|
||||
return resources.entity[integration];
|
||||
};
|
||||
|
||||
@@ -85,45 +109,70 @@ export const getComponentIcons = async (
|
||||
hass: HomeAssistant,
|
||||
domain: string,
|
||||
force = false
|
||||
): Promise<ComponentIcons> => {
|
||||
if (!force && resources.entity_component) {
|
||||
return resources.entity_component.then((res) => res[domain]);
|
||||
): Promise<ComponentIcons | undefined> => {
|
||||
// For Cast, old instances can connect to it.
|
||||
if (
|
||||
__BACKWARDS_COMPAT__ &&
|
||||
!atLeastVersion(hass.connection.haVersion, 2024, 2)
|
||||
) {
|
||||
return import("../fake_data/entity_component_icons")
|
||||
.then((mod) => mod.ENTITY_COMPONENT_ICONS)
|
||||
.then((res) => res[domain]);
|
||||
}
|
||||
resources.entity_component = getHassIcons(hass, "entity_component").then(
|
||||
(result) => result.resources
|
||||
);
|
||||
return resources.entity_component.then((res) => res[domain]);
|
||||
|
||||
if (
|
||||
!force &&
|
||||
resources.entity_component.resources &&
|
||||
resources.entity_component.domains?.includes(domain)
|
||||
) {
|
||||
return resources.entity_component.resources.then((res) => res[domain]);
|
||||
}
|
||||
|
||||
if (!isComponentLoaded(hass, domain)) {
|
||||
return undefined;
|
||||
}
|
||||
resources.entity_component.domains = [...hass.config.components];
|
||||
resources.entity_component.resources = getHassIcons(
|
||||
hass,
|
||||
"entity_component"
|
||||
).then((result) => result.resources);
|
||||
return resources.entity_component.resources.then((res) => res[domain]);
|
||||
};
|
||||
|
||||
export const getServiceIcons = async (
|
||||
hass: HomeAssistant,
|
||||
domain?: string,
|
||||
force = false
|
||||
): Promise<ServiceIcons> => {
|
||||
): Promise<ServiceIcons | Record<string, ServiceIcons> | undefined> => {
|
||||
if (!domain) {
|
||||
if (!force && resources.services.all) {
|
||||
return resources.services.all;
|
||||
}
|
||||
resources.services.all = getHassIcons(hass, "services", domain).then(
|
||||
(res) => {
|
||||
resources.services = res.resources;
|
||||
resources.services.domains = res.resources;
|
||||
return res?.resources;
|
||||
}
|
||||
);
|
||||
return resources.services.all;
|
||||
}
|
||||
if (!force && domain && domain in resources.services) {
|
||||
return resources.services[domain];
|
||||
if (!force && domain in resources.services.domains) {
|
||||
return resources.services.domains[domain];
|
||||
}
|
||||
if (resources.services.all && !force) {
|
||||
await resources.services.all;
|
||||
if (domain in resources.services) {
|
||||
return resources.services[domain];
|
||||
if (domain in resources.services.domains) {
|
||||
return resources.services.domains[domain];
|
||||
}
|
||||
}
|
||||
if (!isComponentLoaded(hass, domain)) {
|
||||
return undefined;
|
||||
}
|
||||
const result = getHassIcons(hass, "services", domain);
|
||||
resources.services[domain] = result.then((res) => res?.resources[domain]);
|
||||
return resources.services[domain];
|
||||
resources.services.domains[domain] = result.then(
|
||||
(res) => res?.resources[domain]
|
||||
);
|
||||
return resources.services.domains[domain];
|
||||
};
|
||||
|
||||
export const entityIcon = async (
|
||||
@@ -238,7 +287,7 @@ export const serviceIcon = async (
|
||||
const serviceName = computeObjectId(service);
|
||||
const serviceIcons = await getServiceIcons(hass, domain);
|
||||
if (serviceIcons) {
|
||||
icon = serviceIcons[serviceName];
|
||||
icon = serviceIcons[serviceName] as string;
|
||||
}
|
||||
if (!icon) {
|
||||
icon = await domainIcon(hass, domain);
|
||||
|
@@ -3,6 +3,50 @@ import { navigate } from "../common/navigate";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { subscribeDeviceRegistry } from "./device_registry";
|
||||
|
||||
export enum NetworkType {
|
||||
THREAD = "thread",
|
||||
WIFI = "wifi",
|
||||
ETHERNET = "ethernet",
|
||||
UNKNOWN = "unknown",
|
||||
}
|
||||
|
||||
export enum NodeType {
|
||||
END_DEVICE = "end_device",
|
||||
SLEEPY_END_DEVICE = "sleepy_end_device",
|
||||
ROUTING_END_DEVICE = "routing_end_device",
|
||||
BRIDGE = "bridge",
|
||||
UNKNOWN = "unknown",
|
||||
}
|
||||
|
||||
export interface MatterFabricData {
|
||||
fabric_id: number;
|
||||
vendor_id: number;
|
||||
fabric_index: number;
|
||||
fabric_label?: string;
|
||||
vendor_name?: string;
|
||||
}
|
||||
|
||||
export interface MatterNodeDiagnostics {
|
||||
node_id: number;
|
||||
network_type: NetworkType;
|
||||
node_type: NodeType;
|
||||
network_name?: string;
|
||||
ip_adresses: string[];
|
||||
mac_address?: string;
|
||||
available: boolean;
|
||||
active_fabrics: MatterFabricData[];
|
||||
}
|
||||
|
||||
export interface MatterPingResult {
|
||||
[ip_address: string]: boolean;
|
||||
}
|
||||
|
||||
export interface MatterCommissioningParameters {
|
||||
setup_pin_code: number;
|
||||
setup_manual_code: string;
|
||||
setup_qr_code: string;
|
||||
}
|
||||
|
||||
export const canCommissionMatterExternal = (hass: HomeAssistant) =>
|
||||
hass.auth.external?.config.canCommissionMatter;
|
||||
|
||||
@@ -86,3 +130,50 @@ export const matterSetThread = (
|
||||
type: "matter/set_thread",
|
||||
thread_operation_dataset,
|
||||
});
|
||||
|
||||
export const getMatterNodeDiagnostics = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<MatterNodeDiagnostics> =>
|
||||
hass.callWS({
|
||||
type: "matter/node_diagnostics",
|
||||
device_id,
|
||||
});
|
||||
|
||||
export const pingMatterNode = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<MatterPingResult> =>
|
||||
hass.callWS({
|
||||
type: "matter/ping_node",
|
||||
device_id,
|
||||
});
|
||||
|
||||
export const openMatterCommissioningWindow = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<MatterCommissioningParameters> =>
|
||||
hass.callWS({
|
||||
type: "matter/open_commissioning_window",
|
||||
device_id,
|
||||
});
|
||||
|
||||
export const removeMatterFabric = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string,
|
||||
fabric_index: number
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "matter/remove_matter_fabric",
|
||||
device_id,
|
||||
fabric_index,
|
||||
});
|
||||
|
||||
export const interviewMatterNode = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "matter/interview_node",
|
||||
device_id,
|
||||
});
|
||||
|
@@ -32,6 +32,13 @@ export const fetchRepairsIssues = (conn: Connection) =>
|
||||
type: "repairs/list_issues",
|
||||
});
|
||||
|
||||
export const fetchRepairsIssueData = (conn: Connection, domain, issue_id) =>
|
||||
conn.sendMessagePromise<{ issue_data: { string: any } }>({
|
||||
type: "repairs/get_issue_data",
|
||||
domain,
|
||||
issue_id,
|
||||
});
|
||||
|
||||
export const ignoreRepairsIssue = async (
|
||||
hass: HomeAssistant,
|
||||
issue: RepairsIssue,
|
||||
|
@@ -41,6 +41,7 @@ export type Selector =
|
||||
| NumberSelector
|
||||
| ObjectSelector
|
||||
| AssistPipelineSelector
|
||||
| QRCodeSelector
|
||||
| SelectSelector
|
||||
| SelectorSelector
|
||||
| StateSelector
|
||||
@@ -340,6 +341,15 @@ export interface BackupLocationSelector {
|
||||
backup_location: {} | null;
|
||||
}
|
||||
|
||||
export interface QRCodeSelector {
|
||||
qr_code: {
|
||||
data: string;
|
||||
scale?: number;
|
||||
error_correction_level?: "low" | "medium" | "quartile" | "high";
|
||||
center_image?: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface StringSelector {
|
||||
text: {
|
||||
multiline?: boolean;
|
||||
|
@@ -22,6 +22,7 @@ export interface ThreadDataSet {
|
||||
network_name: string;
|
||||
pan_id: string | null;
|
||||
preferred_border_agent_id: string | null;
|
||||
preferred_extended_address: string | null;
|
||||
preferred: boolean;
|
||||
source: string;
|
||||
}
|
||||
@@ -107,10 +108,12 @@ export const setPreferredThreadDataSet = (
|
||||
export const setPreferredBorderAgent = (
|
||||
hass: HomeAssistant,
|
||||
dataset_id: string,
|
||||
border_agent_id: string
|
||||
border_agent_id: string | null,
|
||||
extended_address: string
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "thread/set_preferred_border_agent_id",
|
||||
type: "thread/set_preferred_border_agent",
|
||||
dataset_id,
|
||||
border_agent_id,
|
||||
extended_address,
|
||||
});
|
||||
|
@@ -1,19 +1,12 @@
|
||||
import { Connection, createCollection } from "home-assistant-js-websocket";
|
||||
import { Store } from "home-assistant-js-websocket/dist/store";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { AreaRegistryEntry } from "./area_registry";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import { AreaRegistryEntry } from "./area_registry";
|
||||
|
||||
const fetchAreaRegistry = (conn: Connection) =>
|
||||
conn
|
||||
.sendMessagePromise({
|
||||
type: "config/area_registry/list",
|
||||
})
|
||||
.then((areas) =>
|
||||
(areas as AreaRegistryEntry[]).sort((ent1, ent2) =>
|
||||
stringCompare(ent1.name, ent2.name)
|
||||
)
|
||||
);
|
||||
conn.sendMessagePromise<AreaRegistryEntry[]>({
|
||||
type: "config/area_registry/list",
|
||||
});
|
||||
|
||||
const subscribeAreaRegistryUpdates = (
|
||||
conn: Connection,
|
||||
|
@@ -73,7 +73,7 @@ export interface ClusterAttributeData {
|
||||
export interface AttributeConfigurationStatus {
|
||||
id: number;
|
||||
name: string;
|
||||
success: boolean | undefined;
|
||||
status: string;
|
||||
min: number;
|
||||
max: number;
|
||||
change: number;
|
||||
|
@@ -2,7 +2,6 @@ import "@material/mwc-button/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-formfield";
|
||||
import "../../components/ha-switch";
|
||||
@@ -82,7 +81,6 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
}
|
||||
)}
|
||||
</p>`}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${!this._disableNewEntities}
|
||||
@@ -109,7 +107,6 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
}
|
||||
)}
|
||||
</p>`}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${!this._disablePolling}
|
||||
|
@@ -14,7 +14,6 @@ import { customElement, property } from "lit/decorators";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-select";
|
||||
import "../../../components/ha-slider";
|
||||
@@ -131,7 +130,6 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
<ha-slider
|
||||
labeled
|
||||
id="input"
|
||||
.dir=${computeRTLDirection(this.hass!)}
|
||||
.value=${Number(stateObj.attributes.volume_level) * 100}
|
||||
@change=${this._selectedValueChanged}
|
||||
></ha-slider>
|
||||
|
@@ -35,6 +35,13 @@ interface EMOutgoingMessageConfigGet extends EMMessage {
|
||||
type: "config/get";
|
||||
}
|
||||
|
||||
interface EMOutgoingMessageScanQRCode extends EMMessage {
|
||||
type: "qr_code/scan";
|
||||
title: string;
|
||||
description: string;
|
||||
alternative_option_label?: string;
|
||||
}
|
||||
|
||||
interface EMOutgoingMessageMatterCommission extends EMMessage {
|
||||
type: "matter/commission";
|
||||
}
|
||||
@@ -48,6 +55,13 @@ type EMOutgoingMessageWithAnswer = {
|
||||
request: EMOutgoingMessageConfigGet;
|
||||
response: ExternalConfig;
|
||||
};
|
||||
"qr_code/scan": {
|
||||
request: EMOutgoingMessageScanQRCode;
|
||||
response:
|
||||
| EMIncomingMessageQRCodeResponseCanceled
|
||||
| EMIncomingMessageQRCodeResponseAlternativeOptions
|
||||
| EMIncomingMessageQRCodeResponseScanResult;
|
||||
};
|
||||
};
|
||||
|
||||
interface EMOutgoingMessageExoplayerPlayHLS extends EMMessage {
|
||||
@@ -158,6 +172,19 @@ interface EMIncomingMessageShowAutomationEditor {
|
||||
};
|
||||
}
|
||||
|
||||
export interface EMIncomingMessageQRCodeResponseCanceled {
|
||||
action: "canceled";
|
||||
}
|
||||
|
||||
export interface EMIncomingMessageQRCodeResponseAlternativeOptions {
|
||||
action: "alternative_options";
|
||||
}
|
||||
|
||||
export interface EMIncomingMessageQRCodeResponseScanResult {
|
||||
action: "scan_result";
|
||||
result: string;
|
||||
}
|
||||
|
||||
export type EMIncomingMessageCommands =
|
||||
| EMIncomingMessageRestart
|
||||
| EMIncomingMessageShowNotifications
|
||||
@@ -180,6 +207,7 @@ export interface ExternalConfig {
|
||||
canCommissionMatter: boolean;
|
||||
canImportThreadCredentials: boolean;
|
||||
hasAssist: boolean;
|
||||
hasQRScanner: number;
|
||||
}
|
||||
|
||||
export class ExternalMessaging {
|
||||
|
@@ -14,7 +14,53 @@ export const demoConfig: HassConfig = {
|
||||
wind_speed: "m/s",
|
||||
accumulated_precipitation: "mm",
|
||||
},
|
||||
components: ["notify.html5", "history", "todo", "forecast_solar", "energy"],
|
||||
components: [
|
||||
"notify.html5",
|
||||
"history",
|
||||
"forecast_solar",
|
||||
"energy",
|
||||
"person",
|
||||
"number",
|
||||
"select",
|
||||
"tts",
|
||||
"datetime",
|
||||
"vacuum",
|
||||
"wake_word",
|
||||
"light",
|
||||
"alarm_control_panel",
|
||||
"text",
|
||||
"lawn_mower",
|
||||
"siren",
|
||||
"input_boolean",
|
||||
"lock",
|
||||
"calendar",
|
||||
"image",
|
||||
"device_tracker",
|
||||
"scene",
|
||||
"script",
|
||||
"todo",
|
||||
"cover",
|
||||
"switch",
|
||||
"button",
|
||||
"water_heater",
|
||||
"binary_sensor",
|
||||
"sensor",
|
||||
"humidifier",
|
||||
"valve",
|
||||
"time",
|
||||
"media_player",
|
||||
"air_quality",
|
||||
"camera",
|
||||
"date",
|
||||
"fan",
|
||||
"automation",
|
||||
"weather",
|
||||
"climate",
|
||||
"stt",
|
||||
"update",
|
||||
"event",
|
||||
"demo",
|
||||
],
|
||||
time_zone: "America/Los_Angeles",
|
||||
config_dir: "/config",
|
||||
version: "DEMO",
|
||||
|
962
src/fake_data/entity_component_icons.ts
Normal file
962
src/fake_data/entity_component_icons.ts
Normal file
@@ -0,0 +1,962 @@
|
||||
import { ComponentIcons } from "../data/icons";
|
||||
|
||||
export const ENTITY_COMPONENT_ICONS: Record<string, ComponentIcons> = {
|
||||
person: {
|
||||
_: {
|
||||
default: "mdi:account",
|
||||
state: {
|
||||
not_home: "mdi:account-arrow-right",
|
||||
},
|
||||
},
|
||||
},
|
||||
number: {
|
||||
_: {
|
||||
default: "mdi:ray-vertex",
|
||||
},
|
||||
apparent_power: {
|
||||
default: "mdi:flash",
|
||||
},
|
||||
aqi: {
|
||||
default: "mdi:air-filter",
|
||||
},
|
||||
atmospheric_pressure: {
|
||||
default: "mdi:thermometer-lines",
|
||||
},
|
||||
battery: {
|
||||
default: "mdi:battery",
|
||||
},
|
||||
carbon_dioxide: {
|
||||
default: "mdi:molecule-co2",
|
||||
},
|
||||
carbon_monoxide: {
|
||||
default: "mdi:molecule-co",
|
||||
},
|
||||
current: {
|
||||
default: "mdi:current-ac",
|
||||
},
|
||||
data_rate: {
|
||||
default: "mdi:transmission-tower",
|
||||
},
|
||||
data_size: {
|
||||
default: "mdi:database",
|
||||
},
|
||||
distance: {
|
||||
default: "mdi:arrow-left-right",
|
||||
},
|
||||
duration: {
|
||||
default: "mdi:progress-clock",
|
||||
},
|
||||
energy: {
|
||||
default: "mdi:lightning-bolt",
|
||||
},
|
||||
energy_storage: {
|
||||
default: "mdi:car-battery",
|
||||
},
|
||||
frequency: {
|
||||
default: "mdi:sine-wave",
|
||||
},
|
||||
gas: {
|
||||
default: "mdi:meter-gas",
|
||||
},
|
||||
humidity: {
|
||||
default: "mdi:water-percent",
|
||||
},
|
||||
illuminance: {
|
||||
default: "mdi:brightness-5",
|
||||
},
|
||||
irradiance: {
|
||||
default: "mdi:sun-wireless",
|
||||
},
|
||||
moisture: {
|
||||
default: "mdi:water-percent",
|
||||
},
|
||||
monetary: {
|
||||
default: "mdi:cash",
|
||||
},
|
||||
nitrogen_dioxide: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
nitrogen_monoxide: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
nitrous_oxide: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
ozone: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
ph: {
|
||||
default: "mdi:ph",
|
||||
},
|
||||
pm1: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
pm10: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
pm25: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
power: {
|
||||
default: "mdi:flash",
|
||||
},
|
||||
power_factor: {
|
||||
default: "mdi:angle-acute",
|
||||
},
|
||||
precipitation: {
|
||||
default: "mdi:weather-rainy",
|
||||
},
|
||||
precipitation_intensity: {
|
||||
default: "mdi:weather-pouring",
|
||||
},
|
||||
pressure: {
|
||||
default: "mdi:gauge",
|
||||
},
|
||||
reactive_power: {
|
||||
default: "mdi:flash",
|
||||
},
|
||||
signal_strength: {
|
||||
default: "mdi:wifi",
|
||||
},
|
||||
sound_pressure: {
|
||||
default: "mdi:ear-hearing",
|
||||
},
|
||||
speed: {
|
||||
default: "mdi:speedometer",
|
||||
},
|
||||
sulfur_dioxide: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
temperature: {
|
||||
default: "mdi:thermometer",
|
||||
},
|
||||
volatile_organic_compounds: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
volatile_organic_compounds_parts: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
voltage: {
|
||||
default: "mdi:sine-wave",
|
||||
},
|
||||
volume: {
|
||||
default: "mdi:car-coolant-level",
|
||||
},
|
||||
volume_storage: {
|
||||
default: "mdi:storage-tank",
|
||||
},
|
||||
water: {
|
||||
default: "mdi:water",
|
||||
},
|
||||
weight: {
|
||||
default: "mdi:weight",
|
||||
},
|
||||
wind_speed: {
|
||||
default: "mdi:weather-windy",
|
||||
},
|
||||
},
|
||||
select: {
|
||||
_: {
|
||||
default: "mdi:format-list-bulleted",
|
||||
},
|
||||
},
|
||||
tts: {
|
||||
_: {
|
||||
default: "mdi:speaker-message",
|
||||
},
|
||||
},
|
||||
datetime: {
|
||||
_: {
|
||||
default: "mdi:calendar-clock",
|
||||
},
|
||||
},
|
||||
vacuum: {
|
||||
_: {
|
||||
default: "mdi:robot-vacuum",
|
||||
},
|
||||
},
|
||||
wake_word: {
|
||||
_: {
|
||||
default: "mdi:chat-sleep",
|
||||
},
|
||||
},
|
||||
light: {
|
||||
_: {
|
||||
default: "mdi:lightbulb",
|
||||
},
|
||||
},
|
||||
alarm_control_panel: {
|
||||
_: {
|
||||
default: "mdi:shield",
|
||||
state: {
|
||||
armed_away: "mdi:shield-lock",
|
||||
armed_custom_bypass: "mdi:security",
|
||||
armed_home: "mdi:shield-home",
|
||||
armed_night: "mdi:shield-moon",
|
||||
armed_vacation: "mdi:shield-airplane",
|
||||
disarmed: "mdi:shield-off",
|
||||
pending: "mdi:shield-outline",
|
||||
triggered: "mdi:bell-ring",
|
||||
},
|
||||
},
|
||||
},
|
||||
text: {
|
||||
_: {
|
||||
default: "mdi:form-textbox",
|
||||
},
|
||||
},
|
||||
lawn_mower: {
|
||||
_: {
|
||||
default: "mdi:robot-mower",
|
||||
},
|
||||
},
|
||||
siren: {
|
||||
_: {
|
||||
default: "mdi:bullhorn",
|
||||
},
|
||||
},
|
||||
input_boolean: {
|
||||
_: {
|
||||
default: "mdi:check-circle-outline",
|
||||
state: {
|
||||
off: "mdi:close-circle-outline",
|
||||
},
|
||||
},
|
||||
},
|
||||
lock: {
|
||||
_: {
|
||||
default: "mdi:lock",
|
||||
state: {
|
||||
jammed: "mdi:lock-alert",
|
||||
locking: "mdi:lock-clock",
|
||||
unlocked: "mdi:lock-open",
|
||||
unlocking: "mdi:lock-clock",
|
||||
},
|
||||
},
|
||||
},
|
||||
calendar: {
|
||||
_: {
|
||||
default: "mdi:calendar",
|
||||
state: {
|
||||
on: "mdi:calendar-check",
|
||||
off: "mdi:calendar-blank",
|
||||
},
|
||||
},
|
||||
},
|
||||
image: {
|
||||
_: {
|
||||
default: "mdi:image",
|
||||
},
|
||||
},
|
||||
device_tracker: {
|
||||
_: {
|
||||
default: "mdi:account",
|
||||
state: {
|
||||
not_home: "mdi:account-arrow-right",
|
||||
},
|
||||
},
|
||||
},
|
||||
scene: {
|
||||
_: {
|
||||
default: "mdi:palette",
|
||||
},
|
||||
},
|
||||
script: {
|
||||
_: {
|
||||
default: "mdi:script-text",
|
||||
state: {
|
||||
on: "mdi:script-text-play",
|
||||
},
|
||||
},
|
||||
},
|
||||
todo: {
|
||||
_: {
|
||||
default: "mdi:clipboard-list",
|
||||
},
|
||||
},
|
||||
cover: {
|
||||
_: {
|
||||
default: "mdi:window-open",
|
||||
state: {
|
||||
closed: "mdi:window-closed",
|
||||
closing: "mdi:arrow-down-box",
|
||||
opening: "mdi:arrow-up-box",
|
||||
},
|
||||
},
|
||||
blind: {
|
||||
default: "mdi:blinds-horizontal",
|
||||
state: {
|
||||
closed: "mdi:blinds-horizontal-closed",
|
||||
closing: "mdi:arrow-down-box",
|
||||
opening: "mdi:arrow-up-box",
|
||||
},
|
||||
},
|
||||
curtain: {
|
||||
default: "mdi:curtains",
|
||||
state: {
|
||||
closed: "mdi:curtains-closed",
|
||||
closing: "mdi:arrow-collapse-horizontal",
|
||||
opening: "mdi:arrow-split-vertical",
|
||||
},
|
||||
},
|
||||
damper: {
|
||||
default: "mdi:circle",
|
||||
state: {
|
||||
closed: "mdi:circle-slice-8",
|
||||
},
|
||||
},
|
||||
door: {
|
||||
default: "mdi:door-open",
|
||||
state: {
|
||||
closed: "mdi:door-closed",
|
||||
},
|
||||
},
|
||||
garage: {
|
||||
default: "mdi:garage-open",
|
||||
state: {
|
||||
closed: "mdi:garage",
|
||||
closing: "mdi:arrow-down-box",
|
||||
opening: "mdi:arrow-up-box",
|
||||
},
|
||||
},
|
||||
gate: {
|
||||
default: "mdi:gate-open",
|
||||
state: {
|
||||
closed: "mdi:gate",
|
||||
closing: "mdi:arrow-right",
|
||||
opening: "mdi:arrow-right",
|
||||
},
|
||||
},
|
||||
shade: {
|
||||
default: "mdi:roller-shade",
|
||||
state: {
|
||||
closed: "mdi:roller-shade-closed",
|
||||
closing: "mdi:arrow-down-box",
|
||||
opening: "mdi:arrow-up-box",
|
||||
},
|
||||
},
|
||||
shutter: {
|
||||
default: "mdi:window-shutter-open",
|
||||
state: {
|
||||
closed: "mdi:window-shutter",
|
||||
closing: "mdi:arrow-down-box",
|
||||
opening: "mdi:arrow-up-box",
|
||||
},
|
||||
},
|
||||
window: {
|
||||
default: "mdi:window-open",
|
||||
state: {
|
||||
closed: "mdi:window-closed",
|
||||
closing: "mdi:arrow-down-box",
|
||||
opening: "mdi:arrow-up-box",
|
||||
},
|
||||
},
|
||||
},
|
||||
switch: {
|
||||
_: {
|
||||
default: "mdi:toggle-switch-variant",
|
||||
},
|
||||
switch: {
|
||||
default: "mdi:toggle-switch-variant",
|
||||
state: {
|
||||
off: "mdi:toggle-switch-variant-off",
|
||||
},
|
||||
},
|
||||
outlet: {
|
||||
default: "mdi:power-plug",
|
||||
state: {
|
||||
off: "mdi:power-plug-off",
|
||||
},
|
||||
},
|
||||
},
|
||||
button: {
|
||||
_: {
|
||||
default: "mdi:button-pointer",
|
||||
},
|
||||
restart: {
|
||||
default: "mdi:restart",
|
||||
},
|
||||
identify: {
|
||||
default: "mdi:crosshairs-question",
|
||||
},
|
||||
update: {
|
||||
default: "mdi:package-up",
|
||||
},
|
||||
},
|
||||
water_heater: {
|
||||
_: {
|
||||
default: "mdi:water-boiler",
|
||||
state: {
|
||||
off: "mdi:water-boiler-off",
|
||||
},
|
||||
state_attributes: {
|
||||
operation_mode: {
|
||||
default: "mdi:circle-medium",
|
||||
state: {
|
||||
eco: "mdi:leaf",
|
||||
electric: "mdi:lightning-bolt",
|
||||
gas: "mdi:fire-circle",
|
||||
heat_pump: "mdi:heat-wave",
|
||||
high_demand: "mdi:finance",
|
||||
off: "mdi:power",
|
||||
performance: "mdi:rocket-launch",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
binary_sensor: {
|
||||
_: {
|
||||
default: "mdi:radiobox-blank",
|
||||
state: {
|
||||
on: "mdi:checkbox-marked-circle",
|
||||
},
|
||||
},
|
||||
battery: {
|
||||
default: "mdi:battery",
|
||||
state: {
|
||||
on: "mdi:battery-outline",
|
||||
},
|
||||
},
|
||||
battery_charging: {
|
||||
default: "mdi:battery",
|
||||
state: {
|
||||
on: "mdi:battery-charging",
|
||||
},
|
||||
},
|
||||
carbon_monoxide: {
|
||||
default: "mdi:smoke-detector",
|
||||
state: {
|
||||
on: "mdi:smoke-detector-alert",
|
||||
},
|
||||
},
|
||||
cold: {
|
||||
default: "mdi:thermometer",
|
||||
state: {
|
||||
on: "mdi:snowflake",
|
||||
},
|
||||
},
|
||||
connectivity: {
|
||||
default: "mdi:close-network-outline",
|
||||
state: {
|
||||
on: "mdi:check-network-outline",
|
||||
},
|
||||
},
|
||||
door: {
|
||||
default: "mdi:door-closed",
|
||||
state: {
|
||||
on: "mdi:door-open",
|
||||
},
|
||||
},
|
||||
garage_door: {
|
||||
default: "mdi:garage",
|
||||
state: {
|
||||
on: "mdi:garage-open",
|
||||
},
|
||||
},
|
||||
gas: {
|
||||
default: "mdi:check-circle",
|
||||
state: {
|
||||
on: "mdi:alert-circle",
|
||||
},
|
||||
},
|
||||
heat: {
|
||||
default: "mdi:thermometer",
|
||||
state: {
|
||||
on: "mdi:fire",
|
||||
},
|
||||
},
|
||||
light: {
|
||||
default: "mdi:brightness-5",
|
||||
state: {
|
||||
on: "mdi:brightness-7",
|
||||
},
|
||||
},
|
||||
lock: {
|
||||
default: "mdi:lock",
|
||||
state: {
|
||||
on: "mdi:lock-open",
|
||||
},
|
||||
},
|
||||
moisture: {
|
||||
default: "mdi:water-off",
|
||||
state: {
|
||||
on: "mdi:water",
|
||||
},
|
||||
},
|
||||
motion: {
|
||||
default: "mdi:motion-sensor-off",
|
||||
state: {
|
||||
on: "mdi:motion-sensor",
|
||||
},
|
||||
},
|
||||
moving: {
|
||||
default: "mdi:arrow-right",
|
||||
state: {
|
||||
on: "mdi:octagon",
|
||||
},
|
||||
},
|
||||
occupancy: {
|
||||
default: "mdi:home-outline",
|
||||
state: {
|
||||
on: "mdi:home",
|
||||
},
|
||||
},
|
||||
opening: {
|
||||
default: "mdi:square",
|
||||
state: {
|
||||
on: "mdi:square-outline",
|
||||
},
|
||||
},
|
||||
plug: {
|
||||
default: "mdi:power-plug-off",
|
||||
state: {
|
||||
on: "mdi:power-plug",
|
||||
},
|
||||
},
|
||||
power: {
|
||||
default: "mdi:power-plug-off",
|
||||
state: {
|
||||
on: "mdi:power-plug",
|
||||
},
|
||||
},
|
||||
presence: {
|
||||
default: "mdi:home-outline",
|
||||
state: {
|
||||
on: "mdi:home",
|
||||
},
|
||||
},
|
||||
problem: {
|
||||
default: "mdi:check-circle",
|
||||
state: {
|
||||
on: "mdi:alert-circle",
|
||||
},
|
||||
},
|
||||
running: {
|
||||
default: "mdi:stop",
|
||||
state: {
|
||||
on: "mdi:play",
|
||||
},
|
||||
},
|
||||
safety: {
|
||||
default: "mdi:check-circle",
|
||||
state: {
|
||||
on: "mdi:alert-circle",
|
||||
},
|
||||
},
|
||||
smoke: {
|
||||
default: "mdi:smoke-detector-variant",
|
||||
state: {
|
||||
on: "mdi:smoke-detector-variant-alert",
|
||||
},
|
||||
},
|
||||
sound: {
|
||||
default: "mdi:music-note-off",
|
||||
state: {
|
||||
on: "mdi:music-note",
|
||||
},
|
||||
},
|
||||
tamper: {
|
||||
default: "mdi:check-circle",
|
||||
state: {
|
||||
on: "mdi:alert-circle",
|
||||
},
|
||||
},
|
||||
update: {
|
||||
default: "mdi:package",
|
||||
state: {
|
||||
on: "mdi:package-up",
|
||||
},
|
||||
},
|
||||
vibration: {
|
||||
default: "mdi:crop-portrait",
|
||||
state: {
|
||||
on: "mdi:vibrate",
|
||||
},
|
||||
},
|
||||
window: {
|
||||
default: "mdi:window-closed",
|
||||
state: {
|
||||
on: "mdi:window-open",
|
||||
},
|
||||
},
|
||||
},
|
||||
sensor: {
|
||||
_: {
|
||||
default: "mdi:eye",
|
||||
},
|
||||
apparent_power: {
|
||||
default: "mdi:flash",
|
||||
},
|
||||
aqi: {
|
||||
default: "mdi:air-filter",
|
||||
},
|
||||
atmospheric_pressure: {
|
||||
default: "mdi:thermometer-lines",
|
||||
},
|
||||
carbon_dioxide: {
|
||||
default: "mdi:molecule-co2",
|
||||
},
|
||||
carbon_monoxide: {
|
||||
default: "mdi:molecule-co",
|
||||
},
|
||||
current: {
|
||||
default: "mdi:current-ac",
|
||||
},
|
||||
data_rate: {
|
||||
default: "mdi:transmission-tower",
|
||||
},
|
||||
data_size: {
|
||||
default: "mdi:database",
|
||||
},
|
||||
date: {
|
||||
default: "mdi:calendar",
|
||||
},
|
||||
distance: {
|
||||
default: "mdi:arrow-left-right",
|
||||
},
|
||||
duration: {
|
||||
default: "mdi:progress-clock",
|
||||
},
|
||||
energy: {
|
||||
default: "mdi:lightning-bolt",
|
||||
},
|
||||
energy_storage: {
|
||||
default: "mdi:car-battery",
|
||||
},
|
||||
enum: {
|
||||
default: "mdi:eye",
|
||||
},
|
||||
frequency: {
|
||||
default: "mdi:sine-wave",
|
||||
},
|
||||
gas: {
|
||||
default: "mdi:meter-gas",
|
||||
},
|
||||
humidity: {
|
||||
default: "mdi:water-percent",
|
||||
},
|
||||
illuminance: {
|
||||
default: "mdi:brightness-5",
|
||||
},
|
||||
irradiance: {
|
||||
default: "mdi:sun-wireless",
|
||||
},
|
||||
moisture: {
|
||||
default: "mdi:water-percent",
|
||||
},
|
||||
monetary: {
|
||||
default: "mdi:cash",
|
||||
},
|
||||
nitrogen_dioxide: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
nitrogen_monoxide: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
nitrous_oxide: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
ozone: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
ph: {
|
||||
default: "mdi:ph",
|
||||
},
|
||||
pm1: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
pm10: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
pm25: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
power: {
|
||||
default: "mdi:flash",
|
||||
},
|
||||
power_factor: {
|
||||
default: "mdi:angle-acute",
|
||||
},
|
||||
precipitation: {
|
||||
default: "mdi:weather-rainy",
|
||||
},
|
||||
precipitation_intensity: {
|
||||
default: "mdi:weather-pouring",
|
||||
},
|
||||
pressure: {
|
||||
default: "mdi:gauge",
|
||||
},
|
||||
reactive_power: {
|
||||
default: "mdi:flash",
|
||||
},
|
||||
signal_strength: {
|
||||
default: "mdi:wifi",
|
||||
},
|
||||
sound_pressure: {
|
||||
default: "mdi:ear-hearing",
|
||||
},
|
||||
speed: {
|
||||
default: "mdi:speedometer",
|
||||
},
|
||||
sulfur_dioxide: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
temperature: {
|
||||
default: "mdi:thermometer",
|
||||
},
|
||||
timestamp: {
|
||||
default: "mdi:clock",
|
||||
},
|
||||
volatile_organic_compounds: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
volatile_organic_compounds_parts: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
voltage: {
|
||||
default: "mdi:sine-wave",
|
||||
},
|
||||
volume: {
|
||||
default: "mdi:car-coolant-level",
|
||||
},
|
||||
volume_storage: {
|
||||
default: "mdi:storage-tank",
|
||||
},
|
||||
water: {
|
||||
default: "mdi:water",
|
||||
},
|
||||
weight: {
|
||||
default: "mdi:weight",
|
||||
},
|
||||
wind_speed: {
|
||||
default: "mdi:weather-windy",
|
||||
},
|
||||
},
|
||||
humidifier: {
|
||||
_: {
|
||||
default: "mdi:air-humidifier",
|
||||
state: {
|
||||
off: "mdi:air-humidifier-off",
|
||||
},
|
||||
state_attributes: {
|
||||
action: {
|
||||
default: "mdi:circle-medium",
|
||||
state: {
|
||||
drying: "mdi:arrow-down-bold",
|
||||
humidifying: "mdi:arrow-up-bold",
|
||||
idle: "mdi:clock-outline",
|
||||
off: "mdi:power",
|
||||
},
|
||||
},
|
||||
mode: {
|
||||
default: "mdi:circle-medium",
|
||||
state: {
|
||||
auto: "mdi:refresh-auto",
|
||||
away: "mdi:account-arrow-right",
|
||||
baby: "mdi:baby-carriage",
|
||||
boost: "mdi:rocket-launch",
|
||||
comfort: "mdi:sofa",
|
||||
eco: "mdi:leaf",
|
||||
home: "mdi:home",
|
||||
normal: "mdi:water-percent",
|
||||
sleep: "mdi:power-sleep",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
valve: {
|
||||
_: {
|
||||
default: "mdi:pipe-valve",
|
||||
},
|
||||
gas: {
|
||||
default: "mdi:meter-gas",
|
||||
},
|
||||
water: {
|
||||
default: "mdi:pipe-valve",
|
||||
},
|
||||
},
|
||||
time: {
|
||||
_: {
|
||||
default: "mdi:clock",
|
||||
},
|
||||
},
|
||||
media_player: {
|
||||
_: {
|
||||
default: "mdi:cast",
|
||||
state: {
|
||||
off: "mdi:cast-off",
|
||||
paused: "mdi:cast-connected",
|
||||
playing: "mdi:cast-connected",
|
||||
},
|
||||
},
|
||||
receiver: {
|
||||
default: "mdi:audio-video",
|
||||
state: {
|
||||
off: "mdi:audio-video-off",
|
||||
},
|
||||
},
|
||||
speaker: {
|
||||
default: "mdi:speaker",
|
||||
state: {
|
||||
off: "mdi:speaker-off",
|
||||
paused: "mdi:speaker-pause",
|
||||
playing: "mdi:speaker-play",
|
||||
},
|
||||
},
|
||||
tv: {
|
||||
default: "mdi:television",
|
||||
state: {
|
||||
off: "mdi:television-off",
|
||||
paused: "mdi:television-pause",
|
||||
playing: "mdi:television-play",
|
||||
},
|
||||
},
|
||||
},
|
||||
air_quality: {
|
||||
_: {
|
||||
default: "mdi:air-filter",
|
||||
},
|
||||
},
|
||||
camera: {
|
||||
_: {
|
||||
default: "mdi:video",
|
||||
state: {
|
||||
off: "mdi:video-off",
|
||||
},
|
||||
},
|
||||
},
|
||||
date: {
|
||||
_: {
|
||||
default: "mdi:calendar",
|
||||
},
|
||||
},
|
||||
fan: {
|
||||
_: {
|
||||
default: "mdi:fan",
|
||||
state: {
|
||||
off: "mdi:fan-off",
|
||||
},
|
||||
state_attributes: {
|
||||
direction: {
|
||||
default: "mdi:rotate-right",
|
||||
state: {
|
||||
reverse: "mdi:rotate-left",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
automation: {
|
||||
_: {
|
||||
default: "mdi:robot",
|
||||
state: {
|
||||
off: "mdi:robot-off",
|
||||
unavailable: "mdi:robot-confused",
|
||||
},
|
||||
},
|
||||
},
|
||||
weather: {
|
||||
_: {
|
||||
default: "mdi:weather-partly-cloudy",
|
||||
state: {
|
||||
"clear-night": "mdi:weather-night",
|
||||
cloudy: "mdi:weather-cloudy",
|
||||
exceptional: "mdi:alert-circle-outline",
|
||||
fog: "mdi:weather-fog",
|
||||
hail: "mdi:weather-hail",
|
||||
lightning: "mdi:weather-lightning",
|
||||
"lightning-rainy": "mdi:weather-lightning-rainy",
|
||||
pouring: "mdi:weather-pouring",
|
||||
rainy: "mdi:weather-rainy",
|
||||
snowy: "mdi:weather-snowy",
|
||||
"snowy-rainy": "mdi:weather-snowy-rainy",
|
||||
sunny: "mdi:weather-sunny",
|
||||
windy: "mdi:weather-windy",
|
||||
"windy-variant": "mdi:weather-windy-variant",
|
||||
},
|
||||
},
|
||||
},
|
||||
climate: {
|
||||
_: {
|
||||
default: "mdi:thermostat",
|
||||
state_attributes: {
|
||||
fan_mode: {
|
||||
default: "mdi:circle-medium",
|
||||
state: {
|
||||
diffuse: "mdi:weather-windy",
|
||||
focus: "mdi:target",
|
||||
high: "mdi:speedometer",
|
||||
low: "mdi:speedometer-slow",
|
||||
medium: "mdi:speedometer-medium",
|
||||
middle: "mdi:speedometer-medium",
|
||||
off: "mdi:fan-off",
|
||||
on: "mdi:fan",
|
||||
},
|
||||
},
|
||||
hvac_action: {
|
||||
default: "mdi:circle-medium",
|
||||
state: {
|
||||
cooling: "mdi:snowflake",
|
||||
drying: "mdi:water-percent",
|
||||
fan: "mdi:fan",
|
||||
heating: "mdi:fire",
|
||||
idle: "mdi:clock-outline",
|
||||
off: "mdi:power",
|
||||
preheating: "mdi:heat-wave",
|
||||
},
|
||||
},
|
||||
preset_mode: {
|
||||
default: "mdi:circle-medium",
|
||||
state: {
|
||||
activity: "mdi:motion-sensor",
|
||||
away: "mdi:account-arrow-right",
|
||||
boost: "mdi:rocket-launch",
|
||||
comfort: "mdi:sofa",
|
||||
eco: "mdi:leaf",
|
||||
home: "mdi:home",
|
||||
sleep: "mdi:bed",
|
||||
},
|
||||
},
|
||||
swing_mode: {
|
||||
default: "mdi:circle-medium",
|
||||
state: {
|
||||
both: "mdi:arrow-all",
|
||||
horizontal: "mdi:arrow-left-right",
|
||||
off: "mdi:arrow-oscillating-off",
|
||||
on: "mdi:arrow-oscillating",
|
||||
vertical: "mdi:arrow-up-down",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
stt: {
|
||||
_: {
|
||||
default: "mdi:microphone-message",
|
||||
},
|
||||
},
|
||||
update: {
|
||||
_: {
|
||||
default: "mdi:package-up",
|
||||
state: {
|
||||
off: "mdi:package",
|
||||
},
|
||||
},
|
||||
},
|
||||
event: {
|
||||
_: {
|
||||
default: "mdi:eye-check",
|
||||
},
|
||||
button: {
|
||||
default: "mdi:gesture-tap-button",
|
||||
},
|
||||
doorbell: {
|
||||
default: "mdi:doorbell",
|
||||
},
|
||||
motion: {
|
||||
default: "mdi:motion-sensor",
|
||||
},
|
||||
},
|
||||
};
|
@@ -1,15 +1,6 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, eventOptions, property } from "lit/decorators";
|
||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
import "../components/ha-menu-button";
|
||||
import { HomeAssistant } from "../types";
|
||||
@@ -34,17 +25,6 @@ class HassSubpage extends LitElement {
|
||||
// @ts-ignore
|
||||
@restoreScroll(".content") private _savedScrollPos?: number;
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
if (!changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.locale !== this.hass.locale) {
|
||||
toggleAttribute(this, "rtl", computeRTL(this.hass));
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="toolbar">
|
||||
@@ -160,6 +140,9 @@ class HassSubpage extends LitElement {
|
||||
#fab {
|
||||
position: absolute;
|
||||
right: calc(16px + env(safe-area-inset-right));
|
||||
inset-inline-end: calc(16px + env(safe-area-inset-right));
|
||||
inset-inline-start: initial;
|
||||
|
||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -169,15 +152,8 @@ class HassSubpage extends LitElement {
|
||||
#fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
:host([rtl]) #fab {
|
||||
right: auto;
|
||||
left: calc(16px + env(safe-area-inset-left));
|
||||
}
|
||||
:host([rtl][is-wide]) #fab {
|
||||
bottom: 24px;
|
||||
left: 24px;
|
||||
right: auto;
|
||||
inset-inline-end: 24px;
|
||||
inset-inline-start: initial;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -4,7 +4,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||
import "../components/data-table/ha-data-table";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
@@ -244,7 +243,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
||||
.selectable=${this.selectable}
|
||||
.hasFab=${this.hasFab}
|
||||
.id=${this.id}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
.clickable=${this.clickable}
|
||||
.appendRow=${this.appendRow}
|
||||
>
|
||||
|
@@ -13,7 +13,6 @@ import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
import "../components/ha-menu-button";
|
||||
import "../components/ha-svg-icon";
|
||||
@@ -58,8 +57,6 @@ class HassTabsSubpage extends LitElement {
|
||||
@property({ type: Boolean, reflect: true, attribute: "is-wide" })
|
||||
public isWide = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public rtl = false;
|
||||
|
||||
@state() private _activeTab?: PageNavigation;
|
||||
|
||||
// @ts-ignore
|
||||
@@ -123,14 +120,6 @@ class HassTabsSubpage extends LitElement {
|
||||
`${this.route.prefix}${this.route.path}`.includes(tab.path)
|
||||
);
|
||||
}
|
||||
if (changedProperties.has("hass")) {
|
||||
const oldHass = changedProperties.get("hass") as
|
||||
| HomeAssistant
|
||||
| undefined;
|
||||
if (!oldHass || oldHass.language !== this.hass.language) {
|
||||
this.rtl = computeRTL(this.hass);
|
||||
}
|
||||
}
|
||||
super.willUpdate(changedProperties);
|
||||
}
|
||||
|
||||
@@ -334,6 +323,8 @@ class HassTabsSubpage extends LitElement {
|
||||
#fab {
|
||||
position: fixed;
|
||||
right: calc(16px + env(safe-area-inset-right));
|
||||
inset-inline-end: calc(16px + env(safe-area-inset-right));
|
||||
inset-inline-start: initial;
|
||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -343,15 +334,8 @@ class HassTabsSubpage extends LitElement {
|
||||
#fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
:host([rtl]) #fab {
|
||||
right: auto;
|
||||
left: calc(16px + env(safe-area-inset-left));
|
||||
}
|
||||
:host([rtl][is-wide]) #fab {
|
||||
bottom: 24px;
|
||||
left: 24px;
|
||||
right: auto;
|
||||
inset-inline-end: 24px;
|
||||
inset-inline-start: initial;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -11,7 +11,6 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent, HASSDomEvent } from "../common/dom/fire_event";
|
||||
import { listenMediaQuery } from "../common/dom/media_query";
|
||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||
import "../components/ha-drawer";
|
||||
import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer";
|
||||
import type { HomeAssistant, Route } from "../types";
|
||||
@@ -62,7 +61,6 @@ export class HomeAssistantMain extends LitElement {
|
||||
<ha-drawer
|
||||
.type=${sidebarNarrow ? "modal" : ""}
|
||||
.open=${sidebarNarrow ? this._drawerOpen : undefined}
|
||||
.direction=${computeRTLDirection(this.hass)}
|
||||
@MDCDrawer:closed=${this._drawerClosed}
|
||||
>
|
||||
<ha-sidebar
|
||||
|
@@ -28,7 +28,6 @@ import { useAmPm } from "../../common/datetime/use_am_pm";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import "../../components/ha-button-toggle-group";
|
||||
import "../../components/ha-fab";
|
||||
import "../../components/ha-icon-button-next";
|
||||
@@ -169,7 +168,6 @@ export class HAFullCalendar extends LitElement {
|
||||
.buttons=${viewToggleButtons}
|
||||
.active=${this._activeView}
|
||||
@value-changed=${this._handleView}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
></ha-button-toggle-group>
|
||||
`
|
||||
: html`
|
||||
@@ -203,7 +201,6 @@ export class HAFullCalendar extends LitElement {
|
||||
.buttons=${viewToggleButtons}
|
||||
.active=${this._activeView}
|
||||
@value-changed=${this._handleView}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
></ha-button-toggle-group>
|
||||
</div>
|
||||
`}
|
||||
@@ -503,6 +500,8 @@ export class HAFullCalendar extends LitElement {
|
||||
position: absolute;
|
||||
bottom: 32px;
|
||||
right: 32px;
|
||||
inset-inline-end: 32px;
|
||||
inset-inline-start: initial;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
@@ -9,6 +9,7 @@ import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-picture-upload";
|
||||
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
|
||||
import "../../../components/ha-settings-row";
|
||||
import "../../../components/ha-icon-picker";
|
||||
import "../../../components/ha-textfield";
|
||||
import { AreaRegistryEntryMutableParams } from "../../../data/area_registry";
|
||||
import { CropOptions } from "../../../dialogs/image-cropper-dialog/show-image-cropper-dialog";
|
||||
@@ -32,6 +33,8 @@ class DialogAreaDetail extends LitElement {
|
||||
|
||||
@state() private _picture!: string | null;
|
||||
|
||||
@state() private _icon!: string | null;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _params?: AreaRegistryDetailDialogParams;
|
||||
@@ -46,6 +49,7 @@ class DialogAreaDetail extends LitElement {
|
||||
this._name = this._params.entry ? this._params.entry.name : "";
|
||||
this._aliases = this._params.entry ? this._params.entry.aliases : [];
|
||||
this._picture = this._params.entry?.picture || null;
|
||||
this._icon = this._params.entry?.icon || null;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
@@ -101,6 +105,13 @@ class DialogAreaDetail extends LitElement {
|
||||
dialogInitialFocus
|
||||
></ha-textfield>
|
||||
|
||||
<ha-icon-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._icon}
|
||||
@value-changed=${this._iconChanged}
|
||||
.label=${this.hass.localize("ui.panel.config.areas.editor.icon")}
|
||||
></ha-icon-picker>
|
||||
|
||||
<ha-picture-upload
|
||||
.hass=${this.hass}
|
||||
.value=${this._picture}
|
||||
@@ -152,23 +163,30 @@ class DialogAreaDetail extends LitElement {
|
||||
this._name = ev.target.value;
|
||||
}
|
||||
|
||||
private _iconChanged(ev) {
|
||||
this._error = undefined;
|
||||
this._icon = ev.detail.value;
|
||||
}
|
||||
|
||||
private _pictureChanged(ev: ValueChangedEvent<string | null>) {
|
||||
this._error = undefined;
|
||||
this._picture = (ev.target as HaPictureUpload).value;
|
||||
}
|
||||
|
||||
private async _updateEntry() {
|
||||
const create = !this._params!.entry;
|
||||
this._submitting = true;
|
||||
try {
|
||||
const values: AreaRegistryEntryMutableParams = {
|
||||
name: this._name.trim(),
|
||||
picture: this._picture,
|
||||
picture: this._picture || (create ? undefined : null),
|
||||
icon: this._icon || (create ? undefined : null),
|
||||
aliases: this._aliases,
|
||||
};
|
||||
if (this._params!.entry) {
|
||||
await this._params!.updateEntry!(values);
|
||||
} else {
|
||||
if (create) {
|
||||
await this._params!.createEntry!(values);
|
||||
} else {
|
||||
await this._params!.updateEntry!(values);
|
||||
}
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
@@ -189,6 +207,7 @@ class DialogAreaDetail extends LitElement {
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-textfield,
|
||||
ha-icon-picker,
|
||||
ha-picture-upload {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
|
@@ -1,11 +1,9 @@
|
||||
import { consume } from "@lit-labs/context";
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-list";
|
||||
import { mdiDelete, mdiDotsVertical, mdiImagePlus, mdiPencil } from "@mdi/js";
|
||||
import {
|
||||
HassEntity,
|
||||
UnsubscribeFunc,
|
||||
} from "home-assistant-js-websocket/dist/types";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { HassEntity } from "home-assistant-js-websocket/dist/types";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -18,33 +16,31 @@ import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-list-item";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
deleteAreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
updateAreaRegistryEntry,
|
||||
} from "../../../data/area_registry";
|
||||
import { AutomationEntity } from "../../../data/automation";
|
||||
import { fullEntitiesContext } from "../../../data/context";
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
computeDeviceName,
|
||||
sortDeviceRegistryByName,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
computeEntityRegistryName,
|
||||
EntityRegistryEntry,
|
||||
computeEntityRegistryName,
|
||||
sortEntityRegistryByName,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { SceneEntity } from "../../../data/scene";
|
||||
import { ScriptEntity } from "../../../data/script";
|
||||
import { findRelated, RelatedResult } from "../../../data/search";
|
||||
import { RelatedResult, findRelated } from "../../../data/search";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../../logbook/ha-logbook";
|
||||
@@ -52,7 +48,6 @@ import {
|
||||
loadAreaRegistryDetailDialog,
|
||||
showAreaRegistryDetailDialog,
|
||||
} from "./show-dialog-area-registry-detail";
|
||||
import "../../../components/ha-list-item";
|
||||
|
||||
declare type NameAndEntity<EntityType extends HassEntity> = {
|
||||
name: string;
|
||||
@@ -60,7 +55,7 @@ declare type NameAndEntity<EntityType extends HassEntity> = {
|
||||
};
|
||||
|
||||
@customElement("ha-config-area-page")
|
||||
class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
class HaConfigAreaPage extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public areaId!: string;
|
||||
@@ -71,24 +66,14 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public showAdvanced = false;
|
||||
|
||||
@state() public _areas!: AreaRegistryEntry[];
|
||||
|
||||
@state() public _devices!: DeviceRegistryEntry[];
|
||||
|
||||
@state() public _entities!: EntityRegistryEntry[];
|
||||
@state()
|
||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entityReg!: EntityRegistryEntry[];
|
||||
|
||||
@state() private _related?: RelatedResult;
|
||||
|
||||
private _logbookTime = { recent: 86400 };
|
||||
|
||||
private _area = memoizeOne(
|
||||
(
|
||||
areaId: string,
|
||||
areas: AreaRegistryEntry[]
|
||||
): AreaRegistryEntry | undefined =>
|
||||
areas.find((area) => area.area_id === areaId)
|
||||
);
|
||||
|
||||
private _memberships = memoizeOne(
|
||||
(
|
||||
areaId: string,
|
||||
@@ -150,26 +135,12 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
||||
return [
|
||||
subscribeAreaRegistry(this.hass.connection, (areas) => {
|
||||
this._areas = areas;
|
||||
}),
|
||||
subscribeDeviceRegistry(this.hass.connection, (entries) => {
|
||||
this._devices = entries;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||
this._entities = entries;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._areas || !this._devices || !this._entities) {
|
||||
if (!this.hass.areas || !this.hass.devices || !this.hass.entities) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const area = this._area(this.areaId, this._areas);
|
||||
const area = this.hass.areas[this.areaId];
|
||||
|
||||
if (!area) {
|
||||
return html`
|
||||
@@ -182,8 +153,8 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
const memberships = this._memberships(
|
||||
this.areaId,
|
||||
this._devices,
|
||||
this._entities
|
||||
Object.values(this.hass.devices),
|
||||
this._entityReg
|
||||
);
|
||||
const { devices, entities } = memberships;
|
||||
|
||||
@@ -617,7 +588,7 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _renderScript(name: string, entityState: ScriptEntity) {
|
||||
const entry = this._entities.find(
|
||||
const entry = this._entityReg.find(
|
||||
(e) => e.entity_id === entityState.entity_id
|
||||
);
|
||||
let url = `/config/script/show/${entityState.entity_id}`;
|
||||
@@ -657,7 +628,7 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private async _deleteConfirm() {
|
||||
const area = this._area(this.areaId, this._areas);
|
||||
const area = this.hass.areas[this.areaId];
|
||||
showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.areas.delete.confirmation_title",
|
||||
@@ -686,7 +657,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
font-weight: 500;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
width: 100%;
|
||||
|
@@ -1,7 +1,13 @@
|
||||
import { mdiHelpCircle, mdiPlus } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { formatListWithAnds } from "../../../common/string/format-list";
|
||||
@@ -11,19 +17,9 @@ import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
createAreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../../data/area_registry";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
@@ -33,7 +29,7 @@ import {
|
||||
} from "./show-dialog-area-registry-detail";
|
||||
|
||||
@customElement("ha-config-areas-dashboard")
|
||||
export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
export class HaConfigAreasDashboard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public isWide = false;
|
||||
@@ -42,24 +38,18 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@state() private _areas!: AreaRegistryEntry[];
|
||||
|
||||
@state() private _devices!: DeviceRegistryEntry[];
|
||||
|
||||
@state() private _entities!: EntityRegistryEntry[];
|
||||
|
||||
private _processAreas = memoizeOne(
|
||||
(
|
||||
areas: AreaRegistryEntry[],
|
||||
devices: DeviceRegistryEntry[],
|
||||
entities: EntityRegistryEntry[]
|
||||
) =>
|
||||
areas.map((area) => {
|
||||
areas: HomeAssistant["areas"],
|
||||
devices: HomeAssistant["devices"],
|
||||
entities: HomeAssistant["entities"]
|
||||
) => {
|
||||
const processArea = (area: AreaRegistryEntry) => {
|
||||
let noDevicesInArea = 0;
|
||||
let noServicesInArea = 0;
|
||||
let noEntitiesInArea = 0;
|
||||
|
||||
for (const device of devices) {
|
||||
for (const device of Object.values(devices)) {
|
||||
if (device.area_id === area.area_id) {
|
||||
if (device.entry_type === "service") {
|
||||
noServicesInArea++;
|
||||
@@ -69,7 +59,7 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const entity of entities) {
|
||||
for (const entity of Object.values(entities)) {
|
||||
if (entity.area_id === area.area_id) {
|
||||
noEntitiesInArea++;
|
||||
}
|
||||
@@ -81,24 +71,22 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
services: noServicesInArea,
|
||||
entities: noEntitiesInArea,
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
return Object.values(areas).map(processArea);
|
||||
}
|
||||
);
|
||||
|
||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
||||
return [
|
||||
subscribeAreaRegistry(this.hass.connection, (areas) => {
|
||||
this._areas = areas;
|
||||
}),
|
||||
subscribeDeviceRegistry(this.hass.connection, (entries) => {
|
||||
this._devices = entries;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||
this._entities = entries;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const areas =
|
||||
!this.hass.areas || !this.hass.devices || !this.hass.entities
|
||||
? undefined
|
||||
: this._processAreas(
|
||||
this.hass.areas,
|
||||
this.hass.devices,
|
||||
this.hass.entities
|
||||
);
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
@@ -115,52 +103,11 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
@click=${this._showHelp}
|
||||
></ha-icon-button>
|
||||
<div class="container">
|
||||
${!this._areas || !this._devices || !this._entities
|
||||
? ""
|
||||
: this._processAreas(
|
||||
this._areas,
|
||||
this._devices,
|
||||
this._entities
|
||||
).map(
|
||||
(area) =>
|
||||
html`<a href=${`/config/areas/area/${area.area_id}`}
|
||||
><ha-card outlined>
|
||||
<div
|
||||
style=${styleMap({
|
||||
backgroundImage: area.picture
|
||||
? `url(${area.picture})`
|
||||
: undefined,
|
||||
})}
|
||||
class="picture ${!area.picture ? "placeholder" : ""}"
|
||||
></div>
|
||||
<h1 class="card-header">${area.name}</h1>
|
||||
<div class="card-content">
|
||||
<div>
|
||||
${formatListWithAnds(
|
||||
this.hass.locale,
|
||||
[
|
||||
area.devices &&
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.devices",
|
||||
{ count: area.devices }
|
||||
),
|
||||
area.services &&
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.services",
|
||||
{ count: area.services }
|
||||
),
|
||||
area.entities &&
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.entities",
|
||||
{ count: area.entities }
|
||||
),
|
||||
].filter((v): v is string => Boolean(v))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card></a
|
||||
>`
|
||||
)}
|
||||
${areas?.length
|
||||
? html`<div class="areas">
|
||||
${areas.map((area) => this._renderArea(area))}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
@@ -176,13 +123,55 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderArea(area) {
|
||||
return html`<a href=${`/config/areas/area/${area.area_id}`}>
|
||||
<ha-card outlined>
|
||||
<div
|
||||
style=${styleMap({
|
||||
backgroundImage: area.picture ? `url(${area.picture})` : undefined,
|
||||
})}
|
||||
class="picture ${!area.picture ? "placeholder" : ""}"
|
||||
>
|
||||
${!area.picture && area.icon
|
||||
? html`<ha-icon .icon=${area.icon}></ha-icon>`
|
||||
: ""}
|
||||
</div>
|
||||
<h1 class="card-header">${area.name}</h1>
|
||||
<div class="card-content">
|
||||
<div>
|
||||
${formatListWithAnds(
|
||||
this.hass.locale,
|
||||
[
|
||||
area.devices &&
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.devices",
|
||||
{ count: area.devices }
|
||||
),
|
||||
area.services &&
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.services",
|
||||
{ count: area.services }
|
||||
),
|
||||
area.entities &&
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.entities",
|
||||
{ count: area.entities }
|
||||
),
|
||||
].filter((v): v is string => Boolean(v))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
</a>`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
loadAreaRegistryDetailDialog();
|
||||
}
|
||||
|
||||
private _createArea() {
|
||||
this._openDialog();
|
||||
this._openAreaDialog();
|
||||
}
|
||||
|
||||
private _showHelp() {
|
||||
@@ -202,7 +191,7 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
private _openDialog(entry?: AreaRegistryEntry) {
|
||||
private _openAreaDialog(entry?: AreaRegistryEntry) {
|
||||
showAreaRegistryDetailDialog(this, {
|
||||
entry,
|
||||
createEntry: async (values) =>
|
||||
@@ -213,14 +202,17 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
grid-gap: 16px 16px;
|
||||
padding: 8px 16px 16px;
|
||||
margin: 0 auto 64px auto;
|
||||
max-width: 2000px;
|
||||
}
|
||||
.container > * {
|
||||
.areas {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
grid-gap: 16px 16px;
|
||||
max-width: 2000px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.areas > * {
|
||||
max-width: 500px;
|
||||
}
|
||||
ha-card {
|
||||
@@ -239,6 +231,12 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
|
||||
background-position: center;
|
||||
position: relative;
|
||||
}
|
||||
.placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
--mdc-icon-size: 48px;
|
||||
}
|
||||
.picture.placeholder::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
|
@@ -77,10 +77,12 @@ export default class HaAutomationAction extends LitElement {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this._showReorder}
|
||||
draggable-selector="ha-automation-action-row"
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
@item-moved=${this._actionMoved}
|
||||
group="actions"
|
||||
.path=${this.path}
|
||||
invert-swap
|
||||
>
|
||||
<div class="actions">
|
||||
${repeat(
|
||||
@@ -101,7 +103,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
${this._showReorder
|
||||
${this._showReorder && !this.disabled
|
||||
? html`
|
||||
<div class="handle" slot="icons">
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
@@ -111,30 +113,29 @@ export default class HaAutomationAction extends LitElement {
|
||||
</ha-automation-action-row>
|
||||
`
|
||||
)}
|
||||
<div class="buttons">
|
||||
<ha-button
|
||||
outlined
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.add"
|
||||
)}
|
||||
@click=${this._addActionDialog}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
<ha-button
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.add_building_block"
|
||||
)}
|
||||
@click=${this._addActionBuildingBlockDialog}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
</div>
|
||||
</div>
|
||||
</ha-sortable>
|
||||
<div class="buttons">
|
||||
<ha-button
|
||||
outlined
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.add"
|
||||
)}
|
||||
@click=${this._addActionDialog}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
<ha-button
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.add_building_block"
|
||||
)}
|
||||
@click=${this._addActionBuildingBlockDialog}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -266,22 +267,29 @@ export default class HaAutomationAction extends LitElement {
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.actions {
|
||||
padding: 16px;
|
||||
margin: -16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.sortable-ghost {
|
||||
background: none;
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
}
|
||||
.sortable-drag {
|
||||
background: none;
|
||||
}
|
||||
ha-automation-action-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
overflow: hidden;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px 4px;
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
}
|
||||
@@ -293,6 +301,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
order: 1;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -122,9 +122,11 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this._showReorder}
|
||||
draggable-selector=".option"
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
group="choose-options"
|
||||
.path=${[...(this.path ?? []), "choose"]}
|
||||
invert-swap
|
||||
>
|
||||
<div class="options">
|
||||
${repeat(
|
||||
@@ -148,7 +150,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
? ""
|
||||
: this._getDescription(option))}
|
||||
</h3>
|
||||
${this._showReorder
|
||||
${this._showReorder && !this.disabled
|
||||
? html`
|
||||
<div class="handle" slot="icons">
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
@@ -276,18 +278,21 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
<div class="buttons">
|
||||
<ha-button
|
||||
outlined
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.add_option"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._addOption}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
</div>
|
||||
</div>
|
||||
</ha-sortable>
|
||||
<ha-button
|
||||
outlined
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.add_option"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._addOption}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
|
||||
${this._showDefault || action.default
|
||||
? html`
|
||||
<h2>
|
||||
@@ -296,7 +301,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
)}:
|
||||
</h2>
|
||||
<ha-automation-action
|
||||
.path=${[...(this.path ?? []), "choose", "default"]}
|
||||
.path=${[...(this.path ?? []), "default"]}
|
||||
.actions=${ensureArray(action.default) || []}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._defaultChanged}
|
||||
@@ -502,8 +507,19 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.option {
|
||||
margin: 0 0 16px 0;
|
||||
.options {
|
||||
padding: 16px;
|
||||
margin: -16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.sortable-ghost {
|
||||
background: none;
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
}
|
||||
.sortable-drag {
|
||||
background: none;
|
||||
}
|
||||
.add-card mwc-button {
|
||||
display: block;
|
||||
@@ -539,7 +555,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
padding: 0 16px 16px 16px;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px 4px;
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
}
|
||||
@@ -547,6 +563,12 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
pointer-events: none;
|
||||
height: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
order: 1;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -117,10 +117,12 @@ export default class HaAutomationCondition extends LitElement {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this._showReorder}
|
||||
draggable-selector="ha-automation-condition-row"
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
@item-moved=${this._conditionMoved}
|
||||
group="conditions"
|
||||
.path=${this.path}
|
||||
invert-swap
|
||||
>
|
||||
<div class="conditions">
|
||||
${repeat(
|
||||
@@ -141,7 +143,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
${this._showReorder
|
||||
${this._showReorder && !this.disabled
|
||||
? html`
|
||||
<div class="handle" slot="icons">
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
@@ -151,29 +153,29 @@ export default class HaAutomationCondition extends LitElement {
|
||||
</ha-automation-condition-row>
|
||||
`
|
||||
)}
|
||||
<div class="buttons">
|
||||
<ha-button
|
||||
outlined
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.add"
|
||||
)}
|
||||
@click=${this._addConditionDialog}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
<ha-button
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.add_building_block"
|
||||
)}
|
||||
@click=${this._addConditionBuildingBlockDialog}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
</div>
|
||||
</div>
|
||||
</ha-sortable>
|
||||
<div class="buttons">
|
||||
<ha-button
|
||||
outlined
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.add"
|
||||
)}
|
||||
@click=${this._addConditionDialog}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
<ha-button
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.add_building_block"
|
||||
)}
|
||||
@click=${this._addConditionBuildingBlockDialog}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -291,22 +293,32 @@ export default class HaAutomationCondition extends LitElement {
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.conditions {
|
||||
padding: 16px;
|
||||
margin: -16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.sortable-ghost {
|
||||
background: none;
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
}
|
||||
.sortable-drag {
|
||||
background: none;
|
||||
}
|
||||
ha-automation-condition-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
.buttons {
|
||||
order: 1;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
overflow: hidden;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px 4px;
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
mdiCheck,
|
||||
mdiContentDuplicate,
|
||||
@@ -35,6 +34,7 @@ import "../../../components/ha-fab";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import "../../../components/ha-list-item";
|
||||
import {
|
||||
AutomationConfig,
|
||||
AutomationEntity,
|
||||
@@ -150,7 +150,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<mwc-list-item
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._showInfo}
|
||||
@@ -160,20 +160,20 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
slot="graphic"
|
||||
.path=${mdiInformationOutline}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</ha-list-item>
|
||||
|
||||
<mwc-list-item
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._runActions}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.run")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</ha-list-item>
|
||||
|
||||
${stateObj && this._config && this.narrow
|
||||
? html`<a href="/config/automation/trace/${this._config.id}">
|
||||
<mwc-list-item graphic="icon">
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.show_trace"
|
||||
)}
|
||||
@@ -181,22 +181,22 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
slot="graphic"
|
||||
.path=${mdiTransitConnection}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
|
||||
<mwc-list-item
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._promptAutomationAlias}
|
||||
.disabled=${!this.automationId || this._mode === "yaml"}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.rename")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</ha-list-item>
|
||||
|
||||
${this._config && !("use_blueprint" in this._config)
|
||||
? html`
|
||||
<mwc-list-item
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._promptAutomationMode}
|
||||
.disabled=${this._readOnly || this._mode === "yaml"}
|
||||
@@ -208,11 +208,11 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
slot="graphic"
|
||||
.path=${mdiDebugStepOver}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</ha-list-item>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<mwc-list-item
|
||||
<ha-list-item
|
||||
.disabled=${!this._readOnly && !this.automationId}
|
||||
graphic="icon"
|
||||
@click=${this._duplicate}
|
||||
@@ -226,11 +226,11 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
slot="graphic"
|
||||
.path=${mdiContentDuplicate}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</ha-list-item>
|
||||
|
||||
<li divider role="separator"></li>
|
||||
|
||||
<mwc-list-item graphic="icon" @click=${this._switchUiMode}>
|
||||
<ha-list-item graphic="icon" @click=${this._switchUiMode}>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.edit_ui")}
|
||||
${this._mode === "gui"
|
||||
? html`<ha-svg-icon
|
||||
@@ -239,8 +239,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
.path=${mdiCheck}
|
||||
></ha-svg-icon>`
|
||||
: ``}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item graphic="icon" @click=${this._switchYamlMode}>
|
||||
</ha-list-item>
|
||||
<ha-list-item graphic="icon" @click=${this._switchYamlMode}>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.edit_yaml")}
|
||||
${this._mode === "yaml"
|
||||
? html`<ha-svg-icon
|
||||
@@ -249,11 +249,11 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
.path=${mdiCheck}
|
||||
></ha-svg-icon>`
|
||||
: ``}
|
||||
</mwc-list-item>
|
||||
</ha-list-item>
|
||||
|
||||
<li divider role="separator"></li>
|
||||
|
||||
<mwc-list-item
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._toggle}
|
||||
@@ -267,9 +267,9 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</ha-list-item>
|
||||
|
||||
<mwc-list-item
|
||||
<ha-list-item
|
||||
.disabled=${!this.automationId}
|
||||
class=${classMap({ warning: Boolean(this.automationId) })}
|
||||
graphic="icon"
|
||||
@@ -282,7 +282,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
.path=${mdiDelete}
|
||||
>
|
||||
</ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
|
||||
${this._config
|
||||
|
@@ -232,6 +232,7 @@ export class HaAutomationTrace extends LitElement {
|
||||
<div class="main">
|
||||
<div class="graph">
|
||||
<hat-script-graph
|
||||
.hass=${this.hass}
|
||||
.trace=${this._trace}
|
||||
.selected=${this._selected?.path}
|
||||
@graph-node-selected=${this._pickNode}
|
||||
|
@@ -74,10 +74,12 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this._showReorder}
|
||||
draggable-selector="ha-automation-trigger-row"
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
@item-moved=${this._triggerMoved}
|
||||
group="triggers"
|
||||
.path=${this.path}
|
||||
invert-swap
|
||||
>
|
||||
<div class="triggers">
|
||||
${repeat(
|
||||
@@ -97,7 +99,7 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this._showReorder
|
||||
${this._showReorder && !this.disabled
|
||||
? html`
|
||||
<div class="handle" slot="icons">
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
@@ -107,18 +109,20 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
</ha-automation-trigger-row>
|
||||
`
|
||||
)}
|
||||
<div class="buttons">
|
||||
<ha-button
|
||||
outlined
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.add"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._addTriggerDialog}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
</div>
|
||||
</div>
|
||||
</ha-sortable>
|
||||
<ha-button
|
||||
outlined
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.add"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._addTriggerDialog}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -240,22 +244,29 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.triggers {
|
||||
padding: 16px;
|
||||
margin: -16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.sortable-ghost {
|
||||
background: none;
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
}
|
||||
.sortable-drag {
|
||||
background: none;
|
||||
}
|
||||
ha-automation-trigger-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
border-radius: var(--ha-card-border-radius, 16px);
|
||||
overflow: hidden;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px 4px;
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
}
|
||||
@@ -263,6 +274,12 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
pointer-events: none;
|
||||
height: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
order: 1;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import "@material/mwc-button";
|
||||
import { css, html, LitElement, PropertyValues } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { formatDateTime } from "../../../../common/datetime/format_date_time";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import { debounce } from "../../../../common/util/debounce";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-card";
|
||||
@@ -37,8 +36,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _subscription?: SubscriptionInfo;
|
||||
|
||||
@state() private _rtlDirection: "rtl" | "ltr" = "rtl";
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<hass-subpage
|
||||
@@ -172,13 +169,11 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
<cloud-remote-pref
|
||||
.hass=${this.hass}
|
||||
.cloudStatus=${this.cloudStatus}
|
||||
dir=${this._rtlDirection}
|
||||
></cloud-remote-pref>
|
||||
|
||||
<cloud-tts-pref
|
||||
.hass=${this.hass}
|
||||
.cloudStatus=${this.cloudStatus}
|
||||
dir=${this._rtlDirection}
|
||||
></cloud-tts-pref>
|
||||
|
||||
<ha-tip .hass=${this.hass}>
|
||||
@@ -193,7 +188,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.cloudStatus=${this.cloudStatus}
|
||||
dir=${this._rtlDirection}
|
||||
></cloud-webhooks>
|
||||
</ha-config-section>
|
||||
</div>
|
||||
@@ -205,15 +199,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
this._fetchSubscriptionInfo();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("hass")) {
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.locale !== this.hass.locale) {
|
||||
this._rtlDirection = computeRTLDirection(this.hass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override hassSubscribe() {
|
||||
const googleCheck = debounce(
|
||||
() => {
|
||||
@@ -272,10 +257,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
fireEvent(this, "ha-refresh-cloud-status");
|
||||
}
|
||||
|
||||
_computeRTLDirection(hass) {
|
||||
return computeRTLDirection(hass);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
haStyle,
|
||||
|
@@ -179,14 +179,12 @@ export class CloudRemotePref extends LitElement {
|
||||
.header-actions {
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
inset-inline-end: 24px;
|
||||
inset-inline-start: initial;
|
||||
top: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
:host([dir="rtl"]) .header-actions {
|
||||
right: auto;
|
||||
left: 24px;
|
||||
}
|
||||
.header-actions .icon-link {
|
||||
margin-top: -16px;
|
||||
margin-right: 8px;
|
||||
|
@@ -177,12 +177,10 @@ export class CloudTTSPref extends LitElement {
|
||||
.example {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
inset-inline-end: 16px;
|
||||
inset-inline-start: initial;
|
||||
top: 16px;
|
||||
}
|
||||
:host([dir="rtl"]) .example {
|
||||
right: auto;
|
||||
left: 24px;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
}
|
||||
|
@@ -144,15 +144,6 @@ export class CloudLogin extends LitElement {
|
||||
"ui.panel.config.cloud.login.password_error_msg"
|
||||
)}
|
||||
></ha-textfield>
|
||||
<button
|
||||
class="link pwd-forgot-link"
|
||||
.disabled=${this._requestInProgress}
|
||||
@click=${this._handleForgotPassword}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.forgot_password"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
@@ -162,6 +153,15 @@ export class CloudLogin extends LitElement {
|
||||
"ui.panel.config.cloud.login.sign_in"
|
||||
)}</ha-progress-button
|
||||
>
|
||||
<button
|
||||
class="link pwd-forgot-link"
|
||||
.disabled=${this._requestInProgress}
|
||||
@click=${this._handleForgotPassword}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.forgot_password"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
@@ -311,11 +311,6 @@ export class CloudLogin extends LitElement {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pwd-forgot-link {
|
||||
color: var(--secondary-text-color) !important;
|
||||
text-align: right !important;
|
||||
align-self: flex-end;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
mdiAccessPoint,
|
||||
mdiChatProcessing,
|
||||
mdiChatQuestion,
|
||||
mdiExportVariant,
|
||||
} from "@mdi/js";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
NetworkType,
|
||||
getMatterNodeDiagnostics,
|
||||
} from "../../../../../../data/matter";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { showMatterReinterviewNodeDialog } from "../../../../integrations/integration-panels/matter/show-dialog-matter-reinterview-node";
|
||||
import { showMatterPingNodeDialog } from "../../../../integrations/integration-panels/matter/show-dialog-matter-ping-node";
|
||||
import { showMatterOpenCommissioningWindowDialog } from "../../../../integrations/integration-panels/matter/show-dialog-matter-open-commissioning-window";
|
||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||
import { showMatterManageFabricsDialog } from "../../../../integrations/integration-panels/matter/show-dialog-matter-manage-fabrics";
|
||||
import { navigate } from "../../../../../../common/navigate";
|
||||
|
||||
export const getMatterDeviceActions = async (
|
||||
el: HTMLElement,
|
||||
hass: HomeAssistant,
|
||||
device: DeviceRegistryEntry
|
||||
): Promise<DeviceAction[]> => {
|
||||
if (device.via_device_id !== null) {
|
||||
// only show device actions for top level nodes (so not bridged)
|
||||
return [];
|
||||
}
|
||||
|
||||
const nodeDiagnostics = await getMatterNodeDiagnostics(hass, device.id);
|
||||
|
||||
const actions: DeviceAction[] = [];
|
||||
|
||||
if (nodeDiagnostics.available) {
|
||||
// actions that can only be performed if the device is alive
|
||||
actions.push({
|
||||
label: hass.localize(
|
||||
"ui.panel.config.matter.device_actions.open_commissioning_window"
|
||||
),
|
||||
icon: mdiExportVariant,
|
||||
action: () =>
|
||||
showMatterOpenCommissioningWindowDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
});
|
||||
actions.push({
|
||||
label: hass.localize(
|
||||
"ui.panel.config.matter.device_actions.manage_fabrics"
|
||||
),
|
||||
icon: mdiExportVariant,
|
||||
action: () =>
|
||||
showMatterManageFabricsDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
});
|
||||
actions.push({
|
||||
label: hass.localize(
|
||||
"ui.panel.config.matter.device_actions.reinterview_device"
|
||||
),
|
||||
icon: mdiChatProcessing,
|
||||
action: () =>
|
||||
showMatterReinterviewNodeDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (nodeDiagnostics.network_type === NetworkType.THREAD) {
|
||||
actions.push({
|
||||
label: hass.localize(
|
||||
"ui.panel.config.matter.device_actions.view_thread_network"
|
||||
),
|
||||
icon: mdiAccessPoint,
|
||||
action: () => navigate("/config/thread"),
|
||||
});
|
||||
}
|
||||
|
||||
actions.push({
|
||||
label: hass.localize("ui.panel.config.matter.device_actions.ping_device"),
|
||||
icon: mdiChatQuestion,
|
||||
action: () =>
|
||||
showMatterPingNodeDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
});
|
||||
|
||||
return actions;
|
||||
};
|
@@ -0,0 +1,174 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../../components/ha-expansion-panel";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
getMatterNodeDiagnostics,
|
||||
MatterNodeDiagnostics,
|
||||
} from "../../../../../../data/matter";
|
||||
import "@material/mwc-list";
|
||||
import "../../../../../../components/ha-list-item";
|
||||
import { SubscribeMixin } from "../../../../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
|
||||
@customElement("ha-device-info-matter")
|
||||
export class HaDeviceInfoMatter extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public device!: DeviceRegistryEntry;
|
||||
|
||||
@state() private _nodeDiagnostics?: MatterNodeDiagnostics;
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues) {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("device")) {
|
||||
this._fetchNodeDetails();
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchNodeDetails() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.device.via_device_id !== null) {
|
||||
// only show device details for top level nodes (so not bridged)
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._nodeDiagnostics = await getMatterNodeDiagnostics(
|
||||
this.hass,
|
||||
this.device.id
|
||||
);
|
||||
} catch (err: any) {
|
||||
this._nodeDiagnostics = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._nodeDiagnostics) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.device_info"
|
||||
)}
|
||||
>
|
||||
<div class="row">
|
||||
<span class="name"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.node_id"
|
||||
)}:</span
|
||||
>
|
||||
<span class="value">${this._nodeDiagnostics.node_id}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="name"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.network_type"
|
||||
)}:</span
|
||||
>
|
||||
<span class="value"
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.matter.network_type.${this._nodeDiagnostics.network_type}`
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="name"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.node_type"
|
||||
)}:</span
|
||||
>
|
||||
<span class="value"
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.matter.node_type.${this._nodeDiagnostics.node_type}`
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
${this._nodeDiagnostics.network_name
|
||||
? html`
|
||||
<div class="row">
|
||||
<span class="name"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.network_name"
|
||||
)}:</span
|
||||
>
|
||||
<span class="value">${this._nodeDiagnostics.network_name}</span>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${this._nodeDiagnostics.mac_address
|
||||
? html`
|
||||
<div class="row">
|
||||
<span class="name"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.mac_address"
|
||||
)}:</span
|
||||
>
|
||||
<span class="value">${this._nodeDiagnostics.mac_address}</span>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<div class="row">
|
||||
<span class="name"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.device_info.ip_adresses"
|
||||
)}:</span
|
||||
>
|
||||
<span class="value"
|
||||
>${this._nodeDiagnostics.ip_adresses.map(
|
||||
(ip) => html`${ip}<br />`
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
h4 {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
div {
|
||||
word-break: break-all;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.value {
|
||||
text-align: right;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
margin: 8px -16px 0;
|
||||
--expansion-panel-summary-padding: 0 16px;
|
||||
--expansion-panel-content-padding: 0 16px;
|
||||
--ha-card-border-radius: 0px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-device-info-matter": HaDeviceInfoMatter;
|
||||
}
|
||||
}
|
@@ -9,7 +9,6 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { computeStateName } from "../../../../../../common/entity/compute_state_name";
|
||||
import { computeRTLDirection } from "../../../../../../common/util/compute_rtl";
|
||||
import "../../../../../../components/ha-dialog";
|
||||
import "../../../../../../components/ha-formfield";
|
||||
import "../../../../../../components/ha-switch";
|
||||
@@ -51,8 +50,6 @@ class DialogMQTTDeviceDebugInfo extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const dir = computeRTLDirection(this.hass!);
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@@ -72,7 +69,6 @@ class DialogMQTTDeviceDebugInfo extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.mqtt_device_debug_info.deserialize"
|
||||
)}
|
||||
.dir=${dir}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._showDeserialized}
|
||||
@@ -87,7 +83,6 @@ class DialogMQTTDeviceDebugInfo extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.mqtt_device_debug_info.show_as_yaml"
|
||||
)}
|
||||
.dir=${dir}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._showAsYaml}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user