Merge branch 'dev' into entity-filter/add_value_entity_id_compatibility

This commit is contained in:
Paul Bottein 2024-03-12 14:52:57 +01:00 committed by GitHub
commit cf51598f9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
628 changed files with 17832 additions and 9716 deletions

View File

@ -5,6 +5,7 @@
"context": ".."
},
"appPort": "8124:8123",
"postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev",
"postStartCommand": "script/bootstrap",
"containerEnv": {
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"

View File

@ -26,7 +26,7 @@ jobs:
ref: dev
- name: Setup Node
uses: actions/setup-node@v4.0.1
uses: actions/setup-node@v4.0.2
with:
node-version-file: ".nvmrc"
cache: yarn
@ -62,7 +62,7 @@ jobs:
ref: master
- name: Setup Node
uses: actions/setup-node@v4.0.1
uses: actions/setup-node@v4.0.2
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@ -26,7 +26,7 @@ jobs:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.1
- name: Setup Node
uses: actions/setup-node@v4.0.1
uses: actions/setup-node@v4.0.2
with:
node-version-file: ".nvmrc"
cache: yarn
@ -37,7 +37,7 @@ jobs:
- name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
- name: Setup lint cache
uses: actions/cache@v3.3.3
uses: actions/cache@v4.0.1
with:
path: |
node_modules/.cache/prettier
@ -60,7 +60,7 @@ jobs:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.1
- name: Setup Node
uses: actions/setup-node@v4.0.1
uses: actions/setup-node@v4.0.2
with:
node-version-file: ".nvmrc"
cache: yarn
@ -78,7 +78,7 @@ jobs:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.1
- name: Setup Node
uses: actions/setup-node@v4.0.1
uses: actions/setup-node@v4.0.2
with:
node-version-file: ".nvmrc"
cache: yarn
@ -89,7 +89,7 @@ jobs:
env:
IS_TEST: "true"
- name: Upload bundle stats
uses: actions/upload-artifact@v4.1.0
uses: actions/upload-artifact@v4.3.1
with:
name: frontend-bundle-stats
path: build/stats/*.json
@ -102,7 +102,7 @@ jobs:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.1
- name: Setup Node
uses: actions/setup-node@v4.0.1
uses: actions/setup-node@v4.0.2
with:
node-version-file: ".nvmrc"
cache: yarn
@ -113,7 +113,7 @@ jobs:
env:
IS_TEST: "true"
- name: Upload bundle stats
uses: actions/upload-artifact@v4.1.0
uses: actions/upload-artifact@v4.3.1
with:
name: supervisor-bundle-stats
path: build/stats/*.json

View File

@ -27,7 +27,7 @@ jobs:
ref: dev
- name: Setup Node
uses: actions/setup-node@v4.0.1
uses: actions/setup-node@v4.0.2
with:
node-version-file: ".nvmrc"
cache: yarn
@ -63,7 +63,7 @@ jobs:
ref: master
- name: Setup Node
uses: actions/setup-node@v4.0.1
uses: actions/setup-node@v4.0.2
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@v4.1.1
- name: Setup Node
uses: actions/setup-node@v4.0.1
uses: actions/setup-node@v4.0.2
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@v4.1.1
- name: Setup Node
uses: actions/setup-node@v4.0.1
uses: actions/setup-node@v4.0.2
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@ -28,7 +28,7 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Node
uses: actions/setup-node@v4.0.1
uses: actions/setup-node@v4.0.2
with:
node-version-file: ".nvmrc"
cache: yarn
@ -57,14 +57,14 @@ jobs:
run: tar -czvf translations.tar.gz translations
- name: Upload build artifacts
uses: actions/upload-artifact@v4.1.0
uses: actions/upload-artifact@v4.3.1
with:
name: wheels
path: dist/home_assistant_frontend*.whl
if-no-files-found: error
- name: Upload translations
uses: actions/upload-artifact@v4.1.0
uses: actions/upload-artifact@v4.3.1
with:
name: translations
path: translations.tar.gz

View File

@ -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 }}

View File

@ -34,7 +34,7 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Node
uses: actions/setup-node@v4.0.1
uses: actions/setup-node@v4.0.2
with:
node-version-file: ".nvmrc"
cache: yarn
@ -55,7 +55,7 @@ jobs:
script/release
- name: Upload release assets
uses: softprops/action-gh-release@v0.1.15
uses: softprops/action-gh-release@v2.0.2
with:
files: |
dist/*.whl

View File

@ -0,0 +1,18 @@
diff --git a/dist/hls.light.mjs b/dist/hls.light.mjs
index eed9d788fafdb159975e1a2eb08ac88ba9c9ac33..ace881935e6665946f1c8110ebd2f739cde4427e 100644
--- a/dist/hls.light.mjs
+++ b/dist/hls.light.mjs
@@ -20523,9 +20523,9 @@ class Hls {
}
Hls.defaultConfig = void 0;
-var KeySystemFormats = empty.KeySystemFormats;
-var KeySystems = empty.KeySystems;
-var SubtitleStreamController = empty.SubtitleStreamController;
-var TimelineController = empty.TimelineController;
+var KeySystemFormats = empty;
+var KeySystems = empty;
+var SubtitleStreamController = empty;
+var TimelineController = empty;
export { AbrController, AttrList, Cues as AudioStreamController, Cues as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, Cues as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, Cues as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, Cues as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
//# sourceMappingURL=hls.light.mjs.map

View File

@ -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();

View 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

View File

@ -6,4 +6,4 @@ enableGlobalCache: false
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.0.2.cjs
yarnPath: .yarn/releases/yarn-4.1.1.cjs

View File

@ -115,7 +115,9 @@ gulp.task("webpack-prod-app", () =>
gulp.task("webpack-dev-server-demo", () =>
runDevServer({
compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })),
compiler: webpack(
createDemoConfig({ isProdBuild: false, latestBuild: true })
),
contentBase: paths.demo_output_root,
port: 8090,
})
@ -131,7 +133,9 @@ gulp.task("webpack-prod-demo", () =>
gulp.task("webpack-dev-server-cast", () =>
runDevServer({
compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })),
compiler: webpack(
createCastConfig({ isProdBuild: false, latestBuild: true })
),
contentBase: paths.cast_output_root,
port: 8080,
// Accessible from the network, because that's how Cast hits it.
@ -174,8 +178,9 @@ gulp.task("webpack-prod-hassio", () =>
gulp.task("webpack-dev-server-gallery", () =>
runDevServer({
// We don't use the es5 build, but the dev server will fuck up the publicPath if we don't
compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })),
compiler: webpack(
createGalleryConfig({ isProdBuild: false, latestBuild: true })
),
contentBase: paths.gallery_output_root,
port: 8100,
listenHost: "0.0.0.0",

View File

@ -31,11 +31,11 @@ import "./hc-layout";
@customElement("hc-cast")
class HcCast extends LitElement {
@property() public auth!: Auth;
@property({ attribute: false }) public auth!: Auth;
@property() public connection!: Connection;
@property({ attribute: false }) public connection!: Connection;
@property() public castManager!: CastManager;
@property({ attribute: false }) public castManager!: CastManager;
@state() private askWrite = false;
@ -241,6 +241,8 @@ class HcCast extends LitElement {
mwc-button ha-svg-icon {
margin-right: 8px;
margin-inline-end: 8px;
margin-inline-start: initial;
height: 18px;
}

View File

@ -10,13 +10,13 @@ import "../../../../src/components/ha-card";
@customElement("hc-layout")
class HcLayout extends LitElement {
@property() public subtitle?: string | undefined;
@property() public subtitle?: string;
@property() public auth?: Auth;
@property({ attribute: false }) public auth?: Auth;
@property() public connection?: Connection;
@property({ attribute: false }) public connection?: Connection;
@property() public user?: HassUser;
@property({ attribute: false }) public user?: HassUser;
protected render(): TemplateResult {
return html`

View File

@ -28,23 +28,23 @@ class HcLaunchScreen extends LitElement {
:host {
display: block;
height: 100vh;
padding-top: 64px;
background-color: white;
background-color: #f2f4f9;
font-size: 24px;
}
.container {
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;
color: #1d2126;
}
`;
}

View File

@ -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++) {

View File

@ -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>
`;
@ -205,7 +205,6 @@ export class HcMain extends HassElement {
expires_in: 0,
}),
});
this._hassUUID = msg.hassUUID;
} catch (err: any) {
const errorMessage = this._getErrorMessage(err);
this._error = errorMessage;
@ -225,6 +224,17 @@ export class HcMain extends HassElement {
this.hass.connection.close();
}
this.initializeHass(auth, connection);
if (this._hassUUID !== msg.hassUUID) {
this._hassUUID = msg.hassUUID;
this._lovelaceConfig = undefined;
this._urlPath = undefined;
this._lovelacePath = null;
if (this._unsubLovelace) {
this._unsubLovelace();
this._unsubLovelace = undefined;
}
resourcesLoaded = false;
}
this._error = undefined;
this._sendStatus();
}
@ -233,7 +243,7 @@ export class HcMain extends HassElement {
this._showDemo = false;
// We should not get this command before we are connected.
// Means a client got out of sync. Let's send status to them.
if (!this.hass) {
if (!this.hass?.connected) {
this._sendStatus(msg.senderId!);
this._error = "Cannot show Lovelace because we're not connected.";
this._sendError(
@ -260,7 +270,7 @@ export class HcMain extends HassElement {
}
this._error = undefined;
if (msg.urlPath === "lovelace") {
if (msg.urlPath === "lovelace" || msg.urlPath === undefined) {
msg.urlPath = null;
}
this._lovelacePath = msg.viewPath;
@ -275,7 +285,7 @@ export class HcMain extends HassElement {
],
};
this._urlPath = "energy";
this._lovelacePath = 0;
this._lovelacePath = null;
this._sendStatus();
return;
}
@ -284,6 +294,7 @@ export class HcMain extends HassElement {
this._lovelaceConfig = undefined;
if (this._unsubLovelace) {
this._unsubLovelace();
this._unsubLovelace = undefined;
}
const llColl = atLeastVersion(this.hass.connection.haVersion, 0, 107)
? getLovelaceCollection(this.hass.connection, msg.urlPath)

View File

@ -4,6 +4,7 @@ import { energyEntities } from "../stubs/entities";
import { DemoConfig } from "./types";
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
() => import("./sections").then((mod) => mod.demoSections),
() => import("./arsaboo").then((mod) => mod.demoArsaboo),
() => import("./teachingbirds").then((mod) => mod.demoTeachingbirds),
() => import("./kernehed").then((mod) => mod.demoKernehed),

View File

@ -0,0 +1,16 @@
import { html } from "lit";
import { DemoConfig } from "../types";
export const demoLovelaceDescription: DemoConfig["description"] = (
localize
) => html`
<p>
${localize("ui.panel.page-demo.config.sections.description", {
blog_post: html`<a
href="https://www.home-assistant.io/blog/2024/03/04/dashboard-chapter-1/"
target="_blank"
>${localize("ui.panel.page-demo.config.sections.description_blog_post")}
</a>`,
})}
</p>
`;

View File

@ -0,0 +1,474 @@
import { convertEntities } from "../../../../src/fake_data/entity";
import { DemoConfig } from "../types";
export const demoEntitiesSections: DemoConfig["entities"] = () =>
convertEntities({
"cover.living_room_garden_shutter": {
entity_id: "cover.living_room_garden_shutter",
state: "open",
attributes: {
current_position: 100,
device_class: "shutter",
friendly_name: "Living room garden shutter",
supported_features: 15,
},
},
"cover.living_room_graveyard_shutter": {
entity_id: "cover.living_room_graveyard_shutter",
state: "open",
attributes: {
current_position: 100,
device_class: "shutter",
friendly_name: "Living room graveyard shutter",
supported_features: 15,
},
},
"cover.living_room_left_shutter": {
entity_id: "cover.living_room_left_shutter",
state: "open",
attributes: {
current_position: 100,
device_class: "shutter",
friendly_name: "Living room left shutter",
supported_features: 15,
},
},
"cover.living_room_right_shutter": {
entity_id: "cover.living_room_right_shutter",
state: "open",
attributes: {
current_position: 100,
device_class: "shutter",
friendly_name: "Living room right shutter",
supported_features: 15,
},
},
"light.floor_lamp": {
entity_id: "light.floor_lamp",
state: "on",
attributes: {
min_color_temp_kelvin: 2000,
max_color_temp_kelvin: 6535,
min_mireds: 153,
max_mireds: 500,
supported_color_modes: ["color_temp", "xy"],
color_mode: "color_temp",
brightness: 178,
color_temp_kelvin: 2583,
color_temp: 387,
hs_color: [28.664, 69.597],
rgb_color: [255, 162, 77],
xy_color: [0.538, 0.389],
icon: "mdi:floor-lamp",
friendly_name: "Floor lamp",
supported_features: 44,
},
},
"light.living_room_spotlights": {
entity_id: "light.living_room_spotlights",
state: "on",
attributes: {
supported_color_modes: ["brightness"],
color_mode: "brightness",
brightness: 126,
icon: "mdi:ceiling-light-multiple",
friendly_name: "Living room spotlights",
supported_features: 32,
},
},
"light.bar_lamp": {
entity_id: "light.bar_lamp",
state: "on",
attributes: {
min_color_temp_kelvin: 2202,
max_color_temp_kelvin: 4504,
min_mireds: 222,
max_mireds: 454,
effect_list: ["None", "candle"],
supported_color_modes: ["color_temp"],
effect: null,
color_mode: null,
brightness: null,
color_temp_kelvin: null,
color_temp: null,
hs_color: null,
rgb_color: null,
xy_color: null,
mode: "normal",
dynamics: "none",
icon: "mdi:lightbulb-variant",
friendly_name: "Bar lamp",
supported_features: 44,
},
},
"sensor.living_room_temperature": {
entity_id: "sensor.living_room_temperature",
state: "22.8",
attributes: {
state_class: "measurement",
unit_of_measurement: "°C",
device_class: "temperature",
friendly_name: "Living room Temperature",
},
},
"media_player.living_room_nest_mini": {
entity_id: "media_player.living_room_nest_mini",
state: "off",
attributes: {
device_class: "speaker",
friendly_name: "Living room Nest Mini",
supported_features: 152461,
},
},
"cover.kitchen_shutter": {
entity_id: "cover.kitchen_shutter",
state: "open",
attributes: {
current_position: 100,
device_class: "shutter",
friendly_name: "Kitchen shutter ",
supported_features: 15,
},
},
"light.kitchen_spotlights": {
entity_id: "light.kitchen_spotlights",
state: "off",
attributes: {
supported_color_modes: ["brightness"],
color_mode: null,
brightness: null,
icon: "mdi:ceiling-light-multiple",
friendly_name: "Kitchen spotlights ",
supported_features: 32,
},
},
"light.worktop_spotlights": {
entity_id: "light.worktop_spotlights",
state: "off",
attributes: {
supported_color_modes: ["brightness"],
color_mode: null,
brightness: null,
icon: "mdi:ceiling-light-multiple",
friendly_name: "Worktop spotlights ",
supported_features: 32,
},
},
"binary_sensor.fridge_door": {
entity_id: "binary_sensor.fridge_door",
state: "off",
attributes: {
device_class: "door",
icon: "mdi:fridge",
friendly_name: "Fridge door",
},
},
"media_player.kitchen_nest_audio": {
entity_id: "media_player.kitchen_nest_audio",
state: "on",
attributes: {
device_class: "speaker",
friendly_name: "Kitchen Nest Audio",
supported_features: 152461,
},
},
"binary_sensor.tesla_wall_connector_vehicle_connected": {
entity_id: "binary_sensor.tesla_wall_connector_vehicle_connected",
state: "off",
attributes: {
device_class: "plug",
friendly_name: "Wall Connector Vehicle connected",
},
},
"sensor.tesla_wall_connector_session_energy": {
entity_id: "sensor.tesla_wall_connector_session_energy",
state: "16.3",
attributes: {
state_class: "total_increasing",
unit_of_measurement: "kWh",
device_class: "energy",
friendly_name: "Tesla Wall Connector Session energy",
},
},
"sensor.electric_meter_power": {
entity_id: "sensor.electric_meter_power",
state: "797.86",
attributes: {
state_class: "measurement",
unit_of_measurement: "W",
device_class: "power",
icon: "mdi:meter-electric",
friendly_name: "Electric meter Power",
},
},
"sensor.eletric_meter_voltage": {
entity_id: "sensor.eletric_meter_voltage",
state: "232.19",
attributes: {
state_class: "measurement",
unit_of_measurement: "V",
device_class: "voltage",
friendly_name: "Electric meter voltage",
},
},
"sensor.electricity_maps_grid_fossil_fuel_percentage": {
entity_id: "sensor.electricity_maps_grid_fossil_fuel_percentage",
state: "9.84",
attributes: {
state_class: "measurement",
country_code: "FR",
unit_of_measurement: "%",
attribution: "Data provided by Electricity Maps",
icon: "mdi:barrel",
friendly_name: "Electricity Maps Grid fossil fuel percentage",
},
},
"sensor.electricity_maps_co2_intensity": {
entity_id: "sensor.electricity_maps_co2_intensity",
state: "62.0",
attributes: {
state_class: "measurement",
country_code: "FR",
unit_of_measurement: "gCO2eq/kWh",
attribution: "Data provided by Electricity Maps",
friendly_name: "Electricity Maps CO2 intensity",
icon: "mdi:molecule-co2",
},
},
"sun.sun": {
entity_id: "sun.sun",
state: "above_horizon",
attributes: {
next_dawn: "2024-03-05T05:50:21.964405+00:00",
next_dusk: "2024-03-04T18:08:54.311334+00:00",
next_midnight: "2024-03-05T00:00:00+00:00",
next_noon: "2024-03-05T12:00:05+00:00",
next_rising: "2024-03-05T06:23:42.739159+00:00",
next_setting: "2024-03-04T17:35:26.271171+00:00",
elevation: 30.38,
azimuth: 204.42,
rising: false,
friendly_name: "Sun",
},
},
"sensor.rain": {
entity_id: "sensor.moon_phase",
state: "7.2",
attributes: {
state_class: "total_increasing",
unit_of_measurement: "mm",
device_class: "precipitation",
friendly_name: "Rain",
},
},
"climate.ground_floor": {
entity_id: "climate.ground_floor",
state: "heat",
attributes: {
hvac_modes: ["auto", "heat", "off"],
min_temp: 7,
max_temp: 35,
preset_modes: [
"comfort",
"away",
"eco",
"frost_protection",
"external",
"home",
],
current_temperature: 20.8,
temperature: 21,
preset_mode: "comfort",
icon: "mdi:home-floor-0",
friendly_name: "Ground floor Thermostat",
supported_features: 401,
},
},
"climate.first_floor": {
entity_id: "climate.first_floor",
state: "heat",
attributes: {
hvac_modes: ["auto", "heat", "off"],
min_temp: 7,
max_temp: 35,
preset_modes: [
"comfort",
"away",
"eco",
"frost_protection",
"external",
"home",
],
current_temperature: 21.7,
temperature: 21,
preset_mode: "comfort",
icon: "mdi:home-floor-1",
friendly_name: "First floor Thermostat",
supported_features: 401,
},
},
"cover.study_shutter": {
entity_id: "cover.study_shutter",
state: "open",
attributes: {
current_position: 100,
device_class: "shutter",
friendly_name: "Study shutter",
supported_features: 15,
},
},
"light.study_spotlights": {
entity_id: "light.study_spotlights",
state: "off",
attributes: {
supported_color_modes: ["brightness"],
color_mode: null,
brightness: null,
icon: "mdi:ceiling-light-multiple",
friendly_name: "Study spotlights",
supported_features: 32,
},
},
"media_player.study_nest_hub": {
entity_id: "media_player.study_nest_hub",
state: "off",
attributes: {
friendly_name: "Study Nest Hub",
supported_features: 152461,
},
},
"sensor.standing_desk_height": {
entity_id: "sensor.standing_desk_height",
state: "72",
attributes: {
unit_of_measurement: "cm",
icon: "mdi:tape-measure",
friendly_name: "Standing desk Height",
},
},
"light.outdoor_light": {
entity_id: "light.outdoor_light",
state: "on",
attributes: {
supported_color_modes: ["brightness"],
color_mode: null,
brightness: 255,
icon: "mdi:outdoor-lamp",
friendly_name: "Outdoor light",
supported_features: 32,
},
},
"light.flood_light": {
entity_id: "light.flood_light",
state: "off",
attributes: {
effect_list: ["None", "candle"],
supported_color_modes: ["brightness"],
effect: null,
color_mode: null,
brightness: null,
mode: "normal",
dynamics: "none",
icon: "mdi:light-flood-down",
friendly_name: "Flood light",
supported_features: 44,
},
},
"sensor.outdoor_motion_sensor_temperature": {
entity_id: "sensor.outdoor_motion_sensor_temperature",
state: "10.2",
attributes: {
state_class: "measurement",
unit_of_measurement: "°C",
device_class: "temperature",
friendly_name: "Outdoor motion sensor Temperature",
},
},
"binary_sensor.outdoor_motion_sensor_motion": {
entity_id: "binary_sensor.outdoor_motion_sensor_motion",
state: "off",
attributes: {
device_class: "motion",
friendly_name: "Outdoor motion sensor Motion",
},
},
"sensor.outdoor_motion_sensor_illuminance": {
entity_id: "sensor.outdoor_motion_sensor_illuminance",
state: "555",
attributes: {
state_class: "measurement",
light_level: 27444,
unit_of_measurement: "lx",
device_class: "illuminance",
friendly_name: "Outdoor motion sensor Illuminance",
},
},
"automation.home_assistant_auto_update": {
entity_id: "automation.home_assistant_auto_update",
state: "off",
attributes: {
id: "1700669321947",
last_triggered: "2024-02-29T18:02:05.343139+00:00",
mode: "queued",
current: 0,
max: 50,
icon: "mdi:auto-mode",
friendly_name: "Home Assistant Auto-update",
},
},
"update.home_assistant_operating_system_update": {
entity_id: "update.home_assistant_operating_system_update",
state: "off",
attributes: {
auto_update: false,
installed_version: "12.1",
in_progress: false,
latest_version: "12.1",
release_summary: null,
release_url:
"https://github.com/home-assistant/operating-system/commits/dev",
skipped_version: null,
title: "Home Assistant Operating System",
entity_picture:
"https://brands.home-assistant.io/homeassistant/icon.png",
friendly_name: "Home Assistant Operating System Update",
supported_features: 3,
},
},
"update.home_assistant_supervisor_update": {
entity_id: "update.home_assistant_supervisor_update",
state: "off",
attributes: {
auto_update: true,
installed_version: "2024.02.2",
in_progress: false,
latest_version: "2024.02.2",
release_summary: null,
release_url:
"https://github.com/home-assistant/supervisor/commits/main",
skipped_version: null,
title: "Home Assistant Supervisor",
entity_picture: "https://brands.home-assistant.io/hassio/icon.png",
friendly_name: "Home Assistant Supervisor Update",
supported_features: 1,
},
},
"update.home_assistant_core_update": {
entity_id: "update.home_assistant_supervisor_update",
state: "off",
attributes: {
auto_update: false,
installed_version: "2024.4.0",
in_progress: false,
latest_version: "2024.4.0",
release_summary: null,
release_url: "https://github.com/home-assistant/core/commits/dev",
skipped_version: null,
title: "Home Assistant Core",
entity_picture:
"https://brands.home-assistant.io/homeassistant/icon.png",
friendly_name: "Home Assistant Core Update",
supported_features: 11,
},
},
});

View File

@ -0,0 +1,14 @@
import { DemoConfig } from "../types";
import { demoLovelaceDescription } from "./description";
import { demoEntitiesSections } from "./entities";
import { demoLovelaceSections } from "./lovelace";
export const demoSections: DemoConfig = {
authorName: "Home Assistant",
authorUrl: "https://github.com/home-assistant/frontend/",
name: "Home Demo",
description: demoLovelaceDescription,
lovelace: demoLovelaceSections,
entities: demoEntitiesSections,
theme: () => ({}),
};

View File

@ -0,0 +1,281 @@
import { DemoConfig } from "../types";
export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
title: "Home Assistant Demo",
views: [
{
type: "sections",
title: "Demo",
path: "home",
icon: "mdi:home-assistant",
sections: [
{
title: "Welcome 👋",
cards: [{ type: "custom:ha-demo-card" }],
},
{
cards: [
{
type: "tile",
entity: "cover.living_room_garden_shutter",
name: "Garden",
},
{
type: "tile",
entity: "cover.living_room_graveyard_shutter",
name: "Rear",
},
{
type: "tile",
entity: "cover.living_room_left_shutter",
name: "Left",
},
{
type: "tile",
entity: "cover.living_room_right_shutter",
name: "Right",
},
{
type: "tile",
entity: "light.floor_lamp",
},
{
type: "tile",
entity: "light.living_room_spotlights",
name: "Spotlights",
features: [
{
type: "light-brightness",
},
],
},
{
type: "tile",
entity: "light.bar_lamp",
},
{
graph: "line",
type: "sensor",
entity: "sensor.living_room_temperature",
detail: 1,
name: "Temperature",
},
{
type: "tile",
entity: "media_player.living_room_nest_mini",
name: "Nest Mini",
},
],
title: "🛋️ Living room ",
},
{
type: "grid",
cards: [
{
type: "tile",
entity: "cover.kitchen_shutter",
name: "Shutter",
},
{
type: "tile",
entity: "light.kitchen_spotlights",
name: "Spotlights",
features: [
{
type: "light-brightness",
},
],
},
{
type: "tile",
entity: "light.worktop_spotlights",
name: "Worktop",
},
{
type: "tile",
entity: "binary_sensor.fridge_door",
name: "Fridge",
},
{
type: "tile",
entity: "media_player.kitchen_nest_audio",
name: "Nest Audio",
},
],
title: "👩‍🍳 Kitchen",
},
{
type: "grid",
cards: [
{
type: "tile",
entity: "binary_sensor.tesla_wall_connector_vehicle_connected",
name: "EV",
icon: "mdi:car",
},
{
type: "tile",
entity: "sensor.tesla_wall_connector_session_energy",
name: "Last charge",
color: "green",
},
{
type: "tile",
entity: "sensor.electric_meter_power",
color: "deep-orange",
name: "Home power",
},
{
type: "tile",
entity: "sensor.eletric_meter_voltage",
name: "Voltage",
color: "deep-orange",
},
{
type: "tile",
entity: "sensor.electricity_maps_grid_fossil_fuel_percentage",
name: "Fossil fuel",
color: "brown",
},
{
type: "tile",
entity: "sensor.electricity_maps_co2_intensity",
name: "CO2 Intensity",
color: "dark-grey",
},
],
title: "⚡️ Energy",
},
{
type: "grid",
cards: [
{
type: "tile",
entity: "sun.sun",
},
{
type: "tile",
entity: "sensor.rain",
color: "blue",
},
{
features: [
{
type: "target-temperature",
},
],
type: "tile",
name: "Downstairs",
entity: "climate.ground_floor",
state_content: ["preset_mode", "current_temperature"],
},
{
features: [
{
type: "target-temperature",
},
],
type: "tile",
name: "Upstairs",
entity: "climate.first_floor",
state_content: ["preset_mode", "current_temperature"],
},
],
title: "🌤️ Climate",
},
{
type: "grid",
cards: [
{
type: "tile",
entity: "cover.study_shutter",
name: "Shutter",
},
{
type: "tile",
entity: "light.study_spotlights",
name: "Spotlights",
},
{
type: "tile",
entity: "media_player.study_nest_hub",
name: "Nest Hub",
},
{
type: "tile",
entity: "sensor.standing_desk_height",
name: "Desk",
color: "brown",
icon: "mdi:desk",
},
],
title: "🧑‍💻 Study",
},
{
type: "grid",
cards: [
{
type: "tile",
entity: "light.outdoor_light",
name: "Door light",
},
{
type: "tile",
entity: "light.flood_light",
},
{
graph: "line",
type: "sensor",
entity: "sensor.outdoor_motion_sensor_temperature",
detail: 1,
name: "Temperature",
},
{
type: "tile",
entity: "binary_sensor.outdoor_motion_sensor_motion",
name: "Motion",
color: "blue",
},
{
type: "tile",
entity: "sensor.outdoor_motion_sensor_illuminance",
color: "amber",
name: "Illuminance",
},
],
title: "🌳 Outdoor",
},
{
type: "grid",
cards: [
{
type: "tile",
entity: "automation.home_assistant_auto_update",
name: "Auto-update",
color: "green",
},
{
type: "tile",
entity: "update.home_assistant_operating_system_update",
name: "OS",
icon: "mdi:home-assistant",
},
{
type: "tile",
entity: "update.home_assistant_supervisor_update",
icon: "mdi:home-assistant",
name: "Supervisor",
},
{
type: "tile",
entity: "update.home_assistant_core_update",
name: "Core",
icon: "mdi:home-assistant",
},
],
title: "🎉 Updates",
},
],
},
],
});

View File

@ -1,3 +1,4 @@
import { TemplateResult } from "lit";
import { LocalizeFunc } from "../../../src/common/translations/localize";
import { LovelaceConfig } from "../../../src/data/lovelace/config/types";
import { Entity } from "../../../src/fake_data/entity";
@ -7,6 +8,9 @@ export interface DemoConfig {
name: string;
authorName: string;
authorUrl: string;
description?:
| string
| ((localize: LocalizeFunc) => string | TemplateResult<1>);
lovelace: (localize: LocalizeFunc) => LovelaceConfig;
entities: (localize: LocalizeFunc) => Entity[];
theme: () => Record<string, string> | null;

View File

@ -39,32 +39,51 @@ export class HADemoCard extends LitElement implements LovelaceCard {
<div class="picker">
<div class="label">
${this._switching
? html`<ha-circular-progress
indeterminate
></ha-circular-progress>`
? html`
<ha-circular-progress indeterminate></ha-circular-progress>
`
: until(
selectedDemoConfig.then(
(conf) => html`
${conf.name}
<small>
<a target="_blank" href=${conf.authorUrl}>
${this.hass.localize(
"ui.panel.page-demo.cards.demo.demo_by",
{ name: conf.authorName }
)}
</a>
${this.hass.localize(
"ui.panel.page-demo.cards.demo.demo_by",
{
name: html`
<a target="_blank" href=${conf.authorUrl}>
${conf.authorName}
</a>
`,
}
)}
</small>
`
),
""
)}
</div>
<mwc-button @click=${this._nextConfig} .disabled=${this._switching}>
${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")}
</mwc-button>
</div>
<div class="content small-hidden">
${this.hass.localize("ui.panel.page-demo.cards.demo.introduction")}
<div class="content">
<p class="small-hidden">
${this.hass.localize("ui.panel.page-demo.cards.demo.introduction")}
</p>
${until(
selectedDemoConfig.then((conf) => {
if (typeof conf.description === "function") {
return conf.description(this.hass.localize);
}
if (conf.description) {
return html`<p>${conf.description}</p>`;
}
return nothing;
}),
nothing
)}
</div>
<div class="actions small-hidden">
<a href="https://www.home-assistant.io" target="_blank">
@ -108,6 +127,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
css`
a {
color: var(--primary-color);
display: inline-block;
}
.actions a {
@ -115,7 +135,11 @@ export class HADemoCard extends LitElement implements LovelaceCard {
}
.content {
padding: 16px;
padding: 0 16px;
}
.content p {
margin: 16px 0;
}
.picker {
@ -138,9 +162,8 @@ export class HADemoCard extends LitElement implements LovelaceCard {
}
.actions {
padding-left: 8px;
padding: 0px 8px 4px 8px;
}
@media only screen and (max-width: 500px) {
.small-hidden {
display: none;

View File

@ -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);

View File

@ -10,6 +10,7 @@ export const mockConfigEntries = (hass: MockHomeAssistant) => {
supports_options: false,
supports_remove_device: false,
supports_unload: true,
supports_reconfigure: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,

33
demo/src/stubs/icons.ts Normal file
View 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
View 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",
],
},
]);
};

View File

@ -21,4 +21,5 @@ export const mockTodo = (hass: MockHomeAssistant) => {
},
] as TodoItem[],
}));
hass.mockWS("todo/item/subscribe", (_msg, _hass) => () => {});
};

View File

@ -1,5 +1,5 @@
import { Button } from "@material/mwc-button";
import { html, LitElement, css, TemplateResult } from "lit";
import { html, LitElement, css, TemplateResult, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
import { fireEvent } from "../../../src/common/dom/fire_event";
@ -9,7 +9,7 @@ import "../../../src/components/ha-card";
class DemoBlackWhiteRow extends LitElement {
@property() title!: string;
@property() value!: any;
@property() value?: any;
@property({ type: Boolean }) public disabled = false;
@ -45,7 +45,9 @@ class DemoBlackWhiteRow extends LitElement {
</mwc-button>
</div>
</ha-card>
<pre>${JSON.stringify(this.value, undefined, 2)}</pre>
${this.value
? html`<pre>${JSON.stringify(this.value, undefined, 2)}</pre>`
: nothing}
</div>
</div>
`;

View File

@ -10,7 +10,7 @@ import "../ha-demo-options";
@customElement("demo-cards")
class DemoCards extends LitElement {
@property() public configs!: DemoCardConfig[];
@property({ attribute: false }) public configs!: DemoCardConfig[];
@property({ attribute: false }) public hass!: HomeAssistant;

View File

@ -11,7 +11,7 @@ import "./demo-more-info";
class DemoMoreInfos extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entities!: [];
@property({ type: Array }) public entities!: string[];
@state() private _showConfig = false;

View File

@ -17,6 +17,7 @@ export const basicTrace: DemoTrace = {
{
path: "trigger/0",
timestamp: "2021-03-25T04:36:51.223693+00:00",
changed_variables: {},
},
],
"condition/0": [

View File

@ -17,6 +17,7 @@ export const motionLightTrace: DemoTrace = {
{
path: "trigger/0",
timestamp: "2021-03-25T04:36:51.223693+00:00",
changed_variables: {},
},
],
"action/0": [

View File

@ -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";
@ -56,6 +55,7 @@ export class DemoAutomationTraceTimeline extends LitElement {
super.firstUpdated(changedProps);
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
}
static get styles() {

View File

@ -60,6 +60,7 @@ export class DemoAutomationTrace extends LitElement {
super.firstUpdated(changedProps);
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
}
static get styles() {

View File

@ -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: [],
},

View File

@ -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: [],
},
@ -271,6 +275,14 @@ const SCHEMAS: {
selector: { color_temp: {} },
},
color_rgb: { name: "Color", selector: { color_rgb: {} } },
qr_code: {
name: "QR Code",
selector: { qr_code: { data: "https://home-assistant.io" } },
},
constant: {
name: "Constant",
selector: { constant: { value: true, label: "Yes!" } },
},
},
},
{
@ -497,7 +509,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
this.requestUpdate();
};
return html`
<demo-black-white-row .title=${info.name} .value=${this.data[idx]}>
<demo-black-white-row .title=${info.name}>
${["light", "dark"].map((slot) =>
Object.entries(info.input).map(
([key, value]) => html`
@ -530,8 +542,8 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
}
static styles = css`
ha-selector {
width: 60;
ha-settings-row {
--paper-item-body-two-line-min-height: 0;
}
.options {
max-width: 800px;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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", {
@ -82,6 +83,7 @@ class DemoConditional extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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", {
@ -276,6 +277,7 @@ class DemoEntityFilter extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -80,6 +80,9 @@ const CONFIGS = [
> [!CAUTION]
> This is a GitHub caution alert
> [!TIP]
> - This is a list entry in GitHub tip alert
## Lists
Unordered

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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", {
@ -45,7 +46,9 @@ const ENTITIES = [
friendly_name: "Sensibo purifier",
fan_modes: ["low", "high"],
fan_mode: "low",
supported_features: 9,
swing_modes: ["on", "off", "both", "vertical", "horizontal"],
swing_mode: "vertical",
supported_features: 41,
}),
getEntity("climate", "unavailable", "unavailable", {
supported_features: 43,
@ -84,6 +87,14 @@ const CONFIGS = [
fan_modes:
- low
- high
- type: climate-swing-modes
style: icons
swing_modes:
- 'on'
- 'off'
- 'both'
- 'vertical'
- 'horizontal'
`,
},
{
@ -116,6 +127,7 @@ class DemoThermostatEntity extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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 = [
@ -53,6 +54,7 @@ const SENSOR_DEVICE_CLASSES = [
"volatile_organic_compounds_parts",
"voltage",
"volume",
"volume_flow_rate",
"water",
"weight",
"wind_speed",
@ -290,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(
@ -396,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");
}

View File

@ -31,6 +31,7 @@ const createConfigEntry = (
supports_options: false,
supports_remove_device: false,
supports_unload: true,
supports_reconfigure: true,
disabled_by: null,
pref_disable_new_entities: false,
pref_disable_polling: false,

View File

@ -243,6 +243,8 @@ export class HassioAddonStore extends LitElement {
}
.advanced a {
margin-left: 0.5em;
margin-inline-start: 0.5em;
margin-inline-end: initial;
color: var(--primary-color);
}
`;

View File

@ -1188,11 +1188,13 @@ class HassioAddonInfo extends LitElement {
}
.addon-header {
padding-left: 8px;
padding-inline-start: 8px;
padding-inline-end: initial;
font-size: 24px;
color: var(--ha-card-header-color, --primary-text-color);
}
.addon-version {
float: right;
float: var(--float-end);
font-size: 15px;
vertical-align: middle;
}
@ -1261,6 +1263,7 @@ class HassioAddonInfo extends LitElement {
.card-actions {
justify-content: space-between;
display: flex;
direction: var(--direction);
}
.changelog {
display: contents;

View File

@ -395,6 +395,8 @@ export class HassioBackups extends LitElement {
.selected-txt {
font-weight: bold;
padding-left: 16px;
padding-inline-start: 16px;
padding-inline-end: initial;
color: var(--primary-text-color);
}
.table-header .selected-txt {
@ -405,6 +407,8 @@ export class HassioBackups extends LitElement {
}
.header-toolbar .header-btns {
margin-right: -12px;
margin-inline-end: -12px;
margin-inline-start: initial;
}
.header-btns > mwc-button,
.header-btns > ha-icon-button {

View File

@ -60,6 +60,10 @@ class HassioCardContent extends LitElement {
static get styles(): CSSResultGroup {
return css`
:host {
direction: ltr;
}
ha-svg-icon {
margin-right: 24px;
margin-left: 8px;

View File

@ -72,7 +72,7 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
export class SupervisorBackupContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public localize?: LocalizeFunc;
@property({ attribute: false }) public localize?: LocalizeFunc;
@property({ attribute: false }) public supervisor?: Supervisor;
@ -316,6 +316,8 @@ export class SupervisorBackupContent extends LitElement {
display: flex;
flex-direction: column;
margin-left: 30px;
margin-inline-start: 30px;
margin-inline-end: initial;
}
ha-formfield.password {
display: block;
@ -324,6 +326,8 @@ export class SupervisorBackupContent extends LitElement {
.backup-types {
display: flex;
margin-left: -13px;
margin-inline-start: -13px;
margin-inline-end: initial;
}
.sub-header {
margin-top: 8px;

View File

@ -37,6 +37,8 @@ class SupervisorFormfieldLabel extends LitElement {
}
.label {
margin-right: 4px;
margin-inline-end: 4px;
margin-inline-start: initial;
}
.version {
color: var(--secondary-text-color);
@ -45,6 +47,8 @@ class SupervisorFormfieldLabel extends LitElement {
max-height: 22px;
max-width: 22px;
margin-right: 8px;
margin-inline-end: 8px;
margin-inline-start: initial;
}
`;
}

View File

@ -64,6 +64,8 @@ class SupervisorMetric extends LitElement {
.value {
width: 48px;
padding-right: 4px;
padding-inline-start: initial;
padding-inline-end: 4px;
flex-shrink: 0;
}
`;

View File

@ -138,6 +138,9 @@ class HassioCreateBackupDialog extends LitElement {
haStyle,
haStyleDialog,
css`
:host {
direction: var(--direction);
}
ha-circular-progress {
display: block;
text-align: center;

View File

@ -154,12 +154,16 @@ class HassioHardwareDialog extends LitElement {
ha-icon-button {
position: absolute;
right: 16px;
inset-inline-end: 16px;
inset-inline-start: initial;
top: 10px;
text-decoration: none;
color: var(--primary-text-color);
}
h2 {
margin: 18px 42px 0 18px;
margin-inline-start: 18px;
margin-inline-end: 42px;
color: var(--primary-text-color);
}

View File

@ -597,6 +597,8 @@ export class DialogHassioNetwork
mwc-button.scan {
margin-left: 8px;
margin-inline-start: 8px;
margin-inline-end: initial;
}
.container {

View File

@ -229,6 +229,8 @@ class HassioRegistriesDialog extends LitElement {
ha-icon-button {
color: var(--error-color);
margin-right: -10px;
margin-inline-end: -10px;
margin-inline-start: initial;
}
`,
];

View File

@ -1,7 +1,5 @@
import "@material/mwc-button/mwc-button";
import { mdiDelete, mdiDeleteOff } from "@mdi/js";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
@ -27,6 +25,8 @@ import type { HomeAssistant } from "../../../../src/types";
import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
import type { HaTextField } from "../../../../src/components/ha-textfield";
import "../../../../src/components/ha-textfield";
import "../../../../src/components/ha-list-new";
import "../../../../src/components/ha-list-item-new";
@customElement("dialog-hassio-repositories")
class HassioRepositoriesDialog extends LitElement {
@ -106,44 +106,46 @@ class HassioRepositoriesDialog extends LitElement {
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<div class="form">
${repositories.length
? repositories.map(
(repo) => html`
<paper-item class="option">
<paper-item-body three-line>
<div>${repo.name}</div>
<div secondary>${repo.maintainer}</div>
<div secondary>${repo.url}</div>
</paper-item-body>
<div class="delete">
<ha-icon-button
.label=${this._dialogParams!.supervisor.localize(
"dialog.repositories.remove"
)}
.disabled=${usedRepositories.includes(repo.slug)}
.slug=${repo.slug}
.path=${usedRepositories.includes(repo.slug)
? mdiDeleteOff
: mdiDelete}
@click=${this._removeRepository}
>
</ha-icon-button>
<simple-tooltip
animation-delay="0"
position="bottom"
offset="1"
>
${this._dialogParams!.supervisor.localize(
usedRepositories.includes(repo.slug)
? "dialog.repositories.used"
: "dialog.repositories.remove"
)}
</simple-tooltip>
</div>
</paper-item>
`
)
: html`<paper-item> No repositories </paper-item>`}
<ha-list-new>
${repositories.length
? repositories.map(
(repo) => html`
<ha-list-item-new class="option">
${repo.name}
<div slot="supporting-text">
<div>${repo.maintainer}</div>
<div>${repo.url}</div>
</div>
<div class="delete" slot="end">
<ha-icon-button
.label=${this._dialogParams!.supervisor.localize(
"dialog.repositories.remove"
)}
.disabled=${usedRepositories.includes(repo.slug)}
.slug=${repo.slug}
.path=${usedRepositories.includes(repo.slug)
? mdiDeleteOff
: mdiDelete}
@click=${this._removeRepository}
>
</ha-icon-button>
<simple-tooltip
animation-delay="0"
position="bottom"
offset="1"
>
${this._dialogParams!.supervisor.localize(
usedRepositories.includes(repo.slug)
? "dialog.repositories.used"
: "dialog.repositories.remove"
)}
</simple-tooltip>
</div>
</ha-list-item-new>
`
)
: html`<ha-list-item-new> No repositories </ha-list-item-new>`}
</ha-list-new>
<div class="layout horizontal bottom">
<ha-textfield
class="flex-auto"
@ -195,6 +197,8 @@ class HassioRepositoriesDialog extends LitElement {
}
mwc-button {
margin-left: 8px;
margin-inline-start: 8px;
margin-inline-end: initial;
}
ha-circular-progress {
display: block;
@ -204,6 +208,9 @@ class HassioRepositoriesDialog extends LitElement {
div.delete ha-icon-button {
color: var(--error-color);
}
ha-list-item-new {
position: relative;
}
`,
];
}

View File

@ -360,7 +360,7 @@ class HassioIngressView extends LitElement {
}
.main-title {
margin: 0 0 0 24px;
margin: var(--margin-title);
line-height: 20px;
flex-grow: 1;
}

View File

@ -19,10 +19,14 @@ export const hassioStyle = css`
letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-headline_-_line-height);
padding-left: 8px;
padding-inline-start: 8px;
padding-inline-end: initial;
}
.description {
margin-top: 4px;
padding-left: 8px;
padding-inline-start: 8px;
padding-inline-end: initial;
}
.card-group {
display: grid;

View File

@ -73,6 +73,8 @@ class HassioSystem extends LitElement {
color: var(--primary-text-color);
font-size: 2em;
padding-left: 8px;
padding-inline-start: 8px;
padding-inline-end: initial;
margin-bottom: 8px;
}
hassio-supervisor-log {

View File

@ -25,36 +25,36 @@
"license": "Apache-2.0",
"type": "module",
"dependencies": {
"@babel/runtime": "7.23.8",
"@babel/runtime": "7.24.0",
"@braintree/sanitize-url": "7.0.0",
"@codemirror/autocomplete": "6.12.0",
"@codemirror/autocomplete": "6.13.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",
"@codemirror/view": "6.23.0",
"@codemirror/search": "6.5.6",
"@codemirror/state": "6.4.1",
"@codemirror/view": "6.25.1",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "6.12.2",
"@formatjs/intl-displaynames": "6.6.6",
"@formatjs/intl-getcanonicallocales": "2.3.0",
"@formatjs/intl-listformat": "7.5.5",
"@formatjs/intl-locale": "3.4.5",
"@formatjs/intl-numberformat": "8.9.2",
"@formatjs/intl-numberformat": "8.10.0",
"@formatjs/intl-pluralrules": "5.2.12",
"@formatjs/intl-relativetimeformat": "11.2.12",
"@fullcalendar/core": "6.1.10",
"@fullcalendar/daygrid": "6.1.10",
"@fullcalendar/interaction": "6.1.10",
"@fullcalendar/list": "6.1.10",
"@fullcalendar/luxon3": "6.1.10",
"@fullcalendar/timegrid": "6.1.10",
"@fullcalendar/core": "6.1.11",
"@fullcalendar/daygrid": "6.1.11",
"@fullcalendar/interaction": "6.1.11",
"@fullcalendar/list": "6.1.11",
"@fullcalendar/luxon3": "6.1.11",
"@fullcalendar/timegrid": "6.1.11",
"@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": "7.0.18",
"@lrnwebcomponents/simple-tooltip": "8.0.2",
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
"@material/mwc-base": "0.27.0",
@ -72,6 +72,7 @@
"@material/mwc-radio": "0.27.0",
"@material/mwc-ripple": "0.27.0",
"@material/mwc-select": "0.27.0",
"@material/mwc-snackbar": "0.27.0",
"@material/mwc-switch": "0.27.0",
"@material/mwc-tab": "0.27.0",
"@material/mwc-tab-bar": "0.27.0",
@ -80,17 +81,16 @@
"@material/mwc-top-app-bar": "0.27.0",
"@material/mwc-top-app-bar-fixed": "0.27.0",
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
"@material/web": "=1.1.1",
"@material/web": "=1.3.0",
"@mdi/js": "7.4.47",
"@mdi/svg": "7.4.47",
"@polymer/paper-item": "3.0.1",
"@polymer/paper-listbox": "3.0.1",
"@polymer/paper-tabs": "3.1.0",
"@polymer/paper-toast": "3.0.1",
"@polymer/polymer": "3.5.1",
"@thomasloven/round-slider": "0.6.0",
"@vaadin/combo-box": "24.3.3",
"@vaadin/vaadin-themable-mixin": "24.3.3",
"@vaadin/combo-box": "24.3.8",
"@vaadin/vaadin-themable-mixin": "24.3.8",
"@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
@ -98,9 +98,10 @@
"@webcomponents/scoped-custom-element-registry": "0.0.9",
"@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1",
"chart.js": "4.4.1",
"chart.js": "4.4.2",
"color-name": "2.0.0",
"comlink": "4.4.1",
"core-js": "3.35.0",
"core-js": "3.36.0",
"cropperjs": "1.6.1",
"date-fns": "2.30.0",
"date-fns-tz": "2.0.0",
@ -109,16 +110,16 @@
"element-internals-polyfill": "1.3.10",
"fuse.js": "7.0.0",
"google-timezones-json": "1.2.0",
"hls.js": "1.5.1",
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
"home-assistant-js-websocket": "9.1.0",
"idb-keyval": "6.2.1",
"intl-messageformat": "10.5.10",
"intl-messageformat": "10.5.11",
"js-yaml": "4.1.0",
"leaflet": "1.9.4",
"leaflet-draw": "1.0.4",
"lit": "2.8.0",
"luxon": "3.4.4",
"marked": "11.1.1",
"marked": "12.0.1",
"memoize-one": "6.0.0",
"node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "0.3.2",
@ -146,20 +147,20 @@
"workbox-precaching": "7.0.0",
"workbox-routing": "7.0.0",
"workbox-strategies": "7.0.0",
"xss": "1.0.14"
"xss": "1.0.15"
},
"devDependencies": {
"@babel/core": "7.23.7",
"@babel/helper-define-polyfill-provider": "0.4.4",
"@babel/plugin-proposal-decorators": "7.23.7",
"@babel/plugin-transform-runtime": "7.23.7",
"@babel/preset-env": "7.23.8",
"@babel/core": "7.24.0",
"@babel/helper-define-polyfill-provider": "0.6.0",
"@babel/plugin-proposal-decorators": "7.24.0",
"@babel/plugin-transform-runtime": "7.24.0",
"@babel/preset-env": "7.24.0",
"@babel/preset-typescript": "7.23.3",
"@bundle-stats/plugin-webpack-filter": "4.8.4",
"@bundle-stats/plugin-webpack-filter": "4.12.0",
"@koa/cors": "5.0.0",
"@lokalise/node-api": "12.1.0",
"@octokit/auth-oauth-device": "6.0.1",
"@octokit/plugin-retry": "6.0.1",
"@octokit/auth-oauth-device": "7.0.1",
"@octokit/plugin-retry": "7.0.3",
"@octokit/rest": "20.0.2",
"@open-wc/dev-server-hmr": "0.1.4",
"@rollup/plugin-babel": "6.0.4",
@ -169,62 +170,63 @@
"@rollup/plugin-replace": "5.0.5",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.13",
"@types/chromecast-caf-sender": "1.0.8",
"@types/chromecast-caf-sender": "1.0.9",
"@types/color-name": "1.1.3",
"@types/glob": "8.1.0",
"@types/html-minifier-terser": "7.0.2",
"@types/js-yaml": "4.0.9",
"@types/leaflet": "1.9.8",
"@types/leaflet-draw": "1.0.11",
"@types/luxon": "3.4.1",
"@types/luxon": "3.4.2",
"@types/mocha": "10.0.6",
"@types/qrcode": "1.5.5",
"@types/serve-handler": "6.1.4",
"@types/sortablejs": "1.15.7",
"@types/tar": "6.1.10",
"@types/sortablejs": "1.15.8",
"@types/tar": "6.1.11",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "6.19.0",
"@typescript-eslint/parser": "6.19.0",
"@typescript-eslint/eslint-plugin": "7.1.1",
"@typescript-eslint/parser": "7.1.1",
"@web/dev-server": "0.1.38",
"@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3",
"babel-plugin-template-html-minifier": "4.1.0",
"chai": "5.0.0",
"chai": "5.1.0",
"del": "7.1.0",
"eslint": "8.56.0",
"eslint": "8.57.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "17.1.0",
"eslint-config-airbnb-typescript": "18.0.0",
"eslint-config-prettier": "9.1.0",
"eslint-import-resolver-webpack": "0.13.8",
"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-unused-imports": "3.0.0",
"eslint-plugin-lit-a11y": "4.1.2",
"eslint-plugin-unused-imports": "3.1.0",
"eslint-plugin-wc": "2.0.4",
"fancy-log": "2.0.0",
"fs-extra": "11.2.0",
"glob": "10.3.10",
"gulp": "4.0.2",
"gulp-flatmap": "1.0.2",
"gulp-json-transform": "0.4.8",
"gulp-merge-json": "2.1.2",
"gulp-json-transform": "0.5.0",
"gulp-merge-json": "2.2.1",
"gulp-rename": "2.0.0",
"gulp-zopfli-green": "6.0.1",
"html-minifier-terser": "7.2.0",
"husky": "8.0.3",
"husky": "9.0.11",
"instant-mocha": "1.5.2",
"jszip": "3.10.1",
"lint-staged": "15.2.0",
"lint-staged": "15.2.2",
"lit-analyzer": "2.0.3",
"lodash.template": "4.5.0",
"magic-string": "0.30.5",
"magic-string": "0.30.8",
"map-stream": "0.0.7",
"mocha": "10.2.0",
"mocha": "10.3.0",
"object-hash": "3.0.0",
"open": "10.0.3",
"open": "10.1.0",
"pinst": "3.0.0",
"prettier": "3.2.2",
"prettier": "3.2.5",
"rollup": "2.79.1",
"rollup-plugin-string": "3.0.0",
"rollup-plugin-terser": "7.0.2",
@ -237,15 +239,15 @@
"terser-webpack-plugin": "5.3.10",
"transform-async-modules-webpack-plugin": "1.0.2",
"ts-lit-plugin": "2.0.2",
"typescript": "5.3.3",
"typescript": "5.4.2",
"vinyl-buffer": "1.0.1",
"vinyl-source-stream": "2.0.0",
"webpack": "5.89.0",
"webpack": "5.90.3",
"webpack-cli": "5.1.4",
"webpack-dev-server": "4.15.1",
"webpack-dev-server": "5.0.2",
"webpack-manifest-plugin": "5.0.0",
"webpack-stats-plugin": "1.1.3",
"webpackbar": "6.0.0",
"webpackbar": "6.0.1",
"workbox-build": "7.0.0"
},
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
@ -255,8 +257,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.1"
}

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20240112.0"
version = "20240228.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"

View File

@ -40,6 +40,7 @@ if [ -n "$ref" ]; then
echo "Installing Home Assistant core at ${ref}..."
python3 -m pip install --user --upgrade --src "$HOME/src" \
--editable "git+${coreURL}@${ref}#egg=homeassistant"
(cd ~/src/homeassistant && exec python3 -m script.translations develop --all)
fi
if [ ! -d "${WD}/config" ]; then

View File

@ -34,7 +34,7 @@ export class HaAuthFlow extends LitElement {
@property() public oauth2State?: string;
@property() public localize!: LocalizeFunc;
@property({ attribute: false }) public localize!: LocalizeFunc;
@property({ attribute: false }) public step?: DataEntryFlowStep;
@ -93,6 +93,8 @@ export class HaAuthFlow extends LitElement {
<style>
ha-auth-flow .store-token {
margin-left: -16px;
margin-inline-start: -16px;
margin-inline-end: initial;
}
a.forgot-password {
color: var(--primary-color);

View File

@ -149,6 +149,8 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
text-decoration: none;
color: var(--primary-text-color);
margin-right: 16px;
margin-inline-end: 16px;
margin-inline-start: initial;
}
h1 {
font-size: 28px;

View File

@ -18,9 +18,9 @@ declare global {
@customElement("ha-pick-auth-provider")
export class HaPickAuthProvider extends LitElement {
@property() public authProviders: AuthProvider[] = [];
@property({ attribute: false }) public authProviders: AuthProvider[] = [];
@property() public localize!: LocalizeFunc;
@property({ attribute: false }) public localize!: LocalizeFunc;
protected render() {
return html`

View File

@ -1,19 +1,25 @@
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
import { HomeAssistant } from "../../types";
import { ensureArray } from "../array/ensure-array";
import { isComponentLoaded } from "./is_component_loaded";
export const canShowPage = (hass: HomeAssistant, page: PageNavigation) =>
(isCore(page) || isLoadedIntegration(hass, page)) &&
!hideAdvancedPage(hass, page);
!hideAdvancedPage(hass, page) &&
isNotLoadedIntegration(hass, page);
const isLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) =>
page.component
? isComponentLoaded(hass, page.component)
: page.components
? page.components.some((integration) =>
isComponentLoaded(hass, integration)
)
: true;
!page.component ||
ensureArray(page.component).some((integration) =>
isComponentLoaded(hass, integration)
);
const isNotLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) =>
!page.not_component ||
!ensureArray(page.not_component).some((integration) =>
isComponentLoaded(hass, integration)
);
const isCore = (page: PageNavigation) => page.core;
const isAdvancedPage = (page: PageNavigation) => page.advancedOnly;
const userWantsAdvanced = (hass: HomeAssistant) => hass.userData?.showAdvanced;

View File

@ -1,6 +1,7 @@
/** Constants to be used in the frontend. */
import {
mdiAccount,
mdiAirFilter,
mdiAlert,
mdiAngleAcute,
@ -18,7 +19,6 @@ import {
mdiChatSleep,
mdiClipboardList,
mdiClock,
mdiCloudUpload,
mdiCog,
mdiCommentAlert,
mdiCounter,
@ -48,11 +48,14 @@ import {
mdiMoleculeCo2,
mdiPalette,
mdiPh,
mdiPipe,
mdiProgressClock,
mdiRayVertex,
mdiRemote,
mdiRobot,
mdiRobotMower,
mdiRobotVacuum,
mdiRoomService,
mdiScriptText,
mdiSineWave,
mdiSpeakerMessage,
@ -62,6 +65,7 @@ import {
mdiThermometerLines,
mdiThermostat,
mdiTimerOutline,
mdiToggleSwitch,
mdiTransmissionTower,
mdiWater,
mdiWaterPercent,
@ -70,6 +74,7 @@ import {
mdiWeatherRainy,
mdiWeatherWindy,
mdiWeight,
mdiWhiteBalanceSunny,
mdiWifi,
} from "@mdi/js";
@ -79,6 +84,9 @@ import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
// Arrays with values should be alphabetically sorted if order doesn't matter.
// Each constant should have a description what it is supposed to be used for.
/** Icon to use when no icon specified for service. */
export const DEFAULT_SERVICE_ICON = mdiRoomService;
/** Icon to use when no icon specified for domain. */
export const DEFAULT_DOMAIN_ICON = mdiBookmark;
@ -86,20 +94,23 @@ export const DEFAULT_DOMAIN_ICON = mdiBookmark;
export const FIXED_DOMAIN_ICONS = {
air_quality: mdiAirFilter,
alert: mdiAlert,
automation: mdiRobot,
calendar: mdiCalendar,
climate: mdiThermostat,
configurator: mdiCog,
conversation: mdiMicrophoneMessage,
counter: mdiCounter,
datetime: mdiCalendarClock,
date: mdiCalendar,
datetime: mdiCalendarClock,
demo: mdiHomeAssistant,
device_tracker: mdiAccount,
google_assistant: mdiGoogleAssistant,
group: mdiGoogleCirclesCommunities,
homeassistant: mdiHomeAssistant,
homekit: mdiHomeAutomation,
image: mdiImage,
image_processing: mdiImageFilterFrames,
image: mdiImage,
input_boolean: mdiToggleSwitch,
input_button: mdiButtonPointer,
input_datetime: mdiCalendarClock,
input_number: mdiRayVertex,
@ -111,6 +122,7 @@ export const FIXED_DOMAIN_ICONS = {
notify: mdiCommentAlert,
number: mdiRayVertex,
persistent_notification: mdiBell,
person: mdiAccount,
plant: mdiFlower,
proximity: mdiAppleSafari,
remote: mdiRemote,
@ -122,12 +134,12 @@ export const FIXED_DOMAIN_ICONS = {
simple_alarm: mdiBell,
siren: mdiBullhorn,
stt: mdiMicrophoneMessage,
sun: mdiWhiteBalanceSunny,
text: mdiFormTextbox,
todo: mdiClipboardList,
time: mdiClock,
timer: mdiTimerOutline,
todo: mdiClipboardList,
tts: mdiSpeakerMessage,
updater: mdiCloudUpload,
vacuum: mdiRobotVacuum,
wake_word: mdiChatSleep,
weather: mdiWeatherPartlyCloudy,
@ -180,6 +192,7 @@ export const FIXED_DEVICE_CLASS_ICONS = {
volatile_organic_compounds_parts: mdiMolecule,
voltage: mdiSineWave,
volume: mdiCarCoolantLevel,
volume_flow_rate: mdiPipe,
water: mdiWater,
weight: mdiWeight,
wind_speed: mdiWeatherWindy,

View File

@ -41,7 +41,9 @@ export const applyThemesOnElement = (
// If there is no explicitly desired dark mode provided, we automatically
// use the active one from `themes`.
const darkMode =
themeSettings?.dark !== undefined ? themeSettings.dark : themes.darkMode;
themeSettings?.dark !== undefined
? themeSettings.dark
: themes?.darkMode || false;
let cacheKey = themeToApply;
let themeRules: Partial<ThemeVars> = {};

View File

@ -1,8 +1,13 @@
import { MAIN_WINDOW_NAME } from "../../data/main_window";
export const mainWindow =
window.name === MAIN_WINDOW_NAME
? window
: parent.name === MAIN_WINDOW_NAME
? parent
: top!;
export const mainWindow = (() => {
try {
return window.name === MAIN_WINDOW_NAME
? window
: parent.name === MAIN_WINDOW_NAME
? parent
: top!;
} catch {
return window;
}
})();

View File

@ -1,36 +0,0 @@
/** Return an icon representing a alarm panel state. */
import {
mdiShieldLock,
mdiShieldAirplane,
mdiShieldHome,
mdiShieldMoon,
mdiSecurity,
mdiShieldOutline,
mdiBellRing,
mdiShieldOff,
mdiShield,
} from "@mdi/js";
export const alarmPanelIcon = (state?: string) => {
switch (state) {
case "armed_away":
return mdiShieldLock;
case "armed_vacation":
return mdiShieldAirplane;
case "armed_home":
return mdiShieldHome;
case "armed_night":
return mdiShieldMoon;
case "armed_custom_bypass":
return mdiSecurity;
case "pending":
return mdiShieldOutline;
case "triggered":
return mdiBellRing;
case "disarmed":
return mdiShieldOff;
default:
return mdiShield;
}
};

View File

@ -1,46 +0,0 @@
/** Return an icon representing a attribute. */
import { mdiCircleMedium, mdiCreation } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import {
computeFanModeIcon,
computeHvacModeIcon,
computePresetModeIcon,
computeSwingModeIcon,
} from "../../data/climate";
import { computeHumidiferModeIcon } from "../../data/humidifier";
import { computeDomain } from "./compute_domain";
const iconGenerators: Record<string, Record<string, (value: any) => string>> = {
climate: {
fan_mode: computeFanModeIcon,
hvac_mode: computeHvacModeIcon,
preset_mode: computePresetModeIcon,
swing_mode: computeSwingModeIcon,
},
humidifier: {
mode: computeHumidiferModeIcon,
},
light: {
effect: () => mdiCreation,
},
fan: {
preset_mode: () => mdiCircleMedium,
},
};
export const attributeIconPath = (
state: HassEntity | undefined,
attribute: string,
attributeValue?: string
) => {
if (!state) {
return undefined;
}
const domain = computeDomain(state.entity_id);
if (iconGenerators[domain]?.[attribute]) {
return iconGenerators[domain]?.[attribute](
attributeValue || state.attributes[attribute]
);
}
return undefined;
};

View File

@ -1,92 +1,59 @@
/** Return an icon representing a battery state. */
import {
mdiBattery,
mdiBattery10,
mdiBattery20,
mdiBattery30,
mdiBattery40,
mdiBattery50,
mdiBattery60,
mdiBattery70,
mdiBattery80,
mdiBattery90,
mdiBatteryAlert,
mdiBatteryAlertVariantOutline,
mdiBatteryCharging,
mdiBatteryCharging10,
mdiBatteryCharging20,
mdiBatteryCharging30,
mdiBatteryCharging40,
mdiBatteryCharging50,
mdiBatteryCharging60,
mdiBatteryCharging70,
mdiBatteryCharging80,
mdiBatteryCharging90,
mdiBatteryChargingOutline,
mdiBatteryUnknown,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
const BATTERY_ICONS = {
10: mdiBattery10,
20: mdiBattery20,
30: mdiBattery30,
40: mdiBattery40,
50: mdiBattery50,
60: mdiBattery60,
70: mdiBattery70,
80: mdiBattery80,
90: mdiBattery90,
100: mdiBattery,
10: "mdi:battery-10",
20: "mdi:battery-20",
30: "mdi:battery-30",
40: "mdi:battery-40",
50: "mdi:battery-50",
60: "mdi:battery-60",
70: "mdi:battery-70",
80: "mdi:battery-80",
90: "mdi:battery-90",
100: "mdi:battery",
};
const BATTERY_CHARGING_ICONS = {
10: mdiBatteryCharging10,
20: mdiBatteryCharging20,
30: mdiBatteryCharging30,
40: mdiBatteryCharging40,
50: mdiBatteryCharging50,
60: mdiBatteryCharging60,
70: mdiBatteryCharging70,
80: mdiBatteryCharging80,
90: mdiBatteryCharging90,
100: mdiBatteryCharging,
10: "mdi:battery-charging-10",
20: "mdi:battery-charging-20",
30: "mdi:battery-charging-30",
40: "mdi:battery-charging-40",
50: "mdi:battery-charging-50",
60: "mdi:battery-charging-60",
70: "mdi:battery-charging-70",
80: "mdi:battery-charging-80",
90: "mdi:battery-charging-90",
100: "mdi:battery-charging",
};
export const batteryStateIcon = (
batteryState: HassEntity,
batteryChargingState?: HassEntity
) => {
const battery = batteryState.state;
const batteryCharging =
batteryChargingState && batteryChargingState.state === "on";
return batteryIcon(battery, batteryCharging);
export const batteryIcon = (stateObj: HassEntity, state?: string) => {
const level = state ?? stateObj.state;
return batteryLevelIcon(level);
};
export const batteryIcon = (
batteryState: number | string,
batteryCharging?: boolean
) => {
const batteryValue = Number(batteryState);
export const batteryLevelIcon = (
batteryLevel: number | string,
isBatteryCharging?: boolean
): string => {
const batteryValue = Number(batteryLevel);
if (isNaN(batteryValue)) {
if (batteryState === "off") {
return mdiBattery;
if (batteryLevel === "off") {
return "mdi:battery";
}
if (batteryState === "on") {
return mdiBatteryAlert;
if (batteryLevel === "on") {
return "mdi:battery-alert";
}
return mdiBatteryUnknown;
return "mdi:battery-unknown";
}
const batteryRound = Math.round(batteryValue / 10) * 10;
if (batteryCharging && batteryValue >= 10) {
if (isBatteryCharging && batteryValue >= 10) {
return BATTERY_CHARGING_ICONS[batteryRound];
}
if (batteryCharging) {
return mdiBatteryChargingOutline;
if (isBatteryCharging) {
return "mdi:battery-charging-outline";
}
if (batteryValue <= 5) {
return mdiBatteryAlertVariantOutline;
return "mdi:battery-alert-variant-outline";
}
return BATTERY_ICONS[batteryRound];
};

View File

@ -1,108 +0,0 @@
import {
mdiAlertCircle,
mdiBattery,
mdiBatteryCharging,
mdiBatteryOutline,
mdiBrightness5,
mdiBrightness7,
mdiCheckboxMarkedCircle,
mdiCheckNetworkOutline,
mdiCloseNetworkOutline,
mdiCheckCircle,
mdiCropPortrait,
mdiDoorClosed,
mdiDoorOpen,
mdiFire,
mdiGarage,
mdiGarageOpen,
mdiHome,
mdiHomeOutline,
mdiLock,
mdiLockOpen,
mdiMusicNote,
mdiMusicNoteOff,
mdiMotionSensor,
mdiMotionSensorOff,
mdiPackage,
mdiPackageUp,
mdiPlay,
mdiPowerPlug,
mdiPowerPlugOff,
mdiRadioboxBlank,
mdiSnowflake,
mdiSmokeDetector,
mdiSmokeDetectorAlert,
mdiSmokeDetectorVariant,
mdiSmokeDetectorVariantAlert,
mdiSquare,
mdiSquareOutline,
mdiStop,
mdiThermometer,
mdiVibrate,
mdiWater,
mdiWaterOff,
mdiWindowClosed,
mdiWindowOpen,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
/** Return an icon representing a binary sensor state. */
export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
const is_off = state === "off";
switch (stateObj?.attributes.device_class) {
case "battery":
return is_off ? mdiBattery : mdiBatteryOutline;
case "battery_charging":
return is_off ? mdiBattery : mdiBatteryCharging;
case "carbon_monoxide":
return is_off ? mdiSmokeDetector : mdiSmokeDetectorAlert;
case "cold":
return is_off ? mdiThermometer : mdiSnowflake;
case "connectivity":
return is_off ? mdiCloseNetworkOutline : mdiCheckNetworkOutline;
case "door":
return is_off ? mdiDoorClosed : mdiDoorOpen;
case "garage_door":
return is_off ? mdiGarage : mdiGarageOpen;
case "power":
return is_off ? mdiPowerPlugOff : mdiPowerPlug;
case "gas":
case "problem":
case "safety":
case "tamper":
return is_off ? mdiCheckCircle : mdiAlertCircle;
case "smoke":
return is_off ? mdiSmokeDetectorVariant : mdiSmokeDetectorVariantAlert;
case "heat":
return is_off ? mdiThermometer : mdiFire;
case "light":
return is_off ? mdiBrightness5 : mdiBrightness7;
case "lock":
return is_off ? mdiLock : mdiLockOpen;
case "moisture":
return is_off ? mdiWaterOff : mdiWater;
case "motion":
return is_off ? mdiMotionSensorOff : mdiMotionSensor;
case "occupancy":
return is_off ? mdiHomeOutline : mdiHome;
case "opening":
return is_off ? mdiSquare : mdiSquareOutline;
case "plug":
return is_off ? mdiPowerPlugOff : mdiPowerPlug;
case "presence":
return is_off ? mdiHomeOutline : mdiHome;
case "running":
return is_off ? mdiStop : mdiPlay;
case "sound":
return is_off ? mdiMusicNoteOff : mdiMusicNote;
case "update":
return is_off ? mdiPackage : mdiPackageUp;
case "vibration":
return is_off ? mdiCropPortrait : mdiVibrate;
case "window":
return is_off ? mdiWindowClosed : mdiWindowOpen;
default:
return is_off ? mdiRadioboxBlank : mdiCheckboxMarkedCircle;
}
};

View File

@ -53,9 +53,7 @@ export const computeAttributeValueDisplay = (
if (domain === "weather") {
unit = getWeatherUnit(config, stateObj as WeatherEntity, attribute);
}
if (TEMPERATURE_ATTRIBUTES.has(attribute)) {
} else if (TEMPERATURE_ATTRIBUTES.has(attribute)) {
unit = config.unit_system.temperature;
}

View File

@ -1,132 +1,12 @@
/** Return an icon representing a cover state. */
import {
mdiArrowUpBox,
mdiArrowDownBox,
mdiGarage,
mdiGarageOpen,
mdiGateArrowRight,
mdiGate,
mdiGateOpen,
mdiDoorOpen,
mdiDoorClosed,
mdiCircle,
mdiWindowShutter,
mdiWindowShutterOpen,
mdiBlindsHorizontal,
mdiBlindsHorizontalClosed,
mdiRollerShade,
mdiRollerShadeClosed,
mdiWindowClosed,
mdiWindowOpen,
mdiArrowExpandHorizontal,
mdiArrowUp,
mdiArrowCollapseHorizontal,
mdiArrowDown,
mdiCircleSlice8,
mdiArrowSplitVertical,
mdiCurtains,
mdiCurtainsClosed,
mdiArrowExpandHorizontal,
mdiArrowUp,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
export const coverIcon = (state?: string, stateObj?: HassEntity): string => {
const open = state !== "closed";
switch (stateObj?.attributes.device_class) {
case "garage":
switch (state) {
case "opening":
return mdiArrowUpBox;
case "closing":
return mdiArrowDownBox;
case "closed":
return mdiGarage;
default:
return mdiGarageOpen;
}
case "gate":
switch (state) {
case "opening":
case "closing":
return mdiGateArrowRight;
case "closed":
return mdiGate;
default:
return mdiGateOpen;
}
case "door":
return open ? mdiDoorOpen : mdiDoorClosed;
case "damper":
return open ? mdiCircle : mdiCircleSlice8;
case "shutter":
switch (state) {
case "opening":
return mdiArrowUpBox;
case "closing":
return mdiArrowDownBox;
case "closed":
return mdiWindowShutter;
default:
return mdiWindowShutterOpen;
}
case "curtain":
switch (state) {
case "opening":
return mdiArrowSplitVertical;
case "closing":
return mdiArrowCollapseHorizontal;
case "closed":
return mdiCurtainsClosed;
default:
return mdiCurtains;
}
case "blind":
switch (state) {
case "opening":
return mdiArrowUpBox;
case "closing":
return mdiArrowDownBox;
case "closed":
return mdiBlindsHorizontalClosed;
default:
return mdiBlindsHorizontal;
}
case "shade":
switch (state) {
case "opening":
return mdiArrowUpBox;
case "closing":
return mdiArrowDownBox;
case "closed":
return mdiRollerShadeClosed;
default:
return mdiRollerShade;
}
case "window":
switch (state) {
case "opening":
return mdiArrowUpBox;
case "closing":
return mdiArrowDownBox;
case "closed":
return mdiWindowClosed;
default:
return mdiWindowOpen;
}
}
switch (state) {
case "opening":
return mdiArrowUpBox;
case "closing":
return mdiArrowDownBox;
case "closed":
return mdiWindowClosed;
default:
return mdiWindowOpen;
}
};
export const computeOpenIcon = (stateObj: HassEntity): string => {
switch (stateObj.attributes.device_class) {
case "awning":

View File

@ -0,0 +1,16 @@
import { HassEntity } from "home-assistant-js-websocket";
export const deviceTrackerIcon = (stateObj: HassEntity, state?: string) => {
const compareState = state ?? stateObj.state;
if (stateObj?.attributes.source_type === "router") {
return compareState === "home" ? "mdi:lan-connect" : "mdi:lan-disconnect";
}
if (
["bluetooth", "bluetooth_le"].includes(stateObj?.attributes.source_type)
) {
return compareState === "home" ? "mdi:bluetooth-connect" : "mdi:bluetooth";
}
return compareState === "not_home"
? "mdi:account-arrow-right"
: "mdi:account";
};

View File

@ -1,301 +0,0 @@
import {
mdiAccount,
mdiAccountArrowRight,
mdiAirHumidifier,
mdiAirHumidifierOff,
mdiAudioVideo,
mdiAudioVideoOff,
mdiBluetooth,
mdiBluetoothConnect,
mdiButtonPointer,
mdiCalendar,
mdiCast,
mdiCastConnected,
mdiCastOff,
mdiChartSankey,
mdiCheckCircleOutline,
mdiClock,
mdiCloseCircleOutline,
mdiCrosshairsQuestion,
mdiDoorbell,
mdiEyeCheck,
mdiFan,
mdiFanOff,
mdiGestureTapButton,
mdiLanConnect,
mdiLanDisconnect,
mdiLock,
mdiLockAlert,
mdiLockClock,
mdiLockOpen,
mdiMeterGas,
mdiMotionSensor,
mdiPackage,
mdiPackageDown,
mdiPackageUp,
mdiPipeValve,
mdiPowerPlug,
mdiPowerPlugOff,
mdiRestart,
mdiRobot,
mdiRobotConfused,
mdiRobotOff,
mdiSpeaker,
mdiSpeakerOff,
mdiSpeakerPause,
mdiSpeakerPlay,
mdiSwapHorizontal,
mdiTelevision,
mdiTelevisionOff,
mdiTelevisionPause,
mdiTelevisionPlay,
mdiToggleSwitchVariant,
mdiToggleSwitchVariantOff,
mdiVideo,
mdiVideoOff,
mdiWaterBoiler,
mdiWaterBoilerOff,
mdiWeatherNight,
mdiWhiteBalanceSunny,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { UpdateEntity, updateIsInstalling } from "../../data/update";
import { weatherIcon } from "../../data/weather";
/**
* Return the icon to be used for a domain.
*
* Optionally pass in a state to influence the domain icon.
*/
import { DEFAULT_DOMAIN_ICON, FIXED_DOMAIN_ICONS } from "../const";
import { alarmPanelIcon } from "./alarm_panel_icon";
import { binarySensorIcon } from "./binary_sensor_icon";
import { coverIcon } from "./cover_icon";
import { numberIcon } from "./number_icon";
import { sensorIcon } from "./sensor_icon";
export const domainIcon = (
domain: string,
stateObj?: HassEntity,
state?: string
): string => {
const icon = domainIconWithoutDefault(domain, stateObj, state);
if (icon) {
return icon;
}
// eslint-disable-next-line
console.warn(`Unable to find icon for domain ${domain}`);
return DEFAULT_DOMAIN_ICON;
};
export const domainIconWithoutDefault = (
domain: string,
stateObj?: HassEntity,
state?: string
): string | undefined => {
const compareState = state !== undefined ? state : stateObj?.state;
switch (domain) {
case "alarm_control_panel":
return alarmPanelIcon(compareState);
case "automation":
return compareState === "unavailable"
? mdiRobotConfused
: compareState === "off"
? mdiRobotOff
: mdiRobot;
case "binary_sensor":
return binarySensorIcon(compareState, stateObj);
case "button":
switch (stateObj?.attributes.device_class) {
case "identify":
return mdiCrosshairsQuestion;
case "restart":
return mdiRestart;
case "update":
return mdiPackageUp;
default:
return mdiButtonPointer;
}
case "camera":
return compareState === "off" ? mdiVideoOff : mdiVideo;
case "cover":
return coverIcon(compareState, stateObj);
case "device_tracker":
if (stateObj?.attributes.source_type === "router") {
return compareState === "home" ? mdiLanConnect : mdiLanDisconnect;
}
if (
["bluetooth", "bluetooth_le"].includes(stateObj?.attributes.source_type)
) {
return compareState === "home" ? mdiBluetoothConnect : mdiBluetooth;
}
return compareState === "not_home" ? mdiAccountArrowRight : mdiAccount;
case "event":
switch (stateObj?.attributes.device_class) {
case "doorbell":
return mdiDoorbell;
case "button":
return mdiGestureTapButton;
case "motion":
return mdiMotionSensor;
default:
return mdiEyeCheck;
}
case "fan":
return compareState === "off" ? mdiFanOff : mdiFan;
case "humidifier":
return compareState === "off" ? mdiAirHumidifierOff : mdiAirHumidifier;
case "input_boolean":
return compareState === "on"
? mdiCheckCircleOutline
: mdiCloseCircleOutline;
case "input_datetime":
if (!stateObj?.attributes.has_date) {
return mdiClock;
}
if (!stateObj.attributes.has_time) {
return mdiCalendar;
}
break;
case "lock":
switch (compareState) {
case "unlocked":
return mdiLockOpen;
case "jammed":
return mdiLockAlert;
case "locking":
case "unlocking":
return mdiLockClock;
default:
return mdiLock;
}
case "media_player":
switch (stateObj?.attributes.device_class) {
case "speaker":
switch (compareState) {
case "playing":
return mdiSpeakerPlay;
case "paused":
return mdiSpeakerPause;
case "off":
return mdiSpeakerOff;
default:
return mdiSpeaker;
}
case "tv":
switch (compareState) {
case "playing":
return mdiTelevisionPlay;
case "paused":
return mdiTelevisionPause;
case "off":
return mdiTelevisionOff;
default:
return mdiTelevision;
}
case "receiver":
switch (compareState) {
case "off":
return mdiAudioVideoOff;
default:
return mdiAudioVideo;
}
default:
switch (compareState) {
case "playing":
case "paused":
return mdiCastConnected;
case "off":
return mdiCastOff;
default:
return mdiCast;
}
}
case "number": {
const icon = numberIcon(stateObj);
if (icon) {
return icon;
}
break;
}
case "person":
return compareState === "not_home" ? mdiAccountArrowRight : mdiAccount;
case "switch":
switch (stateObj?.attributes.device_class) {
case "outlet":
return compareState === "on" ? mdiPowerPlug : mdiPowerPlugOff;
case "switch":
return compareState === "on"
? mdiToggleSwitchVariant
: mdiToggleSwitchVariantOff;
default:
return mdiToggleSwitchVariant;
}
case "sensor": {
const icon = sensorIcon(stateObj);
if (icon) {
return icon;
}
break;
}
case "sun":
return stateObj?.state === "above_horizon"
? mdiWhiteBalanceSunny
: mdiWeatherNight;
case "switch_as_x":
return mdiSwapHorizontal;
case "threshold":
return mdiChartSankey;
case "update":
return compareState === "on"
? updateIsInstalling(stateObj as UpdateEntity)
? mdiPackageDown
: mdiPackageUp
: mdiPackage;
case "valve":
switch (stateObj?.attributes.device_class) {
case "water":
return mdiPipeValve;
case "gas":
return mdiMeterGas;
default:
return mdiPipeValve;
}
case "water_heater":
return compareState === "off" ? mdiWaterBoilerOff : mdiWaterBoiler;
case "weather":
return weatherIcon(stateObj?.state);
}
if (domain in FIXED_DOMAIN_ICONS) {
return FIXED_DOMAIN_ICONS[domain];
}
return undefined;
};

View File

@ -211,6 +211,7 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
"volatile_organic_compounds",
"volatile_organic_compounds_parts",
"voltage",
"volume_flow_rate",
],
state_class: ["measurement", "total", "total_increasing"],
},

View File

@ -1,13 +0,0 @@
/** Return an icon representing a number state. */
import { HassEntity } from "home-assistant-js-websocket";
import { FIXED_DEVICE_CLASS_ICONS } from "../const";
export const numberIcon = (stateObj?: HassEntity): string | undefined => {
const dclass = stateObj?.attributes.device_class;
if (dclass && dclass in FIXED_DEVICE_CLASS_ICONS) {
return FIXED_DEVICE_CLASS_ICONS[dclass];
}
return undefined;
};

View File

@ -1,25 +0,0 @@
/** Return an icon representing a sensor state. */
import { mdiBattery, mdiThermometer } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { SENSOR_DEVICE_CLASS_BATTERY } from "../../data/sensor";
import { FIXED_DEVICE_CLASS_ICONS, UNIT_C, UNIT_F } from "../const";
import { batteryStateIcon } from "./battery_icon";
export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
const dclass = stateObj?.attributes.device_class;
if (dclass && dclass in FIXED_DEVICE_CLASS_ICONS) {
return FIXED_DEVICE_CLASS_ICONS[dclass];
}
if (dclass === SENSOR_DEVICE_CLASS_BATTERY) {
return stateObj ? batteryStateIcon(stateObj) : mdiBattery;
}
const unit = stateObj?.attributes.unit_of_measurement;
if (unit === UNIT_C || unit === UNIT_F) {
return mdiThermometer;
}
return undefined;
};

View File

@ -0,0 +1,42 @@
import { HassEntity } from "home-assistant-js-websocket";
import { computeStateDomain } from "./compute_state_domain";
import { updateIcon } from "./update_icon";
import { deviceTrackerIcon } from "./device_tracker_icon";
import { batteryIcon } from "./battery_icon";
export const stateIcon = (
stateObj: HassEntity,
state?: string
): string | undefined => {
const domain = computeStateDomain(stateObj);
const compareState = state ?? stateObj.state;
const dc = stateObj.attributes.device_class;
switch (domain) {
case "update":
return updateIcon(stateObj, compareState);
case "sensor":
if (dc === "battery") {
return batteryIcon(stateObj, compareState);
}
break;
case "device_tracker":
return deviceTrackerIcon(stateObj, compareState);
case "sun":
return compareState === "above_horizon"
? "mdi:white-balance-sunny"
: "mdi:weather-night";
case "input_datetime":
if (!stateObj.attributes.has_date) {
return "mdi:clock";
}
if (!stateObj.attributes.has_time) {
return "mdi:calendar";
}
break;
}
return undefined;
};

View File

@ -1,12 +0,0 @@
/** Return an icon representing a state. */
import { HassEntity } from "home-assistant-js-websocket";
import { DEFAULT_DOMAIN_ICON } from "../const";
import { computeDomain } from "./compute_domain";
import { domainIcon } from "./domain_icon";
export const stateIconPath = (state: HassEntity | undefined) => {
if (!state) {
return DEFAULT_DOMAIN_ICON;
}
return domainIcon(computeDomain(state.entity_id), state);
};

View File

@ -0,0 +1,11 @@
import { HassEntity } from "home-assistant-js-websocket";
import { UpdateEntity, updateIsInstalling } from "../../data/update";
export const updateIcon = (stateObj: HassEntity, state?: string) => {
const compareState = state ?? stateObj.state;
return compareState === "on"
? updateIsInstalling(stateObj as UpdateEntity)
? "mdi:package-down"
: "mdi:package-up"
: "mdi:package";
};

Some files were not shown because too many files have changed in this diff Show More