mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-12 06:29:46 +00:00
Compare commits
36 Commits
dev-tools-
...
20211209.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cdc3d11181 | ||
![]() |
02bdeebc82 | ||
![]() |
60c7669d8f | ||
![]() |
919bf94a03 | ||
![]() |
ead5e288eb | ||
![]() |
add8a702cc | ||
![]() |
39774c0e02 | ||
![]() |
149f381bc3 | ||
![]() |
faccb12430 | ||
![]() |
7039bae9be | ||
![]() |
0a7b703d57 | ||
![]() |
24e8028e8f | ||
![]() |
8f729e2a95 | ||
![]() |
8412cd71cb | ||
![]() |
5c78b74005 | ||
![]() |
2459477ec4 | ||
![]() |
a065740c91 | ||
![]() |
f3104d3c93 | ||
![]() |
1916c179b4 | ||
![]() |
e8b9766eb6 | ||
![]() |
ff7a2c8cb7 | ||
![]() |
7ccde2cb41 | ||
![]() |
d6b9b16f02 | ||
![]() |
66df15007a | ||
![]() |
f164d21c44 | ||
![]() |
911d322aac | ||
![]() |
419879ee7a | ||
![]() |
c3e1a2edf0 | ||
![]() |
bc9195f7d5 | ||
![]() |
7f1a321075 | ||
![]() |
72b9f8636d | ||
![]() |
c9cd316c0c | ||
![]() |
6cf3580fb4 | ||
![]() |
5d91aefb55 | ||
![]() |
e3c0530941 | ||
![]() |
2c9223ed80 |
@@ -48,7 +48,6 @@ 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 {
|
||||
@@ -60,7 +59,6 @@ declare global {
|
||||
type updateType = "os" | "supervisor" | "core" | "addon";
|
||||
|
||||
const changelogUrl = (
|
||||
hass: HomeAssistant,
|
||||
entry: updateType,
|
||||
version: string
|
||||
): string | undefined => {
|
||||
@@ -68,17 +66,19 @@ const changelogUrl = (
|
||||
return undefined;
|
||||
}
|
||||
if (entry === "core") {
|
||||
return version?.includes("dev")
|
||||
return version.includes("dev")
|
||||
? "https://github.com/home-assistant/core/commits/dev"
|
||||
: documentationUrl(hass, "/latest-release-notes/");
|
||||
: version.includes("b")
|
||||
? "https://next.home-assistant.io/latest-release-notes/"
|
||||
: "https://www.home-assistant.io/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}`;
|
||||
}
|
||||
@@ -120,7 +120,7 @@ class UpdateAvailableCard extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const changelog = changelogUrl(this.hass, this._updateType, this._version);
|
||||
const changelog = changelogUrl(this._updateType, this._version_latest);
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20211203.0",
|
||||
version="20211209.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/frontend",
|
||||
author="The Home Assistant Authors",
|
||||
|
@@ -96,7 +96,11 @@ export class HaDateInput extends LitElement {
|
||||
attr-for-value="value"
|
||||
.i18n=${i18n}
|
||||
>
|
||||
<paper-input .label=${this.label} no-label-float>
|
||||
<paper-input
|
||||
.label=${this.label}
|
||||
.disabled=${this.disabled}
|
||||
no-label-float
|
||||
>
|
||||
<ha-svg-icon slot="suffix" .path=${mdiCalendar}></ha-svg-icon>
|
||||
</paper-input>
|
||||
</vaadin-date-picker-light>`;
|
||||
|
@@ -122,14 +122,20 @@ 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: {
|
||||
hours,
|
||||
minutes,
|
||||
seconds: this._seconds,
|
||||
milliseconds: this._milliseconds,
|
||||
...{ [unit]: value },
|
||||
},
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -39,6 +39,7 @@ 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>
|
||||
`;
|
||||
@@ -53,6 +54,10 @@ 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, {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select/mwc-select";
|
||||
import type { Select } from "@material/mwc-select/mwc-select";
|
||||
import { mdiCamera } from "@mdi/js";
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import type QrScanner from "qr-scanner";
|
||||
@@ -8,6 +8,7 @@ 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";
|
||||
|
||||
@customElement("ha-qr-scanner")
|
||||
class HaQrScanner extends LitElement {
|
||||
@@ -58,29 +59,37 @@ class HaQrScanner extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
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
|
||||
return html`${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
${navigator.mediaDevices
|
||||
? html`<video></video>
|
||||
<div id="canvas-container"></div>`
|
||||
<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.panel.config.zwave_js.add_node.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
|
||||
? "You can only use your camera to scan a QR core when using HTTPS."
|
||||
@@ -135,16 +144,23 @@ class HaQrScanner extends LitElement {
|
||||
};
|
||||
|
||||
private _cameraChanged(ev: CustomEvent): void {
|
||||
this._qrScanner?.setCamera((ev.target as Select).value);
|
||||
this._qrScanner?.setCamera((ev.target as any).value);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
canvas {
|
||||
width: 100%;
|
||||
}
|
||||
mwc-select {
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
#canvas-container {
|
||||
position: relative;
|
||||
}
|
||||
ha-button-menu {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
background: #727272b2;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -43,6 +43,7 @@ import {
|
||||
PersistentNotification,
|
||||
subscribeNotifications,
|
||||
} from "../data/persistent_notification";
|
||||
import { 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";
|
||||
@@ -266,6 +267,11 @@ class HaSidebar extends LitElement {
|
||||
subscribeNotifications(this.hass.connection, (notifications) => {
|
||||
this._notifications = notifications;
|
||||
});
|
||||
|
||||
// Temporary workaround for a bug in Android. Can be removed in Home Assistant 2022.2
|
||||
if (this.hass.auth.external) {
|
||||
getExternalConfig(this.hass.auth.external);
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
|
@@ -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;
|
||||
securityClasses: SecurityClass[];
|
||||
/**
|
||||
* Additional properties to be stored in this provisioning entry, e.g. the device ID from a scanned QR code
|
||||
*/
|
||||
[prop: string]: any;
|
||||
security_classes: SecurityClass[];
|
||||
additional_properties: {
|
||||
nodeId?: number;
|
||||
[prop: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RequestedGrant {
|
||||
@@ -278,7 +278,7 @@ export const setZwaveDataCollectionPreference = (
|
||||
export const fetchZwaveProvisioningEntries = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string
|
||||
): Promise<any> =>
|
||||
): Promise<ZwaveJSProvisioningEntry[]> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/get_provisioning_entries",
|
||||
entry_id,
|
||||
|
@@ -37,6 +37,25 @@ 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 }) =>
|
||||
@@ -52,23 +71,7 @@ const authProm = isExternal
|
||||
const connProm = async (auth) => {
|
||||
try {
|
||||
const conn = await createConnection({ auth });
|
||||
// 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}` : ""}`
|
||||
);
|
||||
}
|
||||
|
||||
clearUrlParams();
|
||||
return { auth, conn };
|
||||
} catch (err: any) {
|
||||
if (err !== ERR_INVALID_AUTH) {
|
||||
@@ -85,6 +88,7 @@ const connProm = async (auth) => {
|
||||
}
|
||||
auth = await authProm();
|
||||
const conn = await createConnection({ auth });
|
||||
clearUrlParams();
|
||||
return { auth, conn };
|
||||
}
|
||||
};
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* Auth class that connects to a native app for authentication.
|
||||
*/
|
||||
import { Auth } from "home-assistant-js-websocket";
|
||||
import { ExternalMessaging, InternalMessage } from "./external_messaging";
|
||||
import { ExternalMessaging, EMMessage } 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: InternalMessage);
|
||||
postMessage(payload: EMMessage);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { Connection } from "home-assistant-js-websocket";
|
||||
import {
|
||||
externalForwardConnectionEvents,
|
||||
externalForwardHaptics,
|
||||
@@ -7,39 +8,50 @@ const CALLBACK_EXTERNAL_BUS = "externalBus";
|
||||
|
||||
interface CommandInFlight {
|
||||
resolve: (data: any) => void;
|
||||
reject: (err: ExternalError) => void;
|
||||
reject: (err: EMError) => void;
|
||||
}
|
||||
|
||||
export interface InternalMessage {
|
||||
export interface EMMessage {
|
||||
id?: number;
|
||||
type: string;
|
||||
payload?: unknown;
|
||||
}
|
||||
|
||||
interface ExternalError {
|
||||
interface EMError {
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface ExternalMessageResult {
|
||||
interface EMMessageResultSuccess {
|
||||
id: number;
|
||||
type: "result";
|
||||
success: true;
|
||||
result: unknown;
|
||||
}
|
||||
|
||||
interface ExternalMessageResultError {
|
||||
interface EMMessageResultError {
|
||||
id: number;
|
||||
type: "result";
|
||||
success: false;
|
||||
error: ExternalError;
|
||||
error: EMError;
|
||||
}
|
||||
|
||||
type ExternalMessage = ExternalMessageResult | ExternalMessageResultError;
|
||||
interface EMExternalMessageRestart {
|
||||
id: number;
|
||||
type: "command";
|
||||
command: "restart";
|
||||
}
|
||||
|
||||
type ExternalMessage =
|
||||
| EMMessageResultSuccess
|
||||
| EMMessageResultError
|
||||
| EMExternalMessageRestart;
|
||||
|
||||
export class ExternalMessaging {
|
||||
public commands: { [msgId: number]: CommandInFlight } = {};
|
||||
|
||||
public connection?: Connection;
|
||||
|
||||
public cache: Record<string, any> = {};
|
||||
|
||||
public msgId = 0;
|
||||
@@ -54,7 +66,7 @@ export class ExternalMessaging {
|
||||
* Send message to external app that expects a response.
|
||||
* @param msg message to send
|
||||
*/
|
||||
public sendMessage<T>(msg: InternalMessage): Promise<T> {
|
||||
public sendMessage<T>(msg: EMMessage): Promise<T> {
|
||||
const msgId = ++this.msgId;
|
||||
msg.id = msgId;
|
||||
|
||||
@@ -69,7 +81,9 @@ export class ExternalMessaging {
|
||||
* Send message to external app without expecting a response.
|
||||
* @param msg message to send
|
||||
*/
|
||||
public fireMessage(msg: InternalMessage) {
|
||||
public fireMessage(
|
||||
msg: EMMessage | EMMessageResultSuccess | EMMessageResultError
|
||||
) {
|
||||
if (!msg.id) {
|
||||
msg.id = ++this.msgId;
|
||||
}
|
||||
@@ -82,6 +96,43 @@ 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) {
|
||||
@@ -99,7 +150,7 @@ export class ExternalMessaging {
|
||||
}
|
||||
}
|
||||
|
||||
protected _sendExternal(msg: InternalMessage) {
|
||||
protected _sendExternal(msg: EMMessage) {
|
||||
if (__DEV__) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("Sending message to external app", msg);
|
||||
|
@@ -127,7 +127,9 @@ export class HassRouterPage extends ReactiveElement {
|
||||
|
||||
// Update the url if we know where we're mounted.
|
||||
if (route) {
|
||||
navigate(`${route.prefix}/${result}`, { replace: true });
|
||||
navigate(`${route.prefix}/${result}${location.search}`, {
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -51,7 +51,9 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
const path = curPath();
|
||||
|
||||
if (["", "/"].includes(path)) {
|
||||
navigate(`/${getStorageDefaultPanelUrlPath()}`, { replace: true });
|
||||
navigate(`/${getStorageDefaultPanelUrlPath()}${location.search}`, {
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
this._route = {
|
||||
prefix: "",
|
||||
|
@@ -138,7 +138,8 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", { value: ev.detail.value });
|
||||
// @ts-ignore
|
||||
fireEvent(this, "value-changed", { value: ev.detail.value, yaml: true });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@@ -109,6 +109,7 @@ 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}
|
||||
@@ -127,6 +128,12 @@ 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:
|
||||
|
@@ -1,17 +1,27 @@
|
||||
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";
|
||||
import "../../../../../components/ha-duration-input";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
|
||||
const stateConditionStruct = object({
|
||||
condition: literal("state"),
|
||||
entity_id: string(),
|
||||
attribute: optional(string()),
|
||||
state: string(),
|
||||
for: optional(union([string(), forDictStruct])),
|
||||
});
|
||||
|
||||
@customElement("ha-automation-condition-state")
|
||||
export class HaStateCondition extends LitElement implements ConditionElement {
|
||||
@@ -23,19 +33,14 @@ export class HaStateCondition extends LitElement implements ConditionElement {
|
||||
return { entity_id: "", state: "" };
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
8
src/panels/config/automation/structs.ts
Normal file
8
src/panels/config/automation/structs.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { object, optional, number } from "superstruct";
|
||||
|
||||
export const forDictStruct = object({
|
||||
days: optional(number()),
|
||||
hours: optional(number()),
|
||||
minutes: optional(number()),
|
||||
seconds: optional(number()),
|
||||
});
|
@@ -68,7 +68,7 @@ export const handleChangeEvent = (element: TriggerElement, ev: CustomEvent) => {
|
||||
}
|
||||
|
||||
let newTrigger: Trigger;
|
||||
if (!newVal) {
|
||||
if (newVal === undefined || newVal === "") {
|
||||
newTrigger = { ...element.trigger };
|
||||
delete newTrigger[name];
|
||||
} else {
|
||||
@@ -291,6 +291,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
}
|
||||
this._warnings = undefined;
|
||||
fireEvent(this, "value-changed", { value: ev.detail.value });
|
||||
}
|
||||
|
||||
|
@@ -1,19 +1,30 @@
|
||||
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 { 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 "../../../../../components/ha-duration-input";
|
||||
import { forDictStruct } from "../../structs";
|
||||
import {
|
||||
handleChangeEvent,
|
||||
TriggerElement,
|
||||
} from "../ha-automation-trigger-row";
|
||||
|
||||
const stateTriggerStruct = object({
|
||||
platform: literal("state"),
|
||||
entity_id: 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;
|
||||
@@ -24,9 +35,9 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
|
||||
return { entity_id: "" };
|
||||
}
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues) {
|
||||
public shouldUpdate(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("trigger")) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Check for templates in trigger. If found, revert to YAML mode.
|
||||
if (this.trigger && hasTemplate(this.trigger)) {
|
||||
@@ -35,7 +46,15 @@ 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() {
|
||||
|
@@ -224,7 +224,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config"
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.automations}
|
||||
.tabs=${configSections.blueprints}
|
||||
.columns=${this._columns(this.narrow, this.hass.language)}
|
||||
.data=${this._processedBlueprints(this.blueprints)}
|
||||
id="entity_id"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiCellphoneCog, mdiCloudLock } from "@mdi/js";
|
||||
import { mdiCloudLock } from "@mdi/js";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import {
|
||||
@@ -110,29 +110,10 @@ 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>
|
||||
@@ -142,13 +123,6 @@ 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,
|
||||
@@ -157,7 +131,7 @@ class HaConfigDashboard extends LitElement {
|
||||
border-bottom: var(--app-header-border-bottom);
|
||||
--header-height: 55px;
|
||||
}
|
||||
ha-card:last-child {
|
||||
:host(:not([narrow])) ha-card:last-child {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
ha-config-section {
|
||||
@@ -178,7 +152,7 @@ class HaConfigDashboard extends LitElement {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
:host([narrow]) ha-card {
|
||||
background-color: var(--primary-background-color);
|
||||
border-radius: 0;
|
||||
box-shadow: unset;
|
||||
}
|
||||
|
||||
|
@@ -6,6 +6,7 @@ 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";
|
||||
|
||||
@@ -19,10 +20,16 @@ class HaConfigNavigation extends LitElement {
|
||||
|
||||
@property() public pages!: PageNavigation[];
|
||||
|
||||
@property() public externalConfig?: ExternalConfig;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.pages.map((page) =>
|
||||
canShowPage(this.hass, page)
|
||||
(
|
||||
page.path === "#external-app-configuration"
|
||||
? this.externalConfig?.hasSettingsScreen
|
||||
: canShowPage(this.hass, page)
|
||||
)
|
||||
? html`
|
||||
<a href=${page.path} aria-role="option" tabindex="-1">
|
||||
<paper-icon-item @click=${this._entryClicked}>
|
||||
@@ -36,8 +43,7 @@ class HaConfigNavigation extends LitElement {
|
||||
<paper-item-body two-line>
|
||||
${page.name ||
|
||||
this.hass.localize(
|
||||
page.translationKey ||
|
||||
`ui.panel.config.${page.component}.caption`
|
||||
`ui.panel.config.dashboard.${page.translationKey}.title`
|
||||
)}
|
||||
${page.component === "cloud" && (page.info as CloudStatus)
|
||||
? page.info.logged_in
|
||||
@@ -61,7 +67,7 @@ class HaConfigNavigation extends LitElement {
|
||||
<div secondary>
|
||||
${page.description ||
|
||||
this.hass.localize(
|
||||
`ui.panel.config.${page.component}.description`
|
||||
`ui.panel.config.dashboard.${page.translationKey}.description`
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
@@ -77,6 +83,16 @@ 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 {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
mdiAccount,
|
||||
mdiBadgeAccountHorizontal,
|
||||
mdiCellphoneCog,
|
||||
mdiCog,
|
||||
mdiDevices,
|
||||
mdiHomeAssistant,
|
||||
@@ -48,73 +49,69 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
||||
dashboard: [
|
||||
{
|
||||
path: "/config/integrations",
|
||||
name: "Devices & Services",
|
||||
description: "Integrations, devices, entities and areas",
|
||||
translationKey: "devices",
|
||||
iconPath: mdiDevices,
|
||||
iconColor: "#0D47A1",
|
||||
core: true,
|
||||
},
|
||||
{
|
||||
path: "/config/automation",
|
||||
name: "Automations & Scenes",
|
||||
description: "Automations, blueprints, scenes and scripts",
|
||||
translationKey: "automations",
|
||||
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",
|
||||
name: "Add-ons & Backups (Supervisor)",
|
||||
description: "Create backups, check logs or reboot your system",
|
||||
translationKey: "supervisor",
|
||||
iconPath: mdiHomeAssistant,
|
||||
iconColor: "#4084CD",
|
||||
component: "hassio",
|
||||
},
|
||||
{
|
||||
path: "/config/lovelace/dashboards",
|
||||
name: "Dashboards",
|
||||
description: "Create customized sets of cards to control your home",
|
||||
translationKey: "dashboards",
|
||||
iconPath: mdiViewDashboard,
|
||||
iconColor: "#B1345C",
|
||||
component: "lovelace",
|
||||
},
|
||||
{
|
||||
path: "/config/energy",
|
||||
name: "Energy",
|
||||
description: "Monitor your energy production and consumption",
|
||||
translationKey: "energy",
|
||||
iconPath: mdiLightningBolt,
|
||||
iconColor: "#F1C447",
|
||||
component: "energy",
|
||||
},
|
||||
{
|
||||
path: "/config/tags",
|
||||
name: "Tags",
|
||||
description:
|
||||
"Trigger automations when a NFC tag, QR code, etc. is scanned",
|
||||
translationKey: "tags",
|
||||
iconPath: mdiNfcVariant,
|
||||
iconColor: "#616161",
|
||||
component: "tag",
|
||||
},
|
||||
{
|
||||
path: "/config/person",
|
||||
name: "People & Zones",
|
||||
description: "Manage the people and zones that Home Assistant tracks",
|
||||
translationKey: "people",
|
||||
iconPath: mdiAccount,
|
||||
iconColor: "#E48629",
|
||||
components: ["person", "zone", "users"],
|
||||
},
|
||||
{
|
||||
path: "#external-app-configuration",
|
||||
translationKey: "companion",
|
||||
iconPath: mdiCellphoneCog,
|
||||
iconColor: "#8E24AA",
|
||||
},
|
||||
{
|
||||
path: "/config/core",
|
||||
name: "Settings",
|
||||
description: "Basic settings, server controls, logs and info",
|
||||
translationKey: "settings",
|
||||
iconPath: mdiCog,
|
||||
iconColor: "#4A5963",
|
||||
core: true,
|
||||
@@ -155,13 +152,6 @@ 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",
|
||||
@@ -183,8 +173,6 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
||||
iconPath: mdiScriptText,
|
||||
iconColor: "#518C43",
|
||||
},
|
||||
],
|
||||
helpers: [
|
||||
{
|
||||
component: "helpers",
|
||||
path: "/config/helpers",
|
||||
@@ -194,6 +182,15 @@ 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",
|
||||
@@ -447,9 +444,19 @@ 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;
|
||||
}
|
||||
|
@@ -132,7 +132,7 @@ export class HaConfigHelpers extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config"
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.helpers}
|
||||
.tabs=${configSections.automations}
|
||||
.columns=${this._columns(this.narrow, this.hass.language)}
|
||||
.data=${this._getItems(this._stateItems)}
|
||||
@row-click=${this._openEditDialog}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
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";
|
||||
@@ -40,13 +41,14 @@ class HaConfigInfo extends LitElement {
|
||||
href=${documentationUrl(this.hass, "")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
><img
|
||||
src="/static/icons/favicon-192x192.png"
|
||||
height="192"
|
||||
alt=${this.hass.localize(
|
||||
>
|
||||
<ha-logo-svg
|
||||
title=${this.hass.localize(
|
||||
"ui.panel.config.info.home_assistant_logo"
|
||||
)}
|
||||
/></a>
|
||||
>
|
||||
</ha-logo-svg>
|
||||
</a>
|
||||
<br />
|
||||
<h2>Home Assistant ${hass.connection.haVersion}</h2>
|
||||
<p>
|
||||
@@ -193,6 +195,11 @@ class HaConfigInfo extends LitElement {
|
||||
margin: 0 auto;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
ha-logo-svg {
|
||||
padding: 12px;
|
||||
height: 180px;
|
||||
width: 180px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
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";
|
||||
@@ -45,6 +44,8 @@ export interface ZWaveJSAddNodeDevice {
|
||||
class DialogZWaveJSAddNode extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: ZWaveJSAddNodeDialogParams;
|
||||
|
||||
@state() private _entryId?: string;
|
||||
|
||||
@state() private _status?:
|
||||
@@ -91,6 +92,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
}
|
||||
|
||||
public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._entryId = params.entry_id;
|
||||
this._status = "loading";
|
||||
this._checkSmartStartSupport();
|
||||
@@ -180,17 +182,9 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
.localize=${this.hass.localize}
|
||||
@qr-code-scanned=${this._qrCodeScanned}
|
||||
></ha-qr-scanner>
|
||||
<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>`
|
||||
<mwc-button slot="secondaryAction" @click=${this._startOver}>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.back")}
|
||||
</mwc-button>`
|
||||
: this._status === "validate_dsk_enter_pin"
|
||||
? html`
|
||||
<p>
|
||||
@@ -271,7 +265,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._startInclusion}>
|
||||
<mwc-button slot="primaryAction" @click=${this._startOver}>
|
||||
Retry
|
||||
</mwc-button>
|
||||
`
|
||||
@@ -370,7 +364,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "failed"
|
||||
@@ -507,15 +501,6 @@ 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;
|
||||
@@ -562,17 +547,16 @@ 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(undefined, undefined, {
|
||||
dsk: "34673-15546-46480-39591-32400-22155-07715-45994",
|
||||
security_classes: [0, 1, 7],
|
||||
});
|
||||
this._startInclusion(provisioningInfo);
|
||||
} else {
|
||||
this._error = "This QR code is not supported";
|
||||
this._status = "failed";
|
||||
@@ -630,6 +614,11 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
ZWaveFeature.SmartStart
|
||||
)
|
||||
).supported;
|
||||
this._supportsSmartStart = true;
|
||||
}
|
||||
|
||||
private _startOver(_ev: Event) {
|
||||
this._startInclusion();
|
||||
}
|
||||
|
||||
private _startInclusion(
|
||||
@@ -693,6 +682,9 @@ 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") {
|
||||
|
@@ -2,6 +2,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
|
||||
export interface ZWaveJSAddNodeDialogParams {
|
||||
entry_id: string;
|
||||
addedCallback?: () => void;
|
||||
}
|
||||
|
||||
export const loadAddNodeDialog = () => import("./dialog-zwave_js-add-node");
|
||||
|
@@ -411,6 +411,7 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
private async _addNodeClicked() {
|
||||
showZWaveJSAddNodeDialog(this, {
|
||||
entry_id: this.configEntryId!,
|
||||
addedCallback: () => this._fetchData(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -327,6 +327,9 @@ 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;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiDelete } from "@mdi/js";
|
||||
import { mdiCheckCircle, mdiCloseCircleOutline, mdiDelete } from "@mdi/js";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -42,17 +42,42 @@ 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,
|
||||
},
|
||||
securityClasses: {
|
||||
security_classes: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.provisioned.security_classes"
|
||||
),
|
||||
width: "15%",
|
||||
width: "30%",
|
||||
hidden: narrow,
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
@@ -60,7 +85,7 @@ class ZWaveJSProvisioned extends LitElement {
|
||||
securityClasses
|
||||
.map((secClass) =>
|
||||
this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${SecurityClass[secClass]}`
|
||||
`ui.panel.config.zwave_js.security_classes.${SecurityClass[secClass]}.title`
|
||||
)
|
||||
)
|
||||
.join(", "),
|
||||
@@ -70,6 +95,7 @@ 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(
|
||||
@@ -97,6 +123,8 @@ 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"
|
||||
@@ -113,11 +141,8 @@ class ZWaveJSProvisioned extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
await unprovisionZwaveSmartStartNode(
|
||||
this.hass,
|
||||
this.configEntryId,
|
||||
ev.currentTarget.provisioningEntry.dsk
|
||||
);
|
||||
await unprovisionZwaveSmartStartNode(this.hass, this.configEntryId, dsk);
|
||||
this._fetchData();
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -106,6 +106,7 @@ class DialogUserDetail extends LitElement {
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.disabled=${user.system_generated}
|
||||
.checked=${this._localOnly}
|
||||
@change=${this._localOnlyChanged}
|
||||
>
|
||||
|
@@ -18,6 +18,7 @@ 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";
|
||||
@@ -40,6 +41,10 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
ha-expansion-panel {
|
||||
margin: 0 8px 16px;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
@@ -135,72 +140,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">
|
||||
<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>
|
||||
|
@@ -25,6 +25,7 @@ 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";
|
||||
@@ -83,8 +84,11 @@ export class HuiAreaCard
|
||||
return document.createElement("hui-area-card-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(): AreaCardConfig {
|
||||
return { type: "area", area: "" };
|
||||
public static async getStubConfig(
|
||||
hass: HomeAssistant
|
||||
): Promise<AreaCardConfig> {
|
||||
const areas = await subscribeOne(hass.connection, subscribeAreaRegistry);
|
||||
return { type: "area", area: areas[0]?.area_id || "" };
|
||||
}
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -358,12 +362,12 @@ export class HuiAreaCard
|
||||
});
|
||||
|
||||
let cameraEntityId: string | undefined;
|
||||
if ("camera" in entitiesByDomain) {
|
||||
if (this._config.show_camera && "camera" in entitiesByDomain) {
|
||||
cameraEntityId = entitiesByDomain.camera[0].entity_id;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card class=${area.picture ? "image" : ""}>
|
||||
<ha-card class=${area.picture || cameraEntityId ? "image" : ""}>
|
||||
${area.picture || cameraEntityId
|
||||
? html`<hui-image
|
||||
.config=${this._config}
|
||||
|
@@ -79,6 +79,7 @@ export interface EntitiesCardConfig extends LovelaceCardConfig {
|
||||
export interface AreaCardConfig extends LovelaceCardConfig {
|
||||
area: string;
|
||||
navigation_path?: string;
|
||||
show_camera?: boolean;
|
||||
}
|
||||
|
||||
export interface ButtonCardConfig extends LovelaceCardConfig {
|
||||
|
@@ -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, object, optional, string } from "superstruct";
|
||||
import { assert, assign, boolean, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-area-picker";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@@ -11,6 +11,8 @@ 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,
|
||||
@@ -18,6 +20,7 @@ const cardConfigStruct = assign(
|
||||
area: optional(string()),
|
||||
navigation_path: optional(string()),
|
||||
theme: optional(string()),
|
||||
show_camera: optional(boolean()),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -47,6 +50,10 @@ 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``;
|
||||
@@ -64,6 +71,18 @@ 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"
|
||||
@@ -88,7 +107,8 @@ export class HuiAreaCardEditor
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const value = ev.detail.value;
|
||||
const value =
|
||||
target.checked !== undefined ? target.checked : ev.detail.value;
|
||||
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
|
@@ -13,7 +13,7 @@ export const getCardStubConfig = async (
|
||||
const elClass = await getCardElementClass(type);
|
||||
|
||||
if (elClass && elClass.getStubConfig) {
|
||||
const classStubConfig = elClass.getStubConfig(
|
||||
const classStubConfig = await elClass.getStubConfig(
|
||||
hass,
|
||||
entities,
|
||||
entitiesFallback
|
||||
|
@@ -13,7 +13,7 @@ export const getHeaderFooterStubConfig = async (
|
||||
const elClass = await getHeaderFooterElementClass(type);
|
||||
|
||||
if (elClass && elClass.getStubConfig) {
|
||||
const classStubConfig = elClass.getStubConfig(
|
||||
const classStubConfig = await elClass.getStubConfig(
|
||||
hass,
|
||||
entities,
|
||||
entitiesFallback
|
||||
|
@@ -91,6 +91,7 @@ export const coreCards: Card[] = [
|
||||
},
|
||||
{
|
||||
type: "area",
|
||||
showElement: true,
|
||||
},
|
||||
{
|
||||
type: "conditional",
|
||||
|
@@ -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 } from "../../../data/entity";
|
||||
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { setValue } from "../../../data/input_text";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
@@ -67,6 +67,12 @@ 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!);
|
||||
}
|
||||
|
@@ -698,7 +698,7 @@ class HUIRoot extends LitElement {
|
||||
|
||||
private _navigateToView(path: string | number, replace?: boolean) {
|
||||
if (!this.lovelace!.editMode) {
|
||||
navigate(`${this.route!.prefix}/${path}`, { replace });
|
||||
navigate(`${this.route!.prefix}/${path}${location.search}`, { replace });
|
||||
return;
|
||||
}
|
||||
navigate(`${this.route!.prefix}/${path}?${addSearchParam({ edit: "1" })}`, {
|
||||
|
@@ -95,6 +95,7 @@ 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)",
|
||||
|
15
src/state/external-mixin.ts
Normal file
15
src/state/external-mixin.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
@@ -6,6 +6,7 @@ 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";
|
||||
@@ -31,4 +32,5 @@ export class HassElement extends ext(HassBaseEl, [
|
||||
hapticMixin,
|
||||
panelTitleMixin,
|
||||
loggingMixin,
|
||||
ExternalMixin,
|
||||
]) {}
|
||||
|
@@ -131,5 +131,7 @@ 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" });
|
||||
}
|
||||
};
|
||||
|
@@ -878,8 +878,7 @@
|
||||
"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_state_array_support": "Multiple state values not supported in visual editor"
|
||||
"no_template_editor_support": "Templates not supported in visual editor"
|
||||
},
|
||||
"supervisor": {
|
||||
"title": "Could not load the Supervisor panel!",
|
||||
@@ -929,9 +928,47 @@
|
||||
},
|
||||
"config": {
|
||||
"header": "Configure Home Assistant",
|
||||
"advanced_mode": {
|
||||
"hint_enable": "Missing config options? Enable advanced mode on",
|
||||
"link_profile_page": "your profile page"
|
||||
"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": "Manage blueprints"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"editor": {
|
||||
@@ -1514,7 +1551,7 @@
|
||||
"extra_fields": {
|
||||
"above": "Above",
|
||||
"below": "Below",
|
||||
"for": "Duration",
|
||||
"for": "Duration (optional)",
|
||||
"zone": "[%key:ui::panel::config::automation::editor::triggers::type::zone::label%]"
|
||||
}
|
||||
},
|
||||
@@ -1537,9 +1574,9 @@
|
||||
"state": {
|
||||
"label": "State",
|
||||
"attribute": "Attribute (optional)",
|
||||
"from": "From",
|
||||
"for": "For",
|
||||
"to": "To"
|
||||
"from": "From (optional)",
|
||||
"for": "For (optional)",
|
||||
"to": "To (optional)"
|
||||
},
|
||||
"homeassistant": {
|
||||
"label": "Home Assistant",
|
||||
@@ -2814,7 +2851,7 @@
|
||||
"node_id": "Device ID",
|
||||
"home_id": "Home ID",
|
||||
"source": "Source",
|
||||
"close": "Close",
|
||||
"back": "Back",
|
||||
"add_node": "Add device",
|
||||
"remove_node": "Remove device",
|
||||
"reconfigure_server": "Re-configure Server",
|
||||
@@ -2897,6 +2934,8 @@
|
||||
"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."
|
||||
},
|
||||
@@ -3255,7 +3294,8 @@
|
||||
},
|
||||
"area": {
|
||||
"name": "Area",
|
||||
"description": "The Area card automatically displays entities of a specific area."
|
||||
"description": "The Area card automatically displays entities of a specific area.",
|
||||
"show_camera": "Show camera feed instead of area picture"
|
||||
},
|
||||
"calendar": {
|
||||
"name": "Calendar",
|
||||
|
@@ -2,16 +2,16 @@ import { assert } from "chai";
|
||||
|
||||
import {
|
||||
ExternalMessaging,
|
||||
InternalMessage,
|
||||
EMMessage,
|
||||
} from "../../src/external_app/external_messaging";
|
||||
|
||||
// @ts-ignore
|
||||
global.__DEV__ = true;
|
||||
|
||||
class MockExternalMessaging extends ExternalMessaging {
|
||||
public mockSent: InternalMessage[] = [];
|
||||
public mockSent: EMMessage[] = [];
|
||||
|
||||
protected _sendExternal(msg: InternalMessage) {
|
||||
protected _sendExternal(msg: EMMessage) {
|
||||
this.mockSent.push(msg);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user