mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-14 03:39:26 +00:00
Compare commits
44 Commits
20211206.0
...
Move-parti
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a0b11eb357 | ||
![]() |
6f9b2ee569 | ||
![]() |
4ebdca2a46 | ||
![]() |
fc700fdaf0 | ||
![]() |
d8e12f4280 | ||
![]() |
86114758c3 | ||
![]() |
792278cf17 | ||
![]() |
b8832f2121 | ||
![]() |
76339c90f7 | ||
![]() |
b3d4451035 | ||
![]() |
dc58481918 | ||
![]() |
14af735507 | ||
![]() |
a7b558b64a | ||
![]() |
b7665bef6f | ||
![]() |
5ec37a35f1 | ||
![]() |
91bb2ddcc4 | ||
![]() |
85168b3a35 | ||
![]() |
942150cda2 | ||
![]() |
2606d55895 | ||
![]() |
1f671198aa | ||
![]() |
deb65e7108 | ||
![]() |
cd00f7f874 | ||
![]() |
2b0359edba | ||
![]() |
35e9687170 | ||
![]() |
b730676914 | ||
![]() |
2890192c05 | ||
![]() |
bfb84a834f | ||
![]() |
ca6fd6c770 | ||
![]() |
585648ac4c | ||
![]() |
bec5c564b6 | ||
![]() |
48c66e6349 | ||
![]() |
cea40610c0 | ||
![]() |
0c3fd8f3ad | ||
![]() |
02bdeebc82 | ||
![]() |
60c7669d8f | ||
![]() |
919bf94a03 | ||
![]() |
ead5e288eb | ||
![]() |
add8a702cc | ||
![]() |
39774c0e02 | ||
![]() |
149f381bc3 | ||
![]() |
faccb12430 | ||
![]() |
7039bae9be | ||
![]() |
0a7b703d57 | ||
![]() |
24e8028e8f |
@@ -206,6 +206,7 @@ const createDeviceRegistryEntries = (
|
|||||||
model: "Mock Device",
|
model: "Mock Device",
|
||||||
name: "Tag Reader",
|
name: "Tag Reader",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
|
hw_version: "1.0.0",
|
||||||
id: "mock-device-id",
|
id: "mock-device-id",
|
||||||
identifiers: [],
|
identifiers: [],
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
|
@@ -29,10 +29,6 @@ import {
|
|||||||
HassioAddonDetails,
|
HassioAddonDetails,
|
||||||
updateHassioAddon,
|
updateHassioAddon,
|
||||||
} from "../../../src/data/hassio/addon";
|
} from "../../../src/data/hassio/addon";
|
||||||
import {
|
|
||||||
createHassioPartialBackup,
|
|
||||||
HassioPartialBackupCreateParams,
|
|
||||||
} from "../../../src/data/hassio/backup";
|
|
||||||
import {
|
import {
|
||||||
extractApiErrorMessage,
|
extractApiErrorMessage,
|
||||||
ignoreSupervisorError,
|
ignoreSupervisorError,
|
||||||
@@ -103,7 +99,7 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
|
|
||||||
@state() private _addonInfo?: HassioAddonDetails;
|
@state() private _addonInfo?: HassioAddonDetails;
|
||||||
|
|
||||||
@state() private _action: "backup" | "update" | null = null;
|
@state() private _updating = false;
|
||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
@@ -120,7 +116,7 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
const changelog = changelogUrl(this._updateType, this._version);
|
const changelog = changelogUrl(this._updateType, this._version_latest);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card
|
<ha-card
|
||||||
@@ -132,7 +128,13 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
${this._error
|
${this._error
|
||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
${this._action === null
|
${this._version === this._version_latest
|
||||||
|
? html`<p>
|
||||||
|
${this.supervisor.localize("update_available.no_update", {
|
||||||
|
name: this._name,
|
||||||
|
})}
|
||||||
|
</p>`
|
||||||
|
: !this._updating
|
||||||
? html`
|
? html`
|
||||||
${this._changelogContent
|
${this._changelogContent
|
||||||
? html`
|
? html`
|
||||||
@@ -166,18 +168,13 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
: html`<ha-circular-progress alt="Updating" size="large" active>
|
: html`<ha-circular-progress alt="Updating" size="large" active>
|
||||||
</ha-circular-progress>
|
</ha-circular-progress>
|
||||||
<p class="progress-text">
|
<p class="progress-text">
|
||||||
${this._action === "update"
|
${this.supervisor.localize("update_available.updating", {
|
||||||
? this.supervisor.localize("update_available.updating", {
|
name: this._name,
|
||||||
name: this._name,
|
version: this._version_latest,
|
||||||
version: this._version_latest,
|
})}
|
||||||
})
|
|
||||||
: this.supervisor.localize(
|
|
||||||
"update_available.creating_backup",
|
|
||||||
{ name: this._name }
|
|
||||||
)}
|
|
||||||
</p>`}
|
</p>`}
|
||||||
</div>
|
</div>
|
||||||
${this._action === null
|
${this._version !== this._version_latest && !this._updating
|
||||||
? html`
|
? html`
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
${changelog
|
${changelog
|
||||||
@@ -224,6 +221,9 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get _shouldCreateBackup(): boolean {
|
get _shouldCreateBackup(): boolean {
|
||||||
|
if (this._updateType && !["core", "addon"].includes(this._updateType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const checkbox = this.shadowRoot?.querySelector("ha-checkbox");
|
const checkbox = this.shadowRoot?.querySelector("ha-checkbox");
|
||||||
if (checkbox) {
|
if (checkbox) {
|
||||||
return checkbox.checked;
|
return checkbox.checked;
|
||||||
@@ -310,37 +310,16 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
|
|
||||||
private async _update() {
|
private async _update() {
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
if (this._shouldCreateBackup) {
|
this._updating = true;
|
||||||
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 {
|
try {
|
||||||
if (this._updateType === "addon") {
|
if (this._updateType === "addon") {
|
||||||
await updateHassioAddon(this.hass, this.addonSlug!);
|
await updateHassioAddon(
|
||||||
|
this.hass,
|
||||||
|
this.addonSlug!,
|
||||||
|
this._shouldCreateBackup
|
||||||
|
);
|
||||||
} else if (this._updateType === "core") {
|
} else if (this._updateType === "core") {
|
||||||
await updateCore(this.hass);
|
await updateCore(this.hass, this._shouldCreateBackup);
|
||||||
} else if (this._updateType === "os") {
|
} else if (this._updateType === "os") {
|
||||||
await updateOS(this.hass);
|
await updateOS(this.hass);
|
||||||
} else if (this._updateType === "supervisor") {
|
} else if (this._updateType === "supervisor") {
|
||||||
@@ -349,7 +328,7 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||||
this._error = extractApiErrorMessage(err);
|
this._error = extractApiErrorMessage(err);
|
||||||
this._action = null;
|
this._updating = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20211206.0",
|
version="20211215.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/frontend",
|
url="https://github.com/home-assistant/frontend",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@@ -208,6 +208,7 @@ export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
|
|||||||
export const DOMAINS_INPUT_ROW = [
|
export const DOMAINS_INPUT_ROW = [
|
||||||
"cover",
|
"cover",
|
||||||
"fan",
|
"fan",
|
||||||
|
"group",
|
||||||
"humidifier",
|
"humidifier",
|
||||||
"input_boolean",
|
"input_boolean",
|
||||||
"input_datetime",
|
"input_datetime",
|
||||||
|
@@ -46,6 +46,7 @@ class HaAlert extends LitElement {
|
|||||||
rtl: this.rtl,
|
rtl: this.rtl,
|
||||||
[this.alertType]: true,
|
[this.alertType]: true,
|
||||||
})}"
|
})}"
|
||||||
|
role="alert"
|
||||||
>
|
>
|
||||||
<div class="icon ${this.title ? "" : "no-title"}">
|
<div class="icon ${this.title ? "" : "no-title"}">
|
||||||
<slot name="icon">
|
<slot name="icon">
|
||||||
|
@@ -56,6 +56,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
@click=${this._handleClick}
|
@click=${this._handleClick}
|
||||||
|
.label=${this.hass.localize("ui.components.related-filter-menu.filter")}
|
||||||
.path=${mdiFilterVariant}
|
.path=${mdiFilterVariant}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<mwc-menu-surface
|
<mwc-menu-surface
|
||||||
|
@@ -18,13 +18,9 @@ export class HaChip extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div class="mdc-chip">
|
<div class="mdc-chip ${this.noText ? "no-text" : ""}">
|
||||||
${this.hasIcon
|
${this.hasIcon
|
||||||
? html`<div
|
? html`<div class="mdc-chip__icon mdc-chip__icon--leading">
|
||||||
class="mdc-chip__icon mdc-chip__icon--leading ${this.noText
|
|
||||||
? "no-text"
|
|
||||||
: ""}"
|
|
||||||
>
|
|
||||||
<slot name="icon"></slot>
|
<slot name="icon"></slot>
|
||||||
</div>`
|
</div>`
|
||||||
: null}
|
: null}
|
||||||
@@ -49,6 +45,10 @@ export class HaChip extends LitElement {
|
|||||||
color: var(--ha-chip-text-color, var(--primary-text-color));
|
color: var(--ha-chip-text-color, var(--primary-text-color));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mdc-chip.no-text {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.mdc-chip:hover {
|
.mdc-chip:hover {
|
||||||
color: var(--ha-chip-text-color, var(--primary-text-color));
|
color: var(--ha-chip-text-color, var(--primary-text-color));
|
||||||
}
|
}
|
||||||
@@ -57,8 +57,8 @@ export class HaChip extends LitElement {
|
|||||||
--mdc-icon-size: 20px;
|
--mdc-icon-size: 20px;
|
||||||
color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
|
color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
|
||||||
}
|
}
|
||||||
.mdc-chip
|
.mdc-chip.no-text
|
||||||
.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden).no-text {
|
.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) {
|
||||||
margin-right: -4px;
|
margin-right: -4px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -35,7 +35,7 @@ class HaCoverControls extends LitElement {
|
|||||||
hidden: !supportsOpen(this.stateObj),
|
hidden: !supportsOpen(this.stateObj),
|
||||||
})}
|
})}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.open_cover"
|
"ui.dialogs.more_info_control.cover.open_cover"
|
||||||
)}
|
)}
|
||||||
@click=${this._onOpenTap}
|
@click=${this._onOpenTap}
|
||||||
.disabled=${this._computeOpenDisabled()}
|
.disabled=${this._computeOpenDisabled()}
|
||||||
@@ -47,7 +47,7 @@ class HaCoverControls extends LitElement {
|
|||||||
hidden: !supportsStop(this.stateObj),
|
hidden: !supportsStop(this.stateObj),
|
||||||
})}
|
})}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.stop_cover"
|
"ui.dialogs.more_info_control.cover.stop_cover"
|
||||||
)}
|
)}
|
||||||
.path=${mdiStop}
|
.path=${mdiStop}
|
||||||
@click=${this._onStopTap}
|
@click=${this._onStopTap}
|
||||||
@@ -58,7 +58,7 @@ class HaCoverControls extends LitElement {
|
|||||||
hidden: !supportsClose(this.stateObj),
|
hidden: !supportsClose(this.stateObj),
|
||||||
})}
|
})}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.close_cover"
|
"ui.dialogs.more_info_control.cover.close_cover"
|
||||||
)}
|
)}
|
||||||
@click=${this._onCloseTap}
|
@click=${this._onCloseTap}
|
||||||
.disabled=${this._computeClosedDisabled()}
|
.disabled=${this._computeClosedDisabled()}
|
||||||
|
@@ -30,7 +30,7 @@ class HaCoverTiltControls extends LitElement {
|
|||||||
invisible: !supportsOpenTilt(this.stateObj),
|
invisible: !supportsOpenTilt(this.stateObj),
|
||||||
})}
|
})}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.open_tilt_cover"
|
"ui.dialogs.more_info_control.cover.open_tilt_cover"
|
||||||
)}
|
)}
|
||||||
.path=${mdiArrowTopRight}
|
.path=${mdiArrowTopRight}
|
||||||
@click=${this._onOpenTiltTap}
|
@click=${this._onOpenTiltTap}
|
||||||
@@ -40,7 +40,9 @@ class HaCoverTiltControls extends LitElement {
|
|||||||
class=${classMap({
|
class=${classMap({
|
||||||
invisible: !supportsStopTilt(this.stateObj),
|
invisible: !supportsStopTilt(this.stateObj),
|
||||||
})}
|
})}
|
||||||
.label=${this.hass.localize("ui.dialogs.more_info_control.stop_cover")}
|
.label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.cover.stop_cover"
|
||||||
|
)}
|
||||||
.path=${mdiStop}
|
.path=${mdiStop}
|
||||||
@click=${this._onStopTiltTap}
|
@click=${this._onStopTiltTap}
|
||||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
@@ -50,7 +52,7 @@ class HaCoverTiltControls extends LitElement {
|
|||||||
invisible: !supportsCloseTilt(this.stateObj),
|
invisible: !supportsCloseTilt(this.stateObj),
|
||||||
})}
|
})}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.close_tilt_cover"
|
"ui.dialogs.more_info_control.cover.close_tilt_cover"
|
||||||
)}
|
)}
|
||||||
.path=${mdiArrowBottomLeft}
|
.path=${mdiArrowBottomLeft}
|
||||||
@click=${this._onCloseTiltTap}
|
@click=${this._onCloseTiltTap}
|
||||||
|
@@ -122,14 +122,20 @@ class HaDurationInput extends LitElement {
|
|||||||
value %= 60;
|
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", {
|
fireEvent(this, "value-changed", {
|
||||||
value: {
|
value: newValue,
|
||||||
hours,
|
|
||||||
minutes,
|
|
||||||
seconds: this._seconds,
|
|
||||||
milliseconds: this._milliseconds,
|
|
||||||
...{ [unit]: value },
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -66,7 +66,7 @@ export class HaFormString extends LitElement implements HaFormElement {
|
|||||||
${isPassword
|
${isPassword
|
||||||
? html`<ha-icon-button
|
? html`<ha-icon-button
|
||||||
toggles
|
toggles
|
||||||
.label="Click to toggle between masked and clear password"
|
.label=${`${this._unmaskedPassword ? "Hide" : "Show"} password`}
|
||||||
@click=${this._toggleUnmaskedPassword}
|
@click=${this._toggleUnmaskedPassword}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
|
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Formfield } from "@material/mwc-formfield";
|
import { Formfield } from "@material/mwc-formfield";
|
||||||
import { css, CSSResultGroup } from "lit";
|
import { css, CSSResultGroup } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
|
||||||
@customElement("ha-formfield")
|
@customElement("ha-formfield")
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
@@ -13,6 +14,7 @@ export class HaFormfield extends Formfield {
|
|||||||
case "HA-CHECKBOX":
|
case "HA-CHECKBOX":
|
||||||
case "HA-RADIO":
|
case "HA-RADIO":
|
||||||
(input as any).checked = !(input as any).checked;
|
(input as any).checked = !(input as any).checked;
|
||||||
|
fireEvent(input, "change");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
input.click();
|
input.click();
|
||||||
|
@@ -29,7 +29,7 @@ export class HaIconOverflowMenu extends LitElement {
|
|||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${this.narrow
|
${this.narrow
|
||||||
? html` <!-- Collapsed Representation for Small Screens -->
|
? html` <!-- Collapsed representation for small screens -->
|
||||||
<ha-button-menu
|
<ha-button-menu
|
||||||
@click=${this._handleIconOverflowMenuOpened}
|
@click=${this._handleIconOverflowMenuOpened}
|
||||||
@closed=${this._handleIconOverflowMenuClosed}
|
@closed=${this._handleIconOverflowMenuClosed}
|
||||||
@@ -59,8 +59,7 @@ export class HaIconOverflowMenu extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</ha-button-menu>`
|
</ha-button-menu>`
|
||||||
: html`
|
: html`
|
||||||
<!-- Icon Representation for Big Screens -->
|
<!-- Icon representation for big screens -->
|
||||||
|
|
||||||
${this.items.map((item) =>
|
${this.items.map((item) =>
|
||||||
item.narrowOnly
|
item.narrowOnly
|
||||||
? ""
|
? ""
|
||||||
@@ -70,13 +69,12 @@ export class HaIconOverflowMenu extends LitElement {
|
|||||||
${item.tooltip}
|
${item.tooltip}
|
||||||
</paper-tooltip>`
|
</paper-tooltip>`
|
||||||
: ""}
|
: ""}
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
@click=${item.action}
|
@click=${item.action}
|
||||||
.label=${item.label}
|
.label=${item.label}
|
||||||
|
.path=${item.path}
|
||||||
.disabled=${item.disabled}
|
.disabled=${item.disabled}
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon .path=${item.path}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
</div> `
|
</div> `
|
||||||
)}
|
)}
|
||||||
`}
|
`}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@material/mwc-select/mwc-select";
|
import "@material/mwc-select/mwc-select";
|
||||||
import type { Select } from "@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 { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import type QrScanner from "qr-scanner";
|
import type QrScanner from "qr-scanner";
|
||||||
@@ -8,6 +10,8 @@ import { fireEvent } from "../common/dom/fire_event";
|
|||||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import "./ha-alert";
|
import "./ha-alert";
|
||||||
|
import "./ha-button-menu";
|
||||||
|
import "@material/mwc-button/mwc-button";
|
||||||
|
|
||||||
@customElement("ha-qr-scanner")
|
@customElement("ha-qr-scanner")
|
||||||
class HaQrScanner extends LitElement {
|
class HaQrScanner extends LitElement {
|
||||||
@@ -25,6 +29,8 @@ class HaQrScanner extends LitElement {
|
|||||||
|
|
||||||
@query("#canvas-container", true) private _canvasContainer!: HTMLDivElement;
|
@query("#canvas-container", true) private _canvasContainer!: HTMLDivElement;
|
||||||
|
|
||||||
|
@query("mwc-textfield") private _manualInput?: TextField;
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
public disconnectedCallback(): void {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._qrNotFoundCount = 0;
|
this._qrNotFoundCount = 0;
|
||||||
@@ -58,34 +64,53 @@ class HaQrScanner extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`${this._cameras && this._cameras.length > 1
|
return html`${this._error
|
||||||
? 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>`
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
${navigator.mediaDevices
|
${navigator.mediaDevices
|
||||||
? html`<video></video>
|
? html`<video></video>
|
||||||
<div id="canvas-container"></div>`
|
<div id="canvas-container">
|
||||||
: html`<ha-alert alert-type="warning"
|
${this._cameras && this._cameras.length > 1
|
||||||
>${!window.isSecureContext
|
? html`<ha-button-menu
|
||||||
? "You can only use your camera to scan a QR core when using HTTPS."
|
corner="BOTTOM_START"
|
||||||
: "Your browser doesn't support QR scanning."}</ha-alert
|
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>`}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _loadQrScanner() {
|
private async _loadQrScanner() {
|
||||||
@@ -134,17 +159,49 @@ class HaQrScanner extends LitElement {
|
|||||||
fireEvent(this, "qr-code-scanned", { value: qrCodeString });
|
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 {
|
private _cameraChanged(ev: CustomEvent): void {
|
||||||
this._qrScanner?.setCamera((ev.target as Select).value);
|
this._qrScanner?.setCamera((ev.target as any).value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
canvas {
|
canvas {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
mwc-select {
|
#canvas-container {
|
||||||
width: 100%;
|
position: relative;
|
||||||
margin-bottom: 16px;
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ import {
|
|||||||
mdiBell,
|
mdiBell,
|
||||||
mdiCalendar,
|
mdiCalendar,
|
||||||
mdiCart,
|
mdiCart,
|
||||||
|
mdiCellphoneCog,
|
||||||
mdiChartBox,
|
mdiChartBox,
|
||||||
mdiClose,
|
mdiClose,
|
||||||
mdiCog,
|
mdiCog,
|
||||||
@@ -43,6 +44,10 @@ import {
|
|||||||
PersistentNotification,
|
PersistentNotification,
|
||||||
subscribeNotifications,
|
subscribeNotifications,
|
||||||
} from "../data/persistent_notification";
|
} from "../data/persistent_notification";
|
||||||
|
import {
|
||||||
|
ExternalConfig,
|
||||||
|
getExternalConfig,
|
||||||
|
} from "../external_app/external_config";
|
||||||
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
||||||
import { haStyleScrollbar } from "../resources/styles";
|
import { haStyleScrollbar } from "../resources/styles";
|
||||||
import type { HomeAssistant, PanelInfo, Route } from "../types";
|
import type { HomeAssistant, PanelInfo, Route } from "../types";
|
||||||
@@ -187,6 +192,8 @@ class HaSidebar extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public editMode = false;
|
@property({ type: Boolean }) public editMode = false;
|
||||||
|
|
||||||
|
@state() private _externalConfig?: ExternalConfig;
|
||||||
|
|
||||||
@state() private _notifications?: PersistentNotification[];
|
@state() private _notifications?: PersistentNotification[];
|
||||||
|
|
||||||
@state() private _renderEmptySortable = false;
|
@state() private _renderEmptySortable = false;
|
||||||
@@ -233,6 +240,7 @@ class HaSidebar extends LitElement {
|
|||||||
changedProps.has("expanded") ||
|
changedProps.has("expanded") ||
|
||||||
changedProps.has("narrow") ||
|
changedProps.has("narrow") ||
|
||||||
changedProps.has("alwaysExpand") ||
|
changedProps.has("alwaysExpand") ||
|
||||||
|
changedProps.has("_externalConfig") ||
|
||||||
changedProps.has("_notifications") ||
|
changedProps.has("_notifications") ||
|
||||||
changedProps.has("editMode") ||
|
changedProps.has("editMode") ||
|
||||||
changedProps.has("_renderEmptySortable") ||
|
changedProps.has("_renderEmptySortable") ||
|
||||||
@@ -263,6 +271,12 @@ class HaSidebar extends LitElement {
|
|||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
super.firstUpdated(changedProps);
|
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) => {
|
subscribeNotifications(this.hass.connection, (notifications) => {
|
||||||
this._notifications = notifications;
|
this._notifications = notifications;
|
||||||
});
|
});
|
||||||
@@ -358,6 +372,7 @@ class HaSidebar extends LitElement {
|
|||||||
: this._renderPanels(beforeSpacer)}
|
: this._renderPanels(beforeSpacer)}
|
||||||
${this._renderSpacer()}
|
${this._renderSpacer()}
|
||||||
${this._renderPanels(afterSpacer)}
|
${this._renderPanels(afterSpacer)}
|
||||||
|
${this._renderExternalConfiguration()}
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -387,7 +402,7 @@ class HaSidebar extends LitElement {
|
|||||||
) {
|
) {
|
||||||
return html`
|
return html`
|
||||||
<a
|
<a
|
||||||
aria-role="option"
|
role="option"
|
||||||
href=${`/${urlPath}`}
|
href=${`/${urlPath}`}
|
||||||
data-panel=${urlPath}
|
data-panel=${urlPath}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
@@ -492,7 +507,7 @@ class HaSidebar extends LitElement {
|
|||||||
>
|
>
|
||||||
<paper-icon-item
|
<paper-icon-item
|
||||||
class="notifications"
|
class="notifications"
|
||||||
aria-role="option"
|
role="option"
|
||||||
@click=${this._handleShowNotificationDrawer}
|
@click=${this._handleShowNotificationDrawer}
|
||||||
>
|
>
|
||||||
<ha-svg-icon slot="item-icon" .path=${mdiBell}></ha-svg-icon>
|
<ha-svg-icon slot="item-icon" .path=${mdiBell}></ha-svg-icon>
|
||||||
@@ -523,7 +538,7 @@ class HaSidebar extends LitElement {
|
|||||||
href="/profile"
|
href="/profile"
|
||||||
data-panel="panel"
|
data-panel="panel"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
aria-role="option"
|
role="option"
|
||||||
aria-label=${this.hass.localize("panel.profile")}
|
aria-label=${this.hass.localize("panel.profile")}
|
||||||
@mouseenter=${this._itemMouseEnter}
|
@mouseenter=${this._itemMouseEnter}
|
||||||
@mouseleave=${this._itemMouseLeave}
|
@mouseleave=${this._itemMouseLeave}
|
||||||
@@ -542,6 +557,43 @@ class HaSidebar extends LitElement {
|
|||||||
</a>`;
|
</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() {
|
private get _tooltip() {
|
||||||
return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
|
return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,7 @@ export interface DeviceRegistryEntry {
|
|||||||
model: string | null;
|
model: string | null;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
sw_version: string | null;
|
sw_version: string | null;
|
||||||
|
hw_version: string | null;
|
||||||
via_device_id: string | null;
|
via_device_id: string | null;
|
||||||
area_id: string | null;
|
area_id: string | null;
|
||||||
name_by_user: string | null;
|
name_by_user: string | null;
|
||||||
|
@@ -302,7 +302,8 @@ export const installHassioAddon = async (
|
|||||||
|
|
||||||
export const updateHassioAddon = async (
|
export const updateHassioAddon = async (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
slug: string
|
slug: string,
|
||||||
|
backup: boolean
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||||
await hass.callWS({
|
await hass.callWS({
|
||||||
@@ -310,11 +311,13 @@ export const updateHassioAddon = async (
|
|||||||
endpoint: `/store/addons/${slug}/update`,
|
endpoint: `/store/addons/${slug}/update`,
|
||||||
method: "post",
|
method: "post",
|
||||||
timeout: null,
|
timeout: null,
|
||||||
|
data: { backup: backup },
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await hass.callApi<HassioResponse<void>>(
|
await hass.callApi<HassioResponse<void>>(
|
||||||
"POST",
|
"POST",
|
||||||
`hassio/addons/${slug}/update`
|
`hassio/addons/${slug}/update`,
|
||||||
|
{ backup: backup }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -156,6 +156,7 @@ export interface MediaPlayerThumbnail {
|
|||||||
|
|
||||||
export interface ControlButton {
|
export interface ControlButton {
|
||||||
icon: string;
|
icon: string;
|
||||||
|
// Used as key for action as well as tooltip and aria-label translation key
|
||||||
action: string;
|
action: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,15 +6,18 @@ export const restartCore = async (hass: HomeAssistant) => {
|
|||||||
await hass.callService("homeassistant", "restart");
|
await hass.callService("homeassistant", "restart");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateCore = async (hass: HomeAssistant) => {
|
export const updateCore = async (hass: HomeAssistant, backup: boolean) => {
|
||||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||||
await hass.callWS({
|
await hass.callWS({
|
||||||
type: "supervisor/api",
|
type: "supervisor/api",
|
||||||
endpoint: "/core/update",
|
endpoint: "/core/update",
|
||||||
method: "post",
|
method: "post",
|
||||||
timeout: null,
|
timeout: null,
|
||||||
|
data: { backup: backup },
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await hass.callApi<HassioResponse<void>>("POST", `hassio/core/update`);
|
await hass.callApi<HassioResponse<void>>("POST", `hassio/core/update`, {
|
||||||
|
backup: backup,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -65,6 +65,9 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
action=${control.action}
|
action=${control.action}
|
||||||
@click=${this._handleClick}
|
@click=${this._handleClick}
|
||||||
.path=${control.icon}
|
.path=${control.icon}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
`ui.card.media_player.${control.action}`
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
</ha-icon-button>
|
</ha-icon-button>
|
||||||
`
|
`
|
||||||
|
@@ -99,6 +99,8 @@ export class QuickBar extends LitElement {
|
|||||||
|
|
||||||
private _focusSet = false;
|
private _focusSet = false;
|
||||||
|
|
||||||
|
private _focusListElement?: ListItem | null;
|
||||||
|
|
||||||
public async showDialog(params: QuickBarParams) {
|
public async showDialog(params: QuickBarParams) {
|
||||||
this._commandMode = params.commandMode || this._toggleIfAlreadyOpened();
|
this._commandMode = params.commandMode || this._toggleIfAlreadyOpened();
|
||||||
this._initializeItemsIfNeeded();
|
this._initializeItemsIfNeeded();
|
||||||
@@ -317,7 +319,8 @@ export class QuickBar extends LitElement {
|
|||||||
} else if (ev.code === "ArrowDown") {
|
} else if (ev.code === "ArrowDown") {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this._getItemAtIndex(0)?.focus();
|
this._getItemAtIndex(0)?.focus();
|
||||||
this._getItemAtIndex(1)?.focus();
|
this._focusSet = true;
|
||||||
|
this._focusListElement = this._getItemAtIndex(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,6 +353,11 @@ export class QuickBar extends LitElement {
|
|||||||
this._initializeItemsIfNeeded();
|
this._initializeItemsIfNeeded();
|
||||||
this._filter = this._search;
|
this._filter = this._search;
|
||||||
} else {
|
} else {
|
||||||
|
if (this._focusSet && this._focusListElement) {
|
||||||
|
this._focusSet = false;
|
||||||
|
// @ts-ignore
|
||||||
|
this._focusListElement.rippleHandlers.endFocus();
|
||||||
|
}
|
||||||
this._debouncedSetFilter(this._search);
|
this._debouncedSetFilter(this._search);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,12 +374,14 @@ export class QuickBar extends LitElement {
|
|||||||
private _setFocusFirstListItem() {
|
private _setFocusFirstListItem() {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this._getItemAtIndex(0)?.rippleHandlers.startFocus();
|
this._getItemAtIndex(0)?.rippleHandlers.startFocus();
|
||||||
|
this._focusListElement = this._getItemAtIndex(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleListItemKeyDown(ev: KeyboardEvent) {
|
private _handleListItemKeyDown(ev: KeyboardEvent) {
|
||||||
const isSingleCharacter = ev.key.length === 1;
|
const isSingleCharacter = ev.key.length === 1;
|
||||||
const isFirstListItem =
|
const isFirstListItem =
|
||||||
(ev.target as HTMLElement).getAttribute("index") === "0";
|
(ev.target as HTMLElement).getAttribute("index") === "0";
|
||||||
|
this._focusListElement = ev.target as ListItem;
|
||||||
if (ev.key === "ArrowUp") {
|
if (ev.key === "ArrowUp") {
|
||||||
if (isFirstListItem) {
|
if (isFirstListItem) {
|
||||||
this._filterInputField?.focus();
|
this._filterInputField?.focus();
|
||||||
@@ -511,7 +521,13 @@ export class QuickBar extends LitElement {
|
|||||||
if (page.component) {
|
if (page.component) {
|
||||||
const info = this._getNavigationInfoFromConfig(page);
|
const info = this._getNavigationInfoFromConfig(page);
|
||||||
|
|
||||||
if (info) {
|
// 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
|
||||||
|
)
|
||||||
|
) {
|
||||||
items.push(info);
|
items.push(info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
const authProm = isExternal
|
||||||
? () =>
|
? () =>
|
||||||
import("../external_app/external_auth").then(({ createExternalAuth }) =>
|
import("../external_app/external_auth").then(({ createExternalAuth }) =>
|
||||||
@@ -52,23 +71,7 @@ const authProm = isExternal
|
|||||||
const connProm = async (auth) => {
|
const connProm = async (auth) => {
|
||||||
try {
|
try {
|
||||||
const conn = await createConnection({ auth });
|
const conn = await createConnection({ auth });
|
||||||
// Clear auth data from url if we have been able to establish a connection
|
clearUrlParams();
|
||||||
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 };
|
return { auth, conn };
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err !== ERR_INVALID_AUTH) {
|
if (err !== ERR_INVALID_AUTH) {
|
||||||
@@ -85,6 +88,7 @@ const connProm = async (auth) => {
|
|||||||
}
|
}
|
||||||
auth = await authProm();
|
auth = await authProm();
|
||||||
const conn = await createConnection({ auth });
|
const conn = await createConnection({ auth });
|
||||||
|
clearUrlParams();
|
||||||
return { auth, conn };
|
return { auth, conn };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* Auth class that connects to a native app for authentication.
|
* Auth class that connects to a native app for authentication.
|
||||||
*/
|
*/
|
||||||
import { Auth } from "home-assistant-js-websocket";
|
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_SET_TOKEN = "externalAuthSetToken";
|
||||||
const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken";
|
const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken";
|
||||||
@@ -36,7 +36,7 @@ declare global {
|
|||||||
postMessage(payload: BasePayload);
|
postMessage(payload: BasePayload);
|
||||||
};
|
};
|
||||||
externalBus: {
|
externalBus: {
|
||||||
postMessage(payload: InternalMessage);
|
postMessage(payload: EMMessage);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { Connection } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
externalForwardConnectionEvents,
|
externalForwardConnectionEvents,
|
||||||
externalForwardHaptics,
|
externalForwardHaptics,
|
||||||
@@ -7,39 +8,50 @@ const CALLBACK_EXTERNAL_BUS = "externalBus";
|
|||||||
|
|
||||||
interface CommandInFlight {
|
interface CommandInFlight {
|
||||||
resolve: (data: any) => void;
|
resolve: (data: any) => void;
|
||||||
reject: (err: ExternalError) => void;
|
reject: (err: EMError) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InternalMessage {
|
export interface EMMessage {
|
||||||
id?: number;
|
id?: number;
|
||||||
type: string;
|
type: string;
|
||||||
payload?: unknown;
|
payload?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExternalError {
|
interface EMError {
|
||||||
code: string;
|
code: string;
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExternalMessageResult {
|
interface EMMessageResultSuccess {
|
||||||
id: number;
|
id: number;
|
||||||
type: "result";
|
type: "result";
|
||||||
success: true;
|
success: true;
|
||||||
result: unknown;
|
result: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExternalMessageResultError {
|
interface EMMessageResultError {
|
||||||
id: number;
|
id: number;
|
||||||
type: "result";
|
type: "result";
|
||||||
success: false;
|
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 {
|
export class ExternalMessaging {
|
||||||
public commands: { [msgId: number]: CommandInFlight } = {};
|
public commands: { [msgId: number]: CommandInFlight } = {};
|
||||||
|
|
||||||
|
public connection?: Connection;
|
||||||
|
|
||||||
public cache: Record<string, any> = {};
|
public cache: Record<string, any> = {};
|
||||||
|
|
||||||
public msgId = 0;
|
public msgId = 0;
|
||||||
@@ -54,7 +66,7 @@ export class ExternalMessaging {
|
|||||||
* Send message to external app that expects a response.
|
* Send message to external app that expects a response.
|
||||||
* @param msg message to send
|
* @param msg message to send
|
||||||
*/
|
*/
|
||||||
public sendMessage<T>(msg: InternalMessage): Promise<T> {
|
public sendMessage<T>(msg: EMMessage): Promise<T> {
|
||||||
const msgId = ++this.msgId;
|
const msgId = ++this.msgId;
|
||||||
msg.id = msgId;
|
msg.id = msgId;
|
||||||
|
|
||||||
@@ -69,7 +81,9 @@ export class ExternalMessaging {
|
|||||||
* Send message to external app without expecting a response.
|
* Send message to external app without expecting a response.
|
||||||
* @param msg message to send
|
* @param msg message to send
|
||||||
*/
|
*/
|
||||||
public fireMessage(msg: InternalMessage) {
|
public fireMessage(
|
||||||
|
msg: EMMessage | EMMessageResultSuccess | EMMessageResultError
|
||||||
|
) {
|
||||||
if (!msg.id) {
|
if (!msg.id) {
|
||||||
msg.id = ++this.msgId;
|
msg.id = ++this.msgId;
|
||||||
}
|
}
|
||||||
@@ -82,6 +96,43 @@ export class ExternalMessaging {
|
|||||||
console.log("Receiving message from external app", msg);
|
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];
|
const pendingCmd = this.commands[msg.id];
|
||||||
|
|
||||||
if (!pendingCmd) {
|
if (!pendingCmd) {
|
||||||
@@ -99,7 +150,7 @@ export class ExternalMessaging {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _sendExternal(msg: InternalMessage) {
|
protected _sendExternal(msg: EMMessage) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log("Sending message to external app", msg);
|
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.
|
// Update the url if we know where we're mounted.
|
||||||
if (route) {
|
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();
|
const path = curPath();
|
||||||
|
|
||||||
if (["", "/"].includes(path)) {
|
if (["", "/"].includes(path)) {
|
||||||
navigate(`/${getStorageDefaultPanelUrlPath()}`, { replace: true });
|
navigate(`/${getStorageDefaultPanelUrlPath()}${location.search}`, {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this._route = {
|
this._route = {
|
||||||
prefix: "",
|
prefix: "",
|
||||||
|
@@ -95,8 +95,11 @@ class OnboardingCreateUser extends LitElement {
|
|||||||
private _handleValueChanged(
|
private _handleValueChanged(
|
||||||
ev: PolymerChangedEvent<HaFormDataContainer>
|
ev: PolymerChangedEvent<HaFormDataContainer>
|
||||||
): void {
|
): void {
|
||||||
|
const nameChanged = ev.detail.value.name !== this._newUser.name;
|
||||||
this._newUser = ev.detail.value;
|
this._newUser = ev.detail.value;
|
||||||
this._maybePopulateUsername();
|
if (nameChanged) {
|
||||||
|
this._maybePopulateUsername();
|
||||||
|
}
|
||||||
this._formError.password_confirm =
|
this._formError.password_confirm =
|
||||||
this._newUser.password !== this._newUser.password_confirm
|
this._newUser.password !== this._newUser.password_confirm
|
||||||
? this.localize(
|
? this.localize(
|
||||||
|
@@ -138,7 +138,8 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||||||
if (!ev.detail.isValid) {
|
if (!ev.detail.isValid) {
|
||||||
return;
|
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 {
|
static get styles(): CSSResultGroup {
|
||||||
|
@@ -109,6 +109,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
<ha-automation-condition-editor
|
<ha-automation-condition-editor
|
||||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||||
|
@value-changed=${this._handleChangeEvent}
|
||||||
.yamlMode=${this._yamlMode}
|
.yamlMode=${this._yamlMode}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.condition=${this.condition}
|
.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>) {
|
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||||
switch (ev.detail.index) {
|
switch (ev.detail.index) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@@ -17,9 +17,9 @@ import {
|
|||||||
|
|
||||||
const stateConditionStruct = object({
|
const stateConditionStruct = object({
|
||||||
condition: literal("state"),
|
condition: literal("state"),
|
||||||
entity_id: string(),
|
entity_id: optional(string()),
|
||||||
attribute: optional(string()),
|
attribute: optional(string()),
|
||||||
state: string(),
|
state: optional(string()),
|
||||||
for: optional(union([string(), forDictStruct])),
|
for: optional(union([string(), forDictStruct])),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,4 +1,9 @@
|
|||||||
import { object, optional, number } from "superstruct";
|
import { object, optional, number, string } from "superstruct";
|
||||||
|
|
||||||
|
export const baseTriggerStruct = object({
|
||||||
|
platform: string(),
|
||||||
|
id: optional(string()),
|
||||||
|
});
|
||||||
|
|
||||||
export const forDictStruct = object({
|
export const forDictStruct = object({
|
||||||
days: optional(number()),
|
days: optional(number()),
|
||||||
|
@@ -291,6 +291,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
if (!ev.detail.isValid) {
|
if (!ev.detail.isValid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this._warnings = undefined;
|
||||||
fireEvent(this, "value-changed", { value: ev.detail.value });
|
fireEvent(this, "value-changed", { value: ev.detail.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,15 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import { html, LitElement, PropertyValues } from "lit";
|
import { html, LitElement, PropertyValues } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { assert, literal, object, optional, string, union } from "superstruct";
|
import {
|
||||||
|
assert,
|
||||||
|
assign,
|
||||||
|
literal,
|
||||||
|
object,
|
||||||
|
optional,
|
||||||
|
string,
|
||||||
|
union,
|
||||||
|
} from "superstruct";
|
||||||
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
|
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import { hasTemplate } from "../../../../../common/string/has-template";
|
import { hasTemplate } from "../../../../../common/string/has-template";
|
||||||
@@ -10,20 +18,23 @@ import "../../../../../components/entity/ha-entity-picker";
|
|||||||
import "../../../../../components/ha-duration-input";
|
import "../../../../../components/ha-duration-input";
|
||||||
import { StateTrigger } from "../../../../../data/automation";
|
import { StateTrigger } from "../../../../../data/automation";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import { forDictStruct } from "../../structs";
|
import { baseTriggerStruct, forDictStruct } from "../../structs";
|
||||||
import {
|
import {
|
||||||
handleChangeEvent,
|
handleChangeEvent,
|
||||||
TriggerElement,
|
TriggerElement,
|
||||||
} from "../ha-automation-trigger-row";
|
} from "../ha-automation-trigger-row";
|
||||||
|
|
||||||
const stateTriggerStruct = object({
|
const stateTriggerStruct = assign(
|
||||||
platform: literal("state"),
|
baseTriggerStruct,
|
||||||
entity_id: string(),
|
object({
|
||||||
attribute: optional(string()),
|
platform: literal("state"),
|
||||||
from: optional(string()),
|
entity_id: optional(string()),
|
||||||
to: optional(string()),
|
attribute: optional(string()),
|
||||||
for: optional(union([string(), forDictStruct])),
|
from: optional(string()),
|
||||||
});
|
to: optional(string()),
|
||||||
|
for: optional(union([string(), forDictStruct])),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-state")
|
@customElement("ha-automation-trigger-state")
|
||||||
export class HaStateTrigger extends LitElement implements TriggerElement {
|
export class HaStateTrigger extends LitElement implements TriggerElement {
|
||||||
@@ -39,6 +50,13 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
|
|||||||
if (!changedProperties.has("trigger")) {
|
if (!changedProperties.has("trigger")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
this.trigger.for &&
|
||||||
|
typeof this.trigger.for === "object" &&
|
||||||
|
this.trigger.for.milliseconds === 0
|
||||||
|
) {
|
||||||
|
delete this.trigger.for.milliseconds;
|
||||||
|
}
|
||||||
// Check for templates in trigger. If found, revert to YAML mode.
|
// Check for templates in trigger. If found, revert to YAML mode.
|
||||||
if (this.trigger && hasTemplate(this.trigger)) {
|
if (this.trigger && hasTemplate(this.trigger)) {
|
||||||
fireEvent(
|
fireEvent(
|
||||||
|
@@ -131,7 +131,7 @@ class HaConfigDashboard extends LitElement {
|
|||||||
border-bottom: var(--app-header-border-bottom);
|
border-bottom: var(--app-header-border-bottom);
|
||||||
--header-height: 55px;
|
--header-height: 55px;
|
||||||
}
|
}
|
||||||
ha-card:last-child {
|
:host(:not([narrow])) ha-card:last-child {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
ha-config-section {
|
ha-config-section {
|
||||||
@@ -152,7 +152,7 @@ class HaConfigDashboard extends LitElement {
|
|||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
:host([narrow]) ha-card {
|
:host([narrow]) ha-card {
|
||||||
background-color: var(--primary-background-color);
|
border-radius: 0;
|
||||||
box-shadow: unset;
|
box-shadow: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@ class HaConfigNavigation extends LitElement {
|
|||||||
: canShowPage(this.hass, page)
|
: canShowPage(this.hass, page)
|
||||||
)
|
)
|
||||||
? html`
|
? html`
|
||||||
<a href=${page.path} aria-role="option" tabindex="-1">
|
<a href=${page.path} role="option" tabindex="-1">
|
||||||
<paper-icon-item @click=${this._entryClicked}>
|
<paper-icon-item @click=${this._entryClicked}>
|
||||||
<div
|
<div
|
||||||
class=${page.iconColor ? "icon-background" : ""}
|
class=${page.iconColor ? "icon-background" : ""}
|
||||||
@@ -43,8 +43,7 @@ class HaConfigNavigation extends LitElement {
|
|||||||
<paper-item-body two-line>
|
<paper-item-body two-line>
|
||||||
${page.name ||
|
${page.name ||
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
page.translationKey ||
|
`ui.panel.config.dashboard.${page.translationKey}.title`
|
||||||
`ui.panel.config.${page.component}.caption`
|
|
||||||
)}
|
)}
|
||||||
${page.component === "cloud" && (page.info as CloudStatus)
|
${page.component === "cloud" && (page.info as CloudStatus)
|
||||||
? page.info.logged_in
|
? page.info.logged_in
|
||||||
@@ -68,7 +67,7 @@ class HaConfigNavigation extends LitElement {
|
|||||||
<div secondary>
|
<div secondary>
|
||||||
${page.description ||
|
${page.description ||
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
`ui.panel.config.${page.component}.description`
|
`ui.panel.config.dashboard.${page.translationKey}.description`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
@@ -66,6 +66,17 @@ export class HaDeviceCard extends LitElement {
|
|||||||
</div>
|
</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>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
<slot name="actions"></slot>
|
<slot name="actions"></slot>
|
||||||
|
@@ -49,80 +49,69 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
|||||||
dashboard: [
|
dashboard: [
|
||||||
{
|
{
|
||||||
path: "/config/integrations",
|
path: "/config/integrations",
|
||||||
name: "Devices & Services",
|
translationKey: "devices",
|
||||||
description: "Integrations, devices, entities and areas",
|
|
||||||
iconPath: mdiDevices,
|
iconPath: mdiDevices,
|
||||||
iconColor: "#0D47A1",
|
iconColor: "#0D47A1",
|
||||||
core: true,
|
core: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/automation",
|
path: "/config/automation",
|
||||||
name: "Automations & Scenes",
|
translationKey: "automations",
|
||||||
description: "Manage automations, scenes, scripts and helpers",
|
|
||||||
iconPath: mdiRobot,
|
iconPath: mdiRobot,
|
||||||
iconColor: "#518C43",
|
iconColor: "#518C43",
|
||||||
core: true,
|
core: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/blueprint",
|
path: "/config/blueprint",
|
||||||
name: "Blueprints",
|
translationKey: "blueprints",
|
||||||
description: "Manage blueprints",
|
|
||||||
iconPath: mdiPaletteSwatch,
|
iconPath: mdiPaletteSwatch,
|
||||||
iconColor: "#64B5F6",
|
iconColor: "#64B5F6",
|
||||||
component: "blueprint",
|
component: "blueprint",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/hassio",
|
path: "/hassio",
|
||||||
name: "Add-ons, Backups & Supervisor",
|
translationKey: "supervisor",
|
||||||
description: "Create backups, check logs or reboot your system",
|
|
||||||
iconPath: mdiHomeAssistant,
|
iconPath: mdiHomeAssistant,
|
||||||
iconColor: "#4084CD",
|
iconColor: "#4084CD",
|
||||||
component: "hassio",
|
component: "hassio",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/lovelace/dashboards",
|
path: "/config/lovelace/dashboards",
|
||||||
name: "Dashboards",
|
translationKey: "dashboards",
|
||||||
description: "Create customized sets of cards to control your home",
|
|
||||||
iconPath: mdiViewDashboard,
|
iconPath: mdiViewDashboard,
|
||||||
iconColor: "#B1345C",
|
iconColor: "#B1345C",
|
||||||
component: "lovelace",
|
component: "lovelace",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/energy",
|
path: "/config/energy",
|
||||||
name: "Energy",
|
translationKey: "energy",
|
||||||
description: "Monitor your energy production and consumption",
|
|
||||||
iconPath: mdiLightningBolt,
|
iconPath: mdiLightningBolt,
|
||||||
iconColor: "#F1C447",
|
iconColor: "#F1C447",
|
||||||
component: "energy",
|
component: "energy",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/tags",
|
path: "/config/tags",
|
||||||
name: "Tags",
|
translationKey: "tags",
|
||||||
description:
|
|
||||||
"Trigger automations when a NFC tag, QR code, etc. is scanned",
|
|
||||||
iconPath: mdiNfcVariant,
|
iconPath: mdiNfcVariant,
|
||||||
iconColor: "#616161",
|
iconColor: "#616161",
|
||||||
component: "tag",
|
component: "tag",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/person",
|
path: "/config/person",
|
||||||
name: "People & Zones",
|
translationKey: "people",
|
||||||
description: "Manage the people and zones that Home Assistant tracks",
|
|
||||||
iconPath: mdiAccount,
|
iconPath: mdiAccount,
|
||||||
iconColor: "#E48629",
|
iconColor: "#E48629",
|
||||||
components: ["person", "zone", "users"],
|
components: ["person", "zone", "users"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "#external-app-configuration",
|
path: "#external-app-configuration",
|
||||||
name: "Companion App",
|
translationKey: "companion",
|
||||||
description: "Location and notifications",
|
|
||||||
iconPath: mdiCellphoneCog,
|
iconPath: mdiCellphoneCog,
|
||||||
iconColor: "#8E24AA",
|
iconColor: "#8E24AA",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/core",
|
path: "/config/server_control",
|
||||||
name: "Settings",
|
translationKey: "settings",
|
||||||
description: "Basic settings, server controls, logs and info",
|
|
||||||
iconPath: mdiCog,
|
iconPath: mdiCog,
|
||||||
iconColor: "#4A5963",
|
iconColor: "#4A5963",
|
||||||
core: true,
|
core: true,
|
||||||
|
@@ -95,7 +95,7 @@ class OZWConfigDashboard extends LitElement {
|
|||||||
<ha-card>
|
<ha-card>
|
||||||
<a
|
<a
|
||||||
href="/config/ozw/network/${instance.ozw_instance}"
|
href="/config/ozw/network/${instance.ozw_instance}"
|
||||||
aria-role="option"
|
role="option"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<paper-icon-item>
|
<paper-icon-item>
|
||||||
|
@@ -129,7 +129,11 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
<span
|
<span
|
||||||
>[[localize('ui.panel.config.zwave.node_management.header')]]</span
|
>[[localize('ui.panel.config.zwave.node_management.header')]]</span
|
||||||
>
|
>
|
||||||
<ha-icon-button class="toggle-help-icon" on-click="toggleHelp">
|
<ha-icon-button
|
||||||
|
class="toggle-help-icon"
|
||||||
|
on-click="toggleHelp"
|
||||||
|
label="[[localize('ui.common.help')]]"
|
||||||
|
>
|
||||||
<ha-icon icon="hass:help-circle"></ha-icon>
|
<ha-icon icon="hass:help-circle"></ha-icon>
|
||||||
</ha-icon-button>
|
</ha-icon-button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import type { TextField } from "@material/mwc-textfield/mwc-textfield";
|
|
||||||
import "@material/mwc-textfield/mwc-textfield";
|
import "@material/mwc-textfield/mwc-textfield";
|
||||||
import { mdiAlertCircle, mdiCheckCircle, mdiQrcodeScan } from "@mdi/js";
|
import { mdiAlertCircle, mdiCheckCircle, mdiQrcodeScan } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
@@ -179,21 +178,16 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
Search device
|
Search device
|
||||||
</mwc-button>`
|
</mwc-button>`
|
||||||
: this._status === "qr_scan"
|
: this._status === "qr_scan"
|
||||||
? html`<ha-qr-scanner
|
? html`${this._error
|
||||||
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
|
: ""}
|
||||||
|
<ha-qr-scanner
|
||||||
.localize=${this.hass.localize}
|
.localize=${this.hass.localize}
|
||||||
@qr-code-scanned=${this._qrCodeScanned}
|
@qr-code-scanned=${this._qrCodeScanned}
|
||||||
></ha-qr-scanner>
|
></ha-qr-scanner>
|
||||||
<p>
|
<mwc-button slot="secondaryAction" @click=${this._startOver}>
|
||||||
If scanning doesn't work, you can enter the QR code value
|
${this.hass.localize("ui.panel.config.zwave_js.common.back")}
|
||||||
manually:
|
</mwc-button>`
|
||||||
</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"
|
: this._status === "validate_dsk_enter_pin"
|
||||||
? html`
|
? html`
|
||||||
<p>
|
<p>
|
||||||
@@ -203,9 +197,9 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
</p>
|
</p>
|
||||||
${
|
${
|
||||||
this._error
|
this._error
|
||||||
? html`<ha-alert alert-type="error"
|
? html`<ha-alert alert-type="error">
|
||||||
>${this._error}</ha-alert
|
${this._error}
|
||||||
>`
|
</ha-alert>`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
@@ -274,7 +268,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
We have not found any device in inclusion mode. Make sure the
|
We have not found any device in inclusion mode. Make sure the
|
||||||
device is active and in inclusion mode.
|
device is active and in inclusion mode.
|
||||||
</p>
|
</p>
|
||||||
<mwc-button slot="primaryAction" @click=${this._startInclusion}>
|
<mwc-button slot="primaryAction" @click=${this._startOver}>
|
||||||
Retry
|
Retry
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
`
|
`
|
||||||
@@ -373,7 +367,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
<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>
|
</mwc-button>
|
||||||
`
|
`
|
||||||
: this._status === "failed"
|
: this._status === "failed"
|
||||||
@@ -510,15 +504,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
this._status = "qr_scan";
|
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 {
|
private _qrCodeScanned(ev: CustomEvent): void {
|
||||||
if (this._qrProcessing) {
|
if (this._qrProcessing) {
|
||||||
return;
|
return;
|
||||||
@@ -574,11 +559,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
}
|
}
|
||||||
} else if (provisioningInfo.version === 0) {
|
} else if (provisioningInfo.version === 0) {
|
||||||
this._inclusionStrategy = InclusionStrategy.Security_S2;
|
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 {
|
} else {
|
||||||
this._error = "This QR code is not supported";
|
this._error = "This QR code is not supported";
|
||||||
this._status = "failed";
|
this._status = "failed";
|
||||||
@@ -638,6 +619,10 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
).supported;
|
).supported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _startOver(_ev: Event) {
|
||||||
|
this._startInclusion();
|
||||||
|
}
|
||||||
|
|
||||||
private _startInclusion(
|
private _startInclusion(
|
||||||
qrProvisioningInformation?: QRProvisioningInformation,
|
qrProvisioningInformation?: QRProvisioningInformation,
|
||||||
qrCodeString?: string,
|
qrCodeString?: string,
|
||||||
|
@@ -5,6 +5,7 @@ import "@polymer/paper-input/paper-input";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { componentsWithService } from "../../../common/config/components_with_service";
|
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/buttons/ha-call-service-button";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import { checkCoreConfig } from "../../../data/core";
|
import { checkCoreConfig } from "../../../data/core";
|
||||||
@@ -157,18 +158,20 @@ export class HaConfigServerControl extends LitElement {
|
|||||||
"ui.panel.config.server_control.section.server_management.restart"
|
"ui.panel.config.server_control.section.server_management.restart"
|
||||||
)}
|
)}
|
||||||
</ha-call-service-button>
|
</ha-call-service-button>
|
||||||
<ha-call-service-button
|
${!isComponentLoaded(this.hass, "hassio")
|
||||||
class="warning"
|
? html`<ha-call-service-button
|
||||||
.hass=${this.hass}
|
class="warning"
|
||||||
domain="homeassistant"
|
.hass=${this.hass}
|
||||||
service="stop"
|
domain="homeassistant"
|
||||||
confirmation=${this.hass.localize(
|
service="stop"
|
||||||
"ui.panel.config.server_control.section.server_management.confirm_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"
|
>${this.hass.localize(
|
||||||
)}
|
"ui.panel.config.server_control.section.server_management.stop"
|
||||||
</ha-call-service-button>
|
)}
|
||||||
|
</ha-call-service-button>`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
|
|
||||||
|
@@ -106,6 +106,7 @@ class DialogUserDetail extends LitElement {
|
|||||||
.dir=${computeRTLDirection(this.hass)}
|
.dir=${computeRTLDirection(this.hass)}
|
||||||
>
|
>
|
||||||
<ha-switch
|
<ha-switch
|
||||||
|
.disabled=${user.system_generated}
|
||||||
.checked=${this._localOnly}
|
.checked=${this._localOnly}
|
||||||
@change=${this._localOnlyChanged}
|
@change=${this._localOnlyChanged}
|
||||||
>
|
>
|
||||||
|
@@ -18,6 +18,7 @@ import "../../../components/ha-code-editor";
|
|||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import "../../../components/ha-checkbox";
|
import "../../../components/ha-checkbox";
|
||||||
|
import "../../../components/ha-expansion-panel";
|
||||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||||
@@ -40,6 +41,10 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-expansion-panel {
|
||||||
|
margin: 0 8px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.inputs {
|
.inputs {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
@@ -135,72 +140,77 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
<h1>
|
||||||
[[localize('ui.panel.developer-tools.tabs.states.current_entities')]]
|
[[localize('ui.panel.developer-tools.tabs.states.current_entities')]]
|
||||||
</h1>
|
</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">
|
<div class="table-wrapper">
|
||||||
<table class="entities">
|
<table class="entities">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -348,6 +358,11 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
"computeEntities(hass, _entityFilter, _stateFilter, _attributeFilter)",
|
"computeEntities(hass, _entityFilter, _stateFilter, _attributeFilter)",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_expanded: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
|
||||||
narrow: {
|
narrow: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
reflectToAttribute: true,
|
reflectToAttribute: true,
|
||||||
@@ -371,6 +386,7 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
this._entity = state;
|
this._entity = state;
|
||||||
this._state = state.state;
|
this._state = state.state;
|
||||||
this._stateAttributes = dump(state.attributes);
|
this._stateAttributes = dump(state.attributes);
|
||||||
|
this._expanded = true;
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,6 +404,11 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
this._entity = state;
|
this._entity = state;
|
||||||
this._state = state.state;
|
this._state = state.state;
|
||||||
this._stateAttributes = dump(state.attributes);
|
this._stateAttributes = dump(state.attributes);
|
||||||
|
this._expanded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
expandedChanged(ev) {
|
||||||
|
this._expanded = ev.detail.expanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
entityMoreInfo(ev) {
|
entityMoreInfo(ev) {
|
||||||
|
@@ -26,7 +26,7 @@ import {
|
|||||||
rgb2hex,
|
rgb2hex,
|
||||||
rgb2lab,
|
rgb2lab,
|
||||||
} from "../../../../common/color/convert-color";
|
} from "../../../../common/color/convert-color";
|
||||||
import { labDarken } from "../../../../common/color/lab";
|
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
||||||
import {
|
import {
|
||||||
EnergyData,
|
EnergyData,
|
||||||
getEnergyDataCollection,
|
getEnergyDataCollection,
|
||||||
@@ -247,10 +247,15 @@ export class HuiEnergyGasGraphCard
|
|||||||
const data: ChartDataset<"bar" | "line">[] = [];
|
const data: ChartDataset<"bar" | "line">[] = [];
|
||||||
const entity = this.hass.states[source.stat_energy_from];
|
const entity = this.hass.states[source.stat_energy_from];
|
||||||
|
|
||||||
const borderColor =
|
const modifiedColor =
|
||||||
idx > 0
|
idx > 0
|
||||||
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(gasColor)), idx)))
|
? this.hass.themes.darkMode
|
||||||
: gasColor;
|
? labBrighten(rgb2lab(hex2rgb(gasColor)), idx)
|
||||||
|
: labDarken(rgb2lab(hex2rgb(gasColor)), idx)
|
||||||
|
: undefined;
|
||||||
|
const borderColor = modifiedColor
|
||||||
|
? rgb2hex(lab2rgb(modifiedColor))
|
||||||
|
: gasColor;
|
||||||
|
|
||||||
let prevValue: number | null = null;
|
let prevValue: number | null = null;
|
||||||
let prevStart: string | null = null;
|
let prevStart: string | null = null;
|
||||||
|
@@ -1,9 +1,3 @@
|
|||||||
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 {
|
import {
|
||||||
ChartData,
|
ChartData,
|
||||||
ChartDataset,
|
ChartDataset,
|
||||||
@@ -17,16 +11,26 @@ import {
|
|||||||
isToday,
|
isToday,
|
||||||
startOfToday,
|
startOfToday,
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { LovelaceCard } from "../../types";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { EnergySolarGraphCardConfig } from "../types";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import {
|
import {
|
||||||
hex2rgb,
|
hex2rgb,
|
||||||
lab2rgb,
|
lab2rgb,
|
||||||
rgb2hex,
|
rgb2hex,
|
||||||
rgb2lab,
|
rgb2lab,
|
||||||
} from "../../../../common/color/convert-color";
|
} from "../../../../common/color/convert-color";
|
||||||
import { labDarken } from "../../../../common/color/lab";
|
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 {
|
import {
|
||||||
EnergyData,
|
EnergyData,
|
||||||
EnergySolarForecasts,
|
EnergySolarForecasts,
|
||||||
@@ -34,15 +38,11 @@ import {
|
|||||||
getEnergySolarForecasts,
|
getEnergySolarForecasts,
|
||||||
SolarSourceTypeEnergyPreference,
|
SolarSourceTypeEnergyPreference,
|
||||||
} from "../../../../data/energy";
|
} from "../../../../data/energy";
|
||||||
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 { FrontendLocaleData } from "../../../../data/translation";
|
import { FrontendLocaleData } from "../../../../data/translation";
|
||||||
import { formatTime } from "../../../../common/datetime/format_time";
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import { LovelaceCard } from "../../types";
|
||||||
|
import { EnergySolarGraphCardConfig } from "../types";
|
||||||
|
|
||||||
@customElement("hui-energy-solar-graph-card")
|
@customElement("hui-energy-solar-graph-card")
|
||||||
export class HuiEnergySolarGraphCard
|
export class HuiEnergySolarGraphCard
|
||||||
@@ -258,10 +258,15 @@ export class HuiEnergySolarGraphCard
|
|||||||
const data: ChartDataset<"bar" | "line">[] = [];
|
const data: ChartDataset<"bar" | "line">[] = [];
|
||||||
const entity = this.hass.states[source.stat_energy_from];
|
const entity = this.hass.states[source.stat_energy_from];
|
||||||
|
|
||||||
const borderColor =
|
const modifiedColor =
|
||||||
idx > 0
|
idx > 0
|
||||||
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx)))
|
? this.hass.themes.darkMode
|
||||||
: solarColor;
|
? labBrighten(rgb2lab(hex2rgb(solarColor)), idx)
|
||||||
|
: labDarken(rgb2lab(hex2rgb(solarColor)), idx)
|
||||||
|
: undefined;
|
||||||
|
const borderColor = modifiedColor
|
||||||
|
? rgb2hex(lab2rgb(modifiedColor))
|
||||||
|
: solarColor;
|
||||||
|
|
||||||
let prevValue: number | null = null;
|
let prevValue: number | null = null;
|
||||||
let prevStart: string | null = null;
|
let prevStart: string | null = null;
|
||||||
|
@@ -17,7 +17,7 @@ import {
|
|||||||
rgb2lab,
|
rgb2lab,
|
||||||
hex2rgb,
|
hex2rgb,
|
||||||
} from "../../../../common/color/convert-color";
|
} from "../../../../common/color/convert-color";
|
||||||
import { labDarken } from "../../../../common/color/lab";
|
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
||||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||||
import { formatNumber } from "../../../../common/number/format_number";
|
import { formatNumber } from "../../../../common/number/format_number";
|
||||||
import "../../../../components/chart/statistics-chart";
|
import "../../../../components/chart/statistics-chart";
|
||||||
@@ -170,12 +170,17 @@ export class HuiEnergySourcesTableCard
|
|||||||
this._data!.stats[source.stat_energy_from]
|
this._data!.stats[source.stat_energy_from]
|
||||||
) || 0;
|
) || 0;
|
||||||
totalSolar += energy;
|
totalSolar += energy;
|
||||||
const color =
|
|
||||||
|
const modifiedColor =
|
||||||
idx > 0
|
idx > 0
|
||||||
? rgb2hex(
|
? this.hass.themes.darkMode
|
||||||
lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx))
|
? labBrighten(rgb2lab(hex2rgb(solarColor)), idx)
|
||||||
)
|
: labDarken(rgb2lab(hex2rgb(solarColor)), idx)
|
||||||
: solarColor;
|
: undefined;
|
||||||
|
const color = modifiedColor
|
||||||
|
? rgb2hex(lab2rgb(modifiedColor))
|
||||||
|
: solarColor;
|
||||||
|
|
||||||
return html`<tr class="mdc-data-table__row">
|
return html`<tr class="mdc-data-table__row">
|
||||||
<td class="mdc-data-table__cell cell-bullet">
|
<td class="mdc-data-table__cell cell-bullet">
|
||||||
<div
|
<div
|
||||||
@@ -229,22 +234,26 @@ export class HuiEnergySourcesTableCard
|
|||||||
this._data!.stats[source.stat_energy_to]
|
this._data!.stats[source.stat_energy_to]
|
||||||
) || 0;
|
) || 0;
|
||||||
totalBattery += energyFrom - energyTo;
|
totalBattery += energyFrom - energyTo;
|
||||||
const fromColor =
|
|
||||||
|
const modifiedFromColor =
|
||||||
idx > 0
|
idx > 0
|
||||||
? rgb2hex(
|
? this.hass.themes.darkMode
|
||||||
lab2rgb(
|
? labBrighten(rgb2lab(hex2rgb(batteryFromColor)), idx)
|
||||||
labDarken(rgb2lab(hex2rgb(batteryFromColor)), idx)
|
: labDarken(rgb2lab(hex2rgb(batteryFromColor)), idx)
|
||||||
)
|
: undefined;
|
||||||
)
|
const fromColor = modifiedFromColor
|
||||||
: batteryFromColor;
|
? rgb2hex(lab2rgb(modifiedFromColor))
|
||||||
const toColor =
|
: batteryFromColor;
|
||||||
|
const modifiedToColor =
|
||||||
idx > 0
|
idx > 0
|
||||||
? rgb2hex(
|
? this.hass.themes.darkMode
|
||||||
lab2rgb(
|
? labBrighten(rgb2lab(hex2rgb(batteryToColor)), idx)
|
||||||
labDarken(rgb2lab(hex2rgb(batteryToColor)), idx)
|
: labDarken(rgb2lab(hex2rgb(batteryToColor)), idx)
|
||||||
)
|
: undefined;
|
||||||
)
|
const toColor = modifiedToColor
|
||||||
: batteryToColor;
|
? rgb2hex(lab2rgb(modifiedToColor))
|
||||||
|
: batteryToColor;
|
||||||
|
|
||||||
return html`<tr class="mdc-data-table__row">
|
return html`<tr class="mdc-data-table__row">
|
||||||
<td class="mdc-data-table__cell cell-bullet">
|
<td class="mdc-data-table__cell cell-bullet">
|
||||||
<div
|
<div
|
||||||
@@ -331,14 +340,17 @@ export class HuiEnergySourcesTableCard
|
|||||||
if (cost !== null) {
|
if (cost !== null) {
|
||||||
totalGridCost += cost;
|
totalGridCost += cost;
|
||||||
}
|
}
|
||||||
const color =
|
|
||||||
|
const modifiedColor =
|
||||||
idx > 0
|
idx > 0
|
||||||
? rgb2hex(
|
? this.hass.themes.darkMode
|
||||||
lab2rgb(
|
? labBrighten(rgb2lab(hex2rgb(consumptionColor)), idx)
|
||||||
labDarken(rgb2lab(hex2rgb(consumptionColor)), idx)
|
: labDarken(rgb2lab(hex2rgb(consumptionColor)), idx)
|
||||||
)
|
: undefined;
|
||||||
)
|
const color = modifiedColor
|
||||||
: consumptionColor;
|
? rgb2hex(lab2rgb(modifiedColor))
|
||||||
|
: consumptionColor;
|
||||||
|
|
||||||
return html`<tr class="mdc-data-table__row">
|
return html`<tr class="mdc-data-table__row">
|
||||||
<td class="mdc-data-table__cell cell-bullet">
|
<td class="mdc-data-table__cell cell-bullet">
|
||||||
<div
|
<div
|
||||||
@@ -391,12 +403,17 @@ export class HuiEnergySourcesTableCard
|
|||||||
if (cost !== null) {
|
if (cost !== null) {
|
||||||
totalGridCost += cost;
|
totalGridCost += cost;
|
||||||
}
|
}
|
||||||
const color =
|
|
||||||
|
const modifiedColor =
|
||||||
idx > 0
|
idx > 0
|
||||||
? rgb2hex(
|
? this.hass.themes.darkMode
|
||||||
lab2rgb(labDarken(rgb2lab(hex2rgb(returnColor)), idx))
|
? labBrighten(rgb2lab(hex2rgb(returnColor)), idx)
|
||||||
)
|
: labDarken(rgb2lab(hex2rgb(returnColor)), idx)
|
||||||
: returnColor;
|
: undefined;
|
||||||
|
const color = modifiedColor
|
||||||
|
? rgb2hex(lab2rgb(modifiedColor))
|
||||||
|
: returnColor;
|
||||||
|
|
||||||
return html`<tr class="mdc-data-table__row">
|
return html`<tr class="mdc-data-table__row">
|
||||||
<td class="mdc-data-table__cell cell-bullet">
|
<td class="mdc-data-table__cell cell-bullet">
|
||||||
<div
|
<div
|
||||||
@@ -473,12 +490,17 @@ export class HuiEnergySourcesTableCard
|
|||||||
if (cost !== null) {
|
if (cost !== null) {
|
||||||
totalGasCost += cost;
|
totalGasCost += cost;
|
||||||
}
|
}
|
||||||
const color =
|
|
||||||
|
const modifiedColor =
|
||||||
idx > 0
|
idx > 0
|
||||||
? rgb2hex(
|
? this.hass.themes.darkMode
|
||||||
lab2rgb(labDarken(rgb2lab(hex2rgb(gasColor)), idx))
|
? labBrighten(rgb2lab(hex2rgb(gasColor)), idx)
|
||||||
)
|
: labDarken(rgb2lab(hex2rgb(gasColor)), idx)
|
||||||
: gasColor;
|
: undefined;
|
||||||
|
const color = modifiedColor
|
||||||
|
? rgb2hex(lab2rgb(modifiedColor))
|
||||||
|
: gasColor;
|
||||||
|
|
||||||
return html`<tr class="mdc-data-table__row">
|
return html`<tr class="mdc-data-table__row">
|
||||||
<td class="mdc-data-table__cell cell-bullet">
|
<td class="mdc-data-table__cell cell-bullet">
|
||||||
<div
|
<div
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
||||||
import {
|
import {
|
||||||
startOfToday,
|
addHours,
|
||||||
|
differenceInDays,
|
||||||
endOfToday,
|
endOfToday,
|
||||||
isToday,
|
isToday,
|
||||||
differenceInDays,
|
startOfToday,
|
||||||
addHours,
|
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
rgb2hex,
|
rgb2hex,
|
||||||
rgb2lab,
|
rgb2lab,
|
||||||
} from "../../../../common/color/convert-color";
|
} from "../../../../common/color/convert-color";
|
||||||
import { labDarken } from "../../../../common/color/lab";
|
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
||||||
import { formatTime } from "../../../../common/datetime/format_time";
|
import { formatTime } from "../../../../common/datetime/format_time";
|
||||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||||
import {
|
import {
|
||||||
@@ -477,10 +477,16 @@ export class HuiEnergyUsageGraphCard
|
|||||||
Object.entries(sources).forEach(([statId, source], idx) => {
|
Object.entries(sources).forEach(([statId, source], idx) => {
|
||||||
const data: ChartDataset<"bar">[] = [];
|
const data: ChartDataset<"bar">[] = [];
|
||||||
const entity = this.hass.states[statId];
|
const entity = this.hass.states[statId];
|
||||||
const borderColor =
|
|
||||||
|
const modifiedColor =
|
||||||
idx > 0
|
idx > 0
|
||||||
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(colors[type])), idx)))
|
? this.hass.themes.darkMode
|
||||||
: colors[type];
|
? labBrighten(rgb2lab(hex2rgb(colors[type])), idx)
|
||||||
|
: labDarken(rgb2lab(hex2rgb(colors[type])), idx)
|
||||||
|
: undefined;
|
||||||
|
const borderColor = modifiedColor
|
||||||
|
? rgb2hex(lab2rgb(modifiedColor))
|
||||||
|
: colors[type];
|
||||||
|
|
||||||
data.push({
|
data.push({
|
||||||
label:
|
label:
|
||||||
|
@@ -235,6 +235,9 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
<div>
|
<div>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.path=${mdiDotsVertical}
|
.path=${mdiDotsVertical}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.cards.show_more_info"
|
||||||
|
)}
|
||||||
class="more-info"
|
class="more-info"
|
||||||
@click=${this._handleMoreInfo}
|
@click=${this._handleMoreInfo}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
|
@@ -19,7 +19,7 @@ import {
|
|||||||
svg,
|
svg,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state, query } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { UNIT_F } from "../../../common/const";
|
import { UNIT_F } from "../../../common/const";
|
||||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||||
@@ -427,6 +427,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||||||
@click=${this._handleAction}
|
@click=${this._handleAction}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
.path=${modeIcons[mode]}
|
.path=${modeIcons[mode]}
|
||||||
|
.label=${this.hass!.localize(`component.climate.state._.${mode}`)}
|
||||||
>
|
>
|
||||||
</ha-icon-button>
|
</ha-icon-button>
|
||||||
`;
|
`;
|
||||||
|
@@ -50,6 +50,7 @@ export class HuiButtonsBase extends LitElement {
|
|||||||
.stateObj=${stateObj}
|
.stateObj=${stateObj}
|
||||||
.overrideIcon=${entityConf.icon}
|
.overrideIcon=${entityConf.icon}
|
||||||
.overrideImage=${entityConf.image}
|
.overrideImage=${entityConf.image}
|
||||||
|
class=${name ? "" : "no-text"}
|
||||||
stateColor
|
stateColor
|
||||||
slot="icon"
|
slot="icon"
|
||||||
></state-badge>
|
></state-badge>
|
||||||
@@ -85,9 +86,21 @@ export class HuiButtonsBase extends LitElement {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
state-badge {
|
state-badge {
|
||||||
|
display: inline-flex;
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
text-align: start;
|
|
||||||
color: var(--secondary-text-color);
|
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 {
|
ha-chip {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
@@ -30,6 +30,7 @@ import "../../../components/ha-slider";
|
|||||||
import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
|
import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
|
||||||
import {
|
import {
|
||||||
computeMediaDescription,
|
computeMediaDescription,
|
||||||
|
ControlButton,
|
||||||
MediaPlayerEntity,
|
MediaPlayerEntity,
|
||||||
SUPPORT_NEXT_TRACK,
|
SUPPORT_NEXT_TRACK,
|
||||||
SUPPORT_PAUSE,
|
SUPPORT_PAUSE,
|
||||||
@@ -108,6 +109,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const entityState = stateObj.state;
|
const entityState = stateObj.state;
|
||||||
|
const controlButton = this._computeControlButton(stateObj);
|
||||||
|
|
||||||
const buttons = html`
|
const buttons = html`
|
||||||
${!this._narrow &&
|
${!this._narrow &&
|
||||||
@@ -116,6 +118,9 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
? html`
|
? html`
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.path=${mdiSkipPrevious}
|
.path=${mdiSkipPrevious}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.card.media_player.media_previous_track"
|
||||||
|
)}
|
||||||
@click=${this._previousTrack}
|
@click=${this._previousTrack}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
`
|
`
|
||||||
@@ -130,7 +135,10 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
supportsFeature(stateObj, SUPPORT_PAUSE)))
|
supportsFeature(stateObj, SUPPORT_PAUSE)))
|
||||||
? html`
|
? html`
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.path=${this._computeControlIcon(stateObj)}
|
.path=${controlButton.icon}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
`ui.card.media_player.${controlButton.action}`
|
||||||
|
)}
|
||||||
@click=${this._playPauseStop}
|
@click=${this._playPauseStop}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
`
|
`
|
||||||
@@ -140,6 +148,9 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
? html`
|
? html`
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.path=${mdiSkipNext}
|
.path=${mdiSkipNext}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.card.media_player.media_next_track"
|
||||||
|
)}
|
||||||
@click=${this._nextTrack}
|
@click=${this._nextTrack}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
`
|
`
|
||||||
@@ -162,6 +173,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
? html`
|
? html`
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.path=${mdiPower}
|
.path=${mdiPower}
|
||||||
|
.label=${this.hass.localize("ui.card.media_player.turn_on")}
|
||||||
@click=${this._togglePower}
|
@click=${this._togglePower}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
`
|
`
|
||||||
@@ -175,6 +187,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
? html`
|
? html`
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.path=${mdiPower}
|
.path=${mdiPower}
|
||||||
|
.label=${this.hass.localize("ui.card.media_player.turn_off")}
|
||||||
@click=${this._togglePower}
|
@click=${this._togglePower}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
`
|
`
|
||||||
@@ -193,6 +206,13 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
.path=${stateObj.attributes.is_volume_muted
|
.path=${stateObj.attributes.is_volume_muted
|
||||||
? mdiVolumeOff
|
? mdiVolumeOff
|
||||||
: mdiVolumeHigh}
|
: mdiVolumeHigh}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
`ui.card.media_player.${
|
||||||
|
stateObj.attributes.is_volume_muted
|
||||||
|
? "media_volume_mute"
|
||||||
|
: "media_volume_unmute"
|
||||||
|
}`
|
||||||
|
)}
|
||||||
@click=${this._toggleMute}
|
@click=${this._toggleMute}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
`
|
`
|
||||||
@@ -214,10 +234,16 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
? html`
|
? html`
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.path=${mdiVolumeMinus}
|
.path=${mdiVolumeMinus}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.card.media_player.media_volume_down"
|
||||||
|
)}
|
||||||
@click=${this._volumeDown}
|
@click=${this._volumeDown}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.path=${mdiVolumePlus}
|
.path=${mdiVolumePlus}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.card.media_player.media_volume_up"
|
||||||
|
)}
|
||||||
@click=${this._volumeUp}
|
@click=${this._volumeUp}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
`
|
`
|
||||||
@@ -249,14 +275,14 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
this._veryNarrow = (this.clientWidth || 0) < 225;
|
this._veryNarrow = (this.clientWidth || 0) < 225;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeControlIcon(stateObj: HassEntity): string {
|
private _computeControlButton(stateObj: HassEntity): ControlButton {
|
||||||
return stateObj.state === "on"
|
return stateObj.state === "on"
|
||||||
? mdiPlayPause
|
? { icon: mdiPlayPause, action: "media_play_pause" }
|
||||||
: stateObj.state !== "playing"
|
: stateObj.state !== "playing"
|
||||||
? mdiPlay
|
? { icon: mdiPlay, action: "media_play" }
|
||||||
: supportsFeature(stateObj, SUPPORT_PAUSE)
|
: supportsFeature(stateObj, SUPPORT_PAUSE)
|
||||||
? mdiPause
|
? { icon: mdiPause, action: "media_pause" }
|
||||||
: mdiStop;
|
: { icon: mdiStop, action: "media_stop" };
|
||||||
}
|
}
|
||||||
|
|
||||||
private _togglePower(): void {
|
private _togglePower(): void {
|
||||||
|
@@ -698,7 +698,7 @@ class HUIRoot extends LitElement {
|
|||||||
|
|
||||||
private _navigateToView(path: string | number, replace?: boolean) {
|
private _navigateToView(path: string | number, replace?: boolean) {
|
||||||
if (!this.lovelace!.editMode) {
|
if (!this.lovelace!.editMode) {
|
||||||
navigate(`${this.route!.prefix}/${path}`, { replace });
|
navigate(`${this.route!.prefix}/${path}${location.search}`, { replace });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
navigate(`${this.route!.prefix}/${path}?${addSearchParam({ edit: "1" })}`, {
|
navigate(`${this.route!.prefix}/${path}?${addSearchParam({ edit: "1" })}`, {
|
||||||
|
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 { hapticMixin } from "./haptic-mixin";
|
||||||
import { HassBaseEl } from "./hass-base-mixin";
|
import { HassBaseEl } from "./hass-base-mixin";
|
||||||
import { loggingMixin } from "./logging-mixin";
|
import { loggingMixin } from "./logging-mixin";
|
||||||
|
import { ExternalMixin } from "./external-mixin";
|
||||||
import MoreInfoMixin from "./more-info-mixin";
|
import MoreInfoMixin from "./more-info-mixin";
|
||||||
import NotificationMixin from "./notification-mixin";
|
import NotificationMixin from "./notification-mixin";
|
||||||
import { panelTitleMixin } from "./panel-title-mixin";
|
import { panelTitleMixin } from "./panel-title-mixin";
|
||||||
@@ -31,4 +32,5 @@ export class HassElement extends ext(HassBaseEl, [
|
|||||||
hapticMixin,
|
hapticMixin,
|
||||||
panelTitleMixin,
|
panelTitleMixin,
|
||||||
loggingMixin,
|
loggingMixin,
|
||||||
|
ExternalMixin,
|
||||||
]) {}
|
]) {}
|
||||||
|
@@ -131,5 +131,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
|||||||
(themeMeta.getAttribute("default-content") as string);
|
(themeMeta.getAttribute("default-content") as string);
|
||||||
themeMeta.setAttribute("content", themeColor);
|
themeMeta.setAttribute("content", themeColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.hass!.auth.external?.fireMessage({ type: "theme-update" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -195,8 +195,14 @@
|
|||||||
"turn_off": "Turn off",
|
"turn_off": "Turn off",
|
||||||
"media_play": "Play",
|
"media_play": "Play",
|
||||||
"media_play_pause": "Play/pause",
|
"media_play_pause": "Play/pause",
|
||||||
"media_next_track": "Next",
|
"media_pause": "Pause",
|
||||||
"media_previous_track": "Previous",
|
"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",
|
||||||
"text_to_speak": "Text to speak"
|
"text_to_speak": "Text to speak"
|
||||||
},
|
},
|
||||||
"persistent_notification": {
|
"persistent_notification": {
|
||||||
@@ -291,12 +297,14 @@
|
|||||||
"undo": "Undo",
|
"undo": "Undo",
|
||||||
"move": "Move",
|
"move": "Move",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
"submit": "Submit",
|
||||||
"rename": "Rename",
|
"rename": "Rename",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"not_now": "Not now",
|
"not_now": "Not now",
|
||||||
"skip": "Skip",
|
"skip": "Skip",
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
|
"overflow_menu": "Overflow menu",
|
||||||
"help": "Help",
|
"help": "Help",
|
||||||
"successfully_saved": "Successfully saved",
|
"successfully_saved": "Successfully saved",
|
||||||
"successfully_deleted": "Successfully deleted",
|
"successfully_deleted": "Successfully deleted",
|
||||||
@@ -421,6 +429,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"related-filter-menu": {
|
"related-filter-menu": {
|
||||||
|
"filter": "Filter",
|
||||||
"filter_by_entity": "Filter by entity",
|
"filter_by_entity": "Filter by entity",
|
||||||
"filter_by_device": "Filter by device",
|
"filter_by_device": "Filter by device",
|
||||||
"filter_by_area": "Filter by area",
|
"filter_by_area": "Filter by area",
|
||||||
@@ -538,6 +547,13 @@
|
|||||||
},
|
},
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"expansion_header": "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": {
|
"dialogs": {
|
||||||
@@ -678,8 +694,8 @@
|
|||||||
"open_cover": "Open cover",
|
"open_cover": "Open cover",
|
||||||
"close_cover": "Close cover",
|
"close_cover": "Close cover",
|
||||||
"open_tilt_cover": "Open cover tilt",
|
"open_tilt_cover": "Open cover tilt",
|
||||||
"close_tile_cover": "Close cover tilt",
|
"close_tilt_cover": "Close cover tilt",
|
||||||
"stop_cover": "Stop cover from moving"
|
"stop_cover": "Stop cover"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity_registry": {
|
"entity_registry": {
|
||||||
@@ -912,6 +928,7 @@
|
|||||||
"dismiss": "Dismiss"
|
"dismiss": "Dismiss"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
|
"external_app_configuration": "App Configuration",
|
||||||
"sidebar_toggle": "Sidebar Toggle",
|
"sidebar_toggle": "Sidebar Toggle",
|
||||||
"done": "Done",
|
"done": "Done",
|
||||||
"hide_panel": "Hide panel",
|
"hide_panel": "Hide panel",
|
||||||
@@ -928,9 +945,47 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"header": "Configure Home Assistant",
|
"header": "Configure Home Assistant",
|
||||||
"advanced_mode": {
|
"dashboard": {
|
||||||
"hint_enable": "Missing config options? Enable advanced mode on",
|
"devices": {
|
||||||
"link_profile_page": "your profile page"
|
"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"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"editor": {
|
"editor": {
|
||||||
@@ -1108,7 +1163,7 @@
|
|||||||
"cost_number": "Use a static price",
|
"cost_number": "Use a static price",
|
||||||
"cost_number_input": "Price per {unit}",
|
"cost_number_input": "Price per {unit}",
|
||||||
"gas_usage": "Gas usage",
|
"gas_usage": "Gas usage",
|
||||||
"m3_or_kWh": "m³ or kWh"
|
"m3_or_kWh": "ft³, m³, Wh, kWh or MWh"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"device_consumption": {
|
"device_consumption": {
|
||||||
@@ -1149,19 +1204,19 @@
|
|||||||
},
|
},
|
||||||
"entity_unexpected_unit_energy": {
|
"entity_unexpected_unit_energy": {
|
||||||
"title": "Unexpected unit of measurement",
|
"title": "Unexpected unit of measurement",
|
||||||
"description": "The following entities do not have the expected units of measurement 'kWh' or 'Wh':"
|
"description": "The following entities do not have the expected units of measurement 'Wh', 'kWh' or 'MWh':"
|
||||||
},
|
},
|
||||||
"entity_unexpected_unit_gas": {
|
"entity_unexpected_unit_gas": {
|
||||||
"title": "Unexpected unit of measurement",
|
"title": "Unexpected unit of measurement",
|
||||||
"description": "The following entities do not have the expected units of measurement 'kWh', 'm³' or 'ft³':"
|
"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:"
|
||||||
},
|
},
|
||||||
"entity_unexpected_unit_energy_price": {
|
"entity_unexpected_unit_energy_price": {
|
||||||
"title": "Unexpected unit of measurement",
|
"title": "Unexpected unit of measurement",
|
||||||
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'' or ''{currency}/Wh'':"
|
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'', ''{currency}/Wh'' or ''{currency}/MWh'':"
|
||||||
},
|
},
|
||||||
"entity_unexpected_unit_gas_price": {
|
"entity_unexpected_unit_gas_price": {
|
||||||
"title": "Unexpected unit of measurement",
|
"title": "Unexpected unit of measurement",
|
||||||
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'', ''{currency}/Wh'', ''{currency}/m³'' or ''{currency}/ft³'':"
|
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'', ''{currency}/Wh'', ''{currency}/MWh'', ''{currency}/m³'' or ''{currency}/ft³'':"
|
||||||
},
|
},
|
||||||
"entity_unexpected_state_class": {
|
"entity_unexpected_state_class": {
|
||||||
"title": "Unexpected state class",
|
"title": "Unexpected state class",
|
||||||
@@ -2389,6 +2444,7 @@
|
|||||||
"manuf": "by {manufacturer}",
|
"manuf": "by {manufacturer}",
|
||||||
"via": "Connected via",
|
"via": "Connected via",
|
||||||
"firmware": "Firmware: {version}",
|
"firmware": "Firmware: {version}",
|
||||||
|
"hardware": "Hardware: {version}",
|
||||||
"unnamed_entry": "Unnamed entry",
|
"unnamed_entry": "Unnamed entry",
|
||||||
"unknown_via_device": "Unknown device",
|
"unknown_via_device": "Unknown device",
|
||||||
"area": "In {area}",
|
"area": "In {area}",
|
||||||
@@ -2477,7 +2533,7 @@
|
|||||||
"admin": "Administrator",
|
"admin": "Administrator",
|
||||||
"group": "Group",
|
"group": "Group",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"local_only": "Can only login from the local network",
|
"local_only": "Can only log in from the local network",
|
||||||
"system_generated": "System generated",
|
"system_generated": "System generated",
|
||||||
"system_generated_users_not_removable": "Unable to remove system generated users.",
|
"system_generated_users_not_removable": "Unable to remove system generated users.",
|
||||||
"system_generated_users_not_editable": "Unable to update system generated users.",
|
"system_generated_users_not_editable": "Unable to update system generated users.",
|
||||||
@@ -2813,7 +2869,7 @@
|
|||||||
"node_id": "Device ID",
|
"node_id": "Device ID",
|
||||||
"home_id": "Home ID",
|
"home_id": "Home ID",
|
||||||
"source": "Source",
|
"source": "Source",
|
||||||
"close": "Close",
|
"back": "Back",
|
||||||
"add_node": "Add device",
|
"add_node": "Add device",
|
||||||
"remove_node": "Remove device",
|
"remove_node": "Remove device",
|
||||||
"reconfigure_server": "Re-configure Server",
|
"reconfigure_server": "Re-configure Server",
|
||||||
@@ -2882,8 +2938,6 @@
|
|||||||
"qr_code": "QR Code",
|
"qr_code": "QR Code",
|
||||||
"qr_code_paragraph": "If your device supports SmartStart you can scan the QR code for easy pairing.",
|
"qr_code_paragraph": "If your device supports SmartStart you can scan the QR code for easy pairing.",
|
||||||
"scan_qr_code": "Scan QR code",
|
"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.",
|
"inclusion_failed": "The device could not be added.",
|
||||||
"check_logs": "Please check the logs for more information.",
|
"check_logs": "Please check the logs for more information.",
|
||||||
"inclusion_finished": "The device has been added.",
|
"inclusion_finished": "The device has been added.",
|
||||||
@@ -2977,7 +3031,8 @@
|
|||||||
"title": "Z-Wave JS Logs",
|
"title": "Z-Wave JS Logs",
|
||||||
"log_level": "Log Level",
|
"log_level": "Log Level",
|
||||||
"subscribed_to_logs": "Subscribed to Z-Wave JS Log Messages…",
|
"subscribed_to_logs": "Subscribed to Z-Wave JS Log Messages…",
|
||||||
"log_level_changed": "Log Level changed to: {level}"
|
"log_level_changed": "Log Level changed to: {level}",
|
||||||
|
"download_logs": "Download logs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4222,7 +4277,8 @@
|
|||||||
"create_backup": "Create backup before updating",
|
"create_backup": "Create backup before updating",
|
||||||
"description": "You have {version} installed. Click update to update to version {newest_version}",
|
"description": "You have {version} installed. Click update to update to version {newest_version}",
|
||||||
"updating": "Updating {name} to version {version}",
|
"updating": "Updating {name} to version {version}",
|
||||||
"creating_backup": "Creating backup of {name}"
|
"creating_backup": "Creating backup of {name}",
|
||||||
|
"no_update": "No update available for {name}"
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"restart": {
|
"restart": {
|
||||||
|
@@ -2,16 +2,16 @@ import { assert } from "chai";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ExternalMessaging,
|
ExternalMessaging,
|
||||||
InternalMessage,
|
EMMessage,
|
||||||
} from "../../src/external_app/external_messaging";
|
} from "../../src/external_app/external_messaging";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
global.__DEV__ = true;
|
global.__DEV__ = true;
|
||||||
|
|
||||||
class MockExternalMessaging extends ExternalMessaging {
|
class MockExternalMessaging extends ExternalMessaging {
|
||||||
public mockSent: InternalMessage[] = [];
|
public mockSent: EMMessage[] = [];
|
||||||
|
|
||||||
protected _sendExternal(msg: InternalMessage) {
|
protected _sendExternal(msg: EMMessage) {
|
||||||
this.mockSent.push(msg);
|
this.mockSent.push(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user