Compare commits

..

1 Commits

Author SHA1 Message Date
Paulus Schoutsen
94215dc50b dev tools dialog 2021-12-05 17:40:15 -08:00
78 changed files with 641 additions and 1010 deletions

View File

@@ -206,7 +206,6 @@ const createDeviceRegistryEntries = (
model: "Mock Device",
name: "Tag Reader",
sw_version: null,
hw_version: "1.0.0",
id: "mock-device-id",
identifiers: [],
via_device_id: null,

View File

@@ -29,6 +29,10 @@ import {
HassioAddonDetails,
updateHassioAddon,
} from "../../../src/data/hassio/addon";
import {
createHassioPartialBackup,
HassioPartialBackupCreateParams,
} from "../../../src/data/hassio/backup";
import {
extractApiErrorMessage,
ignoreSupervisorError,
@@ -44,6 +48,7 @@ import "../../../src/layouts/hass-subpage";
import "../../../src/layouts/hass-tabs-subpage";
import { SUPERVISOR_UPDATE_NAMES } from "../../../src/panels/config/dashboard/ha-config-updates";
import { HomeAssistant, Route } from "../../../src/types";
import { documentationUrl } from "../../../src/util/documentation-url";
import { addonArchIsSupported, extractChangelog } from "../util/addon";
declare global {
@@ -55,6 +60,7 @@ declare global {
type updateType = "os" | "supervisor" | "core" | "addon";
const changelogUrl = (
hass: HomeAssistant,
entry: updateType,
version: string
): string | undefined => {
@@ -62,19 +68,17 @@ const changelogUrl = (
return undefined;
}
if (entry === "core") {
return version.includes("dev")
return version?.includes("dev")
? "https://github.com/home-assistant/core/commits/dev"
: version.includes("b")
? "https://next.home-assistant.io/latest-release-notes/"
: "https://www.home-assistant.io/latest-release-notes/";
: documentationUrl(hass, "/latest-release-notes/");
}
if (entry === "os") {
return version.includes("dev")
return version?.includes("dev")
? "https://github.com/home-assistant/operating-system/commits/dev"
: `https://github.com/home-assistant/operating-system/releases/tag/${version}`;
}
if (entry === "supervisor") {
return version.includes("dev")
return version?.includes("dev")
? "https://github.com/home-assistant/supervisor/commits/main"
: `https://github.com/home-assistant/supervisor/releases/tag/${version}`;
}
@@ -99,7 +103,7 @@ class UpdateAvailableCard extends LitElement {
@state() private _addonInfo?: HassioAddonDetails;
@state() private _updating = false;
@state() private _action: "backup" | "update" | null = null;
@state() private _error?: string;
@@ -116,7 +120,7 @@ class UpdateAvailableCard extends LitElement {
return html``;
}
const changelog = changelogUrl(this._updateType, this._version_latest);
const changelog = changelogUrl(this.hass, this._updateType, this._version);
return html`
<ha-card
@@ -128,13 +132,7 @@ class UpdateAvailableCard extends LitElement {
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this._version === this._version_latest
? html`<p>
${this.supervisor.localize("update_available.no_update", {
name: this._name,
})}
</p>`
: !this._updating
${this._action === null
? html`
${this._changelogContent
? html`
@@ -168,13 +166,18 @@ class UpdateAvailableCard extends LitElement {
: html`<ha-circular-progress alt="Updating" size="large" active>
</ha-circular-progress>
<p class="progress-text">
${this.supervisor.localize("update_available.updating", {
name: this._name,
version: this._version_latest,
})}
${this._action === "update"
? this.supervisor.localize("update_available.updating", {
name: this._name,
version: this._version_latest,
})
: this.supervisor.localize(
"update_available.creating_backup",
{ name: this._name }
)}
</p>`}
</div>
${this._version !== this._version_latest && !this._updating
${this._action === null
? html`
<div class="card-actions">
${changelog
@@ -221,9 +224,6 @@ class UpdateAvailableCard extends LitElement {
}
get _shouldCreateBackup(): boolean {
if (this._updateType && !["core", "addon"].includes(this._updateType)) {
return false;
}
const checkbox = this.shadowRoot?.querySelector("ha-checkbox");
if (checkbox) {
return checkbox.checked;
@@ -310,16 +310,37 @@ class UpdateAvailableCard extends LitElement {
private async _update() {
this._error = undefined;
this._updating = true;
if (this._shouldCreateBackup) {
let backupArgs: HassioPartialBackupCreateParams;
if (this._updateType === "addon") {
backupArgs = {
name: `addon_${this.addonSlug}_${this._version}`,
addons: [this.addonSlug!],
homeassistant: false,
};
} else {
backupArgs = {
name: `${this._updateType}_${this._version}`,
folders: ["homeassistant"],
homeassistant: true,
};
}
this._action = "backup";
try {
await createHassioPartialBackup(this.hass, backupArgs);
} catch (err: any) {
this._error = extractApiErrorMessage(err);
this._action = null;
return;
}
}
this._action = "update";
try {
if (this._updateType === "addon") {
await updateHassioAddon(
this.hass,
this.addonSlug!,
this._shouldCreateBackup
);
await updateHassioAddon(this.hass, this.addonSlug!);
} else if (this._updateType === "core") {
await updateCore(this.hass, this._shouldCreateBackup);
await updateCore(this.hass);
} else if (this._updateType === "os") {
await updateOS(this.hass);
} else if (this._updateType === "supervisor") {
@@ -328,7 +349,7 @@ class UpdateAvailableCard extends LitElement {
} catch (err: any) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
this._error = extractApiErrorMessage(err);
this._updating = false;
this._action = null;
return;
}
}

View File

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

View File

@@ -208,7 +208,6 @@ export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
export const DOMAINS_INPUT_ROW = [
"cover",
"fan",
"group",
"humidifier",
"input_boolean",
"input_datetime",

View File

@@ -46,7 +46,6 @@ class HaAlert extends LitElement {
rtl: this.rtl,
[this.alertType]: true,
})}"
role="alert"
>
<div class="icon ${this.title ? "" : "no-title"}">
<slot name="icon">

View File

@@ -56,7 +56,6 @@ export class HaRelatedFilterButtonMenu extends LitElement {
return html`
<ha-icon-button
@click=${this._handleClick}
.label=${this.hass.localize("ui.components.related-filter-menu.filter")}
.path=${mdiFilterVariant}
></ha-icon-button>
<mwc-menu-surface

View File

@@ -18,9 +18,13 @@ export class HaChip extends LitElement {
protected render(): TemplateResult {
return html`
<div class="mdc-chip ${this.noText ? "no-text" : ""}">
<div class="mdc-chip">
${this.hasIcon
? html`<div class="mdc-chip__icon mdc-chip__icon--leading">
? html`<div
class="mdc-chip__icon mdc-chip__icon--leading ${this.noText
? "no-text"
: ""}"
>
<slot name="icon"></slot>
</div>`
: null}
@@ -45,10 +49,6 @@ export class HaChip extends LitElement {
color: var(--ha-chip-text-color, var(--primary-text-color));
}
.mdc-chip.no-text {
padding: 0 10px;
}
.mdc-chip:hover {
color: var(--ha-chip-text-color, var(--primary-text-color));
}
@@ -57,8 +57,8 @@ export class HaChip extends LitElement {
--mdc-icon-size: 20px;
color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
}
.mdc-chip.no-text
.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) {
.mdc-chip
.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden).no-text {
margin-right: -4px;
}
`;

View File

@@ -35,7 +35,7 @@ class HaCoverControls extends LitElement {
hidden: !supportsOpen(this.stateObj),
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.open_cover"
"ui.dialogs.more_info_control.open_cover"
)}
@click=${this._onOpenTap}
.disabled=${this._computeOpenDisabled()}
@@ -47,7 +47,7 @@ class HaCoverControls extends LitElement {
hidden: !supportsStop(this.stateObj),
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.stop_cover"
"ui.dialogs.more_info_control.stop_cover"
)}
.path=${mdiStop}
@click=${this._onStopTap}
@@ -58,7 +58,7 @@ class HaCoverControls extends LitElement {
hidden: !supportsClose(this.stateObj),
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.close_cover"
"ui.dialogs.more_info_control.close_cover"
)}
@click=${this._onCloseTap}
.disabled=${this._computeClosedDisabled()}

View File

@@ -30,7 +30,7 @@ class HaCoverTiltControls extends LitElement {
invisible: !supportsOpenTilt(this.stateObj),
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.open_tilt_cover"
"ui.dialogs.more_info_control.open_tilt_cover"
)}
.path=${mdiArrowTopRight}
@click=${this._onOpenTiltTap}
@@ -40,9 +40,7 @@ class HaCoverTiltControls extends LitElement {
class=${classMap({
invisible: !supportsStopTilt(this.stateObj),
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.stop_cover"
)}
.label=${this.hass.localize("ui.dialogs.more_info_control.stop_cover")}
.path=${mdiStop}
@click=${this._onStopTiltTap}
.disabled=${this.stateObj.state === UNAVAILABLE}
@@ -52,7 +50,7 @@ class HaCoverTiltControls extends LitElement {
invisible: !supportsCloseTilt(this.stateObj),
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.close_tilt_cover"
"ui.dialogs.more_info_control.close_tilt_cover"
)}
.path=${mdiArrowBottomLeft}
@click=${this._onCloseTiltTap}

View File

@@ -96,11 +96,7 @@ export class HaDateInput extends LitElement {
attr-for-value="value"
.i18n=${i18n}
>
<paper-input
.label=${this.label}
.disabled=${this.disabled}
no-label-float
>
<paper-input .label=${this.label} no-label-float>
<ha-svg-icon slot="suffix" .path=${mdiCalendar}></ha-svg-icon>
</paper-input>
</vaadin-date-picker-light>`;

View File

@@ -122,20 +122,14 @@ class HaDurationInput extends LitElement {
value %= 60;
}
const newValue: HaDurationData = {
hours,
minutes,
seconds: this._seconds,
};
if (this.enableMillisecond || this._milliseconds) {
newValue.milliseconds = this._milliseconds;
}
newValue[unit] = value;
fireEvent(this, "value-changed", {
value: newValue,
value: {
hours,
minutes,
seconds: this._seconds,
milliseconds: this._milliseconds,
...{ [unit]: value },
},
});
}
}

View File

@@ -66,7 +66,7 @@ export class HaFormString extends LitElement implements HaFormElement {
${isPassword
? html`<ha-icon-button
toggles
.label=${`${this._unmaskedPassword ? "Hide" : "Show"} password`}
.label="Click to toggle between masked and clear password"
@click=${this._toggleUnmaskedPassword}
tabindex="-1"
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}

View File

@@ -1,7 +1,6 @@
import { Formfield } from "@material/mwc-formfield";
import { css, CSSResultGroup } from "lit";
import { customElement } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
@customElement("ha-formfield")
// @ts-expect-error
@@ -14,7 +13,6 @@ export class HaFormfield extends Formfield {
case "HA-CHECKBOX":
case "HA-RADIO":
(input as any).checked = !(input as any).checked;
fireEvent(input, "change");
break;
default:
input.click();

View File

@@ -29,7 +29,7 @@ export class HaIconOverflowMenu extends LitElement {
protected render(): TemplateResult {
return html`
${this.narrow
? html` <!-- Collapsed representation for small screens -->
? html` <!-- Collapsed Representation for Small Screens -->
<ha-button-menu
@click=${this._handleIconOverflowMenuOpened}
@closed=${this._handleIconOverflowMenuClosed}
@@ -59,7 +59,8 @@ export class HaIconOverflowMenu extends LitElement {
)}
</ha-button-menu>`
: html`
<!-- Icon representation for big screens -->
<!-- Icon Representation for Big Screens -->
${this.items.map((item) =>
item.narrowOnly
? ""
@@ -69,12 +70,13 @@ export class HaIconOverflowMenu extends LitElement {
${item.tooltip}
</paper-tooltip>`
: ""}
<ha-icon-button
<mwc-icon-button
@click=${item.action}
.label=${item.label}
.path=${item.path}
.disabled=${item.disabled}
></ha-icon-button>
>
<ha-svg-icon .path=${item.path}></ha-svg-icon>
</mwc-icon-button>
</div> `
)}
`}

View File

@@ -39,7 +39,6 @@ export class HaPictureUpload extends LitElement {
.uploading=${this._uploading}
.value=${this.value ? html`<img .src=${this.value} />` : ""}
@file-picked=${this._handleFilePicked}
@change=${this._handleFileCleared}
accept="image/png, image/jpeg, image/gif"
></ha-file-upload>
`;
@@ -54,10 +53,6 @@ export class HaPictureUpload extends LitElement {
}
}
private async _handleFileCleared() {
this.value = null;
}
private async _cropFile(file: File) {
if (!["image/png", "image/jpeg", "image/gif"].includes(file.type)) {
showAlertDialog(this, {

View File

@@ -1,8 +1,6 @@
import "@material/mwc-list/mwc-list-item";
import "@material/mwc-select/mwc-select";
import "@material/mwc-textfield/mwc-textfield";
import type { TextField } from "@material/mwc-textfield/mwc-textfield";
import { mdiCamera } from "@mdi/js";
import type { Select } from "@material/mwc-select/mwc-select";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import type QrScanner from "qr-scanner";
@@ -10,8 +8,6 @@ import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { LocalizeFunc } from "../common/translations/localize";
import "./ha-alert";
import "./ha-button-menu";
import "@material/mwc-button/mwc-button";
@customElement("ha-qr-scanner")
class HaQrScanner extends LitElement {
@@ -29,8 +25,6 @@ class HaQrScanner extends LitElement {
@query("#canvas-container", true) private _canvasContainer!: HTMLDivElement;
@query("mwc-textfield") private _manualInput?: TextField;
public disconnectedCallback(): void {
super.disconnectedCallback();
this._qrNotFoundCount = 0;
@@ -64,53 +58,34 @@ class HaQrScanner extends LitElement {
}
protected render(): TemplateResult {
return html`${this._error
return html`${this._cameras && this._cameras.length > 1
? html`<mwc-select
.label=${this.localize(
"ui.panel.config.zwave_js.add_node.select_camera"
)}
fixedMenuPosition
naturalMenuWidth
@closed=${stopPropagation}
@selected=${this._cameraChanged}
>
${this._cameras!.map(
(camera) => html`
<mwc-list-item .value=${camera.id}>${camera.label}</mwc-list-item>
`
)}
</mwc-select>`
: ""}
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${navigator.mediaDevices
? html`<video></video>
<div id="canvas-container">
${this._cameras && this._cameras.length > 1
? html`<ha-button-menu
corner="BOTTOM_START"
fixed
@closed=${stopPropagation}
>
<ha-icon-button
slot="trigger"
.label=${this.localize(
"ui.components.qr-scanner.select_camera"
)}
.path=${mdiCamera}
></ha-icon-button>
${this._cameras!.map(
(camera) => html`
<mwc-list-item
.value=${camera.id}
@click=${this._cameraChanged}
>${camera.label}</mwc-list-item
>
`
)}
</ha-button-menu>`
: ""}
</div>`
: html`<ha-alert alert-type="warning">
${!window.isSecureContext
? this.localize("ui.components.qr-scanner.only_https_supported")
: this.localize("ui.components.qr-scanner.not_supported")}
</ha-alert>
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
<div class="row">
<mwc-textfield
.label=${this.localize("ui.components.qr-scanner.enter_qr_code")}
@keyup=${this._manualKeyup}
@paste=${this._manualPaste}
></mwc-textfield>
<mwc-button @click=${this._manualSubmit}
>${this.localize("ui.common.submit")}</mwc-button
>
</div>`}`;
<div id="canvas-container"></div>`
: html`<ha-alert alert-type="warning"
>${!window.isSecureContext
? "You can only use your camera to scan a QR core when using HTTPS."
: "Your browser doesn't support QR scanning."}</ha-alert
>`}`;
}
private async _loadQrScanner() {
@@ -159,49 +134,17 @@ class HaQrScanner extends LitElement {
fireEvent(this, "qr-code-scanned", { value: qrCodeString });
};
private _manualKeyup(ev: KeyboardEvent) {
if (ev.key === "Enter") {
this._qrCodeScanned((ev.target as TextField).value);
}
}
private _manualPaste(ev: ClipboardEvent) {
this._qrCodeScanned(
// @ts-ignore
(ev.clipboardData || window.clipboardData).getData("text")
);
}
private _manualSubmit() {
this._qrCodeScanned(this._manualInput!.value);
}
private _cameraChanged(ev: CustomEvent): void {
this._qrScanner?.setCamera((ev.target as any).value);
this._qrScanner?.setCamera((ev.target as Select).value);
}
static styles = css`
canvas {
width: 100%;
}
#canvas-container {
position: relative;
}
ha-button-menu {
position: absolute;
bottom: 8px;
right: 8px;
background: #727272b2;
color: white;
border-radius: 50%;
}
.row {
display: flex;
align-items: center;
}
mwc-textfield {
flex: 1;
margin-right: 8px;
mwc-select {
width: 100%;
margin-bottom: 16px;
}
`;
}

View File

@@ -3,7 +3,6 @@ import {
mdiBell,
mdiCalendar,
mdiCart,
mdiCellphoneCog,
mdiChartBox,
mdiClose,
mdiCog,
@@ -44,10 +43,6 @@ import {
PersistentNotification,
subscribeNotifications,
} from "../data/persistent_notification";
import {
ExternalConfig,
getExternalConfig,
} from "../external_app/external_config";
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant, PanelInfo, Route } from "../types";
@@ -192,8 +187,6 @@ class HaSidebar extends LitElement {
@property({ type: Boolean }) public editMode = false;
@state() private _externalConfig?: ExternalConfig;
@state() private _notifications?: PersistentNotification[];
@state() private _renderEmptySortable = false;
@@ -240,7 +233,6 @@ class HaSidebar extends LitElement {
changedProps.has("expanded") ||
changedProps.has("narrow") ||
changedProps.has("alwaysExpand") ||
changedProps.has("_externalConfig") ||
changedProps.has("_notifications") ||
changedProps.has("editMode") ||
changedProps.has("_renderEmptySortable") ||
@@ -271,12 +263,6 @@ class HaSidebar extends LitElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
if (this.hass && this.hass.auth.external) {
getExternalConfig(this.hass.auth.external).then((conf) => {
this._externalConfig = conf;
});
}
subscribeNotifications(this.hass.connection, (notifications) => {
this._notifications = notifications;
});
@@ -372,7 +358,6 @@ class HaSidebar extends LitElement {
: this._renderPanels(beforeSpacer)}
${this._renderSpacer()}
${this._renderPanels(afterSpacer)}
${this._renderExternalConfiguration()}
</paper-listbox>
`;
}
@@ -402,7 +387,7 @@ class HaSidebar extends LitElement {
) {
return html`
<a
role="option"
aria-role="option"
href=${`/${urlPath}`}
data-panel=${urlPath}
tabindex="-1"
@@ -507,7 +492,7 @@ class HaSidebar extends LitElement {
>
<paper-icon-item
class="notifications"
role="option"
aria-role="option"
@click=${this._handleShowNotificationDrawer}
>
<ha-svg-icon slot="item-icon" .path=${mdiBell}></ha-svg-icon>
@@ -538,7 +523,7 @@ class HaSidebar extends LitElement {
href="/profile"
data-panel="panel"
tabindex="-1"
role="option"
aria-role="option"
aria-label=${this.hass.localize("panel.profile")}
@mouseenter=${this._itemMouseEnter}
@mouseleave=${this._itemMouseLeave}
@@ -557,43 +542,6 @@ class HaSidebar extends LitElement {
</a>`;
}
private _renderExternalConfiguration() {
return html`${!this.hass.user?.is_admin &&
this._externalConfig &&
this._externalConfig.hasSettingsScreen
? html`
<a
role="option"
aria-label=${this.hass.localize(
"ui.sidebar.external_app_configuration"
)}
href="#external-app-configuration"
tabindex="-1"
@click=${this._handleExternalAppConfiguration}
@mouseenter=${this._itemMouseEnter}
@mouseleave=${this._itemMouseLeave}
>
<paper-icon-item>
<ha-svg-icon
slot="item-icon"
.path=${mdiCellphoneCog}
></ha-svg-icon>
<span class="item-text">
${this.hass.localize("ui.sidebar.external_app_configuration")}
</span>
</paper-icon-item>
</a>
`
: ""}`;
}
private _handleExternalAppConfiguration(ev: Event) {
ev.preventDefault();
this.hass.auth.external!.fireMessage({
type: "config_screen/show",
});
}
private get _tooltip() {
return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
}

View File

@@ -13,7 +13,6 @@ export interface DeviceRegistryEntry {
model: string | null;
name: string | null;
sw_version: string | null;
hw_version: string | null;
via_device_id: string | null;
area_id: string | null;
name_by_user: string | null;

View File

@@ -302,8 +302,7 @@ export const installHassioAddon = async (
export const updateHassioAddon = async (
hass: HomeAssistant,
slug: string,
backup: boolean
slug: string
): Promise<void> => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
await hass.callWS({
@@ -311,13 +310,11 @@ export const updateHassioAddon = async (
endpoint: `/store/addons/${slug}/update`,
method: "post",
timeout: null,
data: { backup: backup },
});
} else {
await hass.callApi<HassioResponse<void>>(
"POST",
`hassio/addons/${slug}/update`,
{ backup: backup }
`hassio/addons/${slug}/update`
);
}
};

View File

@@ -156,7 +156,6 @@ export interface MediaPlayerThumbnail {
export interface ControlButton {
icon: string;
// Used as key for action as well as tooltip and aria-label translation key
action: string;
}

View File

@@ -6,18 +6,15 @@ export const restartCore = async (hass: HomeAssistant) => {
await hass.callService("homeassistant", "restart");
};
export const updateCore = async (hass: HomeAssistant, backup: boolean) => {
export const updateCore = async (hass: HomeAssistant) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
await hass.callWS({
type: "supervisor/api",
endpoint: "/core/update",
method: "post",
timeout: null,
data: { backup: backup },
});
} else {
await hass.callApi<HassioResponse<void>>("POST", `hassio/core/update`, {
backup: backup,
});
await hass.callApi<HassioResponse<void>>("POST", `hassio/core/update`);
}
};

View File

@@ -208,11 +208,11 @@ export const enum NodeStatus {
export interface ZwaveJSProvisioningEntry {
/** The device specific key (DSK) in the form aaaaa-bbbbb-ccccc-ddddd-eeeee-fffff-11111-22222 */
dsk: string;
security_classes: SecurityClass[];
additional_properties: {
nodeId?: number;
[prop: string]: any;
};
securityClasses: SecurityClass[];
/**
* Additional properties to be stored in this provisioning entry, e.g. the device ID from a scanned QR code
*/
[prop: string]: any;
}
export interface RequestedGrant {
@@ -278,7 +278,7 @@ export const setZwaveDataCollectionPreference = (
export const fetchZwaveProvisioningEntries = (
hass: HomeAssistant,
entry_id: string
): Promise<ZwaveJSProvisioningEntry[]> =>
): Promise<any> =>
hass.callWS({
type: "zwave_js/get_provisioning_entries",
entry_id,

View File

@@ -0,0 +1,138 @@
import { mdiClose } from "@mdi/js";
import "@polymer/paper-tabs";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state, query } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant, Route } from "../../types";
import "../../components/ha-dialog";
import "../../components/ha-tabs";
import "../../components/ha-icon-button";
import "../../panels/developer-tools/developer-tools-router";
import type { HaDialog } from "../../components/ha-dialog";
import "@material/mwc-button/mwc-button";
@customElement("ha-developer-tools-dialog")
export class HaDeveloperToolsDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _opened = false;
@state() private _route: Route = {
prefix: "/developer-tools",
path: "/state",
};
@query("ha-dialog", true) private _dialog!: HaDialog;
public async showDialog(): Promise<void> {
this._opened = true;
}
public async closeDialog(): Promise<void> {
this._opened = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._opened) {
return html``;
}
return html`
<ha-dialog open @closed=${this.closeDialog}>
<div class="header">
<ha-tabs
scrollable
attr-for-selected="page-name"
.selected=${this._route.path.substr(1)}
@iron-activate=${this.handlePageSelected}
>
<paper-tab page-name="state">
${this.hass.localize(
"ui.panel.developer-tools.tabs.states.title"
)}
</paper-tab>
<paper-tab page-name="service">
${this.hass.localize(
"ui.panel.developer-tools.tabs.services.title"
)}
</paper-tab>
<paper-tab page-name="template">
${this.hass.localize(
"ui.panel.developer-tools.tabs.templates.title"
)}
</paper-tab>
<paper-tab page-name="event">
${this.hass.localize(
"ui.panel.developer-tools.tabs.events.title"
)}
</paper-tab>
<paper-tab page-name="statistics">
${this.hass.localize(
"ui.panel.developer-tools.tabs.statistics.title"
)}
</paper-tab>
</ha-tabs>
<ha-icon-button
.path=${mdiClose}
@click=${this.closeDialog}
></ha-icon-button>
</div>
<developer-tools-router
.route=${this._route}
.narrow=${document.body.clientWidth < 600}
.hass=${this.hass}
></developer-tools-router>
</ha-dialog>
`;
}
protected firstUpdated(changedProps: PropertyValues) {
super.updated(changedProps);
this.hass.loadBackendTranslation("title");
this.hass.loadFragmentTranslation("developer-tools");
}
private handlePageSelected(ev) {
const newPage = ev.detail.item.getAttribute("page-name");
if (newPage !== this._route.path.substr(1)) {
this._route = {
prefix: "/developer-tools",
path: `/${newPage}`,
};
} else {
// scrollTo(0, 0);
}
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
ha-dialog {
--mdc-dialog-min-width: 100vw;
--mdc-dialog-min-height: 100vh;
}
.header {
display: flex;
}
ha-tabs {
flex: 1;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-developer-tools-dialog": HaDeveloperToolsDialog;
}
}

View File

@@ -0,0 +1,12 @@
import { fireEvent } from "../../common/dom/fire_event";
export const loadDeveloperToolDialog = () =>
import("./ha-developer-tools-dialog");
export const showDeveloperToolDialog = (element: HTMLElement): void => {
fireEvent(element, "show-dialog", {
dialogTag: "ha-developer-tools-dialog",
dialogImport: loadDeveloperToolDialog,
dialogParams: {},
});
};

View File

@@ -65,9 +65,6 @@ class MoreInfoMediaPlayer extends LitElement {
action=${control.action}
@click=${this._handleClick}
.path=${control.icon}
.label=${this.hass.localize(
`ui.card.media_player.${control.action}`
)}
>
</ha-icon-button>
`

View File

@@ -99,8 +99,6 @@ export class QuickBar extends LitElement {
private _focusSet = false;
private _focusListElement?: ListItem | null;
public async showDialog(params: QuickBarParams) {
this._commandMode = params.commandMode || this._toggleIfAlreadyOpened();
this._initializeItemsIfNeeded();
@@ -319,8 +317,7 @@ export class QuickBar extends LitElement {
} else if (ev.code === "ArrowDown") {
ev.preventDefault();
this._getItemAtIndex(0)?.focus();
this._focusSet = true;
this._focusListElement = this._getItemAtIndex(0);
this._getItemAtIndex(1)?.focus();
}
}
@@ -353,11 +350,6 @@ export class QuickBar extends LitElement {
this._initializeItemsIfNeeded();
this._filter = this._search;
} else {
if (this._focusSet && this._focusListElement) {
this._focusSet = false;
// @ts-ignore
this._focusListElement.rippleHandlers.endFocus();
}
this._debouncedSetFilter(this._search);
}
}
@@ -374,14 +366,12 @@ export class QuickBar extends LitElement {
private _setFocusFirstListItem() {
// @ts-ignore
this._getItemAtIndex(0)?.rippleHandlers.startFocus();
this._focusListElement = this._getItemAtIndex(0);
}
private _handleListItemKeyDown(ev: KeyboardEvent) {
const isSingleCharacter = ev.key.length === 1;
const isFirstListItem =
(ev.target as HTMLElement).getAttribute("index") === "0";
this._focusListElement = ev.target as ListItem;
if (ev.key === "ArrowUp") {
if (isFirstListItem) {
this._filterInputField?.focus();
@@ -521,13 +511,7 @@ export class QuickBar extends LitElement {
if (page.component) {
const info = this._getNavigationInfoFromConfig(page);
// Add to list, but only if we do not already have an entry for the same path and component
if (
info &&
!items.some(
(e) => e.path === info.path && e.component === info.component
)
) {
if (info) {
items.push(info);
}
}

View File

@@ -37,25 +37,6 @@ declare global {
}
}
const clearUrlParams = () => {
// Clear auth data from url if we have been able to establish a connection
if (location.search.includes("auth_callback=1")) {
const searchParams = new URLSearchParams(location.search);
// https://github.com/home-assistant/home-assistant-js-websocket/blob/master/lib/auth.ts
// Remove all data from QueryCallbackData type
searchParams.delete("auth_callback");
searchParams.delete("code");
searchParams.delete("state");
searchParams.delete("storeToken");
const search = searchParams.toString();
history.replaceState(
null,
"",
`${location.pathname}${search ? `?${search}` : ""}`
);
}
};
const authProm = isExternal
? () =>
import("../external_app/external_auth").then(({ createExternalAuth }) =>
@@ -71,7 +52,23 @@ const authProm = isExternal
const connProm = async (auth) => {
try {
const conn = await createConnection({ auth });
clearUrlParams();
// Clear auth data from url if we have been able to establish a connection
if (location.search.includes("auth_callback=1")) {
const searchParams = new URLSearchParams(location.search);
// https://github.com/home-assistant/home-assistant-js-websocket/blob/master/lib/auth.ts
// Remove all data from QueryCallbackData type
searchParams.delete("auth_callback");
searchParams.delete("code");
searchParams.delete("state");
searchParams.delete("storeToken");
const search = searchParams.toString();
history.replaceState(
null,
"",
`${location.pathname}${search ? `?${search}` : ""}`
);
}
return { auth, conn };
} catch (err: any) {
if (err !== ERR_INVALID_AUTH) {
@@ -88,7 +85,6 @@ const connProm = async (auth) => {
}
auth = await authProm();
const conn = await createConnection({ auth });
clearUrlParams();
return { auth, conn };
}
};

View File

@@ -2,7 +2,7 @@
* Auth class that connects to a native app for authentication.
*/
import { Auth } from "home-assistant-js-websocket";
import { ExternalMessaging, EMMessage } from "./external_messaging";
import { ExternalMessaging, InternalMessage } from "./external_messaging";
const CALLBACK_SET_TOKEN = "externalAuthSetToken";
const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken";
@@ -36,7 +36,7 @@ declare global {
postMessage(payload: BasePayload);
};
externalBus: {
postMessage(payload: EMMessage);
postMessage(payload: InternalMessage);
};
};
};

View File

@@ -1,4 +1,3 @@
import { Connection } from "home-assistant-js-websocket";
import {
externalForwardConnectionEvents,
externalForwardHaptics,
@@ -8,50 +7,39 @@ const CALLBACK_EXTERNAL_BUS = "externalBus";
interface CommandInFlight {
resolve: (data: any) => void;
reject: (err: EMError) => void;
reject: (err: ExternalError) => void;
}
export interface EMMessage {
export interface InternalMessage {
id?: number;
type: string;
payload?: unknown;
}
interface EMError {
interface ExternalError {
code: string;
message: string;
}
interface EMMessageResultSuccess {
interface ExternalMessageResult {
id: number;
type: "result";
success: true;
result: unknown;
}
interface EMMessageResultError {
interface ExternalMessageResultError {
id: number;
type: "result";
success: false;
error: EMError;
error: ExternalError;
}
interface EMExternalMessageRestart {
id: number;
type: "command";
command: "restart";
}
type ExternalMessage =
| EMMessageResultSuccess
| EMMessageResultError
| EMExternalMessageRestart;
type ExternalMessage = ExternalMessageResult | ExternalMessageResultError;
export class ExternalMessaging {
public commands: { [msgId: number]: CommandInFlight } = {};
public connection?: Connection;
public cache: Record<string, any> = {};
public msgId = 0;
@@ -66,7 +54,7 @@ export class ExternalMessaging {
* Send message to external app that expects a response.
* @param msg message to send
*/
public sendMessage<T>(msg: EMMessage): Promise<T> {
public sendMessage<T>(msg: InternalMessage): Promise<T> {
const msgId = ++this.msgId;
msg.id = msgId;
@@ -81,9 +69,7 @@ export class ExternalMessaging {
* Send message to external app without expecting a response.
* @param msg message to send
*/
public fireMessage(
msg: EMMessage | EMMessageResultSuccess | EMMessageResultError
) {
public fireMessage(msg: InternalMessage) {
if (!msg.id) {
msg.id = ++this.msgId;
}
@@ -96,43 +82,6 @@ export class ExternalMessaging {
console.log("Receiving message from external app", msg);
}
if (msg.type === "command") {
if (!this.connection) {
// eslint-disable-next-line no-console
console.warn("Received command without having connection set", msg);
this.fireMessage({
id: msg.id,
type: "result",
success: false,
error: {
code: "commands_not_init",
message: `Commands connection not set`,
},
});
} else if (msg.command === "restart") {
this.connection.socket.close();
this.fireMessage({
id: msg.id,
type: "result",
success: true,
result: null,
});
} else {
// eslint-disable-next-line no-console
console.warn("Received unknown command", msg.command, msg);
this.fireMessage({
id: msg.id,
type: "result",
success: false,
error: {
code: "unknown_command",
message: `Unknown command ${msg.command}`,
},
});
}
return;
}
const pendingCmd = this.commands[msg.id];
if (!pendingCmd) {
@@ -150,7 +99,7 @@ export class ExternalMessaging {
}
}
protected _sendExternal(msg: EMMessage) {
protected _sendExternal(msg: InternalMessage) {
if (__DEV__) {
// eslint-disable-next-line no-console
console.log("Sending message to external app", msg);

View File

@@ -127,9 +127,7 @@ export class HassRouterPage extends ReactiveElement {
// Update the url if we know where we're mounted.
if (route) {
navigate(`${route.prefix}/${result}${location.search}`, {
replace: true,
});
navigate(`${route.prefix}/${result}`, { replace: true });
}
}
}

View File

@@ -51,9 +51,7 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
const path = curPath();
if (["", "/"].includes(path)) {
navigate(`/${getStorageDefaultPanelUrlPath()}${location.search}`, {
replace: true,
});
navigate(`/${getStorageDefaultPanelUrlPath()}`, { replace: true });
}
this._route = {
prefix: "",

View File

@@ -95,11 +95,8 @@ class OnboardingCreateUser extends LitElement {
private _handleValueChanged(
ev: PolymerChangedEvent<HaFormDataContainer>
): void {
const nameChanged = ev.detail.value.name !== this._newUser.name;
this._newUser = ev.detail.value;
if (nameChanged) {
this._maybePopulateUsername();
}
this._maybePopulateUsername();
this._formError.password_confirm =
this._newUser.password !== this._newUser.password_confirm
? this.localize(

View File

@@ -138,8 +138,7 @@ export default class HaAutomationConditionEditor extends LitElement {
if (!ev.detail.isValid) {
return;
}
// @ts-ignore
fireEvent(this, "value-changed", { value: ev.detail.value, yaml: true });
fireEvent(this, "value-changed", { value: ev.detail.value });
}
static get styles(): CSSResultGroup {

View File

@@ -109,7 +109,6 @@ export default class HaAutomationConditionRow extends LitElement {
: ""}
<ha-automation-condition-editor
@ui-mode-not-available=${this._handleUiModeNotAvailable}
@value-changed=${this._handleChangeEvent}
.yamlMode=${this._yamlMode}
.hass=${this.hass}
.condition=${this.condition}
@@ -128,12 +127,6 @@ export default class HaAutomationConditionRow extends LitElement {
}
}
private _handleChangeEvent(ev: CustomEvent) {
if (ev.detail.yaml) {
this._warnings = undefined;
}
}
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:

View File

@@ -1,27 +1,17 @@
import "@polymer/paper-input/paper-input";
import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { assert, literal, object, optional, string, union } from "superstruct";
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/entity/ha-entity-attribute-picker";
import "../../../../../components/entity/ha-entity-picker";
import "../../../../../components/ha-duration-input";
import { StateCondition } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import { forDictStruct } from "../../structs";
import {
ConditionElement,
handleChangeEvent,
} from "../ha-automation-condition-row";
const stateConditionStruct = object({
condition: literal("state"),
entity_id: optional(string()),
attribute: optional(string()),
state: optional(string()),
for: optional(union([string(), forDictStruct])),
});
import "../../../../../components/ha-duration-input";
import { fireEvent } from "../../../../../common/dom/fire_event";
@customElement("ha-automation-condition-state")
export class HaStateCondition extends LitElement implements ConditionElement {
@@ -33,14 +23,19 @@ export class HaStateCondition extends LitElement implements ConditionElement {
return { entity_id: "", state: "" };
}
public shouldUpdate(changedProperties: PropertyValues) {
if (changedProperties.has("condition")) {
try {
assert(this.condition, stateConditionStruct);
} catch (e: any) {
fireEvent(this, "ui-mode-not-available", e);
return false;
}
public willUpdate(changedProperties: PropertyValues): boolean {
if (
changedProperties.has("condition") &&
Array.isArray(this.condition?.state)
) {
fireEvent(
this,
"ui-mode-not-available",
Error(this.hass.localize("ui.errors.config.no_state_array_support"))
);
// We have to stop the update if state is an array.
// Otherwise the state will be changed to a comma-separated string by the input element.
return false;
}
return true;
}

View File

@@ -1,13 +0,0 @@
import { object, optional, number, string } from "superstruct";
export const baseTriggerStruct = object({
platform: string(),
id: optional(string()),
});
export const forDictStruct = object({
days: optional(number()),
hours: optional(number()),
minutes: optional(number()),
seconds: optional(number()),
});

View File

@@ -68,7 +68,7 @@ export const handleChangeEvent = (element: TriggerElement, ev: CustomEvent) => {
}
let newTrigger: Trigger;
if (newVal === undefined || newVal === "") {
if (!newVal) {
newTrigger = { ...element.trigger };
delete newTrigger[name];
} else {
@@ -291,7 +291,6 @@ export default class HaAutomationTriggerRow extends LitElement {
if (!ev.detail.isValid) {
return;
}
this._warnings = undefined;
fireEvent(this, "value-changed", { value: ev.detail.value });
}

View File

@@ -1,41 +1,19 @@
import "@polymer/paper-input/paper-input";
import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import {
assert,
assign,
literal,
object,
optional,
string,
union,
} from "superstruct";
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { hasTemplate } from "../../../../../common/string/has-template";
import "../../../../../components/entity/ha-entity-attribute-picker";
import "../../../../../components/entity/ha-entity-picker";
import "../../../../../components/ha-duration-input";
import { StateTrigger } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import { baseTriggerStruct, forDictStruct } from "../../structs";
import "../../../../../components/ha-duration-input";
import {
handleChangeEvent,
TriggerElement,
} from "../ha-automation-trigger-row";
const stateTriggerStruct = assign(
baseTriggerStruct,
object({
platform: literal("state"),
entity_id: optional(string()),
attribute: optional(string()),
from: optional(string()),
to: optional(string()),
for: optional(union([string(), forDictStruct])),
})
);
@customElement("ha-automation-trigger-state")
export class HaStateTrigger extends LitElement implements TriggerElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -46,16 +24,9 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
return { entity_id: "" };
}
public shouldUpdate(changedProperties: PropertyValues) {
public willUpdate(changedProperties: PropertyValues) {
if (!changedProperties.has("trigger")) {
return true;
}
if (
this.trigger.for &&
typeof this.trigger.for === "object" &&
this.trigger.for.milliseconds === 0
) {
delete this.trigger.for.milliseconds;
return;
}
// Check for templates in trigger. If found, revert to YAML mode.
if (this.trigger && hasTemplate(this.trigger)) {
@@ -64,15 +35,7 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
"ui-mode-not-available",
Error(this.hass.localize("ui.errors.config.no_template_editor_support"))
);
return false;
}
try {
assert(this.trigger, stateTriggerStruct);
} catch (e: any) {
fireEvent(this, "ui-mode-not-available", e);
return false;
}
return true;
}
protected render() {

View File

@@ -224,7 +224,7 @@ class HaBlueprintOverview extends LitElement {
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.blueprints}
.tabs=${configSections.automations}
.columns=${this._columns(this.narrow, this.hass.language)}
.data=${this._processedBlueprints(this.blueprints)}
id="entity_id"

View File

@@ -1,4 +1,4 @@
import { mdiCloudLock } from "@mdi/js";
import { mdiCellphoneCog, mdiCloudLock } from "@mdi/js";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import {
@@ -110,10 +110,29 @@ class HaConfigDashboard extends LitElement {
></ha-config-navigation>
`
: ""}
${this._externalConfig?.hasSettingsScreen
? html`
<ha-config-navigation
.hass=${this.hass}
.narrow=${this.narrow}
.showAdvanced=${this.showAdvanced}
.pages=${[
{
path: "#external-app-configuration",
name: "Companion App",
description: "Location and notifications",
iconPath: mdiCellphoneCog,
iconColor: "#37474F",
core: true,
},
]}
@click=${this._handleExternalAppConfiguration}
></ha-config-navigation>
`
: ""}
<ha-config-navigation
.hass=${this.hass}
.narrow=${this.narrow}
.externalConfig=${this._externalConfig}
.showAdvanced=${this.showAdvanced}
.pages=${configSections.dashboard}
></ha-config-navigation>
@@ -123,6 +142,13 @@ class HaConfigDashboard extends LitElement {
`;
}
private _handleExternalAppConfiguration(ev: Event) {
ev.preventDefault();
this.hass.auth.external!.fireMessage({
type: "config_screen/show",
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
@@ -131,7 +157,7 @@ class HaConfigDashboard extends LitElement {
border-bottom: var(--app-header-border-bottom);
--header-height: 55px;
}
:host(:not([narrow])) ha-card:last-child {
ha-card:last-child {
margin-bottom: 24px;
}
ha-config-section {
@@ -152,7 +178,7 @@ class HaConfigDashboard extends LitElement {
padding-bottom: 0;
}
:host([narrow]) ha-card {
border-radius: 0;
background-color: var(--primary-background-color);
box-shadow: unset;
}

View File

@@ -6,7 +6,6 @@ import { canShowPage } from "../../../common/config/can_show_page";
import "../../../components/ha-card";
import "../../../components/ha-icon-next";
import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
import { ExternalConfig } from "../../../external_app/external_config";
import { PageNavigation } from "../../../layouts/hass-tabs-subpage";
import { HomeAssistant } from "../../../types";
@@ -20,18 +19,12 @@ class HaConfigNavigation extends LitElement {
@property() public pages!: PageNavigation[];
@property() public externalConfig?: ExternalConfig;
protected render(): TemplateResult {
return html`
${this.pages.map((page) =>
(
page.path === "#external-app-configuration"
? this.externalConfig?.hasSettingsScreen
: canShowPage(this.hass, page)
)
canShowPage(this.hass, page)
? html`
<a href=${page.path} role="option" tabindex="-1">
<a href=${page.path} aria-role="option" tabindex="-1">
<paper-icon-item @click=${this._entryClicked}>
<div
class=${page.iconColor ? "icon-background" : ""}
@@ -43,7 +36,8 @@ class HaConfigNavigation extends LitElement {
<paper-item-body two-line>
${page.name ||
this.hass.localize(
`ui.panel.config.dashboard.${page.translationKey}.title`
page.translationKey ||
`ui.panel.config.${page.component}.caption`
)}
${page.component === "cloud" && (page.info as CloudStatus)
? page.info.logged_in
@@ -67,7 +61,7 @@ class HaConfigNavigation extends LitElement {
<div secondary>
${page.description ||
this.hass.localize(
`ui.panel.config.dashboard.${page.translationKey}.description`
`ui.panel.config.${page.component}.description`
)}
</div>
`}
@@ -83,16 +77,6 @@ class HaConfigNavigation extends LitElement {
private _entryClicked(ev) {
ev.currentTarget.blur();
if (
ev.currentTarget.parentElement.href.endsWith(
"#external-app-configuration"
)
) {
ev.preventDefault();
this.hass.auth.external!.fireMessage({
type: "config_screen/show",
});
}
}
static get styles(): CSSResultGroup {

View File

@@ -66,17 +66,6 @@ export class HaDeviceCard extends LitElement {
</div>
`
: ""}
${this.device.hw_version
? html`
<div class="extra-info">
${this.hass.localize(
"ui.panel.config.integrations.config_entry.hardware",
"version",
this.device.hw_version
)}
</div>
`
: ""}
<slot></slot>
</div>
<slot name="actions"></slot>

View File

@@ -1,7 +1,6 @@
import {
mdiAccount,
mdiBadgeAccountHorizontal,
mdiCellphoneCog,
mdiCog,
mdiDevices,
mdiHomeAssistant,
@@ -49,69 +48,73 @@ export const configSections: { [name: string]: PageNavigation[] } = {
dashboard: [
{
path: "/config/integrations",
translationKey: "devices",
name: "Devices & Services",
description: "Integrations, devices, entities and areas",
iconPath: mdiDevices,
iconColor: "#0D47A1",
core: true,
},
{
path: "/config/automation",
translationKey: "automations",
name: "Automations & Scenes",
description: "Automations, blueprints, scenes and scripts",
iconPath: mdiRobot,
iconColor: "#518C43",
components: ["automation", "blueprint", "scene", "script"],
},
{
path: "/config/helpers",
name: "Automation Helpers",
description: "Elements that help build automations",
iconPath: mdiTools,
iconColor: "#4D2EA4",
core: true,
},
{
path: "/config/blueprint",
translationKey: "blueprints",
iconPath: mdiPaletteSwatch,
iconColor: "#64B5F6",
component: "blueprint",
},
{
path: "/hassio",
translationKey: "supervisor",
name: "Add-ons & Backups (Supervisor)",
description: "Create backups, check logs or reboot your system",
iconPath: mdiHomeAssistant,
iconColor: "#4084CD",
component: "hassio",
},
{
path: "/config/lovelace/dashboards",
translationKey: "dashboards",
name: "Dashboards",
description: "Create customized sets of cards to control your home",
iconPath: mdiViewDashboard,
iconColor: "#B1345C",
component: "lovelace",
},
{
path: "/config/energy",
translationKey: "energy",
name: "Energy",
description: "Monitor your energy production and consumption",
iconPath: mdiLightningBolt,
iconColor: "#F1C447",
component: "energy",
},
{
path: "/config/tags",
translationKey: "tags",
name: "Tags",
description:
"Trigger automations when a NFC tag, QR code, etc. is scanned",
iconPath: mdiNfcVariant,
iconColor: "#616161",
component: "tag",
},
{
path: "/config/person",
translationKey: "people",
name: "People & Zones",
description: "Manage the people and zones that Home Assistant tracks",
iconPath: mdiAccount,
iconColor: "#E48629",
components: ["person", "zone", "users"],
},
{
path: "#external-app-configuration",
translationKey: "companion",
iconPath: mdiCellphoneCog,
iconColor: "#8E24AA",
},
{
path: "/config/server_control",
translationKey: "settings",
path: "/config/core",
name: "Settings",
description: "Basic settings, server controls, logs and info",
iconPath: mdiCog,
iconColor: "#4A5963",
core: true,
@@ -152,6 +155,13 @@ export const configSections: { [name: string]: PageNavigation[] } = {
},
],
automations: [
{
component: "blueprint",
path: "/config/blueprint",
translationKey: "ui.panel.config.blueprint.caption",
iconPath: mdiPaletteSwatch,
iconColor: "#518C43",
},
{
component: "automation",
path: "/config/automation",
@@ -173,6 +183,8 @@ export const configSections: { [name: string]: PageNavigation[] } = {
iconPath: mdiScriptText,
iconColor: "#518C43",
},
],
helpers: [
{
component: "helpers",
path: "/config/helpers",
@@ -182,15 +194,6 @@ export const configSections: { [name: string]: PageNavigation[] } = {
core: true,
},
],
blueprints: [
{
component: "blueprint",
path: "/config/blueprint",
translationKey: "ui.panel.config.blueprint.caption",
iconPath: mdiPaletteSwatch,
iconColor: "#518C43",
},
],
tags: [
{
component: "tag",
@@ -444,19 +447,9 @@ class HaPanelConfig extends HassRouterPage {
this.hass.loadBackendTranslation("title");
if (isComponentLoaded(this.hass, "cloud")) {
this._updateCloudStatus();
this.addEventListener("connection-status", (ev) => {
if (ev.detail === "connected") {
this._updateCloudStatus();
}
});
}
if (isComponentLoaded(this.hass, "hassio")) {
this._loadSupervisorUpdates();
this.addEventListener("connection-status", (ev) => {
if (ev.detail === "connected") {
this._loadSupervisorUpdates();
}
});
} else {
this._supervisorUpdates = null;
}

View File

@@ -132,7 +132,7 @@ export class HaConfigHelpers extends LitElement {
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.automations}
.tabs=${configSections.helpers}
.columns=${this._columns(this.narrow, this.hass.language)}
.data=${this._getItems(this._stateItems)}
@row-click=${this._openEditDialog}

View File

@@ -1,7 +1,6 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { property } from "lit/decorators";
import "../../../layouts/hass-tabs-subpage";
import "../../../components/ha-logo-svg";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
@@ -41,14 +40,13 @@ class HaConfigInfo extends LitElement {
href=${documentationUrl(this.hass, "")}
target="_blank"
rel="noreferrer"
>
<ha-logo-svg
title=${this.hass.localize(
><img
src="/static/icons/favicon-192x192.png"
height="192"
alt=${this.hass.localize(
"ui.panel.config.info.home_assistant_logo"
)}
>
</ha-logo-svg>
</a>
/></a>
<br />
<h2>Home Assistant ${hass.connection.haVersion}</h2>
<p>
@@ -195,11 +193,6 @@ class HaConfigInfo extends LitElement {
margin: 0 auto;
padding-bottom: 16px;
}
ha-logo-svg {
padding: 12px;
height: 180px;
width: 180px;
}
`,
];
}

View File

@@ -95,7 +95,7 @@ class OZWConfigDashboard extends LitElement {
<ha-card>
<a
href="/config/ozw/network/${instance.ozw_instance}"
role="option"
aria-role="option"
tabindex="-1"
>
<paper-icon-item>

View File

@@ -129,11 +129,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
<span
>[[localize('ui.panel.config.zwave.node_management.header')]]</span
>
<ha-icon-button
class="toggle-help-icon"
on-click="toggleHelp"
label="[[localize('ui.common.help')]]"
>
<ha-icon-button class="toggle-help-icon" on-click="toggleHelp">
<ha-icon icon="hass:help-circle"></ha-icon>
</ha-icon-button>
</div>

View File

@@ -1,4 +1,5 @@
import "@material/mwc-button/mwc-button";
import type { TextField } from "@material/mwc-textfield/mwc-textfield";
import "@material/mwc-textfield/mwc-textfield";
import { mdiAlertCircle, mdiCheckCircle, mdiQrcodeScan } from "@mdi/js";
import "@polymer/paper-input/paper-input";
@@ -44,8 +45,6 @@ export interface ZWaveJSAddNodeDevice {
class DialogZWaveJSAddNode extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: ZWaveJSAddNodeDialogParams;
@state() private _entryId?: string;
@state() private _status?:
@@ -92,7 +91,6 @@ class DialogZWaveJSAddNode extends LitElement {
}
public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise<void> {
this._params = params;
this._entryId = params.entry_id;
this._status = "loading";
this._checkSmartStartSupport();
@@ -178,16 +176,21 @@ class DialogZWaveJSAddNode extends LitElement {
Search device
</mwc-button>`
: this._status === "qr_scan"
? html`${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<ha-qr-scanner
? html`<ha-qr-scanner
.localize=${this.hass.localize}
@qr-code-scanned=${this._qrCodeScanned}
></ha-qr-scanner>
<mwc-button slot="secondaryAction" @click=${this._startOver}>
${this.hass.localize("ui.panel.config.zwave_js.common.back")}
</mwc-button>`
<p>
If scanning doesn't work, you can enter the QR code value
manually:
</p>
<mwc-textfield
.label=${this.hass.localize(
"ui.panel.config.zwave_js.add_node.enter_qr_code"
)}
.disabled=${this._qrProcessing}
@keydown=${this._qrKeyDown}
></mwc-textfield>`
: this._status === "validate_dsk_enter_pin"
? html`
<p>
@@ -197,9 +200,9 @@ class DialogZWaveJSAddNode extends LitElement {
</p>
${
this._error
? html`<ha-alert alert-type="error">
${this._error}
</ha-alert>`
? html`<ha-alert alert-type="error"
>${this._error}</ha-alert
>`
: ""
}
<div class="flex-container">
@@ -268,7 +271,7 @@ class DialogZWaveJSAddNode extends LitElement {
We have not found any device in inclusion mode. Make sure the
device is active and in inclusion mode.
</p>
<mwc-button slot="primaryAction" @click=${this._startOver}>
<mwc-button slot="primaryAction" @click=${this._startInclusion}>
Retry
</mwc-button>
`
@@ -367,7 +370,7 @@ class DialogZWaveJSAddNode extends LitElement {
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.common.close")}
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
</mwc-button>
`
: this._status === "failed"
@@ -504,6 +507,15 @@ class DialogZWaveJSAddNode extends LitElement {
this._status = "qr_scan";
}
private _qrKeyDown(ev: KeyboardEvent) {
if (this._qrProcessing) {
return;
}
if (ev.key === "Enter") {
this._handleQrCodeScanned((ev.target as TextField).value);
}
}
private _qrCodeScanned(ev: CustomEvent): void {
if (this._qrProcessing) {
return;
@@ -550,16 +562,17 @@ class DialogZWaveJSAddNode extends LitElement {
provisioningInfo
);
this._status = "provisioned";
if (this._params?.addedCallback) {
this._params.addedCallback();
}
} catch (err: any) {
this._error = err.message;
this._status = "failed";
}
} else if (provisioningInfo.version === 0) {
this._inclusionStrategy = InclusionStrategy.Security_S2;
this._startInclusion(provisioningInfo);
// this._startInclusion(provisioningInfo);
this._startInclusion(undefined, undefined, {
dsk: "34673-15546-46480-39591-32400-22155-07715-45994",
security_classes: [0, 1, 7],
});
} else {
this._error = "This QR code is not supported";
this._status = "failed";
@@ -619,10 +632,6 @@ class DialogZWaveJSAddNode extends LitElement {
).supported;
}
private _startOver(_ev: Event) {
this._startInclusion();
}
private _startInclusion(
qrProvisioningInformation?: QRProvisioningInformation,
qrCodeString?: string,
@@ -684,9 +693,6 @@ class DialogZWaveJSAddNode extends LitElement {
if (message.event === "interview completed") {
this._unsubscribe();
this._status = "finished";
if (this._params?.addedCallback) {
this._params.addedCallback();
}
}
if (message.event === "interview stage completed") {

View File

@@ -2,7 +2,6 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
export interface ZWaveJSAddNodeDialogParams {
entry_id: string;
addedCallback?: () => void;
}
export const loadAddNodeDialog = () => import("./dialog-zwave_js-add-node");

View File

@@ -411,7 +411,6 @@ class ZWaveJSConfigDashboard extends LitElement {
private async _addNodeClicked() {
showZWaveJSAddNodeDialog(this, {
entry_id: this.configEntryId!,
addedCallback: () => this._fetchData(),
});
}

View File

@@ -327,9 +327,6 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
if (!("states" in item.metadata)) {
return false;
}
if (Object.keys(item.metadata.states).length !== 2) {
return false;
}
if (!(0 in item.metadata.states) || !(1 in item.metadata.states)) {
return false;
}

View File

@@ -1,4 +1,4 @@
import { mdiCheckCircle, mdiCloseCircleOutline, mdiDelete } from "@mdi/js";
import { mdiDelete } from "@mdi/js";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -42,42 +42,17 @@ class ZWaveJSProvisioned extends LitElement {
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({
included: {
title: this.hass.localize(
"ui.panel.config.zwave_js.provisioned.included"
),
type: "icon",
width: "100px",
template: (_info, provisioningEntry: any) =>
provisioningEntry.additional_properties.nodeId
? html`
<ha-svg-icon
.label=${this.hass.localize(
"ui.panel.config.zwave_js.provisioned.included"
)}
.path=${mdiCheckCircle}
></ha-svg-icon>
`
: html`
<ha-svg-icon
.label=${this.hass.localize(
"ui.panel.config.zwave_js.provisioned.not_included"
)}
.path=${mdiCloseCircleOutline}
></ha-svg-icon>
`,
},
dsk: {
title: this.hass.localize("ui.panel.config.zwave_js.provisioned.dsk"),
sortable: true,
filterable: true,
grows: true,
},
security_classes: {
securityClasses: {
title: this.hass.localize(
"ui.panel.config.zwave_js.provisioned.security_classes"
),
width: "30%",
width: "15%",
hidden: narrow,
filterable: true,
sortable: true,
@@ -85,7 +60,7 @@ class ZWaveJSProvisioned extends LitElement {
securityClasses
.map((secClass) =>
this.hass.localize(
`ui.panel.config.zwave_js.security_classes.${SecurityClass[secClass]}.title`
`ui.panel.config.zwave_js.security_classes.${SecurityClass[secClass]}`
)
)
.join(", "),
@@ -95,7 +70,6 @@ class ZWaveJSProvisioned extends LitElement {
"ui.panel.config.zwave_js.provisioned.unprovison"
),
type: "icon-button",
width: "100px",
template: (_info, provisioningEntry: any) => html`
<ha-icon-button
.label=${this.hass.localize(
@@ -123,8 +97,6 @@ class ZWaveJSProvisioned extends LitElement {
}
private _unprovision = async (ev) => {
const dsk = ev.currentTarget.provisioningEntry.dsk;
const confirm = await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.zwave_js.provisioned.confirm_unprovision_title"
@@ -141,8 +113,11 @@ class ZWaveJSProvisioned extends LitElement {
return;
}
await unprovisionZwaveSmartStartNode(this.hass, this.configEntryId, dsk);
this._fetchData();
await unprovisionZwaveSmartStartNode(
this.hass,
this.configEntryId,
ev.currentTarget.provisioningEntry.dsk
);
};
}

View File

@@ -5,7 +5,6 @@ import "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { componentsWithService } from "../../../common/config/components_with_service";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-card";
import { checkCoreConfig } from "../../../data/core";
@@ -158,20 +157,18 @@ export class HaConfigServerControl extends LitElement {
"ui.panel.config.server_control.section.server_management.restart"
)}
</ha-call-service-button>
${!isComponentLoaded(this.hass, "hassio")
? html`<ha-call-service-button
class="warning"
.hass=${this.hass}
domain="homeassistant"
service="stop"
confirmation=${this.hass.localize(
"ui.panel.config.server_control.section.server_management.confirm_stop"
)}
>${this.hass.localize(
"ui.panel.config.server_control.section.server_management.stop"
)}
</ha-call-service-button>`
: ""}
<ha-call-service-button
class="warning"
.hass=${this.hass}
domain="homeassistant"
service="stop"
confirmation=${this.hass.localize(
"ui.panel.config.server_control.section.server_management.confirm_stop"
)}
>${this.hass.localize(
"ui.panel.config.server_control.section.server_management.stop"
)}
</ha-call-service-button>
</div>
</ha-card>

View File

@@ -106,7 +106,6 @@ class DialogUserDetail extends LitElement {
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.disabled=${user.system_generated}
.checked=${this._localOnly}
@change=${this._localOnlyChanged}
>

View File

@@ -18,7 +18,6 @@ import "../../../components/ha-code-editor";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import "../../../components/ha-checkbox";
import "../../../components/ha-expansion-panel";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import { EventsMixin } from "../../../mixins/events-mixin";
import LocalizeMixin from "../../../mixins/localize-mixin";
@@ -41,10 +40,6 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
padding: 16px;
}
ha-expansion-panel {
margin: 0 8px 16px;
}
.inputs {
width: 100%;
max-width: 400px;
@@ -140,77 +135,72 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
padding: 0;
}
</style>
<p>
[[localize('ui.panel.developer-tools.tabs.states.description1')]]<br />
[[localize('ui.panel.developer-tools.tabs.states.description2')]]
</p>
<div class="state-wrapper flex layout horizontal">
<div class="inputs">
<ha-entity-picker
autofocus
hass="[[hass]]"
value="{{_entityId}}"
on-change="entityIdChanged"
allow-custom-entity
></ha-entity-picker>
<paper-input
label="[[localize('ui.panel.developer-tools.tabs.states.state')]]"
required
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
value="{{_state}}"
class="state-input"
></paper-input>
<p>
[[localize('ui.panel.developer-tools.tabs.states.state_attributes')]]
</p>
<ha-code-editor
mode="yaml"
value="[[_stateAttributes]]"
error="[[!validJSON]]"
on-value-changed="_yamlChanged"
></ha-code-editor>
<div class="button-row">
<mwc-button
on-click="handleSetState"
disabled="[[!validJSON]]"
raised
>[[localize('ui.panel.developer-tools.tabs.states.set_state')]]</mwc-button
>
<ha-icon-button
on-click="entityIdChanged"
label="[[localize('ui.common.refresh')]]"
path="[[refreshIcon()]]"
></ha-icon-button>
</div>
</div>
<div class="info">
<template is="dom-if" if="[[_entity]]">
<p>
<b
>[[localize('ui.panel.developer-tools.tabs.states.last_changed')]]:</b
><br />[[lastChangedString(_entity)]]
</p>
<p>
<b
>[[localize('ui.panel.developer-tools.tabs.states.last_updated')]]:</b
><br />[[lastUpdatedString(_entity)]]
</p>
</template>
</div>
</div>
<h1>
[[localize('ui.panel.developer-tools.tabs.states.current_entities')]]
</h1>
<ha-expansion-panel
header="Set state"
outlined
expanded="[[_expanded]]"
on-expanded-changed="expandedChanged"
>
<p>
[[localize('ui.panel.developer-tools.tabs.states.description1')]]<br />
[[localize('ui.panel.developer-tools.tabs.states.description2')]]
</p>
<div class="state-wrapper flex layout horizontal">
<div class="inputs">
<ha-entity-picker
autofocus
hass="[[hass]]"
value="{{_entityId}}"
on-change="entityIdChanged"
allow-custom-entity
></ha-entity-picker>
<paper-input
label="[[localize('ui.panel.developer-tools.tabs.states.state')]]"
required
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
value="{{_state}}"
class="state-input"
></paper-input>
<p>
[[localize('ui.panel.developer-tools.tabs.states.state_attributes')]]
</p>
<ha-code-editor
mode="yaml"
value="[[_stateAttributes]]"
error="[[!validJSON]]"
on-value-changed="_yamlChanged"
></ha-code-editor>
<div class="button-row">
<mwc-button
on-click="handleSetState"
disabled="[[!validJSON]]"
raised
>[[localize('ui.panel.developer-tools.tabs.states.set_state')]]</mwc-button
>
<ha-icon-button
on-click="entityIdChanged"
label="[[localize('ui.common.refresh')]]"
path="[[refreshIcon()]]"
></ha-icon-button>
</div>
</div>
<div class="info">
<template is="dom-if" if="[[_entity]]">
<p>
<b
>[[localize('ui.panel.developer-tools.tabs.states.last_changed')]]:</b
><br />[[lastChangedString(_entity)]]
</p>
<p>
<b
>[[localize('ui.panel.developer-tools.tabs.states.last_updated')]]:</b
><br />[[lastUpdatedString(_entity)]]
</p>
</template>
</div>
</div>
</ha-expansion-panel>
<div class="table-wrapper">
<table class="entities">
<tr>
@@ -358,11 +348,6 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
"computeEntities(hass, _entityFilter, _stateFilter, _attributeFilter)",
},
_expanded: {
type: Boolean,
value: false,
},
narrow: {
type: Boolean,
reflectToAttribute: true,
@@ -386,7 +371,6 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
this._entity = state;
this._state = state.state;
this._stateAttributes = dump(state.attributes);
this._expanded = true;
ev.preventDefault();
}
@@ -404,11 +388,6 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
this._entity = state;
this._state = state.state;
this._stateAttributes = dump(state.attributes);
this._expanded = true;
}
expandedChanged(ev) {
this._expanded = ev.detail.expanded;
}
entityMoreInfo(ev) {

View File

@@ -26,7 +26,7 @@ import {
rgb2hex,
rgb2lab,
} from "../../../../common/color/convert-color";
import { labBrighten, labDarken } from "../../../../common/color/lab";
import { labDarken } from "../../../../common/color/lab";
import {
EnergyData,
getEnergyDataCollection,
@@ -247,15 +247,10 @@ export class HuiEnergyGasGraphCard
const data: ChartDataset<"bar" | "line">[] = [];
const entity = this.hass.states[source.stat_energy_from];
const modifiedColor =
const borderColor =
idx > 0
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(gasColor)), idx)
: labDarken(rgb2lab(hex2rgb(gasColor)), idx)
: undefined;
const borderColor = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: gasColor;
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(gasColor)), idx)))
: gasColor;
let prevValue: number | null = null;
let prevStart: string | null = null;

View File

@@ -1,3 +1,9 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import { classMap } from "lit/directives/class-map";
import "../../../../components/ha-card";
import {
ChartData,
ChartDataset,
@@ -11,26 +17,16 @@ import {
isToday,
startOfToday,
} from "date-fns";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { HomeAssistant } from "../../../../types";
import { LovelaceCard } from "../../types";
import { EnergySolarGraphCardConfig } from "../types";
import {
hex2rgb,
lab2rgb,
rgb2hex,
rgb2lab,
} from "../../../../common/color/convert-color";
import { labBrighten, labDarken } from "../../../../common/color/lab";
import { formatTime } from "../../../../common/datetime/format_time";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import {
formatNumber,
numberFormatToLocale,
} from "../../../../common/number/format_number";
import "../../../../components/chart/ha-chart-base";
import "../../../../components/ha-card";
import { labDarken } from "../../../../common/color/lab";
import {
EnergyData,
EnergySolarForecasts,
@@ -38,11 +34,15 @@ import {
getEnergySolarForecasts,
SolarSourceTypeEnergyPreference,
} from "../../../../data/energy";
import { FrontendLocaleData } from "../../../../data/translation";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import "../../../../components/chart/ha-chart-base";
import {
formatNumber,
numberFormatToLocale,
} from "../../../../common/number/format_number";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../../../types";
import { LovelaceCard } from "../../types";
import { EnergySolarGraphCardConfig } from "../types";
import { FrontendLocaleData } from "../../../../data/translation";
import { formatTime } from "../../../../common/datetime/format_time";
@customElement("hui-energy-solar-graph-card")
export class HuiEnergySolarGraphCard
@@ -258,15 +258,10 @@ export class HuiEnergySolarGraphCard
const data: ChartDataset<"bar" | "line">[] = [];
const entity = this.hass.states[source.stat_energy_from];
const modifiedColor =
const borderColor =
idx > 0
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(solarColor)), idx)
: labDarken(rgb2lab(hex2rgb(solarColor)), idx)
: undefined;
const borderColor = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: solarColor;
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx)))
: solarColor;
let prevValue: number | null = null;
let prevStart: string | null = null;

View File

@@ -17,7 +17,7 @@ import {
rgb2lab,
hex2rgb,
} from "../../../../common/color/convert-color";
import { labBrighten, labDarken } from "../../../../common/color/lab";
import { labDarken } from "../../../../common/color/lab";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import { formatNumber } from "../../../../common/number/format_number";
import "../../../../components/chart/statistics-chart";
@@ -170,17 +170,12 @@ export class HuiEnergySourcesTableCard
this._data!.stats[source.stat_energy_from]
) || 0;
totalSolar += energy;
const modifiedColor =
const color =
idx > 0
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(solarColor)), idx)
: labDarken(rgb2lab(hex2rgb(solarColor)), idx)
: undefined;
const color = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: solarColor;
? rgb2hex(
lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx))
)
: solarColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
@@ -234,26 +229,22 @@ export class HuiEnergySourcesTableCard
this._data!.stats[source.stat_energy_to]
) || 0;
totalBattery += energyFrom - energyTo;
const modifiedFromColor =
const fromColor =
idx > 0
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(batteryFromColor)), idx)
: labDarken(rgb2lab(hex2rgb(batteryFromColor)), idx)
: undefined;
const fromColor = modifiedFromColor
? rgb2hex(lab2rgb(modifiedFromColor))
: batteryFromColor;
const modifiedToColor =
? rgb2hex(
lab2rgb(
labDarken(rgb2lab(hex2rgb(batteryFromColor)), idx)
)
)
: batteryFromColor;
const toColor =
idx > 0
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(batteryToColor)), idx)
: labDarken(rgb2lab(hex2rgb(batteryToColor)), idx)
: undefined;
const toColor = modifiedToColor
? rgb2hex(lab2rgb(modifiedToColor))
: batteryToColor;
? rgb2hex(
lab2rgb(
labDarken(rgb2lab(hex2rgb(batteryToColor)), idx)
)
)
: batteryToColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
@@ -340,17 +331,14 @@ export class HuiEnergySourcesTableCard
if (cost !== null) {
totalGridCost += cost;
}
const modifiedColor =
const color =
idx > 0
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(consumptionColor)), idx)
: labDarken(rgb2lab(hex2rgb(consumptionColor)), idx)
: undefined;
const color = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: consumptionColor;
? rgb2hex(
lab2rgb(
labDarken(rgb2lab(hex2rgb(consumptionColor)), idx)
)
)
: consumptionColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
@@ -403,17 +391,12 @@ export class HuiEnergySourcesTableCard
if (cost !== null) {
totalGridCost += cost;
}
const modifiedColor =
const color =
idx > 0
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(returnColor)), idx)
: labDarken(rgb2lab(hex2rgb(returnColor)), idx)
: undefined;
const color = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: returnColor;
? rgb2hex(
lab2rgb(labDarken(rgb2lab(hex2rgb(returnColor)), idx))
)
: returnColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
@@ -490,17 +473,12 @@ export class HuiEnergySourcesTableCard
if (cost !== null) {
totalGasCost += cost;
}
const modifiedColor =
const color =
idx > 0
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(gasColor)), idx)
: labDarken(rgb2lab(hex2rgb(gasColor)), idx)
: undefined;
const color = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: gasColor;
? rgb2hex(
lab2rgb(labDarken(rgb2lab(hex2rgb(gasColor)), idx))
)
: gasColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div

View File

@@ -1,10 +1,10 @@
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
import {
addHours,
differenceInDays,
startOfToday,
endOfToday,
isToday,
startOfToday,
differenceInDays,
addHours,
} from "date-fns";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
@@ -17,7 +17,7 @@ import {
rgb2hex,
rgb2lab,
} from "../../../../common/color/convert-color";
import { labBrighten, labDarken } from "../../../../common/color/lab";
import { labDarken } from "../../../../common/color/lab";
import { formatTime } from "../../../../common/datetime/format_time";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import {
@@ -477,16 +477,10 @@ export class HuiEnergyUsageGraphCard
Object.entries(sources).forEach(([statId, source], idx) => {
const data: ChartDataset<"bar">[] = [];
const entity = this.hass.states[statId];
const modifiedColor =
const borderColor =
idx > 0
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(colors[type])), idx)
: labDarken(rgb2lab(hex2rgb(colors[type])), idx)
: undefined;
const borderColor = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: colors[type];
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(colors[type])), idx)))
: colors[type];
data.push({
label:

View File

@@ -25,7 +25,6 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import { domainIcon } from "../../../common/entity/domain_icon";
import { navigate } from "../../../common/navigate";
import { formatNumber } from "../../../common/number/format_number";
import { subscribeOne } from "../../../common/util/subscribe-one";
import "../../../components/entity/state-badge";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
@@ -84,11 +83,8 @@ export class HuiAreaCard
return document.createElement("hui-area-card-editor");
}
public static async getStubConfig(
hass: HomeAssistant
): Promise<AreaCardConfig> {
const areas = await subscribeOne(hass.connection, subscribeAreaRegistry);
return { type: "area", area: areas[0]?.area_id || "" };
public static getStubConfig(): AreaCardConfig {
return { type: "area", area: "" };
}
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -362,12 +358,12 @@ export class HuiAreaCard
});
let cameraEntityId: string | undefined;
if (this._config.show_camera && "camera" in entitiesByDomain) {
if ("camera" in entitiesByDomain) {
cameraEntityId = entitiesByDomain.camera[0].entity_id;
}
return html`
<ha-card class=${area.picture || cameraEntityId ? "image" : ""}>
<ha-card class=${area.picture ? "image" : ""}>
${area.picture || cameraEntityId
? html`<hui-image
.config=${this._config}

View File

@@ -235,9 +235,6 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
<div>
<ha-icon-button
.path=${mdiDotsVertical}
.label=${this.hass.localize(
"ui.panel.lovelace.cards.show_more_info"
)}
class="more-info"
@click=${this._handleMoreInfo}
></ha-icon-button>

View File

@@ -19,7 +19,7 @@ import {
svg,
TemplateResult,
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { customElement, property, state, query } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { UNIT_F } from "../../../common/const";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
@@ -427,7 +427,6 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
@click=${this._handleAction}
tabindex="0"
.path=${modeIcons[mode]}
.label=${this.hass!.localize(`component.climate.state._.${mode}`)}
>
</ha-icon-button>
`;

View File

@@ -79,7 +79,6 @@ export interface EntitiesCardConfig extends LovelaceCardConfig {
export interface AreaCardConfig extends LovelaceCardConfig {
area: string;
navigation_path?: string;
show_camera?: boolean;
}
export interface ButtonCardConfig extends LovelaceCardConfig {

View File

@@ -50,7 +50,6 @@ export class HuiButtonsBase extends LitElement {
.stateObj=${stateObj}
.overrideIcon=${entityConf.icon}
.overrideImage=${entityConf.image}
class=${name ? "" : "no-text"}
stateColor
slot="icon"
></state-badge>
@@ -86,21 +85,9 @@ export class HuiButtonsBase extends LitElement {
flex-wrap: wrap;
}
state-badge {
display: inline-flex;
line-height: inherit;
text-align: start;
color: var(--secondary-text-color);
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
margin-left: -4px;
margin-top: -2px;
}
state-badge.no-text {
width: 26px;
height: 26px;
margin-left: -3px;
margin-top: -3px;
}
ha-chip {
padding: 4px;

View File

@@ -1,7 +1,7 @@
import "@polymer/paper-input/paper-input";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, boolean, object, optional, string } from "superstruct";
import { assert, assign, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-area-picker";
import { HomeAssistant } from "../../../../types";
@@ -11,8 +11,6 @@ import { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { EditorTarget } from "../types";
import { configElementStyle } from "./config-elements-style";
import "../../../../components/ha-formfield";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
@@ -20,7 +18,6 @@ const cardConfigStruct = assign(
area: optional(string()),
navigation_path: optional(string()),
theme: optional(string()),
show_camera: optional(boolean()),
})
);
@@ -50,10 +47,6 @@ export class HuiAreaCardEditor
return this._config!.theme || "";
}
get _show_camera(): boolean {
return this._config!.show_camera || false;
}
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
@@ -71,18 +64,6 @@ export class HuiAreaCardEditor
)}
@value-changed=${this._valueChanged}
></ha-area-picker>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.area.show_camera"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.checked=${this._show_camera}
.configValue=${"show_camera"}
@change=${this._valueChanged}
></ha-switch>
</ha-formfield>
<paper-input
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.action-editor.navigation_path"
@@ -107,8 +88,7 @@ export class HuiAreaCardEditor
return;
}
const target = ev.target! as EditorTarget;
const value =
target.checked !== undefined ? target.checked : ev.detail.value;
const value = ev.detail.value;
if (this[`_${target.configValue}`] === value) {
return;

View File

@@ -13,7 +13,7 @@ export const getCardStubConfig = async (
const elClass = await getCardElementClass(type);
if (elClass && elClass.getStubConfig) {
const classStubConfig = await elClass.getStubConfig(
const classStubConfig = elClass.getStubConfig(
hass,
entities,
entitiesFallback

View File

@@ -13,7 +13,7 @@ export const getHeaderFooterStubConfig = async (
const elClass = await getHeaderFooterElementClass(type);
if (elClass && elClass.getStubConfig) {
const classStubConfig = await elClass.getStubConfig(
const classStubConfig = elClass.getStubConfig(
hass,
entities,
entitiesFallback

View File

@@ -91,7 +91,6 @@ export const coreCards: Card[] = [
},
{
type: "area",
showElement: true,
},
{
type: "conditional",

View File

@@ -1,7 +1,7 @@
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity";
import { UNAVAILABLE } from "../../../data/entity";
import { setValue } from "../../../data/input_text";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@@ -67,12 +67,6 @@ class HuiInputTextEntityRow extends LitElement implements LovelaceRow {
const element = this._inputEl;
const stateObj = this.hass!.states[this._config!.entity];
// Filter out invalid text states
if (element.value && UNAVAILABLE_STATES.includes(element.value)) {
element.value = stateObj.state;
return;
}
if (element.value !== stateObj.state) {
setValue(this.hass!, stateObj.entity_id, element.value!);
}

View File

@@ -30,7 +30,6 @@ import "../../../components/ha-slider";
import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
import {
computeMediaDescription,
ControlButton,
MediaPlayerEntity,
SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE,
@@ -109,7 +108,6 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
}
const entityState = stateObj.state;
const controlButton = this._computeControlButton(stateObj);
const buttons = html`
${!this._narrow &&
@@ -118,9 +116,6 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
? html`
<ha-icon-button
.path=${mdiSkipPrevious}
.label=${this.hass.localize(
"ui.card.media_player.media_previous_track"
)}
@click=${this._previousTrack}
></ha-icon-button>
`
@@ -135,10 +130,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
supportsFeature(stateObj, SUPPORT_PAUSE)))
? html`
<ha-icon-button
.path=${controlButton.icon}
.label=${this.hass.localize(
`ui.card.media_player.${controlButton.action}`
)}
.path=${this._computeControlIcon(stateObj)}
@click=${this._playPauseStop}
></ha-icon-button>
`
@@ -148,9 +140,6 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
? html`
<ha-icon-button
.path=${mdiSkipNext}
.label=${this.hass.localize(
"ui.card.media_player.media_next_track"
)}
@click=${this._nextTrack}
></ha-icon-button>
`
@@ -173,7 +162,6 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
? html`
<ha-icon-button
.path=${mdiPower}
.label=${this.hass.localize("ui.card.media_player.turn_on")}
@click=${this._togglePower}
></ha-icon-button>
`
@@ -187,7 +175,6 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
? html`
<ha-icon-button
.path=${mdiPower}
.label=${this.hass.localize("ui.card.media_player.turn_off")}
@click=${this._togglePower}
></ha-icon-button>
`
@@ -206,13 +193,6 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
.path=${stateObj.attributes.is_volume_muted
? mdiVolumeOff
: mdiVolumeHigh}
.label=${this.hass.localize(
`ui.card.media_player.${
stateObj.attributes.is_volume_muted
? "media_volume_mute"
: "media_volume_unmute"
}`
)}
@click=${this._toggleMute}
></ha-icon-button>
`
@@ -234,16 +214,10 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
? html`
<ha-icon-button
.path=${mdiVolumeMinus}
.label=${this.hass.localize(
"ui.card.media_player.media_volume_down"
)}
@click=${this._volumeDown}
></ha-icon-button>
<ha-icon-button
.path=${mdiVolumePlus}
.label=${this.hass.localize(
"ui.card.media_player.media_volume_up"
)}
@click=${this._volumeUp}
></ha-icon-button>
`
@@ -275,14 +249,14 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
this._veryNarrow = (this.clientWidth || 0) < 225;
}
private _computeControlButton(stateObj: HassEntity): ControlButton {
private _computeControlIcon(stateObj: HassEntity): string {
return stateObj.state === "on"
? { icon: mdiPlayPause, action: "media_play_pause" }
? mdiPlayPause
: stateObj.state !== "playing"
? { icon: mdiPlay, action: "media_play" }
? mdiPlay
: supportsFeature(stateObj, SUPPORT_PAUSE)
? { icon: mdiPause, action: "media_pause" }
: { icon: mdiStop, action: "media_stop" };
? mdiPause
: mdiStop;
}
private _togglePower(): void {

View File

@@ -698,7 +698,7 @@ class HUIRoot extends LitElement {
private _navigateToView(path: string | number, replace?: boolean) {
if (!this.lovelace!.editMode) {
navigate(`${this.route!.prefix}/${path}${location.search}`, { replace });
navigate(`${this.route!.prefix}/${path}`, { replace });
return;
}
navigate(`${this.route!.prefix}/${path}?${addSearchParam({ edit: "1" })}`, {

View File

@@ -95,7 +95,6 @@ export const derivedStyles = {
"mdc-theme-text-disabled-on-light": "var(--disabled-text-color)",
"mdc-theme-text-primary-on-background": "var(--primary-text-color)",
"mdc-theme-text-secondary-on-background": "var(--secondary-text-color)",
"mdc-theme-text-hint-on-background": "var(--secondary-text-color)",
"mdc-theme-text-icon-on-background": "var(--secondary-text-color)",
"mdc-theme-error": "var(--error-color)",
"app-header-text-color": "var(--text-primary-color)",

View File

@@ -1,15 +0,0 @@
import { Constructor } from "../types";
import { HassBaseEl } from "./hass-base-mixin";
export const ExternalMixin = <T extends Constructor<HassBaseEl>>(
superClass: T
) =>
class extends superClass {
protected hassConnected() {
super.hassConnected();
if (this.hass!.auth.external) {
this.hass!.auth.external.connection = this.hass!.connection;
}
}
};

View File

@@ -6,7 +6,6 @@ import DisconnectToastMixin from "./disconnect-toast-mixin";
import { hapticMixin } from "./haptic-mixin";
import { HassBaseEl } from "./hass-base-mixin";
import { loggingMixin } from "./logging-mixin";
import { ExternalMixin } from "./external-mixin";
import MoreInfoMixin from "./more-info-mixin";
import NotificationMixin from "./notification-mixin";
import { panelTitleMixin } from "./panel-title-mixin";
@@ -32,5 +31,4 @@ export class HassElement extends ext(HassBaseEl, [
hapticMixin,
panelTitleMixin,
loggingMixin,
ExternalMixin,
]) {}

View File

@@ -1,5 +1,6 @@
import type { PropertyValues } from "lit";
import tinykeys from "tinykeys";
import { showDeveloperToolDialog } from "../dialogs/developert-tools/show-dialog-developer-tools";
import {
QuickBarParams,
showQuickBar,
@@ -32,6 +33,7 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
tinykeys(window, {
e: (ev) => this._showQuickBar(ev),
c: (ev) => this._showQuickBar(ev, true),
d: () => showDeveloperToolDialog(this),
});
}

View File

@@ -131,7 +131,5 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
(themeMeta.getAttribute("default-content") as string);
themeMeta.setAttribute("content", themeColor);
}
this.hass!.auth.external?.fireMessage({ type: "theme-update" });
}
};

View File

@@ -195,14 +195,8 @@
"turn_off": "Turn off",
"media_play": "Play",
"media_play_pause": "Play/pause",
"media_pause": "Pause",
"media_stop": "Stop",
"media_next_track": "Next track",
"media_previous_track": "Previous track",
"media_volume_up": "Volume up",
"media_volume_down": "Volume down",
"media_volume_mute": "Volume mute",
"media_volume_unmute": "Volume unmute",
"media_next_track": "Next",
"media_previous_track": "Previous",
"text_to_speak": "Text to speak"
},
"persistent_notification": {
@@ -297,14 +291,12 @@
"undo": "Undo",
"move": "Move",
"save": "Save",
"submit": "Submit",
"rename": "Rename",
"yes": "Yes",
"no": "No",
"not_now": "Not now",
"skip": "Skip",
"menu": "Menu",
"overflow_menu": "Overflow menu",
"help": "Help",
"successfully_saved": "Successfully saved",
"successfully_deleted": "Successfully deleted",
@@ -429,7 +421,6 @@
}
},
"related-filter-menu": {
"filter": "Filter",
"filter_by_entity": "Filter by entity",
"filter_by_device": "Filter by device",
"filter_by_area": "Filter by area",
@@ -547,13 +538,6 @@
},
"attributes": {
"expansion_header": "Attributes"
},
"qr-scanner": {
"select_camera": "Select camera",
"only_https_supported": "You can only use your camera to scan a QR code when using HTTPS.",
"not_supported": "Your browser doesn't support QR scanning.",
"manual_input": "You can scan the QR code with another QR scanner and paste the code in the input below",
"enter_qr_code": "Enter QR code value"
}
},
"dialogs": {
@@ -694,8 +678,8 @@
"open_cover": "Open cover",
"close_cover": "Close cover",
"open_tilt_cover": "Open cover tilt",
"close_tilt_cover": "Close cover tilt",
"stop_cover": "Stop cover"
"close_tile_cover": "Close cover tilt",
"stop_cover": "Stop cover from moving"
}
},
"entity_registry": {
@@ -894,7 +878,8 @@
"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"
"no_template_editor_support": "Templates not supported in visual editor",
"no_state_array_support": "Multiple state values not supported in visual editor"
},
"supervisor": {
"title": "Could not load the Supervisor panel!",
@@ -928,7 +913,6 @@
"dismiss": "Dismiss"
},
"sidebar": {
"external_app_configuration": "App Configuration",
"sidebar_toggle": "Sidebar Toggle",
"done": "Done",
"hide_panel": "Hide panel",
@@ -945,47 +929,9 @@
},
"config": {
"header": "Configure Home Assistant",
"dashboard": {
"devices": {
"title": "Devices & Services",
"description": "Integrations, devices, entities and areas"
},
"automations": {
"title": "Automations & Scenes",
"description": "Manage automations, scenes, scripts and helpers"
},
"blueprints": {
"title": "Blueprints",
"description": "Pre-made automations and scripts by the community"
},
"supervisor": {
"title": "Add-ons, Backups & Supervisor",
"description": "Create backups, check logs or reboot your system"
},
"dashboards": {
"title": "Dashboards",
"description": "Create customized sets of cards to control your home"
},
"energy": {
"title": "Energy",
"description": "Monitor your energy production and consumption"
},
"tags": {
"title": "Tags",
"description": "Trigger automations when a NFC tag, QR code, etc. is scanned"
},
"people": {
"title": "People & Zones",
"description": "Manage the people and zones that Home Assistant tracks"
},
"companion": {
"title": "Companion App",
"description": "Location and notifications"
},
"settings": {
"title": "Settings",
"description": "Basic settings, server controls, logs and info"
}
"advanced_mode": {
"hint_enable": "Missing config options? Enable advanced mode on",
"link_profile_page": "your profile page"
},
"common": {
"editor": {
@@ -1163,7 +1109,7 @@
"cost_number": "Use a static price",
"cost_number_input": "Price per {unit}",
"gas_usage": "Gas usage",
"m3_or_kWh": "ft³, m³, Wh, kWh or MWh"
"m3_or_kWh": " or kWh"
}
},
"device_consumption": {
@@ -1204,19 +1150,19 @@
},
"entity_unexpected_unit_energy": {
"title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement 'Wh', 'kWh' or 'MWh':"
"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 'Wh', 'kWh' or 'MWh' for an energy sensor or 'm³' or 'ft³' for a gas sensor:"
"description": "The following entities do not have the expected units of measurement 'kWh', 'm³' or 'ft³':"
},
"entity_unexpected_unit_energy_price": {
"title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'', ''{currency}/Wh'' or ''{currency}/MWh'':"
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'' or ''{currency}/Wh'':"
},
"entity_unexpected_unit_gas_price": {
"title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'', ''{currency}/Wh'', ''{currency}/MWh'', ''{currency}/m³'' or ''{currency}/ft³'':"
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'', ''{currency}/Wh'', ''{currency}/m³'' or ''{currency}/ft³'':"
},
"entity_unexpected_state_class": {
"title": "Unexpected state class",
@@ -1568,7 +1514,7 @@
"extra_fields": {
"above": "Above",
"below": "Below",
"for": "Duration (optional)",
"for": "Duration",
"zone": "[%key:ui::panel::config::automation::editor::triggers::type::zone::label%]"
}
},
@@ -1591,9 +1537,9 @@
"state": {
"label": "State",
"attribute": "Attribute (optional)",
"from": "From (optional)",
"for": "For (optional)",
"to": "To (optional)"
"from": "From",
"for": "For",
"to": "To"
},
"homeassistant": {
"label": "Home Assistant",
@@ -2444,7 +2390,6 @@
"manuf": "by {manufacturer}",
"via": "Connected via",
"firmware": "Firmware: {version}",
"hardware": "Hardware: {version}",
"unnamed_entry": "Unnamed entry",
"unknown_via_device": "Unknown device",
"area": "In {area}",
@@ -2533,7 +2478,7 @@
"admin": "Administrator",
"group": "Group",
"active": "Active",
"local_only": "Can only log in from the local network",
"local_only": "Can only login from the local network",
"system_generated": "System generated",
"system_generated_users_not_removable": "Unable to remove system generated users.",
"system_generated_users_not_editable": "Unable to update system generated users.",
@@ -2869,7 +2814,7 @@
"node_id": "Device ID",
"home_id": "Home ID",
"source": "Source",
"back": "Back",
"close": "Close",
"add_node": "Add device",
"remove_node": "Remove device",
"reconfigure_server": "Re-configure Server",
@@ -2938,6 +2883,8 @@
"qr_code": "QR Code",
"qr_code_paragraph": "If your device supports SmartStart you can scan the QR code for easy pairing.",
"scan_qr_code": "Scan QR code",
"enter_qr_code": "Enter QR code value",
"select_camera": "Select camera",
"inclusion_failed": "The device could not be added.",
"check_logs": "Please check the logs for more information.",
"inclusion_finished": "The device has been added.",
@@ -2950,8 +2897,6 @@
"dsk": "DSK",
"security_classes": "Security classes",
"unprovison": "Unprovison",
"included": "Included",
"not_included": "Not Included",
"confirm_unprovision_title": "Are you sure you want to unprovision the device?",
"confirm_unprovision_text": "If you unprovision the device it will not be added to Home Assistant when it is powered on. If it is already added to Home Assistant, removing the provisioned device will not remove it from Home Assistant."
},
@@ -3031,8 +2976,7 @@
"title": "Z-Wave JS Logs",
"log_level": "Log Level",
"subscribed_to_logs": "Subscribed to Z-Wave JS Log Messages…",
"log_level_changed": "Log Level changed to: {level}",
"download_logs": "Download logs"
"log_level_changed": "Log Level changed to: {level}"
}
}
},
@@ -3311,8 +3255,7 @@
},
"area": {
"name": "Area",
"description": "The Area card automatically displays entities of a specific area.",
"show_camera": "Show camera feed instead of area picture"
"description": "The Area card automatically displays entities of a specific area."
},
"calendar": {
"name": "Calendar",
@@ -4277,8 +4220,7 @@
"create_backup": "Create backup before updating",
"description": "You have {version} installed. Click update to update to version {newest_version}",
"updating": "Updating {name} to version {version}",
"creating_backup": "Creating backup of {name}",
"no_update": "No update available for {name}"
"creating_backup": "Creating backup of {name}"
},
"confirm": {
"restart": {

View File

@@ -2,16 +2,16 @@ import { assert } from "chai";
import {
ExternalMessaging,
EMMessage,
InternalMessage,
} from "../../src/external_app/external_messaging";
// @ts-ignore
global.__DEV__ = true;
class MockExternalMessaging extends ExternalMessaging {
public mockSent: EMMessage[] = [];
public mockSent: InternalMessage[] = [];
protected _sendExternal(msg: EMMessage) {
protected _sendExternal(msg: InternalMessage) {
this.mockSent.push(msg);
}
}