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": ".." "context": ".."
}, },
"appPort": "8124:8123", "appPort": "8124:8123",
"postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev",
"postStartCommand": "script/bootstrap", "postStartCommand": "script/bootstrap",
"containerEnv": { "containerEnv": {
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,6 @@ jobs:
pull-requests: read pull-requests: read
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: release-drafter/release-drafter@v5 - uses: release-drafter/release-drafter@v6.0.0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -34,7 +34,7 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.1 uses: actions/setup-node@v4.0.2
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@ -55,7 +55,7 @@ jobs:
script/release script/release
- name: Upload release assets - name: Upload release assets
uses: softprops/action-gh-release@v0.1.15 uses: softprops/action-gh-release@v2.0.2
with: with:
files: | files: |
dist/*.whl 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 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", () => gulp.task("webpack-dev-server-demo", () =>
runDevServer({ runDevServer({
compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })), compiler: webpack(
createDemoConfig({ isProdBuild: false, latestBuild: true })
),
contentBase: paths.demo_output_root, contentBase: paths.demo_output_root,
port: 8090, port: 8090,
}) })
@ -131,7 +133,9 @@ gulp.task("webpack-prod-demo", () =>
gulp.task("webpack-dev-server-cast", () => gulp.task("webpack-dev-server-cast", () =>
runDevServer({ runDevServer({
compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })), compiler: webpack(
createCastConfig({ isProdBuild: false, latestBuild: true })
),
contentBase: paths.cast_output_root, contentBase: paths.cast_output_root,
port: 8080, port: 8080,
// Accessible from the network, because that's how Cast hits it. // 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", () => gulp.task("webpack-dev-server-gallery", () =>
runDevServer({ runDevServer({
// We don't use the es5 build, but the dev server will fuck up the publicPath if we don't compiler: webpack(
compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })), createGalleryConfig({ isProdBuild: false, latestBuild: true })
),
contentBase: paths.gallery_output_root, contentBase: paths.gallery_output_root,
port: 8100, port: 8100,
listenHost: "0.0.0.0", listenHost: "0.0.0.0",

View File

@ -31,11 +31,11 @@ import "./hc-layout";
@customElement("hc-cast") @customElement("hc-cast")
class HcCast extends LitElement { 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; @state() private askWrite = false;
@ -241,6 +241,8 @@ class HcCast extends LitElement {
mwc-button ha-svg-icon { mwc-button ha-svg-icon {
margin-right: 8px; margin-right: 8px;
margin-inline-end: 8px;
margin-inline-start: initial;
height: 18px; height: 18px;
} }

View File

@ -10,13 +10,13 @@ import "../../../../src/components/ha-card";
@customElement("hc-layout") @customElement("hc-layout")
class HcLayout extends LitElement { 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 { protected render(): TemplateResult {
return html` return html`

View File

@ -28,23 +28,23 @@ class HcLaunchScreen extends LitElement {
:host { :host {
display: block; display: block;
height: 100vh; height: 100vh;
padding-top: 64px; background-color: #f2f4f9;
background-color: white;
font-size: 24px; font-size: 24px;
} }
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
text-align: center; text-align: center;
align-items: center;
height: 100%;
justify-content: space-evenly;
} }
img { img {
width: 717px; max-width: 80%;
height: 376px; object-fit: cover;
display: block;
margin: 0 auto;
} }
.status { .status {
padding-right: 54px; color: #1d2126;
} }
`; `;
} }

View File

@ -17,7 +17,7 @@ class HcLovelace extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
public lovelaceConfig!: LovelaceConfig; public lovelaceConfig!: LovelaceConfig;
@property() public viewPath?: string | number; @property() public viewPath?: string | number | null;
@property() public urlPath: string | null = null; @property() public urlPath: string | null = null;
@ -93,6 +93,9 @@ class HcLovelace extends LitElement {
} }
private get _viewIndex() { private get _viewIndex() {
if (this.viewPath === null) {
return 0;
}
const selectedView = this.viewPath; const selectedView = this.viewPath;
const selectedViewInt = parseInt(selectedView as string, 10); const selectedViewInt = parseInt(selectedView as string, 10);
for (let i = 0; i < this.lovelaceConfig.views.length; i++) { 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 _lovelacePath: string | number | null = null;
@state() private _error?: string;
@state() private _urlPath?: string | null; @state() private _urlPath?: string | null;
@state() private _error?: string;
private _hassUUID?: string; private _hassUUID?: string;
private _unsubLovelace?: UnsubscribeFunc; private _unsubLovelace?: UnsubscribeFunc;
@ -81,7 +81,7 @@ export class HcMain extends HassElement {
if ( if (
!this._lovelaceConfig || !this._lovelaceConfig ||
this._lovelacePath === null || this._urlPath === undefined ||
// Guard against part of HA not being loaded yet. // Guard against part of HA not being loaded yet.
!this.hass || !this.hass ||
!this.hass.states || !this.hass.states ||
@ -99,8 +99,8 @@ export class HcMain extends HassElement {
<hc-lovelace <hc-lovelace
.hass=${this.hass} .hass=${this.hass}
.lovelaceConfig=${this._lovelaceConfig} .lovelaceConfig=${this._lovelaceConfig}
.viewPath=${this._lovelacePath}
.urlPath=${this._urlPath} .urlPath=${this._urlPath}
.viewPath=${this._lovelacePath}
@config-refresh=${this._generateDefaultLovelaceConfig} @config-refresh=${this._generateDefaultLovelaceConfig}
></hc-lovelace> ></hc-lovelace>
`; `;
@ -205,7 +205,6 @@ export class HcMain extends HassElement {
expires_in: 0, expires_in: 0,
}), }),
}); });
this._hassUUID = msg.hassUUID;
} catch (err: any) { } catch (err: any) {
const errorMessage = this._getErrorMessage(err); const errorMessage = this._getErrorMessage(err);
this._error = errorMessage; this._error = errorMessage;
@ -225,6 +224,17 @@ export class HcMain extends HassElement {
this.hass.connection.close(); this.hass.connection.close();
} }
this.initializeHass(auth, connection); 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._error = undefined;
this._sendStatus(); this._sendStatus();
} }
@ -233,7 +243,7 @@ export class HcMain extends HassElement {
this._showDemo = false; this._showDemo = false;
// We should not get this command before we are connected. // We should not get this command before we are connected.
// Means a client got out of sync. Let's send status to them. // 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._sendStatus(msg.senderId!);
this._error = "Cannot show Lovelace because we're not connected."; this._error = "Cannot show Lovelace because we're not connected.";
this._sendError( this._sendError(
@ -260,7 +270,7 @@ export class HcMain extends HassElement {
} }
this._error = undefined; this._error = undefined;
if (msg.urlPath === "lovelace") { if (msg.urlPath === "lovelace" || msg.urlPath === undefined) {
msg.urlPath = null; msg.urlPath = null;
} }
this._lovelacePath = msg.viewPath; this._lovelacePath = msg.viewPath;
@ -275,7 +285,7 @@ export class HcMain extends HassElement {
], ],
}; };
this._urlPath = "energy"; this._urlPath = "energy";
this._lovelacePath = 0; this._lovelacePath = null;
this._sendStatus(); this._sendStatus();
return; return;
} }
@ -284,6 +294,7 @@ export class HcMain extends HassElement {
this._lovelaceConfig = undefined; this._lovelaceConfig = undefined;
if (this._unsubLovelace) { if (this._unsubLovelace) {
this._unsubLovelace(); this._unsubLovelace();
this._unsubLovelace = undefined;
} }
const llColl = atLeastVersion(this.hass.connection.haVersion, 0, 107) const llColl = atLeastVersion(this.hass.connection.haVersion, 0, 107)
? getLovelaceCollection(this.hass.connection, msg.urlPath) ? getLovelaceCollection(this.hass.connection, msg.urlPath)

View File

@ -4,6 +4,7 @@ import { energyEntities } from "../stubs/entities";
import { DemoConfig } from "./types"; import { DemoConfig } from "./types";
export const demoConfigs: Array<() => Promise<DemoConfig>> = [ export const demoConfigs: Array<() => Promise<DemoConfig>> = [
() => import("./sections").then((mod) => mod.demoSections),
() => import("./arsaboo").then((mod) => mod.demoArsaboo), () => import("./arsaboo").then((mod) => mod.demoArsaboo),
() => import("./teachingbirds").then((mod) => mod.demoTeachingbirds), () => import("./teachingbirds").then((mod) => mod.demoTeachingbirds),
() => import("./kernehed").then((mod) => mod.demoKernehed), () => 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 { LocalizeFunc } from "../../../src/common/translations/localize";
import { LovelaceConfig } from "../../../src/data/lovelace/config/types"; import { LovelaceConfig } from "../../../src/data/lovelace/config/types";
import { Entity } from "../../../src/fake_data/entity"; import { Entity } from "../../../src/fake_data/entity";
@ -7,6 +8,9 @@ export interface DemoConfig {
name: string; name: string;
authorName: string; authorName: string;
authorUrl: string; authorUrl: string;
description?:
| string
| ((localize: LocalizeFunc) => string | TemplateResult<1>);
lovelace: (localize: LocalizeFunc) => LovelaceConfig; lovelace: (localize: LocalizeFunc) => LovelaceConfig;
entities: (localize: LocalizeFunc) => Entity[]; entities: (localize: LocalizeFunc) => Entity[];
theme: () => Record<string, string> | null; theme: () => Record<string, string> | null;

View File

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

View File

@ -17,12 +17,14 @@ import { energyEntities } from "./stubs/entities";
import { mockEntityRegistry } from "./stubs/entity_registry"; import { mockEntityRegistry } from "./stubs/entity_registry";
import { mockEvents } from "./stubs/events"; import { mockEvents } from "./stubs/events";
import { mockFrontend } from "./stubs/frontend"; import { mockFrontend } from "./stubs/frontend";
import { mockIcons } from "./stubs/icons";
import { mockHistory } from "./stubs/history"; import { mockHistory } from "./stubs/history";
import { mockLovelace } from "./stubs/lovelace"; import { mockLovelace } from "./stubs/lovelace";
import { mockMediaPlayer } from "./stubs/media_player"; import { mockMediaPlayer } from "./stubs/media_player";
import { mockPersistentNotification } from "./stubs/persistent_notification"; import { mockPersistentNotification } from "./stubs/persistent_notification";
import { mockRecorder } from "./stubs/recorder"; import { mockRecorder } from "./stubs/recorder";
import { mockTodo } from "./stubs/todo"; import { mockTodo } from "./stubs/todo";
import { mockSensor } from "./stubs/sensor";
import { mockSystemLog } from "./stubs/system_log"; import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template"; import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations"; import { mockTranslations } from "./stubs/translations";
@ -50,11 +52,13 @@ export class HaDemo extends HomeAssistantAppEl {
mockHistory(hass); mockHistory(hass);
mockRecorder(hass); mockRecorder(hass);
mockTodo(hass); mockTodo(hass);
mockSensor(hass);
mockSystemLog(hass); mockSystemLog(hass);
mockTemplate(hass); mockTemplate(hass);
mockEvents(hass); mockEvents(hass);
mockMediaPlayer(hass); mockMediaPlayer(hass);
mockFrontend(hass); mockFrontend(hass);
mockIcons(hass);
mockEnergy(hass); mockEnergy(hass);
mockPersistentNotification(hass); mockPersistentNotification(hass);
mockConfigEntries(hass); mockConfigEntries(hass);

View File

@ -10,6 +10,7 @@ export const mockConfigEntries = (hass: MockHomeAssistant) => {
supports_options: false, supports_options: false,
supports_remove_device: false, supports_remove_device: false,
supports_unload: true, supports_unload: true,
supports_reconfigure: true,
pref_disable_new_entities: false, pref_disable_new_entities: false,
pref_disable_polling: false, pref_disable_polling: false,
disabled_by: null, 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[], ] as TodoItem[],
})); }));
hass.mockWS("todo/item/subscribe", (_msg, _hass) => () => {});
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@
import { css, html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import "../../../../src/components/trace/hat-script-graph";
import "../../../../src/components/trace/hat-trace-timeline"; import "../../../../src/components/trace/hat-trace-timeline";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
@ -56,6 +55,7 @@ export class DemoAutomationTraceTimeline extends LitElement {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
const hass = provideHass(this); const hass = provideHass(this);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
} }
static get styles() { static get styles() {

View File

@ -60,6 +60,7 @@ export class DemoAutomationTrace extends LitElement {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
const hass = provideHass(this); const hass = provideHass(this);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
} }
static get styles() { 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 { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
import "../../../../src/components/ha-form/ha-form"; import "../../../../src/components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../src/components/ha-form/types"; 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 { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
@ -97,22 +98,25 @@ const DEVICES = [
}, },
]; ];
const AREAS = [ const AREAS: AreaRegistryEntry[] = [
{ {
area_id: "backyard", area_id: "backyard",
name: "Backyard", name: "Backyard",
icon: null,
picture: null, picture: null,
aliases: [], aliases: [],
}, },
{ {
area_id: "bedroom", area_id: "bedroom",
name: "Bedroom", name: "Bedroom",
icon: "mdi:bed",
picture: null, picture: null,
aliases: [], aliases: [],
}, },
{ {
area_id: "livingroom", area_id: "livingroom",
name: "Livingroom", name: "Livingroom",
icon: "mdi:sofa",
picture: null, picture: null,
aliases: [], aliases: [],
}, },

View File

@ -9,6 +9,7 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor"; import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
import "../../../../src/components/ha-selector/ha-selector"; import "../../../../src/components/ha-selector/ha-selector";
import "../../../../src/components/ha-settings-row"; import "../../../../src/components/ha-settings-row";
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
import { BlueprintInput } from "../../../../src/data/blueprint"; import { BlueprintInput } from "../../../../src/data/blueprint";
import { showDialog } from "../../../../src/dialogs/make-dialog-manager"; import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
import { getEntity } from "../../../../src/fake_data/entity"; import { getEntity } from "../../../../src/fake_data/entity";
@ -93,22 +94,25 @@ const DEVICES = [
}, },
]; ];
const AREAS = [ const AREAS: AreaRegistryEntry[] = [
{ {
area_id: "backyard", area_id: "backyard",
name: "Backyard", name: "Backyard",
icon: null,
picture: null, picture: null,
aliases: [], aliases: [],
}, },
{ {
area_id: "bedroom", area_id: "bedroom",
name: "Bedroom", name: "Bedroom",
icon: "mdi:bed",
picture: null, picture: null,
aliases: [], aliases: [],
}, },
{ {
area_id: "livingroom", area_id: "livingroom",
name: "Livingroom", name: "Livingroom",
icon: "mdi:sofa",
picture: null, picture: null,
aliases: [], aliases: [],
}, },
@ -271,6 +275,14 @@ const SCHEMAS: {
selector: { color_temp: {} }, selector: { color_temp: {} },
}, },
color_rgb: { name: "Color", selector: { color_rgb: {} } }, 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(); this.requestUpdate();
}; };
return html` return html`
<demo-black-white-row .title=${info.name} .value=${this.data[idx]}> <demo-black-white-row .title=${info.name}>
${["light", "dark"].map((slot) => ${["light", "dark"].map((slot) =>
Object.entries(info.input).map( Object.entries(info.input).map(
([key, value]) => html` ([key, value]) => html`
@ -530,8 +542,8 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
} }
static styles = css` static styles = css`
ha-selector { ha-settings-row {
width: 60; --paper-item-body-two-line-min-height: 0;
} }
.options { .options {
max-width: 800px; max-width: 800px;

View File

@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity"; import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("alarm_control_panel", "alarm", "disarmed", { getEntity("alarm_control_panel", "alarm", "disarmed", {
@ -84,6 +85,7 @@ class DemoAlarmPanelEntity extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); 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 { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("light", "bed_light", "on", { getEntity("light", "bed_light", "on", {
@ -146,6 +147,7 @@ class DemoArea extends LitElement {
entity_id: "binary_sensor.kitchen_door", 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 { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("light", "controller_1", "on", { getEntity("light", "controller_1", "on", {
@ -82,6 +83,7 @@ class DemoConditional extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); 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 { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("light", "bed_light", "on", { getEntity("light", "bed_light", "on", {
@ -323,6 +324,7 @@ class DemoEntities extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); 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 { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("light", "bed_light", "on", { getEntity("light", "bed_light", "on", {
@ -82,6 +83,7 @@ class DemoButtonEntity extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); 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 { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("device_tracker", "demo_paulus", "work", { getEntity("device_tracker", "demo_paulus", "work", {
@ -276,6 +277,7 @@ class DemoEntityFilter extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); 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 { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("sensor", "brightness", "12", {}), getEntity("sensor", "brightness", "12", {}),
@ -128,6 +129,7 @@ class DemoGaugeEntity extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); 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 { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("device_tracker", "demo_paulus", "home", { getEntity("device_tracker", "demo_paulus", "home", {
@ -238,6 +239,7 @@ class DemoGlanceEntity extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); 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 { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("light", "kitchen_lights", "on", { getEntity("light", "kitchen_lights", "on", {
@ -214,6 +215,7 @@ class DemoStack extends LitElement {
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
mockHistory(hass); mockHistory(hass);
mockIcons(hass);
} }
} }

View File

@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity"; import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("light", "bed_light", "on", { getEntity("light", "bed_light", "on", {
@ -76,6 +77,7 @@ class DemoLightEntity extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
mockIcons(hass);
} }
} }

View File

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

View File

@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity"; import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("light", "bed_light", "on", { getEntity("light", "bed_light", "on", {
@ -138,6 +139,7 @@ class DemoPictureElements extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); 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 { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("light", "kitchen_lights", "on", { getEntity("light", "kitchen_lights", "on", {
@ -93,6 +94,7 @@ class DemoPictureEntity extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); 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 { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("switch", "decorative_lights", "on", { getEntity("switch", "decorative_lights", "on", {
@ -134,6 +135,7 @@ class DemoPictureGlance extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); 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 { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { createPlantEntities } from "../../data/plants"; import { createPlantEntities } from "../../data/plants";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const CONFIGS = [ const CONFIGS = [
{ {
@ -43,6 +44,7 @@ export class DemoPlantEntity extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(createPlantEntities()); 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 { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("climate", "ecobee", "auto", { getEntity("climate", "ecobee", "auto", {
@ -45,7 +46,9 @@ const ENTITIES = [
friendly_name: "Sensibo purifier", friendly_name: "Sensibo purifier",
fan_modes: ["low", "high"], fan_modes: ["low", "high"],
fan_mode: "low", fan_mode: "low",
supported_features: 9, swing_modes: ["on", "off", "both", "vertical", "horizontal"],
swing_mode: "vertical",
supported_features: 41,
}), }),
getEntity("climate", "unavailable", "unavailable", { getEntity("climate", "unavailable", "unavailable", {
supported_features: 43, supported_features: 43,
@ -84,6 +87,14 @@ const CONFIGS = [
fan_modes: fan_modes:
- low - low
- high - 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(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); 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 { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("switch", "tv_outlet", "on", { getEntity("switch", "tv_outlet", "on", {
@ -79,6 +80,18 @@ const CONFIGS = [
color: pink 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", heading: "Unknown entity",
config: ` config: `
@ -172,6 +185,7 @@ class DemoTile extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
mockIcons(hass);
} }
} }

View File

@ -4,6 +4,7 @@ import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { getEntity } from "../../../../src/fake_data/entity"; import { getEntity } from "../../../../src/fake_data/entity";
import { mockTodo } from "../../../../demo/src/stubs/todo"; import { mockTodo } from "../../../../demo/src/stubs/todo";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [ const ENTITIES = [
getEntity("todo", "shopping_list", "2", { getEntity("todo", "shopping_list", "2", {
@ -47,6 +48,7 @@ class DemoTodoListEntity extends LitElement {
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
mockIcons(hass);
mockTodo(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 type { DataTableColumnContainer } from "../../../../src/components/data-table/ha-data-table";
import "../../../../src/components/entity/state-badge"; import "../../../../src/components/entity/state-badge";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import { mockIcons } from "../../../../demo/src/stubs/icons";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
const SENSOR_DEVICE_CLASSES = [ const SENSOR_DEVICE_CLASSES = [
@ -53,6 +54,7 @@ const SENSOR_DEVICE_CLASSES = [
"volatile_organic_compounds_parts", "volatile_organic_compounds_parts",
"voltage", "voltage",
"volume", "volume",
"volume_flow_rate",
"water", "water",
"weight", "weight",
"wind_speed", "wind_speed",
@ -290,6 +292,7 @@ const ENTITIES: HassEntity[] = [
createEntity("water_heater.high_demand", "high_demand"), createEntity("water_heater.high_demand", "high_demand"),
createEntity("water_heater.heat_pump", "heat_pump"), createEntity("water_heater.heat_pump", "heat_pump"),
createEntity("water_heater.gas", "gas"), createEntity("water_heater.gas", "gas"),
createEntity("select.speed", "ridiculous_speed"),
]; ];
function createEntity( function createEntity(
@ -396,6 +399,16 @@ export class DemoEntityState extends LitElement {
protected firstUpdated(changedProps) { protected firstUpdated(changedProps) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
const hass = provideHass(this); 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(null, "en");
hass.updateTranslations("config", "en"); hass.updateTranslations("config", "en");
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,9 +18,9 @@ declare global {
@customElement("ha-pick-auth-provider") @customElement("ha-pick-auth-provider")
export class HaPickAuthProvider extends LitElement { 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() { protected render() {
return html` return html`

View File

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

View File

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

View File

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

View File

@ -1,8 +1,13 @@
import { MAIN_WINDOW_NAME } from "../../data/main_window"; import { MAIN_WINDOW_NAME } from "../../data/main_window";
export const mainWindow = export const mainWindow = (() => {
window.name === MAIN_WINDOW_NAME try {
return window.name === MAIN_WINDOW_NAME
? window ? window
: parent.name === MAIN_WINDOW_NAME : parent.name === MAIN_WINDOW_NAME
? parent ? parent
: top!; : 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"; import { HassEntity } from "home-assistant-js-websocket";
const BATTERY_ICONS = { const BATTERY_ICONS = {
10: mdiBattery10, 10: "mdi:battery-10",
20: mdiBattery20, 20: "mdi:battery-20",
30: mdiBattery30, 30: "mdi:battery-30",
40: mdiBattery40, 40: "mdi:battery-40",
50: mdiBattery50, 50: "mdi:battery-50",
60: mdiBattery60, 60: "mdi:battery-60",
70: mdiBattery70, 70: "mdi:battery-70",
80: mdiBattery80, 80: "mdi:battery-80",
90: mdiBattery90, 90: "mdi:battery-90",
100: mdiBattery, 100: "mdi:battery",
}; };
const BATTERY_CHARGING_ICONS = { const BATTERY_CHARGING_ICONS = {
10: mdiBatteryCharging10, 10: "mdi:battery-charging-10",
20: mdiBatteryCharging20, 20: "mdi:battery-charging-20",
30: mdiBatteryCharging30, 30: "mdi:battery-charging-30",
40: mdiBatteryCharging40, 40: "mdi:battery-charging-40",
50: mdiBatteryCharging50, 50: "mdi:battery-charging-50",
60: mdiBatteryCharging60, 60: "mdi:battery-charging-60",
70: mdiBatteryCharging70, 70: "mdi:battery-charging-70",
80: mdiBatteryCharging80, 80: "mdi:battery-charging-80",
90: mdiBatteryCharging90, 90: "mdi:battery-charging-90",
100: mdiBatteryCharging, 100: "mdi:battery-charging",
}; };
export const batteryStateIcon = ( export const batteryIcon = (stateObj: HassEntity, state?: string) => {
batteryState: HassEntity, const level = state ?? stateObj.state;
batteryChargingState?: HassEntity return batteryLevelIcon(level);
) => {
const battery = batteryState.state;
const batteryCharging =
batteryChargingState && batteryChargingState.state === "on";
return batteryIcon(battery, batteryCharging);
}; };
export const batteryIcon = ( export const batteryLevelIcon = (
batteryState: number | string, batteryLevel: number | string,
batteryCharging?: boolean isBatteryCharging?: boolean
) => { ): string => {
const batteryValue = Number(batteryState); const batteryValue = Number(batteryLevel);
if (isNaN(batteryValue)) { if (isNaN(batteryValue)) {
if (batteryState === "off") { if (batteryLevel === "off") {
return mdiBattery; return "mdi:battery";
} }
if (batteryState === "on") { if (batteryLevel === "on") {
return mdiBatteryAlert; return "mdi:battery-alert";
} }
return mdiBatteryUnknown; return "mdi:battery-unknown";
} }
const batteryRound = Math.round(batteryValue / 10) * 10; const batteryRound = Math.round(batteryValue / 10) * 10;
if (batteryCharging && batteryValue >= 10) { if (isBatteryCharging && batteryValue >= 10) {
return BATTERY_CHARGING_ICONS[batteryRound]; return BATTERY_CHARGING_ICONS[batteryRound];
} }
if (batteryCharging) { if (isBatteryCharging) {
return mdiBatteryChargingOutline; return "mdi:battery-charging-outline";
} }
if (batteryValue <= 5) { if (batteryValue <= 5) {
return mdiBatteryAlertVariantOutline; return "mdi:battery-alert-variant-outline";
} }
return BATTERY_ICONS[batteryRound]; 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") { if (domain === "weather") {
unit = getWeatherUnit(config, stateObj as WeatherEntity, attribute); unit = getWeatherUnit(config, stateObj as WeatherEntity, attribute);
} } else if (TEMPERATURE_ATTRIBUTES.has(attribute)) {
if (TEMPERATURE_ATTRIBUTES.has(attribute)) {
unit = config.unit_system.temperature; unit = config.unit_system.temperature;
} }

View File

@ -1,132 +1,12 @@
/** Return an icon representing a cover state. */ /** Return an icon representing a cover state. */
import { import {
mdiArrowUpBox,
mdiArrowDownBox,
mdiGarage,
mdiGarageOpen,
mdiGateArrowRight,
mdiGate,
mdiGateOpen,
mdiDoorOpen,
mdiDoorClosed,
mdiCircle,
mdiWindowShutter,
mdiWindowShutterOpen,
mdiBlindsHorizontal,
mdiBlindsHorizontalClosed,
mdiRollerShade,
mdiRollerShadeClosed,
mdiWindowClosed,
mdiWindowOpen,
mdiArrowExpandHorizontal,
mdiArrowUp,
mdiArrowCollapseHorizontal, mdiArrowCollapseHorizontal,
mdiArrowDown, mdiArrowDown,
mdiCircleSlice8, mdiArrowExpandHorizontal,
mdiArrowSplitVertical, mdiArrowUp,
mdiCurtains,
mdiCurtainsClosed,
} from "@mdi/js"; } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket"; 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 => { export const computeOpenIcon = (stateObj: HassEntity): string => {
switch (stateObj.attributes.device_class) { switch (stateObj.attributes.device_class) {
case "awning": 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",
"volatile_organic_compounds_parts", "volatile_organic_compounds_parts",
"voltage", "voltage",
"volume_flow_rate",
], ],
state_class: ["measurement", "total", "total_increasing"], 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