mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-31 12:00:26 +00:00
Compare commits
40 Commits
20210830.0
...
data_disk_
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d1605ba196 | ||
![]() |
2240d019f5 | ||
![]() |
cb11c6b3ea | ||
![]() |
5893559951 | ||
![]() |
8408d25cef | ||
![]() |
1ac2ffcf02 | ||
![]() |
6b6c38c2c8 | ||
![]() |
e55df73a91 | ||
![]() |
360c2cbfa3 | ||
![]() |
aba96674f3 | ||
![]() |
5c3d85fc90 | ||
![]() |
6486b7fd4c | ||
![]() |
5f3e980de0 | ||
![]() |
d0edbec5fb | ||
![]() |
5d46963e8a | ||
![]() |
321f441b63 | ||
![]() |
d55bade070 | ||
![]() |
6ba6b821f5 | ||
![]() |
b3dedae115 | ||
![]() |
5a1070c30f | ||
![]() |
40664997e1 | ||
![]() |
c6e83cb7c0 | ||
![]() |
e7e27e794c | ||
![]() |
1073dbe6ab | ||
![]() |
2bd9b5a015 | ||
![]() |
bc09febd2c | ||
![]() |
b2a87c90a2 | ||
![]() |
d6dbbcb0de | ||
![]() |
9ccb5360b3 | ||
![]() |
0187c4faff | ||
![]() |
605172a0bc | ||
![]() |
8565a0d911 | ||
![]() |
61c8d23a7e | ||
![]() |
5e3487ed59 | ||
![]() |
d5a161769c | ||
![]() |
1692f9c2dd | ||
![]() |
0cbac8bb44 | ||
![]() |
35a81e7f11 | ||
![]() |
ac64d293e7 | ||
![]() |
708b8787c5 |
1
.github/workflows/release.yaml
vendored
1
.github/workflows/release.yaml
vendored
@@ -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
@@ -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}>
|
||||
|
@@ -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")
|
||||
|
@@ -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", {
|
||||
|
81
package.json
81
package.json
@@ -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",
|
||||
|
2
setup.py
2
setup.py
@@ -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",
|
||||
|
@@ -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 {
|
||||
|
@@ -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",
|
||||
|
@@ -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),
|
||||
|
@@ -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),
|
||||
})
|
||||
|
@@ -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":
|
||||
|
@@ -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";
|
||||
}
|
||||
|
||||
|
@@ -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());
|
||||
|
@@ -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);
|
||||
|
@@ -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]);
|
||||
|
||||
|
@@ -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 || "")
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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({
|
||||
|
@@ -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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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, {
|
||||
|
@@ -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,
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 = {};
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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(
|
||||
|
@@ -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>
|
||||
`;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
)
|
||||
);
|
||||
|
||||
|
@@ -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();
|
||||
};
|
@@ -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;
|
||||
}
|
||||
|
@@ -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({
|
||||
|
@@ -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 = (
|
||||
|
@@ -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) {
|
||||
|
@@ -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();
|
||||
};
|
||||
|
@@ -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)
|
||||
)
|
||||
|
@@ -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"
|
||||
|
@@ -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()
|
||||
)
|
||||
|
@@ -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";
|
||||
|
@@ -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",
|
||||
|
@@ -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`
|
||||
|
@@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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) {
|
||||
|
@@ -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;
|
||||
|
@@ -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) {
|
||||
|
@@ -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]));
|
||||
}
|
||||
);
|
||||
|
||||
|
@@ -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
|
||||
);
|
||||
|
@@ -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
|
||||
);
|
||||
|
@@ -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}`
|
||||
)
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
)
|
||||
|
@@ -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}`
|
||||
)
|
||||
|
@@ -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) => {
|
||||
|
@@ -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);
|
||||
},
|
||||
|
@@ -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) => {
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -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(
|
||||
|
@@ -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",
|
||||
|
@@ -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 {
|
||||
|
@@ -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);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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 {
|
||||
|
@@ -39,6 +39,7 @@ export interface EntityCardConfig extends LovelaceCardConfig {
|
||||
attribute?: string;
|
||||
unit?: string;
|
||||
theme?: string;
|
||||
state_color?: boolean;
|
||||
}
|
||||
|
||||
export interface EntitiesCardEntityConfig extends EntityConfig {
|
||||
|
@@ -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])
|
||||
)
|
||||
|
@@ -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[] {
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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];
|
||||
|
@@ -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,
|
||||
|
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
@@ -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),
|
||||
|
@@ -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 {
|
||||
|
@@ -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}`
|
||||
|
@@ -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) {
|
||||
|
@@ -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 } {
|
||||
|
@@ -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 } {
|
||||
|
@@ -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")
|
||||
|
@@ -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;
|
||||
}
|
||||
|
4
src/panels/lovelace/views/const.ts
Normal file
4
src/panels/lovelace/views/const.ts
Normal 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];
|
@@ -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() {
|
||||
|
@@ -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
|
||||
|
26
src/resources/array.flat.polyfill.ts
Normal file
26
src/resources/array.flat.polyfill.ts
Normal 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;
|
||||
},
|
||||
});
|
||||
}
|
@@ -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}
|
||||
|
@@ -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 },
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -55,6 +55,7 @@ const hassAttributeUtil = {
|
||||
"carbon_dioxide",
|
||||
"carbon_monoxide",
|
||||
"current",
|
||||
"date",
|
||||
"energy",
|
||||
"humidity",
|
||||
"illuminance",
|
||||
|
Reference in New Issue
Block a user