Compare commits

..

40 Commits

Author SHA1 Message Date
Ludeeus
d1605ba196 Add move data disk 2021-09-16 15:15:57 +00:00
Erik Montnemery
2240d019f5 Specify period when fetching statistics (#10040)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-09-16 11:15:23 +02:00
Philip Allgaier
cb11c6b3ea Check for null action nodes before rendering (#10017) 2021-09-14 14:20:09 -07:00
Jaroslav Hanslík
5893559951 New icon for "on" state of smoke device (#10013) 2021-09-13 12:50:57 +02:00
Paulus Schoutsen
8408d25cef Clean up ha state label badge (#10020) 2021-09-12 10:00:37 +02:00
Paulus Schoutsen
1ac2ffcf02 Bumped version to 20210911.0 2021-09-11 11:57:48 -07:00
Jaroslav Hanslík
6b6c38c2c8 Better icon for alarm panel state "armed night" (#10012) 2021-09-11 11:53:38 -07:00
Joakim Sørensen
e55df73a91 Remove usage of discovery info (#10015) 2021-09-11 11:38:35 -07:00
Michael Irigoyen
360c2cbfa3 Update Material Design Icons to v6.1.95 (#10002)
* Update MDI to v6, add icon deprecation mapping

* Add removed icon path data to build tools

* Resolve incorrect MDI icon import name
2021-09-10 16:49:32 +02:00
Timothy Kist
aba96674f3 Use unicode ellipsis instead of 3 dots "..." (#9997) 2021-09-09 15:43:07 +00:00
Philip Allgaier
5c3d85fc90 Consistent lower-case spelling of "optional" and "required" (#9990) 2021-09-09 15:42:42 +02:00
Philip Allgaier
6486b7fd4c Adjust dev-tools wording: "Active listeners" (#9991) 2021-09-09 15:42:14 +02:00
Paulus Schoutsen
5f3e980de0 Add gas unit error (#9981) 2021-09-07 14:49:08 -07:00
Philip Allgaier
d0edbec5fb Copy resize observer to "non-input" number entity row (#9973) 2021-09-07 17:25:08 +02:00
Ville Skyttä
5d46963e8a Add date device class (#9983) 2021-09-07 17:24:10 +02:00
Philip Allgaier
321f441b63 Handle unavailable vacuums in more-info (#9974) 2021-09-07 10:14:05 +02:00
Paulus Schoutsen
d55bade070 Small tweaks for the create automation from blueprint screen (#9980) 2021-09-07 09:40:25 +02:00
Ville Skyttä
6ba6b821f5 Use SENSOR_DEVICE_CLASS_* constants more (#9982) 2021-09-07 09:21:51 +02:00
Ruben Andrist
b3dedae115 Add ResizeObserver to EntityRow (#9837)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-09-06 18:34:10 +02:00
Philip Allgaier
5a1070c30f Prevent darkMode overwrite by frontend_default_dark_theme (#9519) 2021-09-06 13:13:01 +02:00
Bram Kragten
40664997e1 Use polyfill from toggleAttribute (#9969) 2021-09-06 11:48:08 +02:00
Philip Allgaier
c6e83cb7c0 Add state_color support to entity card (#9617) 2021-09-06 11:26:16 +02:00
Philip Allgaier
e7e27e794c Add refresh button to history panel (#9958) 2021-09-06 09:19:58 +00:00
Sven Naumann
1073dbe6ab number slider: change column width check from 350px to 300px (#8310) 2021-09-06 11:01:28 +02:00
Philip Allgaier
2bd9b5a015 Prevent index access errors in entity quick bar (#9964)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-09-06 09:01:18 +00:00
Philip Allgaier
bc09febd2c Use integration manifest for service documentation URL (#9960) 2021-09-06 08:58:05 +00:00
Philip Allgaier
b2a87c90a2 Proper editor enum validation of timestamp formats (#9965) 2021-09-06 08:42:42 +00:00
Philip Allgaier
d6dbbcb0de Use dynamic struct validation for entities card rows (#9962) 2021-09-06 10:27:29 +02:00
Philip Allgaier
9ccb5360b3 Add icon to attribute row validation (#9963) 2021-09-06 10:26:11 +02:00
Philip Allgaier
0187c4faff Ensure calender follows time format locale (#9966) 2021-09-06 10:22:46 +02:00
Philip Allgaier
605172a0bc Align internal and fecha date/time formatting (#9380) 2021-09-06 10:22:01 +02:00
Bram Kragten
8565a0d911 Remove float value in float form when emptied (#9947) 2021-09-03 20:07:22 +02:00
Bram Kragten
61c8d23a7e Bunch of minor non breaking dep bumps (#9945) 2021-09-03 10:55:10 -07:00
Joakim Sørensen
5e3487ed59 Don't build wheels for alpine 3.13 (#9944) 2021-09-03 11:56:30 +02:00
Joakim Sørensen
d5a161769c Fix removeBackup function after 2021.9 (#9938) 2021-09-02 22:13:11 +02:00
Bram Kragten
1692f9c2dd Change message to info alert (#9930) 2021-09-02 00:40:51 +02:00
Bram Kragten
0cbac8bb44 Polyfill Array.flat (#9917) 2021-09-01 16:23:41 +02:00
Bram Kragten
35a81e7f11 Correct badge warning (and use new styling) (#9926) 2021-09-01 16:23:29 +02:00
Paulus Schoutsen
ac64d293e7 Sort tags in trigger (#9921) 2021-08-31 21:30:35 -07:00
Bram Kragten
708b8787c5 Bump round slider (#9301) 2021-08-31 08:59:32 +02:00
91 changed files with 1746 additions and 1004 deletions

View File

@@ -73,7 +73,6 @@ jobs:
matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
tag:
- "3.9-alpine3.13"
- "3.9-alpine3.14"
steps:
- name: Download requirements.txt

File diff suppressed because one or more lines are too long

View File

@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate";
import { compare } from "../../../src/common/string/compare";
import { stringCompare } from "../../../src/common/string/compare";
import "../../../src/components/ha-card";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../src/resources/styles";
@@ -33,7 +33,7 @@ class HassioAddons extends LitElement {
</ha-card>
`
: this.supervisor.supervisor.addons
.sort((a, b) => compare(a.name, b.name))
.sort((a, b) => stringCompare(a.name, b.name))
.map(
(addon) => html`
<ha-card .addon=${addon} @click=${this._addonTapped}>

View File

@@ -4,7 +4,7 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/common/search/search-input";
import { compare } from "../../../../src/common/string/compare";
import { stringCompare } from "../../../../src/common/string/compare";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
@@ -27,7 +27,7 @@ const _filterDevices = memoizeOne(
.toLocaleLowerCase()
.includes(filter))
)
.sort((a, b) => compare(a.name, b.name))
.sort((a, b) => stringCompare(a.name, b.name))
);
@customElement("dialog-hassio-hardware")

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
@@ -19,6 +18,7 @@ import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
import {
changeHostOptions,
configSyncOS,
dataDiskMove,
rebootHost,
shutdownHost,
updateOS,
@@ -180,21 +180,27 @@ class HassioHostInfo extends LitElement {
`
: ""}
<ha-button-menu
corner="BOTTOM_START"
@action=${this._handleMenuAction}
>
<ha-button-menu corner="BOTTOM_START">
<mwc-icon-button slot="trigger">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>
<mwc-list-item @click=${() => this._handleMenuAction("hardware")}>
${this.supervisor.localize("system.host.hardware")}
</mwc-list-item>
${this.supervisor.host.features.includes("haos")
? html`<mwc-list-item>
? html`<mwc-list-item
@click=${() => this._handleMenuAction("import_from_usb")}
>
${this.supervisor.localize("system.host.import_from_usb")}
</mwc-list-item>`
: ""}
${this.supervisor.host.features.includes("agent")
? html`<mwc-list-item
@click=${() => this._handleMenuAction("data_disk_move")}
>
${this.supervisor.localize("system.host.data_disk_move")}
</mwc-list-item>`
: ""}
</ha-button-menu>
</div>
</ha-card>
@@ -216,14 +222,17 @@ class HassioHostInfo extends LitElement {
return network_info.interfaces.find((a) => a.primary)?.ipv4?.address![0];
});
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
private async _handleMenuAction(action: string) {
switch (action) {
case "hardware":
await this._showHardware();
break;
case 1:
case "import_from_usb":
await this._importFromUSB();
break;
case "data_disk_move":
await this._dataDiskMove();
break;
}
}
@@ -395,6 +404,34 @@ class HassioHostInfo extends LitElement {
}
}
private async _dataDiskMove(): Promise<void> {
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize("system.host.data_disk_move"),
text: html`${this.supervisor.localize(
"dialog.data_disk_move.description",
{ current_path: this.supervisor.os.data_disk }
)} <br /><br />${this.supervisor.localize(
"dialog.data_disk_move.confirm_text"
)}`,
confirmText: this.supervisor.localize("dialog.data_disk_move.move"),
dismissText: this.supervisor.localize("dialog.data_disk_move.cancel"),
});
if (!confirmed) {
return;
}
try {
await dataDiskMove(this.hass);
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
title: this.supervisor.localize("system.host.failed_to_move"),
text: extractApiErrorMessage(err),
});
}
}
}
private async _loadData(): Promise<void> {
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
fireEvent(this, "supervisor-collection-refresh", {

View File

@@ -22,26 +22,26 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"dependencies": {
"@braintree/sanitize-url": "^5.0.1",
"@codemirror/commands": "^0.18.0",
"@codemirror/gutter": "^0.18.0",
"@codemirror/highlight": "^0.18.0",
"@codemirror/history": "^0.18.0",
"@codemirror/legacy-modes": "^0.18.0",
"@codemirror/rectangular-selection": "^0.18.0",
"@codemirror/search": "^0.18.0",
"@codemirror/state": "^0.18.0",
"@codemirror/stream-parser": "^0.18.0",
"@codemirror/text": "^0.18.0",
"@codemirror/view": "^0.18.0",
"@formatjs/intl-getcanonicallocales": "^1.5.10",
"@formatjs/intl-locale": "^2.4.28",
"@formatjs/intl-pluralrules": "^4.0.22",
"@fullcalendar/common": "5.1.0",
"@fullcalendar/core": "5.1.0",
"@fullcalendar/daygrid": "5.1.0",
"@fullcalendar/interaction": "5.1.0",
"@fullcalendar/list": "5.1.0",
"@braintree/sanitize-url": "^5.0.2",
"@codemirror/commands": "^0.19.2",
"@codemirror/gutter": "^0.19.1",
"@codemirror/highlight": "^0.19.2",
"@codemirror/history": "^0.19.0",
"@codemirror/legacy-modes": "^0.19.0",
"@codemirror/rectangular-selection": "^0.19.0",
"@codemirror/search": "^0.19.0",
"@codemirror/state": "^0.19.1",
"@codemirror/stream-parser": "^0.19.1",
"@codemirror/text": "^0.19.2",
"@codemirror/view": "^0.19.4",
"@formatjs/intl-getcanonicallocales": "^1.7.3",
"@formatjs/intl-locale": "^2.4.37",
"@formatjs/intl-pluralrules": "^4.1.3",
"@fullcalendar/common": "5.9.0",
"@fullcalendar/core": "5.9.0",
"@fullcalendar/daygrid": "5.9.0",
"@fullcalendar/interaction": "5.9.0",
"@fullcalendar/list": "5.9.0",
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
"@material/chips": "12.0.0-canary.22d29cbb4.0",
"@material/data-table": "12.0.0-canary.22d29cbb4.0",
@@ -61,8 +61,8 @@
"@material/mwc-tab": "0.22.1",
"@material/mwc-tab-bar": "0.22.1",
"@material/top-app-bar": "12.0.0-canary.22d29cbb4.0",
"@mdi/js": "5.9.55",
"@mdi/svg": "5.9.55",
"@mdi/js": "6.1.95",
"@mdi/svg": "6.1.95",
"@polymer/app-layout": "^3.1.0",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",
@@ -88,9 +88,9 @@
"@polymer/paper-toast": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.4.1",
"@thomasloven/round-slider": "0.5.2",
"@vaadin/vaadin-combo-box": "^20.0.1",
"@vaadin/vaadin-date-picker": "^20.0.1",
"@thomasloven/round-slider": "0.5.4",
"@vaadin/vaadin-combo-box": "^20.0.4",
"@vaadin/vaadin-date-picker": "^20.0.4",
"@vibrant/color": "^3.2.1-alpha.1",
"@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
@@ -99,36 +99,35 @@
"chart.js": "^3.3.2",
"comlink": "^4.3.1",
"core-js": "^3.15.2",
"cropperjs": "^1.5.11",
"date-fns": "^2.22.1",
"cropperjs": "^1.5.12",
"date-fns": "^2.23.0",
"deep-clone-simple": "^1.1.1",
"deep-freeze": "^0.0.1",
"fecha": "^4.2.0",
"fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2",
"hls.js": "^1.0.7",
"hls.js": "^1.0.10",
"home-assistant-js-websocket": "^5.11.1",
"idb-keyval": "^5.0.5",
"intl-messageformat": "^9.6.16",
"idb-keyval": "^5.1.3",
"intl-messageformat": "^9.9.1",
"js-yaml": "^4.1.0",
"leaflet": "^1.7.1",
"leaflet-draw": "^1.0.4",
"lit": "^2.0.0-rc.3",
"lit-vaadin-helpers": "^0.1.3",
"marked": "^2.0.5",
"mdn-polyfills": "^5.16.0",
"marked": "^3.0.2",
"memoize-one": "^5.2.1",
"node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "^0.3.1",
"proxy-polyfill": "^0.3.2",
"punycode": "^2.1.1",
"qrcode": "^1.4.4",
"regenerator-runtime": "^0.13.8",
"resize-observer-polyfill": "^1.5.1",
"roboto-fontface": "^0.10.0",
"sortablejs": "^1.10.2",
"sortablejs": "^1.14.0",
"superstruct": "^0.15.2",
"tinykeys": "^1.1.3",
"tsparticles": "^1.19.2",
"tsparticles": "^1.34.0",
"unfetch": "^4.1.0",
"vis-data": "^7.1.2",
"vis-network": "^8.5.4",
@@ -164,12 +163,12 @@
"@rollup/plugin-replace": "^2.3.2",
"@types/chromecast-caf-receiver": "5.0.12",
"@types/chromecast-caf-sender": "^1.0.3",
"@types/js-yaml": "^4.0.1",
"@types/leaflet": "^1.7.0",
"@types/leaflet-draw": "^1.0.3",
"@types/marked": "^2.0.3",
"@types/mocha": "^8.2.2",
"@types/sortablejs": "^1.10.6",
"@types/js-yaml": "^4",
"@types/leaflet": "^1",
"@types/leaflet-draw": "^1",
"@types/marked": "^2",
"@types/mocha": "^8",
"@types/sortablejs": "^1",
"@types/webspeechapi": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^4.28.3",
"@typescript-eslint/parser": "^4.28.3",

View File

@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20210830.0",
version="20210911.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/frontend",
author="The Home Assistant Authors",

View File

@@ -8,10 +8,6 @@ import {
AuthUrlSearchParams,
fetchAuthProviders,
} from "../data/auth";
import {
DiscoveryInformation,
fetchDiscoveryInformation,
} from "../data/discovery";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
import { registerServiceWorker } from "../util/register-service-worker";
import "./ha-auth-flow";
@@ -29,8 +25,6 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
@state() private _authProviders?: AuthProvider[];
@state() private _discovery?: DiscoveryInformation;
constructor() {
super();
this.translationFragment = "page-authorize";
@@ -58,17 +52,14 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
// the name with a bold tag.
const loggingInWith = document.createElement("div");
loggingInWith.innerText = this.localize(
this._discovery?.location_name
? "ui.panel.page-authorize.logging_in_to_with"
: "ui.panel.page-authorize.logging_in_with",
"locationName",
"LOCATION",
"ui.panel.page-authorize.logging_in_with",
"authProviderName",
"NAME"
);
loggingInWith.innerHTML = loggingInWith.innerHTML
.replace("**LOCATION**", `<b>${this._discovery?.location_name}</b>`)
.replace("**NAME**", `<b>${this._authProvider!.name}</b>`);
loggingInWith.innerHTML = loggingInWith.innerHTML.replace(
"**NAME**",
`<b>${this._authProvider!.name}</b>`
);
const inactiveProviders = this._authProviders.filter(
(prv) => prv !== this._authProvider
@@ -108,7 +99,6 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._fetchAuthProviders();
this._fetchDiscoveryInfo();
if (matchMedia("(prefers-color-scheme: dark)").matches) {
applyThemesOnElement(
@@ -144,10 +134,6 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
}
}
private async _fetchDiscoveryInfo() {
this._discovery = await fetchDiscoveryInformation();
}
private async _fetchAuthProviders() {
// Fetch auth providers
try {

View File

@@ -60,6 +60,7 @@ export const FIXED_DEVICE_CLASS_ICONS = {
current: "hass:current-ac",
carbon_dioxide: "mdi:molecule-co2",
carbon_monoxide: "mdi:molecule-co",
date: "hass:calendar",
energy: "hass:lightning-bolt",
gas: "hass:gas-cylinder",
humidity: "hass:water-percent",

View File

@@ -16,7 +16,7 @@ const formatDateTimeMem = memoizeOne(
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hour12: useAmPm(locale),
})
@@ -34,7 +34,7 @@ const formatDateTimeWithSecondsMem = memoizeOne(
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: useAmPm(locale),

View File

@@ -13,7 +13,7 @@ export const formatTime = toLocaleTimeStringSupportsOptions
const formatTimeMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
hour: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hour12: useAmPm(locale),
})
@@ -28,7 +28,7 @@ export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
const formatTimeWithSecondsMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
hour: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: useAmPm(locale),
@@ -45,7 +45,7 @@ const formatTimeWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "long",
hour: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hour12: useAmPm(locale),
})

View File

@@ -22,8 +22,9 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
case "gas":
case "problem":
case "safety":
case "smoke":
return is_off ? "hass:check-circle" : "hass:alert-circle";
case "smoke":
return is_off ? "hass:check-circle" : "hass:smoke";
case "heat":
return is_off ? "hass:thermometer" : "hass:fire";
case "light":

View File

@@ -2,6 +2,7 @@
import { HassEntity } from "home-assistant-js-websocket";
import { FIXED_DEVICE_CLASS_ICONS, UNIT_C, UNIT_F } from "../const";
import { batteryIcon } from "./battery_icon";
import { SENSOR_DEVICE_CLASS_BATTERY } from "../../data/sensor";
export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
const dclass = stateObj?.attributes.device_class;
@@ -10,7 +11,7 @@ export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
return FIXED_DEVICE_CLASS_ICONS[dclass];
}
if (dclass === "battery") {
if (dclass === SENSOR_DEVICE_CLASS_BATTERY) {
return stateObj ? batteryIcon(stateObj) : "hass:battery";
}

View File

@@ -1,4 +1,4 @@
export const compare = (a: string, b: string) => {
export const stringCompare = (a: string, b: string) => {
if (a < b) {
return -1;
}
@@ -9,5 +9,5 @@ export const compare = (a: string, b: string) => {
return 0;
};
export const caseInsensitiveCompare = (a: string, b: string) =>
compare(a.toLowerCase(), b.toLowerCase());
export const caseInsensitiveStringCompare = (a: string, b: string) =>
stringCompare(a.toLowerCase(), b.toLowerCase());

View File

@@ -1,6 +1,6 @@
import { refine, string } from "superstruct";
const isCustomType = (value: string) => value.startsWith("custom:");
export const isCustomType = (value: string) => value.startsWith("custom:");
export const customType = () =>
refine(string(), "custom element type", isCustomType);

View File

@@ -20,7 +20,7 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain";
import { compare } from "../../common/string/compare";
import { stringCompare } from "../../common/string/compare";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
@@ -226,7 +226,10 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
const sorted = Object.keys(devicesByArea)
.sort((a, b) =>
compare(devicesByArea[a].name || "", devicesByArea[b].name || "")
stringCompare(
devicesByArea[a].name || "",
devicesByArea[b].name || ""
)
)
.map((key) => devicesByArea[key]);

View File

@@ -15,7 +15,7 @@ import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { mdiCheck } from "@mdi/js";
import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain";
import { compare } from "../../common/string/compare";
import { stringCompare } from "../../common/string/compare";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
@@ -242,7 +242,9 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
if (outputDevices.length === 1) {
return outputDevices;
}
return outputDevices.sort((a, b) => compare(a.name || "", b.name || ""));
return outputDevices.sort((a, b) =>
stringCompare(a.name || "", b.name || "")
);
}
);

View File

@@ -106,19 +106,24 @@ export class HaStateLabelBadge extends LitElement {
private _computeValue(domain: string, entityState: HassEntity) {
switch (domain) {
case "alarm_control_panel":
case "binary_sensor":
case "device_tracker":
case "person":
case "updater":
case "scene":
case "sun":
case "alarm_control_panel":
case "timer":
case "updater":
return null;
// @ts-expect-error we don't break and go to default
case "sensor":
if (entityState.attributes.device_class === "moon__phase") {
return null;
}
// eslint-disable-next-line: disable=no-fallthrough
default:
return entityState.attributes.device_class === "moon__phase"
? null
: entityState.state === UNKNOWN
return entityState.state === UNKNOWN ||
entityState.state === UNAVAILABLE
? "-"
: entityState.attributes.unit_of_measurement
? formatNumber(entityState.state, this.hass!.locale)
@@ -160,16 +165,19 @@ export class HaStateLabelBadge extends LitElement {
case "device_tracker":
case "updater":
case "person":
case "scene":
case "sun":
return stateIcon(entityState);
case "timer":
return entityState.state === "active"
? "hass:timer-outline"
: "hass:timer-off-outline";
default:
return entityState?.attributes.device_class === "moon__phase"
case "sensor":
return entityState.attributes.device_class === "moon__phase"
? stateIcon(entityState)
: null;
default:
return null;
}
}

View File

@@ -18,7 +18,7 @@ import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import { computeStateName } from "../../common/entity/compute_state_name";
import { compare } from "../../common/string/compare";
import { stringCompare } from "../../common/string/compare";
import { getStatisticIds, StatisticsMetaData } from "../../data/history";
import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
@@ -165,7 +165,7 @@ export class HaStatisticPicker extends LitElement {
}
if (output.length > 1) {
output.sort((a, b) => compare(a.name || "", b.name || ""));
output.sort((a, b) => stringCompare(a.name || "", b.name || ""));
}
output.push({

View File

@@ -129,38 +129,39 @@ export class StateBadge extends LitElement {
}
static get styles(): CSSResultGroup {
return css`
:host {
position: relative;
display: inline-block;
width: 40px;
color: var(--paper-item-icon-color, #44739e);
border-radius: 50%;
height: 40px;
text-align: center;
background-size: cover;
line-height: 40px;
vertical-align: middle;
box-sizing: border-box;
}
:host(:focus) {
outline: none;
}
:host(:not([icon]):focus) {
border: 2px solid var(--divider-color);
}
:host([icon]:focus) {
background: var(--divider-color);
}
ha-icon {
transition: color 0.3s ease-in-out, filter 0.3s ease-in-out;
}
.missing {
color: #fce588;
}
${iconColorCSS}
`;
return [
iconColorCSS,
css`
:host {
position: relative;
display: inline-block;
width: 40px;
color: var(--paper-item-icon-color, #44739e);
border-radius: 50%;
height: 40px;
text-align: center;
background-size: cover;
line-height: 40px;
vertical-align: middle;
box-sizing: border-box;
}
:host(:focus) {
outline: none;
}
:host(:not([icon]):focus) {
border: 2px solid var(--divider-color);
}
:host([icon]:focus) {
background: var(--divider-color);
}
ha-icon {
transition: color 0.3s ease-in-out, filter 0.3s ease-in-out;
}
.missing {
color: #fce588;
}
`,
];
}
}

View File

@@ -4,7 +4,7 @@ import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { customElement, property, query, state } from "lit/decorators";
import { isComponentLoaded } from "../common/config/is_component_loaded";
import { fireEvent } from "../common/dom/fire_event";
import { compare } from "../common/string/compare";
import { stringCompare } from "../common/string/compare";
import { HassioAddonInfo } from "../data/hassio/addon";
import { fetchHassioSupervisorInfo } from "../data/hassio/supervisor";
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
@@ -97,7 +97,7 @@ class HaAddonPicker extends LitElement {
if (isComponentLoaded(this.hass, "hassio")) {
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
this._addons = supervisorInfo.addons.sort((a, b) =>
compare(a.name, b.name)
stringCompare(a.name, b.name)
);
} else {
showAlertDialog(this, {

View File

@@ -5,6 +5,7 @@ import {
mdiAlertOutline,
mdiCheckboxMarkedCircleOutline,
mdiClose,
mdiInformationOutline,
} from "@mdi/js";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
@@ -13,7 +14,7 @@ import { fireEvent } from "../common/dom/fire_event";
import "./ha-svg-icon";
const ALERT_ICONS = {
info: mdiAlertCircleOutline,
info: mdiInformationOutline,
warning: mdiAlertOutline,
error: mdiAlertCircleOutline,
success: mdiCheckboxMarkedCircleOutline,

View File

@@ -5,7 +5,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import { compare } from "../common/string/compare";
import { stringCompare } from "../common/string/compare";
import { Blueprint, Blueprints, fetchBlueprints } from "../data/blueprint";
import { HomeAssistant } from "../types";
@@ -33,7 +33,7 @@ class HaBluePrintPicker extends LitElement {
...(blueprint as Blueprint).metadata,
path,
}));
return result.sort((a, b) => compare(a.name, b.name));
return result.sort((a, b) => stringCompare(a.name, b.name));
});
protected render(): TemplateResult {

View File

@@ -32,17 +32,19 @@ export class HaFormFloat extends LitElement implements HaFormElement {
.autoValidate=${this.schema.required}
@value-changed=${this._valueChanged}
>
<span suffix="" slot="suffix">${this.suffix}</span>
<span suffix slot="suffix">${this.suffix}</span>
</paper-input>
`;
}
private get _value() {
return this.data || 0;
return this.data;
}
private _valueChanged(ev: Event) {
const value = Number((ev.target as PaperInputElement).value);
const value: number | undefined = (ev.target as PaperInputElement).value
? Number((ev.target as PaperInputElement).value)
: undefined;
if (this._value === value) {
return;
}

View File

@@ -29,7 +29,335 @@ interface DeprecatedIcon {
};
}
const mdiDeprecatedIcons: DeprecatedIcon = {};
const mdiDeprecatedIcons: DeprecatedIcon = {
"adobe-acrobat": {
removeIn: "2021.12",
},
adobe: {
removeIn: "2021.12",
},
"amazon-alexa": {
removeIn: "2021.12",
},
amazon: {
removeIn: "2021.12",
},
"android-auto": {
removeIn: "2021.12",
},
"android-debug-bridge": {
removeIn: "2021.12",
},
"apple-airplay": {
newName: "cast-variant",
removeIn: "2021.12",
},
application: {
newName: "application-outline",
removeIn: "2021.12",
},
"application-cog": {
newName: "application-cog-outline",
removeIn: "2021.12",
},
"application-settings": {
newName: "application-settings-outline",
removeIn: "2021.12",
},
bandcamp: {
removeIn: "2021.12",
},
battlenet: {
removeIn: "2021.12",
},
blogger: {
removeIn: "2021.12",
},
"bolnisi-cross": {
newName: "cross-bolnisi",
removeIn: "2021.12",
},
"boom-gate-up": {
newName: "boom-gate-arrow-up",
removeIn: "2021.12",
},
"boom-gate-up-outline": {
newName: "boom-gate-arrow-up-outline",
removeIn: "2021.12",
},
"boom-gate-down": {
newName: "boom-gate-arrow-down",
removeIn: "2021.12",
},
"boom-gate-down-outline": {
newName: "boom-gate-arrow-down-outline",
removeIn: "2021.12",
},
buddhism: {
newName: "dharmachakra",
removeIn: "2021.12",
},
buffer: {
removeIn: "2021.12",
},
"cash-usd-outline": {
removeIn: "2021.12",
},
"cash-usd": {
removeIn: "2021.12",
},
"cellphone-android": {
newName: "cellphone",
removeIn: "2021.12",
},
"cellphone-erase": {
newName: "cellphone-remove",
removeIn: "2021.12",
},
"cellphone-iphone": {
newName: "cellphone",
removeIn: "2021.12",
},
"celtic-cross": {
newName: "cross-celtic",
removeIn: "2021.12",
},
christianity: {
newName: "cross",
removeIn: "2021.12",
},
"christianity-outline": {
newName: "cross-outline",
removeIn: "2021.12",
},
"concourse-ci": {
removeIn: "2021.12",
},
"currency-usd-circle": {
removeIn: "2021.12",
},
"currency-usd-circle-outline": {
removeIn: "2021.12",
},
"do-not-disturb-off": {
newName: "minus-circle-off",
removeIn: "2021.12",
},
"do-not-disturb": {
newName: "minus-circle",
removeIn: "2021.12",
},
douban: {
removeIn: "2021.12",
},
face: {
newName: "face-man",
removeIn: "2021.12",
},
"face-outline": {
newName: "face-man-outline",
removeIn: "2021.12",
},
"face-profile-woman": {
newName: "face-woman-profile",
removeIn: "2021.12",
},
"face-shimmer": {
newName: "face-man-shimmer",
removeIn: "2021.12",
},
"face-shimmer-outline": {
newName: "face-man-shimmer-outline",
removeIn: "2021.12",
},
"file-pdf": {
newName: "file-pdf-box",
removeIn: "2021.12",
},
"file-pdf-outline": {
newName: "file-pdf-box",
removeIn: "2021.12",
},
"file-pdf-box-outline": {
newName: "file-pdf-box",
removeIn: "2021.12",
},
"flash-circle": {
newName: "lightning-bolt-circle",
removeIn: "2021.12",
},
"floor-lamp-variant": {
newName: "floor-lamp-torchiere-variant",
removeIn: "2021.12",
},
gif: {
newName: "file-gif-box",
removeIn: "2021.12",
},
"google-photos": {
removeIn: "2021.12",
},
gradient: {
newName: "gradient-vertical",
removeIn: "2021.12",
},
hand: {
newName: "hand-front-right",
removeIn: "2021.12",
},
"hand-left": {
newName: "hand-back-left",
removeIn: "2021.12",
},
"hand-right": {
newName: "hand-back-right",
removeIn: "2021.12",
},
hinduism: {
newName: "om",
removeIn: "2021.12",
},
"home-currency-usd": {
removeIn: "2021.12",
},
iframe: {
newName: "application-brackets",
removeIn: "2021.12",
},
"iframe-outline": {
newName: "application-brackets-outline",
removeIn: "2021.12",
},
"iframe-array": {
newName: "application-array",
removeIn: "2021.12",
},
"iframe-array-outline": {
newName: "application-array-outline",
removeIn: "2021.12",
},
"iframe-braces": {
newName: "application-braces",
removeIn: "2021.12",
},
"iframe-braces-outline": {
newName: "application-braces-outline",
removeIn: "2021.12",
},
"iframe-parentheses": {
newName: "application-parentheses",
removeIn: "2021.12",
},
"iframe-parentheses-outline": {
newName: "application-parentheses-outline",
removeIn: "2021.12",
},
"iframe-variable": {
newName: "application-variable",
removeIn: "2021.12",
},
"iframe-variable-outline": {
newName: "application-variable-outline",
removeIn: "2021.12",
},
islam: {
newName: "star-crescent",
removeIn: "2021.12",
},
judaism: {
newName: "star-david",
removeIn: "2021.12",
},
"laptop-chromebook": {
newName: "laptop",
removeIn: "2021.12",
},
"laptop-mac": {
newName: "laptop",
removeIn: "2021.12",
},
"laptop-windows": {
newName: "laptop",
removeIn: "2021.12",
},
"microsoft-edge-legacy": {
removeIn: "2021.12",
},
"microsoft-yammer": {
removeIn: "2021.12",
},
"monitor-clean": {
newName: "monitor-shimmer",
removeIn: "2021.12",
},
"pdf-box": {
newName: "file-pdf-box",
removeIn: "2021.12",
},
pharmacy: {
newName: "mortar-pestle-plus",
removeIn: "2021.12",
},
"plus-one": {
newName: "numeric-positive-1",
removeIn: "2021.12",
},
"poll-box": {
newName: "chart-box",
removeIn: "2021.12",
},
"poll-box-outline": {
newName: "chart-box-outline",
removeIn: "2021.12",
},
sparkles: {
newName: "shimmer",
removeIn: "2021.12",
},
"tablet-ipad": {
newName: "tablet",
removeIn: "2021.12",
},
teach: {
newName: "human-male-board",
removeIn: "2021.12",
},
telegram: {
removeIn: "2021.12",
},
"television-clean": {
newName: "television-shimmer",
removeIn: "2021.12",
},
"text-subject": {
newName: "text-long",
removeIn: "2021.12",
},
"twitter-retweet": {
newName: "repeat-variant",
removeIn: "2021.12",
},
untappd: {
removeIn: "2021.12",
},
vk: {
removeIn: "2021.12",
},
"voice-off": {
newName: "account-voice-off",
removeIn: "2021.12",
},
"xamarian-outline": {
newName: "xamarian",
removeIn: "2021.12",
},
xing: {
removeIn: "2021.12",
},
"y-combinator": {
removeIn: "2021.12",
},
};
const chunks: Chunks = {};

View File

@@ -5,15 +5,18 @@ import {
HassServiceTarget,
} from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state, query } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import { computeDomain } from "../common/entity/compute_domain";
import { computeObjectId } from "../common/entity/compute_object_id";
import {
fetchIntegrationManifest,
IntegrationManifest,
} from "../data/integration";
import { Selector } from "../data/selector";
import { PolymerChangedEvent } from "../polymer-types";
import { HomeAssistant } from "../types";
import { documentationUrl } from "../util/documentation-url";
import "./ha-checkbox";
import "./ha-selector/ha-selector";
import "./ha-service-picker";
@@ -53,6 +56,8 @@ export class HaServiceControl extends LitElement {
@state() private _checkedKeys = new Set();
@state() private _manifest?: IntegrationManifest;
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
protected updated(changedProperties: PropertyValues<this>) {
@@ -72,6 +77,19 @@ export class HaServiceControl extends LitElement {
this.hass.services
);
// Fetch the manifest if we have a service selected and the service domain changed.
// If no service is selected, clear the manifest.
if (this.value?.service) {
if (
!oldValue?.service ||
computeDomain(this.value.service) !== computeDomain(oldValue.service)
) {
this._fetchManifest(computeDomain(this.value?.service));
}
} else {
this._manifest = undefined;
}
if (
serviceData &&
"target" in serviceData &&
@@ -177,12 +195,9 @@ export class HaServiceControl extends LitElement {
></ha-service-picker>
<div class="description">
<p>${serviceData?.description}</p>
${this.value?.service
${this._manifest
? html` <a
href="${documentationUrl(
this.hass,
"/integrations/" + computeDomain(this.value?.service)
)}"
href="${this._manifest.documentation}"
title="${this.hass.localize(
"ui.components.service-control.integration_doc"
)}"
@@ -387,6 +402,15 @@ export class HaServiceControl extends LitElement {
});
}
private async _fetchManifest(integration: string) {
this._manifest = undefined;
try {
this._manifest = await fetchIntegrationManifest(this.hass, integration);
} catch (err) {
// Ignore if loading manifest fails. Probably bad JSON in manifest
}
}
static get styles(): CSSResultGroup {
return css`
ha-settings-row {

View File

@@ -29,7 +29,7 @@ import { LocalStorage } from "../common/decorators/local-storage";
import { fireEvent } from "../common/dom/fire_event";
import { toggleAttribute } from "../common/dom/toggle_attribute";
import { computeDomain } from "../common/entity/compute_domain";
import { compare } from "../common/string/compare";
import { stringCompare } from "../common/string/compare";
import { computeRTL } from "../common/util/compute_rtl";
import { ActionHandlerDetail } from "../data/lovelace";
import {
@@ -96,7 +96,7 @@ const defaultPanelSorter = (
}
if (aLovelace && bLovelace) {
return compare(a.title!, b.title!);
return stringCompare(a.title!, b.title!);
}
if (aLovelace && !bLovelace) {
return -1;
@@ -118,7 +118,7 @@ const defaultPanelSorter = (
return 1;
}
// both not built in, sort by title
return compare(a.title!, b.title!);
return stringCompare(a.title!, b.title!);
};
const computePanels = memoizeOne(

View File

@@ -5,7 +5,7 @@ import {
mdiAsterisk,
mdiCallSplit,
mdiCheckboxBlankOutline,
mdiCheckBoxOutline,
mdiCheckboxOutline,
mdiChevronDown,
mdiChevronRight,
mdiChevronUp,
@@ -167,27 +167,31 @@ export class HatScriptGraph extends LitElement {
<div class="graph-container" ?track=${track_this}>
<hat-graph-node
.iconPath=${!trace || track_this
? mdiCheckBoxOutline
? mdiCheckboxOutline
: mdiCheckboxBlankOutline}
@focus=${this.selectNode(config, branch_path)}
?track=${track_this}
?active=${this.selected === branch_path}
></hat-graph-node>
${ensureArray(branch.sequence).map((action, j) =>
this.render_action_node(
action,
`${branch_path}/sequence/${j}`
)
)}
${branch.sequence !== null
? ensureArray(branch.sequence).map((action, j) =>
this.render_action_node(
action,
`${branch_path}/sequence/${j}`
)
)
: ""}
</div>
`;
})
: ""}
<div ?track=${track_default}>
<hat-graph-spacer ?track=${track_default}></hat-graph-spacer>
${ensureArray(config.default)?.map((action, i) =>
this.render_action_node(action, `${path}/default/${i}`)
)}
${config.default !== null
? ensureArray(config.default)?.map((action, i) =>
this.render_action_node(action, `${path}/default/${i}`)
)
: ""}
</div>
</hat-graph-branch>
`;

View File

@@ -19,6 +19,7 @@ import { ifDefined } from "lit/directives/if-defined";
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
import relativeTime from "../../common/datetime/relative_time";
import { fireEvent } from "../../common/dom/fire_event";
import { toggleAttribute } from "../../common/dom/toggle_attribute";
import { LogbookEntry } from "../../data/logbook";
import {
ChooseAction,
@@ -552,7 +553,7 @@ export class HaAutomationTracer extends LitElement {
this.shadowRoot!.querySelectorAll<HaTimeline>(
"ha-timeline[data-path]"
).forEach((el) => {
el.toggleAttribute("selected", this.selectedPath === el.dataset.path);
toggleAttribute(el, "selected", this.selectedPath === el.dataset.path);
if (!this.allowPick || el.tabIndex === 0) {
return;
}

View File

@@ -7,7 +7,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import { compare } from "../../common/string/compare";
import { stringCompare } from "../../common/string/compare";
import { fetchUsers, User } from "../../data/user";
import { HomeAssistant } from "../../types";
import "../ha-icon-button";
@@ -33,7 +33,7 @@ class HaUserPicker extends LitElement {
return users
.filter((user) => !user.system_generated)
.sort((a, b) => compare(a.name, b.name));
.sort((a, b) => stringCompare(a.name, b.name));
});
protected render(): TemplateResult {

View File

@@ -1,6 +1,6 @@
import { Connection, createCollection } from "home-assistant-js-websocket";
import { Store } from "home-assistant-js-websocket/dist/store";
import { compare } from "../common/string/compare";
import { stringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce";
import { HomeAssistant } from "../types";
@@ -46,7 +46,7 @@ const fetchAreaRegistry = (conn: Connection) =>
})
.then((areas) =>
(areas as AreaRegistryEntry[]).sort((ent1, ent2) =>
compare(ent1.name, ent2.name)
stringCompare(ent1.name, ent2.name)
)
);

View File

@@ -1,16 +0,0 @@
export interface DiscoveryInformation {
uuid: string;
base_url: string | null;
external_url: string | null;
internal_url: string | null;
location_name: string;
installation_type: string;
requires_api_password: boolean;
version: string;
}
export const fetchDiscoveryInformation =
async (): Promise<DiscoveryInformation> => {
const response = await fetch("/api/discovery_info", { method: "GET" });
return response.json();
};

View File

@@ -159,9 +159,11 @@ export const removeBackup = async (hass: HomeAssistant, slug: string) => {
await hass.callWS({
type: "supervisor/api",
endpoint: `/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}/${slug}/remove`,
method: "post",
atLeastVersion(hass.config.version, 2021, 9)
? `backups/${slug}`
: `snapshots/${slug}/remove`
}`,
method: atLeastVersion(hass.config.version, 2021, 9) ? "delete" : "post",
});
return;
}

View File

@@ -22,6 +22,7 @@ export interface HassioHassOSInfo {
update_available: boolean;
version_latest: string | null;
version: string | null;
data_disk: string;
}
export const fetchHassioHostInfo = async (
@@ -113,6 +114,19 @@ export const configSyncOS = async (hass: HomeAssistant) => {
return hass.callApi<HassioResponse<void>>("POST", "hassio/os/config/sync");
};
export const dataDiskMove = async (hass: HomeAssistant) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
return hass.callWS({
type: "supervisor/api",
endpoint: "/os/datadisk/move",
method: "post",
timeout: null,
});
}
return hass.callApi<HassioResponse<void>>("POST", "hassio/os/datadisk/move");
};
export const changeHostOptions = async (hass: HomeAssistant, options: any) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
return hass.callWS({

View File

@@ -294,13 +294,15 @@ export const fetchStatistics = (
hass: HomeAssistant,
startTime: Date,
endTime?: Date,
statistic_ids?: string[]
statistic_ids?: string[],
period: "hour" | "5minute" = "hour"
) =>
hass.callWS<Statistics>({
type: "history/statistics_during_period",
start_time: startTime.toISOString(),
end_time: endTime?.toISOString(),
statistic_ids,
period,
});
export const calculateStatisticSumGrowth = (

View File

@@ -2,7 +2,6 @@ import { clear, get, set, createStore, promisifyRequest } from "idb-keyval";
import { promiseTimeout } from "../common/util/promise-timeout";
import { iconMetadata } from "../resources/icon-metadata";
import { IconMeta } from "../types";
import { isSafari } from "../util/is_safari";
export interface Icons {
[key: string]: string;
@@ -39,22 +38,7 @@ export const getIcon = (iconName: string) =>
toRead = [];
});
let readIconPromise: Promise<void>;
if (isSafari && (indexedDB as any).databases) {
let intervalId: number;
readIconPromise = new Promise<void>((resolveTry) => {
const tryIdb = () => (indexedDB as any).databases().finally(resolveTry);
intervalId = window.setInterval(tryIdb, 100);
tryIdb();
})
.then(() => readIcons())
.finally(() => clearInterval(intervalId));
} else {
readIconPromise = readIcons();
}
promiseTimeout(1000, readIconPromise).catch((e) => {
promiseTimeout(1000, readIcons()).catch((e) => {
// Firefox in private mode doesn't support IDB
// Safari sometime doesn't open the DB so we time out
for (const [, , reject_] of toRead) {

View File

@@ -1,6 +1,15 @@
import { HomeAssistant } from "../types";
import { handleFetchPromise } from "../util/hass-call-api";
export interface InstallationType {
installation_type:
| "Home Assistant Operating System"
| "Home Assistant Container"
| "Home Assistant Supervised"
| "Home Assistant Core"
| "Unknown";
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface OnboardingCoreConfigStepResponse {}
@@ -65,3 +74,15 @@ export const onboardIntegrationStep = (
"onboarding/integration",
params
);
export const fetchInstallationType = async (): Promise<InstallationType> => {
const response = await fetch("/api/onboarding/installation_type", {
method: "GET",
});
if (response.status === 401) {
throw Error("unauthorized");
}
return response.json();
};

View File

@@ -1,5 +1,5 @@
import { html } from "lit";
import { caseInsensitiveCompare } from "../../common/string/compare";
import { caseInsensitiveStringCompare } from "../../common/string/compare";
import {
createConfigFlow,
deleteConfigFlow,
@@ -29,7 +29,7 @@ export const showConfigFlowDialog = (
]);
return handlers.sort((handlerA, handlerB) =>
caseInsensitiveCompare(
caseInsensitiveStringCompare(
domainToName(hass.localize, handlerA),
domainToName(hass.localize, handlerB)
)

View File

@@ -7,6 +7,7 @@ import "../../../components/ha-attributes";
import "../../../components/ha-icon";
import "../../../components/ha-icon-button";
import "../../../components/ha-paper-dropdown-menu";
import { UNAVAILABLE } from "../../../data/entity";
import {
VacuumEntity,
VACUUM_SUPPORT_BATTERY,
@@ -98,31 +99,35 @@ class MoreInfoVacuum extends LitElement {
"fan_speed,fan_speed_list,status,battery_level,battery_icon";
return html`
<div class="flex-horizontal">
${supportsFeature(stateObj, VACUUM_SUPPORT_STATUS)
? html`
<div>
<span class="status-subtitle"
>${this.hass!.localize(
"ui.dialogs.more_info_control.vacuum.status"
)}:
</span>
<span><strong>${stateObj.attributes.status}</strong></span>
</div>
`
: ""}
${supportsFeature(stateObj, VACUUM_SUPPORT_BATTERY)
? html`
<div>
<span>
<ha-icon .icon=${stateObj.attributes.battery_icon}></ha-icon>
${stateObj.attributes.battery_level} %
</span>
</div>
`
: ""}
</div>
${stateObj.state !== UNAVAILABLE
? html` <div class="flex-horizontal">
${supportsFeature(stateObj, VACUUM_SUPPORT_STATUS)
? html`
<div>
<span class="status-subtitle"
>${this.hass!.localize(
"ui.dialogs.more_info_control.vacuum.status"
)}:
</span>
<span><strong>${stateObj.attributes.status}</strong></span>
</div>
`
: ""}
${supportsFeature(stateObj, VACUUM_SUPPORT_BATTERY) &&
stateObj.attributes.battery_level
? html`
<div>
<span>
${stateObj.attributes.battery_level} %
<ha-icon
.icon=${stateObj.attributes.battery_icon}
></ha-icon>
</span>
</div>
`
: ""}
</div>`
: ""}
${VACUUM_COMMANDS.some((item) => item.isVisible(stateObj))
? html`
<div>
@@ -145,6 +150,7 @@ class MoreInfoVacuum extends LitElement {
.title=${this.hass!.localize(
`ui.dialogs.more_info_control.vacuum.${item.translationKey}`
)}
.disabled=${stateObj.state === UNAVAILABLE}
></ha-icon-button>
</div>
`
@@ -161,6 +167,7 @@ class MoreInfoVacuum extends LitElement {
.label=${this.hass!.localize(
"ui.dialogs.more_info_control.vacuum.fan_speed"
)}
.disabled=${stateObj.state === UNAVAILABLE}
>
<paper-listbox
slot="dropdown-content"

View File

@@ -25,7 +25,7 @@ import { computeStateName } from "../../common/entity/compute_state_name";
import { domainIcon } from "../../common/entity/domain_icon";
import { navigate } from "../../common/navigate";
import "../../common/search/search-input";
import { compare } from "../../common/string/compare";
import { stringCompare } from "../../common/string/compare";
import {
fuzzyFilterSort,
ScorableTextItem,
@@ -299,6 +299,10 @@ export class QuickBar extends LitElement {
private _handleSelected(ev: SingleSelectedEvent) {
const index = ev.detail.index;
if (index < 0) {
return;
}
const item = ((ev.target as List).items[index] as any).item;
this.processItemAndCloseDialog(item, index);
}
@@ -395,7 +399,7 @@ export class QuickBar extends LitElement {
};
})
.sort((a, b) =>
compare(a.primaryText.toLowerCase(), b.primaryText.toLowerCase())
stringCompare(a.primaryText.toLowerCase(), b.primaryText.toLowerCase())
);
}
@@ -405,7 +409,7 @@ export class QuickBar extends LitElement {
...this._generateServerControlCommands(),
...this._generateNavigationCommands(),
].sort((a, b) =>
compare(
stringCompare(
a.strings.join(" ").toLowerCase(),
b.strings.join(" ").toLowerCase()
)

View File

@@ -23,6 +23,7 @@ import { subscribePanels } from "../data/ws-panels";
import { subscribeThemes } from "../data/ws-themes";
import { subscribeUser } from "../data/ws-user";
import type { ExternalAuth } from "../external_app/external_auth";
import "../resources/array.flat.polyfill";
import "../resources/safari-14-attachshadow-patch";
import { HomeAssistant } from "../types";
import { MAIN_WINDOW_NAME } from "../data/main_window";

View File

@@ -13,14 +13,12 @@ import { extractSearchParamsObject } from "../common/url/search-params";
import { subscribeOne } from "../common/util/subscribe-one";
import { AuthUrlSearchParams, hassUrl } from "../data/auth";
import {
DiscoveryInformation,
fetchDiscoveryInformation,
} from "../data/discovery";
import {
InstallationType,
fetchOnboardingOverview,
OnboardingResponses,
OnboardingStep,
onboardIntegrationStep,
fetchInstallationType,
} from "../data/onboarding";
import { subscribeUser } from "../data/ws-user";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
@@ -71,7 +69,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
@state() private _steps?: OnboardingStep[];
@state() private _discoveryInformation?: DiscoveryInformation;
@state() private _installation_type?: InstallationType;
protected render(): TemplateResult {
const step = this._curStep()!;
@@ -92,7 +90,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
? html`<onboarding-restore-backup
.localize=${this.localize}
.restoring=${this._restoring}
.discoveryInformation=${this._discoveryInformation}
.installtionType=${this._installation_type}
@restoring=${this._restoringBackup}
>
</onboarding-restore-backup>`
@@ -130,7 +128,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._fetchOnboardingSteps();
this._fetchDiscoveryInformation();
this._fetchInstallationType();
import("./onboarding-integrations");
import("./onboarding-core-config");
registerServiceWorker(this, false);
@@ -174,9 +172,9 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
this._restoring = true;
}
private async _fetchDiscoveryInformation(): Promise<void> {
private async _fetchInstallationType(): Promise<void> {
try {
const response = await fetchDiscoveryInformation();
const response = await fetchInstallationType();
this._supervisor = [
"Home Assistant OS",
"Home Assistant Supervised",

View File

@@ -9,7 +9,7 @@ import {
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { compare } from "../common/string/compare";
import { stringCompare } from "../common/string/compare";
import { isComponentLoaded } from "../common/config/is_component_loaded";
import { LocalizeFunc } from "../common/translations/localize";
import { ConfigEntry, getConfigEntries } from "../data/config_entries";
@@ -105,7 +105,7 @@ class OnboardingIntegrations extends LitElement {
}
);
const content = [...entries, ...discovered]
.sort((a, b) => compare(a[0], b[0]))
.sort((a, b) => stringCompare(a[0], b[0]))
.map((item) => item[1]);
return html`

View File

@@ -6,14 +6,11 @@ import { showHassioBackupDialog } from "../../hassio/src/dialogs/backup/show-dia
import { showBackupUploadDialog } from "../../hassio/src/dialogs/backup/show-dialog-backup-upload";
import type { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-card";
import {
DiscoveryInformation,
fetchDiscoveryInformation,
} from "../data/discovery";
import { makeDialogManager } from "../dialogs/make-dialog-manager";
import { ProvideHassLitMixin } from "../mixins/provide-hass-lit-mixin";
import { haStyle } from "../resources/styles";
import "./onboarding-loading";
import { fetchInstallationType, InstallationType } from "../data/onboarding";
declare global {
interface HASSDomEvents {
@@ -30,7 +27,7 @@ class OnboardingRestoreBackup extends ProvideHassLitMixin(LitElement) {
@property({ type: Boolean }) public restoring = false;
@property({ attribute: false })
public discoveryInformation?: DiscoveryInformation;
public installationType?: InstallationType;
protected render(): TemplateResult {
return this.restoring
@@ -64,17 +61,11 @@ class OnboardingRestoreBackup extends ProvideHassLitMixin(LitElement) {
private async _checkRestoreStatus(): Promise<void> {
if (this.restoring) {
try {
const response = await fetchDiscoveryInformation();
if (
!this.discoveryInformation ||
this.discoveryInformation.uuid !== response.uuid
) {
// When the UUID changes, the restore is complete
await fetchInstallationType();
} catch (err) {
if ((err as Error).message === "unauthorized") {
window.location.replace("/");
}
} catch (err) {
// We fully expected issues with fetching info untill restore is complete.
}
}
}

View File

@@ -23,6 +23,7 @@ import {
} from "lit";
import { property, state } from "lit/decorators";
import memoize from "memoize-one";
import { useAmPm } from "../../common/datetime/use_am_pm";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-button-toggle-group";
import "../../components/ha-icon-button";
@@ -214,6 +215,11 @@ export class HAFullCalendar extends LitElement {
...defaultFullCalendarConfig,
locale: this.hass.language,
initialView: this.initialView,
eventTimeFormat: {
hour: useAmPm(this.hass.locale) ? "numeric" : "2-digit",
minute: useAmPm(this.hass.locale) ? "numeric" : "2-digit",
hour12: useAmPm(this.hass.locale),
},
};
config.dateClick = (info) => this._handleDateClick(info);

View File

@@ -1,7 +1,7 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { compare } from "../../../common/string/compare";
import { stringCompare } from "../../../common/string/compare";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
@@ -104,7 +104,7 @@ class HaConfigAreas extends HassRouterPage {
private _loadData() {
getConfigEntries(this.hass).then((configEntries) => {
this._configEntries = configEntries.sort((conf1, conf2) =>
compare(conf1.title, conf2.title)
stringCompare(conf1.title, conf2.title)
);
});
if (this._unsubs) {

View File

@@ -53,7 +53,8 @@ export class HaBlueprintAutomationEditor extends LitElement {
protected render() {
const blueprint = this._blueprint;
return html`<ha-config-section vertical .isWide=${this.isWide}>
return html`
<ha-config-section vertical .isWide=${this.isWide}>
${!this.narrow
? html` <span slot="header">${this.config.alias}</span> `
: ""}
@@ -118,81 +119,80 @@ export class HaBlueprintAutomationEditor extends LitElement {
</ha-card>
</ha-config-section>
<ha-config-section vertical .isWide=${this.isWide}>
<span slot="header"
>${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.header"
)}</span
>
<ha-card>
<div class="blueprint-picker-container">
${this._blueprints
? Object.keys(this._blueprints).length
? html`
<ha-blueprint-picker
.hass=${this.hass}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.blueprint_to_use"
)}
.blueprints=${this._blueprints}
.value=${this.config.use_blueprint.path}
@value-changed=${this._blueprintChanged}
></ha-blueprint-picker>
`
: this.hass.localize(
"ui.panel.config.automation.editor.blueprint.no_blueprints"
)
: html`<ha-circular-progress active></ha-circular-progress>`}
</div>
<ha-card
class="blueprint"
.header=${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.header"
)}
>
<div class="blueprint-picker-container">
${this._blueprints
? Object.keys(this._blueprints).length
? html`
<ha-blueprint-picker
.hass=${this.hass}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.blueprint_to_use"
)}
.blueprints=${this._blueprints}
.value=${this.config.use_blueprint.path}
@value-changed=${this._blueprintChanged}
></ha-blueprint-picker>
`
: this.hass.localize(
"ui.panel.config.automation.editor.blueprint.no_blueprints"
)
: html`<ha-circular-progress active></ha-circular-progress>`}
</div>
${this.config.use_blueprint.path
? blueprint && "error" in blueprint
? html`<p class="warning padding">
There is an error in this Blueprint: ${blueprint.error}
</p>`
: html`${blueprint?.metadata.description
? html`<ha-markdown
class="card-content"
breaks
.content=${blueprint.metadata.description}
></ha-markdown>`
: ""}
${blueprint?.metadata?.input &&
Object.keys(blueprint.metadata.input).length
? Object.entries(blueprint.metadata.input).map(
([key, value]) =>
html`<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">${value?.name || key}</span>
<span slot="description">${value?.description}</span>
${value?.selector
? html`<ha-selector
.hass=${this.hass}
.selector=${value.selector}
.key=${key}
.value=${(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]) ??
value?.default}
@value-changed=${this._inputChanged}
></ha-selector>`
: html`<paper-input
.key=${key}
required
.value=${(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]) ??
value?.default}
@value-changed=${this._inputChanged}
no-label-float
></paper-input>`}
</ha-settings-row>`
)
: html`<p class="padding">
${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.no_inputs"
)}
</p>`}`
: ""}
</ha-card>
</ha-config-section>`;
${this.config.use_blueprint.path
? blueprint && "error" in blueprint
? html`<p class="warning padding">
There is an error in this Blueprint: ${blueprint.error}
</p>`
: html`${blueprint?.metadata.description
? html`<ha-markdown
class="card-content"
breaks
.content=${blueprint.metadata.description}
></ha-markdown>`
: ""}
${blueprint?.metadata?.input &&
Object.keys(blueprint.metadata.input).length
? Object.entries(blueprint.metadata.input).map(
([key, value]) =>
html`<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">${value?.name || key}</span>
<span slot="description">${value?.description}</span>
${value?.selector
? html`<ha-selector
.hass=${this.hass}
.selector=${value.selector}
.key=${key}
.value=${(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]) ??
value?.default}
@value-changed=${this._inputChanged}
></ha-selector>`
: html`<paper-input
.key=${key}
required
.value=${(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]) ??
value?.default}
@value-changed=${this._inputChanged}
no-label-float
></paper-input>`}
</ha-settings-row>`
)
: html`<p class="padding">
${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.no_inputs"
)}
</p>`}`
: ""}
</ha-card>
`;
}
private async _getBlueprints() {
@@ -267,11 +267,15 @@ export class HaBlueprintAutomationEditor extends LitElement {
return [
haStyle,
css`
ha-card.blueprint {
max-width: 1040px;
margin: 24px auto;
}
.padding {
padding: 16px;
}
.blueprint-picker-container {
padding: 16px;
padding: 0 16px 16px;
}
h3 {
margin: 16px;

View File

@@ -7,6 +7,7 @@ import { fetchTags, Tag } from "../../../../../data/tag";
import { HomeAssistant } from "../../../../../types";
import { TriggerElement } from "../ha-automation-trigger-row";
import "../../../../../components/ha-paper-dropdown-menu";
import { caseInsensitiveStringCompare } from "../../../../../common/string/compare";
@customElement("ha-automation-trigger-tag")
export class HaTagTrigger extends LitElement implements TriggerElement {
@@ -54,6 +55,9 @@ export class HaTagTrigger extends LitElement implements TriggerElement {
private async _fetchTags() {
this._tags = await fetchTags(this.hass);
this._tags.sort((a, b) =>
caseInsensitiveStringCompare(a.name || a.id, b.name || b.id)
);
}
private _tagChanged(ev) {

View File

@@ -6,7 +6,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import { caseInsensitiveCompare } from "../../../../common/string/compare";
import { caseInsensitiveStringCompare } from "../../../../common/string/compare";
import "../../../../components/ha-card";
import "../../../../components/ha-svg-icon";
import "../../../../components/ha-switch";
@@ -139,7 +139,7 @@ export class CloudTTSPref extends LitElement {
languages.push([lang, label]);
}
return languages.sort((a, b) => caseInsensitiveCompare(a[1], b[1]));
return languages.sort((a, b) => caseInsensitiveStringCompare(a[1], b[1]));
});
private getSupportedGenders = memoizeOne(
@@ -160,7 +160,7 @@ export class CloudTTSPref extends LitElement {
}
}
return genders.sort((a, b) => caseInsensitiveCompare(a[1], b[1]));
return genders.sort((a, b) => caseInsensitiveStringCompare(a[1], b[1]));
}
);

View File

@@ -18,7 +18,7 @@ import {
generateFilter,
isEmptyFilter,
} from "../../../../common/entity/entity_filter";
import { compare } from "../../../../common/string/compare";
import { stringCompare } from "../../../../common/string/compare";
import "../../../../components/entity/state-info";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-card";
@@ -295,7 +295,7 @@ class CloudAlexa extends LitElement {
entities.sort((a, b) => {
const stateA = this.hass.states[a.entity_id];
const stateB = this.hass.states[b.entity_id];
return compare(
return stringCompare(
stateA ? computeStateName(stateA) : a.entity_id,
stateB ? computeStateName(stateB) : b.entity_id
);

View File

@@ -18,7 +18,7 @@ import {
generateFilter,
isEmptyFilter,
} from "../../../../common/entity/entity_filter";
import { compare } from "../../../../common/string/compare";
import { stringCompare } from "../../../../common/string/compare";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/state-info";
import "../../../../components/ha-button-menu";
@@ -330,7 +330,7 @@ class CloudGoogleAssistant extends LitElement {
entities.sort((a, b) => {
const stateA = this.hass.states[a.entity_id];
const stateB = this.hass.states[b.entity_id];
return compare(
return stringCompare(
stateA ? computeStateName(stateA) : a.entity_id,
stateB ? computeStateName(stateB) : b.entity_id
);

View File

@@ -6,7 +6,7 @@ import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { compare } from "../../../common/string/compare";
import { stringCompare } from "../../../common/string/compare";
import { slugify } from "../../../common/string/slugify";
import "../../../components/entity/ha-battery-icon";
import "../../../components/ha-icon-next";
@@ -103,7 +103,7 @@ export class HaConfigDevicePage extends LitElement {
stateName: this._computeEntityName(entity),
}))
.sort((ent1, ent2) =>
compare(
stringCompare(
ent1.stateName || `zzz${ent1.entity_id}`,
ent2.stateName || `zzz${ent2.entity_id}`
)

View File

@@ -13,6 +13,7 @@ import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types";
import "../../../components/ha-alert";
import { configSections } from "../ha-panel-config";
import "./components/ha-energy-device-settings";
import "./components/ha-energy-grid-settings";
@@ -77,12 +78,10 @@ class HaConfigEnergy extends LitElement {
.route=${this.route}
.tabs=${configSections.experiences}
>
<ha-card>
<div class="card-content">
After setting up a new device, it can take up to 2 hours for new
data to arrive in your energy dashboard.
</div>
</ha-card>
<ha-alert>
After setting up a new device, it can take up to 2 hours for new data
to arrive in your energy dashboard.
</ha-alert>
<div class="container">
<ha-energy-grid-settings
.hass=${this.hass}
@@ -106,6 +105,7 @@ class HaConfigEnergy extends LitElement {
<ha-energy-gas-settings
.hass=${this.hass}
.preferences=${this._preferences!}
.validationResult=${this._validationResult!}
@value-changed=${this._prefsChanged}
></ha-energy-gas-settings>
<ha-energy-device-settings
@@ -156,7 +156,8 @@ class HaConfigEnergy extends LitElement {
return [
haStyle,
css`
ha-card {
ha-alert {
display: block;
margin: 8px;
}
.container {

View File

@@ -18,7 +18,7 @@ import memoizeOne from "memoize-one";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import "../../../common/search/search-input";
import { caseInsensitiveCompare } from "../../../common/string/compare";
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
import type { LocalizeFunc } from "../../../common/translations/localize";
import { extractSearchParam } from "../../../common/url/search-params";
import { nextRender } from "../../../common/util/render-status";
@@ -495,7 +495,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
})
)
.sort((conf1, conf2) =>
caseInsensitiveCompare(
caseInsensitiveStringCompare(
conf1.localized_domain_name + conf1.title,
conf2.localized_domain_name + conf2.title
)

View File

@@ -23,7 +23,7 @@ import {
updateEntityRegistryEntry,
} from "../../../../../data/entity_registry";
import { EntityRegistryStateEntry } from "../../../devices/ha-config-device-page";
import { compare } from "../../../../../common/string/compare";
import { stringCompare } from "../../../../../common/string/compare";
import { getIeeeTail } from "./functions";
import { slugify } from "../../../../../common/string/slugify";
@@ -49,7 +49,7 @@ class ZHADeviceCard extends SubscribeMixin(LitElement) {
stateName: this._computeEntityName(entity),
}))
.sort((ent1, ent2) =>
compare(
stringCompare(
ent1.stateName || `zzz${ent1.entity_id}`,
ent2.stateName || `zzz${ent2.entity_id}`
)

View File

@@ -4,7 +4,7 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoize from "memoize-one";
import { navigate } from "../../../../common/navigate";
import { compare } from "../../../../common/string/compare";
import { stringCompare } from "../../../../common/string/compare";
import {
DataTableColumnContainer,
RowClickedEvent,
@@ -263,7 +263,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
createDashboard: async (values: LovelaceDashboardCreateParams) => {
const created = await createDashboard(this.hass!, values);
this._dashboards = this._dashboards!.concat(created).sort(
(res1, res2) => compare(res1.url_path, res2.url_path)
(res1, res2) => stringCompare(res1.url_path, res2.url_path)
);
},
updateDashboard: async (values) => {

View File

@@ -5,7 +5,7 @@ import "@polymer/paper-listbox/paper-listbox";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoize from "memoize-one";
import { compare } from "../../../../common/string/compare";
import { stringCompare } from "../../../../common/string/compare";
import {
DataTableColumnContainer,
RowClickedEvent,
@@ -148,7 +148,7 @@ export class HaConfigLovelaceRescources extends LitElement {
createResource: async (values) => {
const created = await createResource(this.hass!, values);
this._resources = this._resources!.concat(created).sort((res1, res2) =>
compare(res1.url, res2.url)
stringCompare(res1.url, res2.url)
);
loadLovelaceResources([created], this.hass!.auth.data.hassUrl);
},

View File

@@ -3,7 +3,7 @@ import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { property, state } from "lit/decorators";
import { compare } from "../../../common/string/compare";
import { stringCompare } from "../../../common/string/compare";
import "../../../components/ha-card";
import "../../../components/ha-fab";
import "../../../components/ha-svg-icon";
@@ -156,10 +156,10 @@ class HaConfigPerson extends LitElement {
const personData = await fetchPersons(this.hass!);
this._storageItems = personData.storage.sort((ent1, ent2) =>
compare(ent1.name, ent2.name)
stringCompare(ent1.name, ent2.name)
);
this._configItems = personData.config.sort((ent1, ent2) =>
compare(ent1.name, ent2.name)
stringCompare(ent1.name, ent2.name)
);
this._openDialogIfPersonSpecifiedInRoute();
}
@@ -221,7 +221,7 @@ class HaConfigPerson extends LitElement {
createEntry: async (values) => {
const created = await createPerson(this.hass!, values);
this._storageItems = this._storageItems!.concat(created).sort(
(ent1, ent2) => compare(ent1.name, ent2.name)
(ent1, ent2) => stringCompare(ent1.name, ent2.name)
);
},
updateEntry: async (values) => {

View File

@@ -18,7 +18,7 @@ import { ifDefined } from "lit/directives/if-defined";
import memoizeOne from "memoize-one";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { navigate } from "../../../common/navigate";
import { compare } from "../../../common/string/compare";
import { stringCompare } from "../../../common/string/compare";
import "../../../components/ha-card";
import "../../../components/ha-fab";
import "../../../components/ha-svg-icon";
@@ -289,7 +289,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
private async _fetchData() {
this._storageItems = (await fetchZones(this.hass!)).sort((ent1, ent2) =>
compare(ent1.name, ent2.name)
stringCompare(ent1.name, ent2.name)
);
this._getStates();
}
@@ -410,7 +410,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
private async _createEntry(values: ZoneMutableParams) {
const created = await createZone(this.hass!, values);
this._storageItems = this._storageItems!.concat(created).sort(
(ent1, ent2) => compare(ent1.name, ent2.name)
(ent1, ent2) => stringCompare(ent1.name, ent2.name)
);
if (this.narrow) {
return;

View File

@@ -105,7 +105,7 @@ class HaPanelDevEvent extends EventsMixin(LocalizeMixin(PolymerElement)) {
<div>
<div class="header">
[[localize( 'ui.panel.developer-tools.tabs.events.available_events'
[[localize( 'ui.panel.developer-tools.tabs.events.active_listeners'
)]]
</div>
<events-list

View File

@@ -1,7 +1,7 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { compare } from "../../../common/string/compare";
import { stringCompare } from "../../../common/string/compare";
import { EventsMixin } from "../../../mixins/events-mixin";
import LocalizeMixin from "../../../mixins/localize-mixin";
@@ -58,7 +58,7 @@ class EventsList extends EventsMixin(LocalizeMixin(PolymerElement)) {
connectedCallback() {
super.connectedCallback();
this.hass.callApi("GET", "events").then((events) => {
this.events = events.sort((e1, e2) => compare(e1.event, e2.event));
this.events = events.sort((e1, e2) => stringCompare(e1.event, e2.event));
});
}

View File

@@ -1,23 +1,24 @@
import { mdiRefresh } from "@mdi/js";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import {
addDays,
endOfToday,
endOfWeek,
endOfYesterday,
startOfToday,
startOfWeek,
startOfYesterday,
} from "date-fns";
import { css, html, LitElement, PropertyValues } from "lit";
import { property, state } from "lit/decorators";
import {
startOfWeek,
endOfWeek,
startOfToday,
endOfToday,
startOfYesterday,
endOfYesterday,
addDays,
} from "date-fns";
import { computeRTL } from "../../common/util/compute_rtl";
import "../../components/chart/state-history-charts";
import "../../components/entity/ha-entity-picker";
import "../../components/ha-circular-progress";
import "../../components/ha-date-range-picker";
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
import "../../components/ha-menu-button";
import "../../components/chart/state-history-charts";
import { computeHistory, fetchDate } from "../../data/history";
import "../../layouts/ha-app-layout";
import { haStyle } from "../../resources/styles";
@@ -64,6 +65,12 @@ class HaPanelHistory extends LitElement {
.narrow=${this.narrow}
></ha-menu-button>
<div main-title>${this.hass.localize("panel.history")}</div>
<mwc-icon-button
@click=${this._refreshHistory}
.disabled=${this._isLoading}
>
<ha-svg-icon .path=${mdiRefresh}></ha-svg-icon>
</mwc-icon-button>
</app-toolbar>
</app-header>
@@ -147,6 +154,10 @@ class HaPanelHistory extends LitElement {
}
}
private _refreshHistory() {
this._getHistory();
}
private async _getHistory() {
this._isLoading = true;
const dateHistory = await fetchDate(

View File

@@ -28,7 +28,7 @@ const ICONS = {
armed_away: "hass:shield-lock",
armed_custom_bypass: "hass:security",
armed_home: "hass:shield-home",
armed_night: "hass:shield-sun",
armed_night: "hass:shield-moon",
armed_vacation: "hass:shield-lock",
disarmed: "hass:shield-check",
pending: "hass:shield-outline",

View File

@@ -250,48 +250,49 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
}
static get styles(): CSSResultGroup {
return css`
ha-card {
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 4% 0;
font-size: 1.2rem;
height: 100%;
box-sizing: border-box;
justify-content: center;
position: relative;
}
return [
iconColorCSS,
css`
ha-card {
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 4% 0;
font-size: 1.2rem;
height: 100%;
box-sizing: border-box;
justify-content: center;
position: relative;
}
ha-card:focus {
outline: none;
}
ha-card:focus {
outline: none;
}
ha-icon {
width: 40%;
height: auto;
color: var(--paper-item-icon-color, #44739e);
--mdc-icon-size: 100%;
}
ha-icon {
width: 40%;
height: auto;
color: var(--paper-item-icon-color, #44739e);
--mdc-icon-size: 100%;
}
ha-icon + span {
margin-top: 8px;
}
ha-icon + span {
margin-top: 8px;
}
ha-icon,
span {
outline: none;
}
ha-icon,
span {
outline: none;
}
.state {
font-size: 0.9rem;
color: var(--secondary-text-color);
}
${iconColorCSS}
`;
.state {
font-size: 0.9rem;
color: var(--secondary-text-color);
}
`,
];
}
private _computeBrightness(stateObj: HassEntity | LightEntity): string {

View File

@@ -7,13 +7,17 @@ import {
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeActiveState } from "../../../common/entity/compute_active_state";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { stateIcon } from "../../../common/entity/state_icon";
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import { formatNumber } from "../../../common/string/format_number";
import { iconColorCSS } from "../../../common/style/icon_color_css";
import "../../../components/ha-card";
import "../../../components/ha-icon";
import { UNAVAILABLE_STATES } from "../../../data/entity";
@@ -106,6 +110,7 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
`;
}
const domain = computeStateDomain(stateObj);
const showUnit = this._config.attribute
? this._config.attribute in stateObj.attributes
: !UNAVAILABLE_STATES.includes(stateObj.state);
@@ -119,6 +124,13 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
<div class="icon">
<ha-icon
.icon=${this._config.icon || stateIcon(stateObj)}
data-domain=${ifDefined(
this._config.state_color ||
(domain === "light" && this._config.state_color !== false)
? domain
: undefined
)}
data-state=${stateObj ? computeActiveState(stateObj) : ""}
></ha-icon>
</div>
</div>
@@ -189,56 +201,59 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
}
static get styles(): CSSResultGroup {
return css`
ha-card {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
cursor: pointer;
outline: none;
}
return [
iconColorCSS,
css`
ha-card {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
cursor: pointer;
outline: none;
}
.header {
display: flex;
padding: 8px 16px 0;
justify-content: space-between;
}
.header {
display: flex;
padding: 8px 16px 0;
justify-content: space-between;
}
.name {
color: var(--secondary-text-color);
line-height: 40px;
font-weight: 500;
font-size: 16px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.name {
color: var(--secondary-text-color);
line-height: 40px;
font-weight: 500;
font-size: 16px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.icon {
color: var(--state-icon-color, #44739e);
line-height: 40px;
}
.icon {
color: var(--state-icon-color, #44739e);
line-height: 40px;
}
.info {
padding: 0px 16px 16px;
margin-top: -4px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 28px;
}
.info {
padding: 0px 16px 16px;
margin-top: -4px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 28px;
}
.value {
font-size: 28px;
margin-right: 4px;
}
.value {
font-size: 28px;
margin-right: 4px;
}
.measurement {
font-size: 18px;
color: var(--secondary-text-color);
}
`;
.measurement {
font-size: 18px;
color: var(--secondary-text-color);
}
`,
];
}
}

View File

@@ -23,6 +23,7 @@ import {
CallServiceActionConfig,
MoreInfoActionConfig,
} from "../../../data/lovelace";
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor";
import { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
import { findEntities } from "../common/find-entities";
@@ -316,7 +317,8 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
? html`
<div>
${computeDomain(entityConf.entity) === "sensor" &&
stateObj.attributes.device_class === "timestamp" &&
stateObj.attributes.device_class ===
SENSOR_DEVICE_CLASS_TIMESTAMP &&
!UNAVAILABLE_STATES.includes(stateObj.state)
? html`
<hui-timestamp-display

View File

@@ -61,7 +61,11 @@ class HuiGridCard extends HuiStackCard<GridCardConfig> {
setConfig(config: GridCardConfig) {
super.setConfig(config);
this.style.setProperty("--grid-card-column-count", String(this.columns));
this.toggleAttribute("square", this.square);
if (this.square) {
this.setAttribute("square", "");
} else {
this.removeAttribute("square");
}
}
static get styles(): CSSResultGroup {

View File

@@ -39,6 +39,7 @@ export interface EntityCardConfig extends LovelaceCardConfig {
attribute?: string;
unit?: string;
theme?: string;
state_color?: boolean;
}
export interface EntitiesCardEntityConfig extends EntityConfig {

View File

@@ -3,7 +3,7 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { splitByGroups } from "../../../common/entity/split_by_groups";
import { compare } from "../../../common/string/compare";
import { stringCompare } from "../../../common/string/compare";
import { LocalizeFunc } from "../../../common/translations/localize";
import type { AreaRegistryEntry } from "../../../data/area_registry";
import type { DeviceRegistryEntry } from "../../../data/device_registry";
@@ -262,7 +262,7 @@ export const generateViewConfig = (
computeCards(
ungroupedEntitites[domain]
.sort((a, b) =>
compare(
stringCompare(
computeStateName(entities[a]),
computeStateName(entities[b])
)

View File

@@ -33,6 +33,7 @@ import "@material/mwc-icon-button/mwc-icon-button";
import "../../../components/ha-svg-icon";
import "@material/mwc-button/mwc-button";
import "../../../components/ha-button-toggle-group";
import { toggleAttribute } from "../../../common/dom/toggle_attribute";
const viewButtons: ToggleButton[] = [
{ label: "Day", value: "day" },
@@ -55,7 +56,7 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
public connectedCallback() {
super.connectedCallback();
this.toggleAttribute("narrow", this.offsetWidth < 600);
toggleAttribute(this, "narrow", this.offsetWidth < 600);
}
public hassSubscribe(): UnsubscribeFunc[] {

View File

@@ -6,7 +6,7 @@ import { formatTime } from "../../../common/datetime/format_time";
import relativeTime from "../../../common/datetime/relative_time";
import { FrontendLocaleData } from "../../../data/translation";
import { HomeAssistant } from "../../../types";
import { TimestampRenderingFormats } from "./types";
import { TimestampRenderingFormat } from "./types";
const FORMATS: {
[key: string]: (ts: Date, lang: FrontendLocaleData) => string;
@@ -23,7 +23,7 @@ class HuiTimestampDisplay extends LitElement {
@property() public ts?: Date;
@property() public format?: TimestampRenderingFormats;
@property() public format?: TimestampRenderingFormat;
@state() private _relative?: string;

View File

@@ -7,9 +7,13 @@ export interface ConditionalBaseConfig extends LovelaceCardConfig {
conditions: Condition[];
}
export type TimestampRenderingFormats =
| "relative"
| "total"
| "date"
| "time"
| "datetime";
export const TIMESTAMP_RENDERING_FORMATS = [
"relative",
"total",
"date",
"time",
"datetime",
] as const;
export type TimestampRenderingFormat =
typeof TIMESTAMP_RENDERING_FORMATS[number];

View File

@@ -8,8 +8,10 @@ import {
any,
array,
assert,
boolean,
assign,
boolean,
dynamic,
enums,
literal,
number,
object,
@@ -19,7 +21,10 @@ import {
union,
} from "superstruct";
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import { customType } from "../../../../common/structs/is-custom-type";
import {
customType,
isCustomType,
} from "../../../../common/structs/is-custom-type";
import { entityId } from "../../../../common/structs/is-entity-id";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/state-badge";
@@ -30,6 +35,7 @@ import "../../../../components/ha-switch";
import type { HomeAssistant } from "../../../../types";
import type { EntitiesCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor";
import { TIMESTAMP_RENDERING_FORMATS } from "../../components/types";
import type { LovelaceRowConfig } from "../../entity-rows/types";
import { headerFooterConfigStructs } from "../../header-footer/structs";
import type { LovelaceCardEditor } from "../../types";
@@ -38,6 +44,7 @@ import "../hui-entities-card-row-editor";
import "../hui-sub-element-editor";
import { processEditorEntities } from "../process-editor-entities";
import { actionConfigStruct } from "../structs/action-struct";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { entitiesConfigStruct } from "../structs/entities-struct";
import {
EditorTarget,
@@ -45,7 +52,6 @@ import {
SubElementEditorConfig,
} from "../types";
import { configElementStyle } from "./config-elements-style";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
const buttonEntitiesRowConfigStruct = object({
type: literal("button"),
@@ -132,6 +138,8 @@ const attributeEntitiesRowConfigStruct = object({
prefix: optional(string()),
suffix: optional(string()),
name: optional(string()),
icon: optional(string()),
format: optional(enums(TIMESTAMP_RENDERING_FORMATS)),
});
const textEntitiesRowConfigStruct = object({
@@ -141,24 +149,53 @@ const textEntitiesRowConfigStruct = object({
icon: optional(string()),
});
const customRowConfigStruct = type({
const customEntitiesRowConfigStruct = type({
type: customType(),
});
const entitiesRowConfigStruct = union([
entitiesConfigStruct,
buttonEntitiesRowConfigStruct,
castEntitiesRowConfigStruct,
conditionalEntitiesRowConfigStruct,
dividerEntitiesRowConfigStruct,
sectionEntitiesRowConfigStruct,
webLinkEntitiesRowConfigStruct,
buttonsEntitiesRowConfigStruct,
attributeEntitiesRowConfigStruct,
callServiceEntitiesRowConfigStruct,
textEntitiesRowConfigStruct,
customRowConfigStruct,
]);
const entitiesRowConfigStruct = dynamic<any>((value) => {
if (value && typeof value === "object" && "type" in value) {
if (isCustomType((value as LovelaceRowConfig).type!)) {
return customEntitiesRowConfigStruct;
}
switch ((value as LovelaceRowConfig).type!) {
case "attribute": {
return attributeEntitiesRowConfigStruct;
}
case "button": {
return buttonEntitiesRowConfigStruct;
}
case "buttons": {
return buttonsEntitiesRowConfigStruct;
}
case "call-service": {
return callServiceEntitiesRowConfigStruct;
}
case "cast": {
return castEntitiesRowConfigStruct;
}
case "conditional": {
return conditionalEntitiesRowConfigStruct;
}
case "divider": {
return dividerEntitiesRowConfigStruct;
}
case "section": {
return sectionEntitiesRowConfigStruct;
}
case "text": {
return textEntitiesRowConfigStruct;
}
case "weblink": {
return webLinkEntitiesRowConfigStruct;
}
}
}
// No "type" property => has to be the default entity row config struct
return entitiesConfigStruct;
});
const cardConfigStruct = assign(
baseLovelaceCardConfig,

View File

@@ -1,7 +1,7 @@
import "@polymer/paper-input/paper-input";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, object, optional, string, assign } from "superstruct";
import { assert, assign, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stateIcon } from "../../../../common/entity/state_icon";
import "../../../../components/entity/ha-entity-attribute-picker";
@@ -13,9 +13,9 @@ import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor";
import { headerFooterConfigStructs } from "../../header-footer/structs";
import { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
@@ -26,6 +26,7 @@ const cardConfigStruct = assign(
attribute: optional(string()),
unit: optional(string()),
theme: optional(string()),
state_color: optional(boolean()),
footer: optional(headerFooterConfigStructs),
})
);
@@ -64,6 +65,10 @@ export class HuiEntityCardEditor
return this._config!.unit || "";
}
get _state_color(): boolean {
return this._config!.state_color ?? false;
}
get _theme(): string {
return this._config!.theme || "";
}
@@ -135,12 +140,27 @@ export class HuiEntityCardEditor
@value-changed=${this._valueChanged}
></paper-input>
</div>
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
<div class="side-by-side">
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
>
</hui-theme-select-editor>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.state_color"
)}
>
<ha-switch
.checked=${this._config!.state_color}
.configValue=${"state_color"}
@change=${this._valueChanged}
>
</ha-switch>
</ha-formfield>
</div>
</div>
`;
}

View File

@@ -1,4 +1,5 @@
import { union, object, string, optional, boolean } from "superstruct";
import { union, object, string, optional, boolean, enums } from "superstruct";
import { TIMESTAMP_RENDERING_FORMATS } from "../../components/types";
import { actionConfigStruct } from "./action-struct";
export const entitiesConfigStruct = union([
@@ -8,7 +9,7 @@ export const entitiesConfigStruct = union([
icon: optional(string()),
image: optional(string()),
secondary_info: optional(string()),
format: optional(string()),
format: optional(enums(TIMESTAMP_RENDERING_FORMATS)),
state_color: optional(boolean()),
tap_action: optional(actionConfigStruct),
hold_action: optional(actionConfigStruct),

View File

@@ -7,6 +7,7 @@ import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import { navigate } from "../../../../common/navigate";
import "../../../../components/ha-circular-progress";
import "../../../../components/ha-dialog";
import "../../../../components/ha-alert";
import "../../../../components/ha-icon-button";
import type {
LovelaceBadgeConfig,
@@ -31,6 +32,11 @@ import {
import "./hui-view-editor";
import "./hui-view-visibility-editor";
import { EditViewDialogParams } from "./show-edit-view-dialog";
import {
DEFAULT_VIEW_LAYOUT,
PANEL_VIEW_LAYOUT,
VIEWS_NO_BADGE_SUPPORT,
} from "../../views/const";
@customElement("hui-dialog-edit-view")
export class HuiDialogEditView extends LitElement {
@@ -50,6 +56,15 @@ export class HuiDialogEditView extends LitElement {
private _curTabIndex = 0;
get _type(): string {
if (!this._config) {
return DEFAULT_VIEW_LAYOUT;
}
return this._config.panel
? PANEL_VIEW_LAYOUT
: this._config.type || DEFAULT_VIEW_LAYOUT;
}
public showDialog(params: EditViewDialogParams): void {
this._params = params;
@@ -107,13 +122,13 @@ export class HuiDialogEditView extends LitElement {
content = html`
${this._badges?.length
? html`
${this._config?.panel
${VIEWS_NO_BADGE_SUPPORT.includes(this._type)
? html`
<p class="warning">
<ha-alert alert-type="warning">
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_badges.panel_mode"
"ui.panel.lovelace.editor.edit_badges.view_no_badges"
)}
</p>
</ha-alert>
`
: ""}
<div class="preview-badges">
@@ -408,10 +423,6 @@ export class HuiDialogEditView extends LitElement {
margin: 12px 16px;
flex-wrap: wrap;
}
.warning {
color: var(--warning-color);
text-align: center;
}
@media all and (min-width: 600px) {
ha-dialog {

View File

@@ -9,6 +9,11 @@ import "../../../../components/ha-switch";
import { LovelaceViewConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types";
import "../../components/hui-theme-select-editor";
import {
DEFAULT_VIEW_LAYOUT,
PANEL_VIEW_LAYOUT,
SIDEBAR_VIEW_LAYOUT,
} from "../../views/const";
import { configElementStyle } from "../config-elements/config-elements-style";
import { EditorTarget } from "../types";
@@ -60,9 +65,11 @@ export class HuiViewEditor extends LitElement {
get _type(): string {
if (!this._config) {
return "masonry";
return DEFAULT_VIEW_LAYOUT;
}
return this._config.panel ? "panel" : this._config.type || "masonry";
return this._config.panel
? PANEL_VIEW_LAYOUT
: this._config.type || DEFAULT_VIEW_LAYOUT;
}
set config(config: LovelaceViewConfig) {
@@ -125,7 +132,7 @@ export class HuiViewEditor extends LitElement {
attr-for-selected="type"
@iron-select=${this._typeChanged}
>
${["masonry", "sidebar", "panel"].map(
${[DEFAULT_VIEW_LAYOUT, SIDEBAR_VIEW_LAYOUT, PANEL_VIEW_LAYOUT].map(
(type) => html`<paper-item .type=${type}>
${this.hass.localize(
`ui.panel.lovelace.editor.edit_view.types.${type}`

View File

@@ -11,7 +11,7 @@ import {
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import { compare } from "../../../../common/string/compare";
import { stringCompare } from "../../../../common/string/compare";
import { HaSwitch } from "../../../../components/ha-switch";
import "../../../../components/user/ha-user-badge";
import { LovelaceViewConfig, ShowViewConfig } from "../../../../data/lovelace";
@@ -43,7 +43,7 @@ export class HuiViewVisibilityEditor extends LitElement {
@state() private _visible!: boolean | ShowViewConfig[];
private _sortedUsers = memoizeOne((users: User[]) =>
users.sort((a, b) => compare(a.name, b.name))
users.sort((a, b) => stringCompare(a.name, b.name))
);
protected firstUpdated(changedProps: PropertyValues) {

View File

@@ -10,11 +10,13 @@ import {
import { customElement, property, state } from "lit/decorators";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
import { debounce } from "../../../common/util/debounce";
import "../../../components/ha-slider";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { setValue } from "../../../data/input_text";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { installResizeObserver } from "../common/install-resize-observer";
import "../components/hui-generic-entity-row";
import { createEntityNotFoundWarning } from "../components/hui-warning";
import { EntityConfig, LovelaceRow } from "./types";
@@ -29,6 +31,8 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow {
private _updated?: boolean;
private _resizeObserver?: ResizeObserver;
public setConfig(config: EntityConfig): void {
if (!config) {
throw new Error("Invalid configuration");
@@ -41,6 +45,11 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow {
if (this._updated && !this._loaded) {
this._initialLoad();
}
this._attachObserver();
}
public disconnectedCallback(): void {
this._resizeObserver?.disconnect();
}
protected firstUpdated(): void {
@@ -48,6 +57,7 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow {
if (this.isConnected && !this._loaded) {
this._initialLoad();
}
this._attachObserver();
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
@@ -120,6 +130,10 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow {
static get styles(): CSSResultGroup {
return css`
:host {
display: block;
cursor: pointer;
}
.flex {
display: flex;
align-items: center;
@@ -137,22 +151,36 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow {
width: 100%;
max-width: 200px;
}
:host {
cursor: pointer;
}
`;
}
private async _initialLoad(): Promise<void> {
this._loaded = true;
await this.updateComplete;
const element = this.shadowRoot!.querySelector(".state") as HTMLElement;
this._measureCard();
}
if (!element || !this.parentElement) {
private _measureCard() {
if (!this.isConnected) {
return;
}
const element = this.shadowRoot!.querySelector(".state") as HTMLElement;
if (!element) {
return;
}
element.hidden = this.clientWidth <= 300;
}
element.hidden = this.parentElement.clientWidth <= 350;
private async _attachObserver(): Promise<void> {
if (!this._resizeObserver) {
await installResizeObserver();
this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false)
);
}
if (this.isConnected) {
this._resizeObserver.observe(this);
}
}
private get _inputElement(): { value: string } {

View File

@@ -10,11 +10,13 @@ import {
import { customElement, property, state } from "lit/decorators";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
import { debounce } from "../../../common/util/debounce";
import "../../../components/ha-slider";
import { UNAVAILABLE } from "../../../data/entity";
import { setValue } from "../../../data/input_text";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { installResizeObserver } from "../common/install-resize-observer";
import "../components/hui-generic-entity-row";
import { createEntityNotFoundWarning } from "../components/hui-warning";
import { EntityConfig, LovelaceRow } from "./types";
@@ -29,6 +31,8 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow {
private _updated?: boolean;
private _resizeObserver?: ResizeObserver;
public setConfig(config: EntityConfig): void {
if (!config) {
throw new Error("Invalid configuration");
@@ -41,6 +45,11 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow {
if (this._updated && !this._loaded) {
this._initialLoad();
}
this._attachObserver();
}
public disconnectedCallback(): void {
this._resizeObserver?.disconnect();
}
protected firstUpdated(): void {
@@ -48,6 +57,7 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow {
if (this.isConnected && !this._loaded) {
this._initialLoad();
}
this._attachObserver();
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
@@ -120,6 +130,10 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow {
static get styles(): CSSResultGroup {
return css`
:host {
cursor: pointer;
display: block;
}
.flex {
display: flex;
align-items: center;
@@ -137,22 +151,36 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow {
width: 100%;
max-width: 200px;
}
:host {
cursor: pointer;
}
`;
}
private async _initialLoad(): Promise<void> {
this._loaded = true;
await this.updateComplete;
const element = this.shadowRoot!.querySelector(".state") as HTMLElement;
this._measureCard();
}
if (!element || !this.parentElement) {
private _measureCard() {
if (!this.isConnected) {
return;
}
const element = this.shadowRoot!.querySelector(".state") as HTMLElement;
if (!element) {
return;
}
element.hidden = this.clientWidth <= 300;
}
element.hidden = this.parentElement.clientWidth <= 350;
private async _attachObserver(): Promise<void> {
if (!this._resizeObserver) {
await installResizeObserver();
this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false)
);
}
if (this.isConnected) {
this._resizeObserver.observe(this);
}
}
private get _inputElement(): { value: string } {

View File

@@ -20,11 +20,11 @@ import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row";
import "../components/hui-timestamp-display";
import { createEntityNotFoundWarning } from "../components/hui-warning";
import { TimestampRenderingFormats } from "../components/types";
import { TimestampRenderingFormat } from "../components/types";
import { LovelaceRow } from "./types";
interface SensorEntityConfig extends EntitiesCardEntityConfig {
format?: TimestampRenderingFormats;
format?: TimestampRenderingFormat;
}
@customElement("hui-sensor-entity-row")

View File

@@ -1,7 +1,7 @@
import { ActionConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { Condition } from "../common/validate-condition";
import { TimestampRenderingFormats } from "../components/types";
import { TimestampRenderingFormat } from "../components/types";
export interface EntityConfig {
entity: string;
@@ -92,5 +92,5 @@ export interface AttributeRowConfig extends EntityConfig {
attribute: string;
prefix?: string;
suffix?: string;
format?: TimestampRenderingFormats;
format?: TimestampRenderingFormat;
}

View File

@@ -0,0 +1,4 @@
export const DEFAULT_VIEW_LAYOUT = "masonry";
export const PANEL_VIEW_LAYOUT = "panel";
export const SIDEBAR_VIEW_LAYOUT = "sidebar";
export const VIEWS_NO_BADGE_SUPPORT = [PANEL_VIEW_LAYOUT, SIDEBAR_VIEW_LAYOUT];

View File

@@ -20,9 +20,7 @@ import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"
import { confDeleteCard } from "../editor/delete-card";
import { generateLovelaceViewStrategy } from "../strategies/get-strategy";
import type { Lovelace, LovelaceBadge, LovelaceCard } from "../types";
const DEFAULT_VIEW_LAYOUT = "masonry";
const PANEL_VIEW_LAYOUT = "panel";
import { PANEL_VIEW_LAYOUT, DEFAULT_VIEW_LAYOUT } from "./const";
declare global {
// for fire event
@@ -131,6 +129,18 @@ export class HUIView extends ReactiveElement {
});
this._layoutElement.hass = this.hass;
const oldHass = changedProperties.get("hass") as
| this["hass"]
| undefined;
if (
!oldHass ||
this.hass.themes !== oldHass.themes ||
this.hass.selectedTheme !== oldHass.selectedTheme
) {
applyThemesOnElement(this, this.hass.themes, this._viewConfigTheme);
}
}
if (changedProperties.has("narrow")) {
this._layoutElement.narrow = this.narrow;
@@ -145,17 +155,6 @@ export class HUIView extends ReactiveElement {
this._layoutElement.badges = this._badges;
}
}
const oldHass = changedProperties.get("hass") as this["hass"] | undefined;
if (
changedProperties.has("hass") &&
(!oldHass ||
this.hass.themes !== oldHass.themes ||
this.hass.selectedTheme !== oldHass.selectedTheme)
) {
applyThemesOnElement(this, this.hass.themes, this._viewConfigTheme);
}
}
private async _initializeConfig() {

View File

@@ -6,7 +6,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { computeStateName } from "../../common/entity/compute_state_name";
import { compare } from "../../common/string/compare";
import { stringCompare } from "../../common/string/compare";
import { createCloseHeading } from "../../components/ha-dialog";
import { UNAVAILABLE_STATES } from "../../data/entity";
import { BROWSER_PLAYER } from "../../data/media-player";
@@ -52,7 +52,9 @@ export class HuiDialogSelectMediaPlayer extends LitElement {
)}</mwc-list-item
>
${this._params.mediaSources
.sort((a, b) => compare(computeStateName(a), computeStateName(b)))
.sort((a, b) =>
stringCompare(computeStateName(a), computeStateName(b))
)
.map(
(source) => html`
<mwc-list-item

View File

@@ -0,0 +1,26 @@
/* eslint-disable no-extend-native */
// @ts-expect-error
if (!Array.prototype.flat) {
Object.defineProperty(Array.prototype, "flat", {
configurable: true,
writable: true,
value: function (...args) {
const depth = typeof args[0] === "undefined" ? 1 : Number(args[0]) || 0;
const result = [];
const forEach = result.forEach;
const flatDeep = (arr: Array<any>, dpth: number) => {
forEach.call(arr, (val) => {
if (dpth > 0 && Array.isArray(val)) {
flatDeep(val, dpth - 1);
} else {
result.push(val);
}
});
};
flatDeep(this, depth);
return result;
},
});
}

View File

@@ -8,6 +8,7 @@ import { computeStateDisplay } from "../common/entity/compute_state_display";
import { computeRTL } from "../common/util/compute_rtl";
import "../components/entity/state-info";
import { UNAVAILABLE_STATES } from "../data/entity";
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../data/sensor";
import "../panels/lovelace/components/hui-timestamp-display";
import { haStyle } from "../resources/styles";
import type { HomeAssistant } from "../types";
@@ -39,7 +40,8 @@ export class StateCardDisplay extends LitElement {
})}"
>
${computeDomain(this.stateObj.entity_id) === "sensor" &&
this.stateObj.attributes.device_class === "timestamp" &&
this.stateObj.attributes.device_class ===
SENSOR_DEVICE_CLASS_TIMESTAMP &&
!UNAVAILABLE_STATES.includes(this.stateObj.state)
? html` <hui-timestamp-display
.hass=${this.hass}

View File

@@ -97,10 +97,6 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
themeSettings
);
// Now determine value that should be stored in the local storage settings
darkMode =
darkMode || !!(darkPreferred && this.hass.themes.default_dark_theme);
if (darkMode !== this.hass.themes.darkMode) {
this._updateHass({
themes: { ...this.hass.themes!, darkMode },

View File

@@ -381,7 +381,7 @@
"clear": "Clear",
"show_areas": "Show areas",
"area": "Area",
"add_new": "Add new area...",
"add_new": "Add new area",
"no_areas": "You don't have any areas",
"no_match": "No matching areas found",
"add_dialog": {
@@ -462,11 +462,11 @@
},
"history_charts": {
"history_disabled": "History integration disabled",
"loading_history": "Loading state history...",
"loading_history": "Loading state history",
"no_history_found": "No state history found."
},
"statistics_charts": {
"loading_statistics": "Loading statistics...",
"loading_statistics": "Loading statistics",
"no_statistics_found": "No statistics found.",
"statistic_types": {
"min": "min",
@@ -700,7 +700,7 @@
"control": "Control",
"related": "Related",
"dismiss": "Dismiss",
"no_unique_id": "This entity (\"{entity_id}\") does not have a unique ID, therefore its settings cannot be managed from the UI. See the {faq_link} for more detail.",
"no_unique_id": "This entity (''{entity_id}'') does not have a unique ID, therefore its settings cannot be managed from the UI. See the {faq_link} for more detail.",
"faq": "documentation",
"info_customize": "You can overwrite some attributes in the {customize_link} section.",
"customize_link": "entity customizations",
@@ -873,12 +873,12 @@
"config": {
"no_type_provided": "No type provided.",
"error_detected": "Configuration errors detected",
"editor_not_available": "No visual editor available for type \"{type}\".",
"editor_not_available": "No visual editor available for type ''{type}''.",
"editor_not_supported": "Visual editor is not supported for this configuration",
"edit_in_yaml_supported": "You can still edit your config in YAML.",
"key_missing": "Required key \"{key}\" is missing.",
"key_not_expected": "Key \"{key}\" is not expected or not supported by the visual editor.",
"key_wrong_type": "The provided value for \"{key}\" is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong}).",
"key_missing": "Required key ''{key}'' is missing.",
"key_not_expected": "Key ''{key}'' is not expected or not supported by the visual editor.",
"key_wrong_type": "The provided value for ''{key}'' is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong}).",
"no_template_editor_support": "Templates not supported in visual editor"
},
"supervisor": {
@@ -904,7 +904,7 @@
},
"notification_toast": {
"service_call_failed": "Failed to call service {service}.",
"connection_lost": "Connection lost. Reconnecting...",
"connection_lost": "Connection lost. Reconnecting",
"started": "Home Assistant has started!",
"starting": "Home Assistant is starting, not everything will be available until it is finished.",
"wrapping_up_startup": "Wrapping up startup, not everything will be available until it is finished.",
@@ -1109,6 +1109,10 @@
"title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement 'kWh' or 'Wh':"
},
"entity_unexpected_unit_gas": {
"title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement 'kWh', 'm³' or 'ft³':"
},
"entity_unexpected_unit_price": {
"title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'' or ''{currency}/Wh'':"
@@ -1208,7 +1212,7 @@
"description": "View the Home Assistant logs",
"details": "Log Details ({level})",
"load_full_log": "Load Full Home Assistant Log",
"loading_log": "Loading error log...",
"loading_log": "Loading error log",
"no_errors": "No errors have been reported",
"no_issues": "There are no new issues!",
"clear": "Clear",
@@ -1349,7 +1353,7 @@
},
"server_management": {
"heading": "Server management",
"introduction": "Control your Home Assistant server... from Home Assistant.",
"introduction": "Control your Home Assistant server from Home Assistant.",
"restart": "Restart",
"confirm_restart": "Are you sure you want to restart Home Assistant?",
"stop": "Stop",
@@ -1494,7 +1498,7 @@
},
"state": {
"label": "State",
"attribute": "Attribute (Optional)",
"attribute": "Attribute (optional)",
"from": "From",
"for": "For",
"to": "To"
@@ -1765,14 +1769,14 @@
},
"add": {
"header": "Import a blueprint",
"import_header": "Blueprint \"{name}\"",
"import_header": "Blueprint ''{name}''",
"import_introduction_link": "You can import blueprints of other users from Github and the {community_link}. Enter the URL of the blueprint below.",
"community_forums": "community forums",
"url": "URL of the blueprint",
"raw_blueprint": "Blueprint content",
"importing": "Loading blueprint...",
"importing": "Loading blueprint",
"import_btn": "Preview blueprint",
"saving": "Importing blueprint...",
"saving": "Importing blueprint",
"save_btn": "Import blueprint",
"error_no_url": "Please enter the URL of the blueprint.",
"unsupported_blueprint": "This blueprint is not supported",
@@ -1943,9 +1947,9 @@
"integrations_introduction2": "Check the website for ",
"integrations_link_all_features": " all available features",
"connected": "Connected",
"connecting": "Connecting...",
"connecting": "Connecting",
"not_connected": "Not Connected",
"fetching_subscription": "Fetching subscription...",
"fetching_subscription": "Fetching subscription",
"tts": {
"title": "Text to Speech",
"info": "Bring personality to your home by having it speak to you by using our Text-to-Speech services. You can use this in automations and scripts by using the {service} service.",
@@ -2017,7 +2021,7 @@
"no_hooks_yet2": " or by creating a ",
"no_hooks_yet_link_automation": "webhook automation",
"link_learn_more": "Learn more about creating webhook-powered automations.",
"loading": "Loading...",
"loading": "Loading",
"manage": "Manage",
"disable_hook_error_msg": "Failed to disable webhook:"
}
@@ -2094,17 +2098,17 @@
"create": "Create automation with device",
"create_disable": "Can't create automation with disabled device",
"triggers": {
"caption": "Do something when...",
"caption": "Do something when",
"no_triggers": "No triggers",
"unknown_trigger": "Unknown trigger"
},
"conditions": {
"caption": "Only do something if...",
"caption": "Only do something if",
"no_conditions": "No conditions",
"unknown_condition": "Unknown condition"
},
"actions": {
"caption": "When something is triggered...",
"caption": "When something is triggered",
"no_actions": "No actions",
"unknown_action": "Unknown action"
},
@@ -2490,7 +2494,7 @@
"wakeup_header": "Wake-up Instructions for",
"wakeup_instructions_source": "Wake-up instructions are sourced from the OpenZWave community device database.",
"start_refresh_button": "Start Refresh",
"refreshing_description": "Refreshing node information...",
"refreshing_description": "Refreshing node information",
"node_status": "Node Status",
"step": "Step"
},
@@ -2571,7 +2575,7 @@
"update_button": "Update Configuration"
},
"add_device_page": {
"spinner": "Searching for ZHA Zigbee devices...",
"spinner": "Searching for ZHA Zigbee devices",
"pairing_mode": "Make sure your devices are in pairing mode. Check the instructions of your device on how to do this.",
"discovered_text": "Devices will show up here once discovered.",
"no_devices_found": "No devices were found, make sure they are in pairing mode and keep them awake while discovering is running.",
@@ -2701,7 +2705,7 @@
},
"network_status": {
"network_stopped": "Z-Wave Network Stopped",
"network_starting": "Starting Z-Wave Network...",
"network_starting": "Starting Z-Wave Network",
"network_starting_note": "This may take a while depending on the size of your network.",
"network_started": "Z-Wave Network Started",
"network_started_note_some_queried": "Awake nodes have been queried. Sleeping nodes will be queried when they wake.",
@@ -2875,7 +2879,7 @@
"logs": {
"title": "Z-Wave JS Logs",
"log_level": "Log Level",
"subscribed_to_logs": "Subscribed to Z-Wave JS Log Messages...",
"subscribed_to_logs": "Subscribed to Z-Wave JS Log Messages",
"log_level_changed": "Log Level changed to: {level}"
}
}
@@ -2884,7 +2888,7 @@
"cards": {
"confirm_delete": "Are you sure you want to delete this card?",
"actions": {
"action_confirmation": "Are you sure you want to run action \"{action}\"?",
"action_confirmation": "Are you sure you want to run action ''{action}''?",
"no_entity_more_info": "No entity provided for more info dialog",
"no_entity_toggle": "No entity provided to toggle",
"no_navigation_path": "No navigation path specified",
@@ -2920,7 +2924,7 @@
"description": "Home Assistant ran into trouble while loading your configuration and is now running in safe mode. Take a look at the error log to see what went wrong."
},
"starting": {
"description": "Home Assistant is starting, please wait..."
"description": "Home Assistant is starting, please wait"
}
},
"unused_entities": {
@@ -3016,7 +3020,7 @@
}
},
"edit_badges": {
"panel_mode": "These badges will not be displayed because this view is in \"Panel Mode\"."
"view_no_badges": "Badges are not be supported by the current view type."
},
"edit_card": {
"header": "Card Configuration",
@@ -3110,8 +3114,8 @@
"change_type": "Change type"
},
"config": {
"required": "Required",
"optional": "Optional"
"required": "required",
"optional": "optional"
},
"entities": {
"name": "Entities",
@@ -3513,7 +3517,6 @@
"page-authorize": {
"initializing": "Initializing",
"authorizing_client": "You're about to give {clientId} access to your Home Assistant instance.",
"logging_in_to_with": "Logging in to **{locationName}** with **{authProviderName}**.",
"logging_in_with": "Logging in with **{authProviderName}**.",
"pick_auth_provider": "Or log in with",
"abort_intro": "Login aborted",
@@ -3664,7 +3667,7 @@
"data": "Event Data (YAML, optional)",
"fire_event": "Fire Event",
"event_fired": "Event {name} fired",
"available_events": "Available Events",
"active_listeners": "Active listeners",
"count_listeners": " ({count} listeners)",
"listen_to_events": "Listen to events",
"listening_to": "Listening to",
@@ -3816,7 +3819,7 @@
"log": "Log"
},
"configuration": {
"no_configuration": "This add-on does not expose configuration for you to mess with...",
"no_configuration": "This add-on does not expose configuration for you to mess with",
"audio": {
"header": "Audio",
"default": "Default",
@@ -4096,6 +4099,7 @@
"failed_to_shutdown": "Failed to shutdown the host",
"failed_to_set_hostname": "Setting hostname failed",
"failed_to_import_from_usb": "Failed to import from USB",
"failed_to_move": "Failed to move data disk",
"used_space": "Used space",
"hostname": "Hostname",
"change_hostname": "Change Hostname",
@@ -4111,7 +4115,8 @@
"confirm_shutdown": "Are you sure you want to shutdown the host?",
"shutdown_host": "Shutdown host",
"hardware": "Hardware",
"import_from_usb": "Import from USB"
"import_from_usb": "Import from USB",
"data_disk_move": "Move to data disk"
},
"core": {
"cpu_usage": "Core CPU Usage",
@@ -4198,6 +4203,12 @@
"id": "ID",
"attributes": "Attributes",
"device_path": "Device path"
},
"data_disk_move": {
"description": "The current path to the data disk is ''{current_path}'', moving the disk will require a reboot of the host which will be done automatically.",
"confirm_text": "Do you want to move now?",
"cancel": "[%key:ui::common::cancel%]",
"move": "Move"
}
}
}

View File

@@ -55,6 +55,7 @@ const hassAttributeUtil = {
"carbon_dioxide",
"carbon_monoxide",
"current",
"date",
"energy",
"humidity",
"illuminance",

836
yarn.lock

File diff suppressed because it is too large Load Diff