mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-13 03:27:21 +00:00
Compare commits
57 Commits
fix-long-t
...
auth-passw
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd316a36a0 | ||
|
|
1eac9fa1cd | ||
|
|
7f819f0020 | ||
|
|
dec1f99a5f | ||
|
|
c705e74fc8 | ||
|
|
01df10f93e | ||
|
|
9877f08cf4 | ||
|
|
02791c51ae | ||
|
|
49683326e6 | ||
|
|
947773a82e | ||
|
|
2a229df624 | ||
|
|
0d4f43472b | ||
|
|
b30e467685 | ||
|
|
a56c0b52d5 | ||
|
|
c17ebfd279 | ||
|
|
5400b1da96 | ||
|
|
69f4a618b2 | ||
|
|
16b8b6698c | ||
|
|
b29a700d40 | ||
|
|
bbb1468439 | ||
|
|
72f9d6a8d3 | ||
|
|
3ec8da1f17 | ||
|
|
dbea3848df | ||
|
|
33871435e1 | ||
|
|
2fb9a56e0b | ||
|
|
14e8f66ed7 | ||
|
|
e6f5072462 | ||
|
|
a64f50fa72 | ||
|
|
bb5f6e88d0 | ||
|
|
6991403203 | ||
|
|
410bd22f8a | ||
|
|
b81d823602 | ||
|
|
7bcbed80d7 | ||
|
|
8fb62ebf5f | ||
|
|
209dd9923f | ||
|
|
c75207e391 | ||
|
|
d957f36927 | ||
|
|
9ac459b6d9 | ||
|
|
e08b2817ba | ||
|
|
4ca13c409b | ||
|
|
0d515e2303 | ||
|
|
a2153bc6aa | ||
|
|
ca171afe6f | ||
|
|
bf4e97bd48 | ||
|
|
8c59a12a03 | ||
|
|
89569355be | ||
|
|
3a41b3bdcf | ||
|
|
12bd7037b3 | ||
|
|
ca4f573be0 | ||
|
|
07fceeab5a | ||
|
|
3aa376e912 | ||
|
|
92d30a8896 | ||
|
|
83876fb9da | ||
|
|
29bdf7877c | ||
|
|
29199e2782 | ||
|
|
68e1378615 | ||
|
|
cf7efb5bfc |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"airbnb-typescript/base",
|
"airbnb-typescript/base",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
"plugin:wc/recommended",
|
"plugin:wc/recommended",
|
||||||
"plugin:lit/recommended",
|
"plugin:lit/recommended",
|
||||||
"prettier",
|
"prettier",
|
||||||
@@ -45,16 +45,16 @@
|
|||||||
"func-names": 0,
|
"func-names": 0,
|
||||||
"prefer-arrow-callback": 0,
|
"prefer-arrow-callback": 0,
|
||||||
"no-underscore-dangle": 0,
|
"no-underscore-dangle": 0,
|
||||||
"no-var": 0,
|
|
||||||
"strict": 0,
|
"strict": 0,
|
||||||
"prefer-spread": 0,
|
"prefer-spread": 0,
|
||||||
"no-plusplus": 0,
|
"no-plusplus": 0,
|
||||||
"no-bitwise": 0,
|
"no-bitwise": 2,
|
||||||
"comma-dangle": 0,
|
"comma-dangle": 0,
|
||||||
"vars-on-top": 0,
|
"vars-on-top": 0,
|
||||||
"no-continue": 0,
|
"no-continue": 0,
|
||||||
"no-param-reassign": 0,
|
"no-param-reassign": 0,
|
||||||
"no-multi-assign": 0,
|
"no-multi-assign": 0,
|
||||||
|
"no-console": 2,
|
||||||
"radix": 0,
|
"radix": 0,
|
||||||
"no-alert": 0,
|
"no-alert": 0,
|
||||||
"no-return-await": 0,
|
"no-return-await": 0,
|
||||||
|
|||||||
@@ -147,6 +147,10 @@
|
|||||||
"path": "M21.11,18.5C20.97,18.5 20.83,18.44 20.71,18.36C20.37,18.13 20.28,17.68 20.5,17.34C21.18,16.34 21.54,15.16 21.54,13.93C21.54,12.71 21.18,11.53 20.5,10.5C20.28,10.18 20.37,9.73 20.71,9.5C21.04,9.28 21.5,9.37 21.72,9.7C22.56,10.95 23,12.41 23,13.93C23,15.45 22.56,16.91 21.72,18.16C21.58,18.37 21.35,18.5 21.11,18.5M19,17.29C18.88,17.29 18.74,17.25 18.61,17.17C18.28,16.94 18.19,16.5 18.42,16.15C18.86,15.5 19.1,14.73 19.1,13.93C19.1,13.14 18.86,12.37 18.42,11.71C18.19,11.37 18.28,10.92 18.61,10.69C18.95,10.47 19.4,10.55 19.63,10.89C20.24,11.79 20.56,12.84 20.56,13.93C20.56,15 20.24,16.07 19.63,16.97C19.5,17.18 19.25,17.29 19,17.29M14.9,15.73C15.89,15.73 16.7,14.92 16.7,13.93C16.7,13.17 16.22,12.5 15.55,12.25C15.5,12.55 15.43,12.85 15.34,13.14C15.23,13.44 14.95,13.64 14.64,13.64C14.57,13.64 14.5,13.62 14.41,13.6C14.03,13.47 13.82,13.06 13.95,12.67C14.09,12.24 14.17,11.78 14.17,11.32C14.17,8.93 12.22,7 9.82,7C8.1,7 6.56,8 5.87,9.5C6.54,9.7 7.16,10.04 7.66,10.54C7.95,10.83 7.95,11.29 7.66,11.58C7.38,11.86 6.91,11.86 6.63,11.58C6.17,11.12 5.56,10.86 4.9,10.86C3.56,10.86 2.46,11.96 2.46,13.3C2.46,14.64 3.56,15.73 4.9,15.73H14.9M15.6,10.75C17.06,11.07 18.17,12.37 18.17,13.93C18.17,15.73 16.7,17.19 14.9,17.19H4.9C2.75,17.19 1,15.45 1,13.3C1,11.34 2.45,9.73 4.33,9.45C5.12,7.12 7.33,5.5 9.82,5.5C12.83,5.5 15.31,7.82 15.6,10.75Z",
|
"path": "M21.11,18.5C20.97,18.5 20.83,18.44 20.71,18.36C20.37,18.13 20.28,17.68 20.5,17.34C21.18,16.34 21.54,15.16 21.54,13.93C21.54,12.71 21.18,11.53 20.5,10.5C20.28,10.18 20.37,9.73 20.71,9.5C21.04,9.28 21.5,9.37 21.72,9.7C22.56,10.95 23,12.41 23,13.93C23,15.45 22.56,16.91 21.72,18.16C21.58,18.37 21.35,18.5 21.11,18.5M19,17.29C18.88,17.29 18.74,17.25 18.61,17.17C18.28,16.94 18.19,16.5 18.42,16.15C18.86,15.5 19.1,14.73 19.1,13.93C19.1,13.14 18.86,12.37 18.42,11.71C18.19,11.37 18.28,10.92 18.61,10.69C18.95,10.47 19.4,10.55 19.63,10.89C20.24,11.79 20.56,12.84 20.56,13.93C20.56,15 20.24,16.07 19.63,16.97C19.5,17.18 19.25,17.29 19,17.29M14.9,15.73C15.89,15.73 16.7,14.92 16.7,13.93C16.7,13.17 16.22,12.5 15.55,12.25C15.5,12.55 15.43,12.85 15.34,13.14C15.23,13.44 14.95,13.64 14.64,13.64C14.57,13.64 14.5,13.62 14.41,13.6C14.03,13.47 13.82,13.06 13.95,12.67C14.09,12.24 14.17,11.78 14.17,11.32C14.17,8.93 12.22,7 9.82,7C8.1,7 6.56,8 5.87,9.5C6.54,9.7 7.16,10.04 7.66,10.54C7.95,10.83 7.95,11.29 7.66,11.58C7.38,11.86 6.91,11.86 6.63,11.58C6.17,11.12 5.56,10.86 4.9,10.86C3.56,10.86 2.46,11.96 2.46,13.3C2.46,14.64 3.56,15.73 4.9,15.73H14.9M15.6,10.75C17.06,11.07 18.17,12.37 18.17,13.93C18.17,15.73 16.7,17.19 14.9,17.19H4.9C2.75,17.19 1,15.45 1,13.3C1,11.34 2.45,9.73 4.33,9.45C5.12,7.12 7.33,5.5 9.82,5.5C12.83,5.5 15.31,7.82 15.6,10.75Z",
|
||||||
"name": "mixcloud"
|
"name": "mixcloud"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "M5.68,3.96L11.41,11.65C11.55,11.84 11.55,12.1 11.41,12.29L5.65,20L5.5,20.18C4.76,21 3.47,21.07 2.64,20.31C1.85,19.59 1.79,18.37 2.43,17.5L6.56,11.97L2.46,6.47C1.83,5.62 1.88,4.39 2.67,3.67L2.82,3.54C3.73,2.87 5,3.05 5.68,3.96M18.32,3.96C19,3.05 20.27,2.87 21.18,3.54L21.33,3.67C22.12,4.39 22.17,5.61 21.54,6.47L17.44,11.97L21.57,17.5C22.21,18.36 22.15,19.59 21.36,20.31C20.53,21.07 19.24,21 18.5,20.18L18.35,20L12.59,12.29C12.45,12.1 12.45,11.84 12.59,11.65L18.32,3.96Z",
|
||||||
|
"name": "mixer"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "M3.25,4.03L19.95,20.73L18.7,22L14.86,18.13C14.77,18.12 14.68,18.09 14.59,18.05C14.26,17.89 14.14,17.62 14.11,17.38L12.18,15.45C12.14,15.53 12.09,15.6 12.05,15.66C11.62,16.26 11.19,16.26 10.86,16.04C10.54,15.83 5.5,12 5.23,11.87C4.95,11.76 4.85,12.03 5.12,13.5C5.39,15 4.95,15.39 4.57,15.45C4.2,15.5 3.06,15.18 3,12.14C2.95,9.11 3.76,8.62 4.14,8.62C4.6,8.62 7.08,10.69 8.84,12.12L2,5.28L3.25,4.03M18.38,16.56C18.75,15.4 19.12,13.8 19.1,12.03V12C19.14,8.5 17.66,5.58 17.66,5.58C17.66,5.58 17.42,4.72 18.12,4.39C18.83,4.06 19.3,4.61 19.3,4.61C21.12,8.22 21,11.64 21,12C21,12.27 21.09,14.96 19.88,18.05L18.38,16.56M15.14,13.31C15.19,12.92 15.22,12.5 15.24,12.03V12C15.14,8.5 14.13,7.21 14.13,7.21C14.13,7.21 13.89,6.34 14.59,6C15.3,5.69 15.77,6.23 15.77,6.23C17.26,8.94 17.16,11.64 17.14,12C17.15,12.2 17.2,13.38 16.82,15L15.14,13.31M10.2,8.38C10.23,7.77 10.59,7.64 10.59,7.64C10.59,7.64 11.19,7.37 11.57,7.8C11.91,8.19 12.72,9.57 12.89,11.07L10.2,8.38Z",
|
"path": "M3.25,4.03L19.95,20.73L18.7,22L14.86,18.13C14.77,18.12 14.68,18.09 14.59,18.05C14.26,17.89 14.14,17.62 14.11,17.38L12.18,15.45C12.14,15.53 12.09,15.6 12.05,15.66C11.62,16.26 11.19,16.26 10.86,16.04C10.54,15.83 5.5,12 5.23,11.87C4.95,11.76 4.85,12.03 5.12,13.5C5.39,15 4.95,15.39 4.57,15.45C4.2,15.5 3.06,15.18 3,12.14C2.95,9.11 3.76,8.62 4.14,8.62C4.6,8.62 7.08,10.69 8.84,12.12L2,5.28L3.25,4.03M18.38,16.56C18.75,15.4 19.12,13.8 19.1,12.03V12C19.14,8.5 17.66,5.58 17.66,5.58C17.66,5.58 17.42,4.72 18.12,4.39C18.83,4.06 19.3,4.61 19.3,4.61C21.12,8.22 21,11.64 21,12C21,12.27 21.09,14.96 19.88,18.05L18.38,16.56M15.14,13.31C15.19,12.92 15.22,12.5 15.24,12.03V12C15.14,8.5 14.13,7.21 14.13,7.21C14.13,7.21 13.89,6.34 14.59,6C15.3,5.69 15.77,6.23 15.77,6.23C17.26,8.94 17.16,11.64 17.14,12C17.15,12.2 17.2,13.38 16.82,15L15.14,13.31M10.2,8.38C10.23,7.77 10.59,7.64 10.59,7.64C10.59,7.64 11.19,7.37 11.57,7.8C11.91,8.19 12.72,9.57 12.89,11.07L10.2,8.38Z",
|
||||||
"name": "nfc-off"
|
"name": "nfc-off"
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ class HcLovelace extends LitElement {
|
|||||||
|
|
||||||
@property() public viewPath?: string | number;
|
@property() public viewPath?: string | number;
|
||||||
|
|
||||||
|
public urlPath?: string | null;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const index = this._viewIndex;
|
const index = this._viewIndex;
|
||||||
if (index === undefined) {
|
if (index === undefined) {
|
||||||
@@ -35,6 +37,7 @@ class HcLovelace extends LitElement {
|
|||||||
const lovelace: Lovelace = {
|
const lovelace: Lovelace = {
|
||||||
config: this.lovelaceConfig,
|
config: this.lovelaceConfig,
|
||||||
editMode: false,
|
editMode: false,
|
||||||
|
urlPath: this.urlPath!,
|
||||||
enableFullEditMode: () => undefined,
|
enableFullEditMode: () => undefined,
|
||||||
mode: "storage",
|
mode: "storage",
|
||||||
language: "en",
|
language: "en",
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ export class HcMain extends HassElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.lovelaceConfig=${this._lovelaceConfig}
|
.lovelaceConfig=${this._lovelaceConfig}
|
||||||
.viewPath=${this._lovelacePath}
|
.viewPath=${this._lovelacePath}
|
||||||
|
.urlPath=${this._urlPath}
|
||||||
@config-refresh=${this._generateLovelaceConfig}
|
@config-refresh=${this._generateLovelaceConfig}
|
||||||
></hc-lovelace>
|
></hc-lovelace>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -242,19 +242,23 @@ class HassioAddonInfo extends LitElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<div class="security">
|
<div class="security">
|
||||||
<ha-label-badge
|
${this.addon.stage !== "stable"
|
||||||
class=${classMap({
|
? html` <ha-label-badge
|
||||||
green: this.addon.stage === "stable",
|
class=${classMap({
|
||||||
yellow: this.addon.stage === "experimental",
|
yellow: this.addon.stage === "experimental",
|
||||||
red: this.addon.stage === "deprecated",
|
red: this.addon.stage === "deprecated",
|
||||||
})}
|
})}
|
||||||
@click=${this._showMoreInfo}
|
@click=${this._showMoreInfo}
|
||||||
id="stage"
|
id="stage"
|
||||||
label="stage"
|
label="stage"
|
||||||
description=""
|
description=""
|
||||||
>
|
>
|
||||||
<ha-svg-icon .path=${STAGE_ICON[this.addon.stage]}></ha-svg-icon>
|
<ha-svg-icon
|
||||||
</ha-label-badge>
|
.path=${STAGE_ICON[this.addon.stage]}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</ha-label-badge>`
|
||||||
|
: ""}
|
||||||
|
|
||||||
<ha-label-badge
|
<ha-label-badge
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
green: [5, 6].includes(Number(this.addon.rating)),
|
green: [5, 6].includes(Number(this.addon.rating)),
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ class HassioMarkdownDialog extends LitElement {
|
|||||||
this._opened = true;
|
this._opened = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public closeDialog() {
|
||||||
|
this._opened = false;
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this._opened) {
|
if (!this._opened) {
|
||||||
return html``;
|
return html``;
|
||||||
@@ -38,7 +42,7 @@ class HassioMarkdownDialog extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<ha-dialog
|
<ha-dialog
|
||||||
open
|
open
|
||||||
@closing=${this._closeDialog}
|
@closed=${this.closeDialog}
|
||||||
.heading=${createCloseHeading(this.hass, this.title)}
|
.heading=${createCloseHeading(this.hass, this.title)}
|
||||||
>
|
>
|
||||||
<ha-markdown .content=${this.content || ""}></ha-markdown>
|
<ha-markdown .content=${this.content || ""}></ha-markdown>
|
||||||
@@ -46,10 +50,6 @@ class HassioMarkdownDialog extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _closeDialog(): void {
|
|
||||||
this._opened = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
|
|||||||
@@ -1,116 +1,35 @@
|
|||||||
import { PolymerElement } from "@polymer/polymer";
|
|
||||||
import {
|
import {
|
||||||
customElement,
|
html,
|
||||||
property,
|
|
||||||
internalProperty,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
|
customElement,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
import "./hassio-router";
|
||||||
|
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
|
||||||
|
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
|
||||||
|
import { HomeAssistant, Route } from "../../src/types";
|
||||||
|
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
|
||||||
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
|
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
|
||||||
import { fireEvent } from "../../src/common/dom/fire_event";
|
import { fireEvent } from "../../src/common/dom/fire_event";
|
||||||
import { navigate } from "../../src/common/navigate";
|
|
||||||
import { fetchHassioAddonInfo } from "../../src/data/hassio/addon";
|
|
||||||
import {
|
|
||||||
fetchHassioHassOsInfo,
|
|
||||||
fetchHassioHostInfo,
|
|
||||||
HassioHassOSInfo,
|
|
||||||
HassioHostInfo,
|
|
||||||
} from "../../src/data/hassio/host";
|
|
||||||
import {
|
|
||||||
createHassioSession,
|
|
||||||
fetchHassioHomeAssistantInfo,
|
|
||||||
fetchHassioSupervisorInfo,
|
|
||||||
fetchHassioInfo,
|
|
||||||
HassioHomeAssistantInfo,
|
|
||||||
HassioInfo,
|
|
||||||
HassioPanelInfo,
|
|
||||||
HassioSupervisorInfo,
|
|
||||||
} from "../../src/data/hassio/supervisor";
|
|
||||||
import {
|
|
||||||
AlertDialogParams,
|
|
||||||
showAlertDialog,
|
|
||||||
} from "../../src/dialogs/generic/show-dialog-box";
|
|
||||||
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
|
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
|
||||||
import {
|
import { atLeastVersion } from "../../src/common/config/version";
|
||||||
HassRouterPage,
|
|
||||||
RouterOptions,
|
|
||||||
} from "../../src/layouts/hass-router-page";
|
|
||||||
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
|
|
||||||
import "../../src/resources/ha-style";
|
|
||||||
import { HomeAssistant } from "../../src/types";
|
|
||||||
// Don't codesplit it, that way the dashboard always loads fast.
|
|
||||||
import "./hassio-panel";
|
|
||||||
|
|
||||||
@customElement("hassio-main")
|
@customElement("hassio-main")
|
||||||
class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
export class HassioMain extends urlSyncMixin(ProvideHassLitMixin(LitElement)) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public panel!: HassioPanelInfo;
|
@property() public panel!: HassioPanelInfo;
|
||||||
|
|
||||||
@property() public narrow!: boolean;
|
@property() public narrow!: boolean;
|
||||||
|
|
||||||
protected routerOptions: RouterOptions = {
|
@property() public route?: Route;
|
||||||
// Hass.io has a page with tabs, so we route all non-matching routes to it.
|
|
||||||
defaultPage: "dashboard",
|
|
||||||
initialLoad: () => this._fetchData(),
|
|
||||||
showLoading: true,
|
|
||||||
routes: {
|
|
||||||
dashboard: {
|
|
||||||
tag: "hassio-panel",
|
|
||||||
cache: true,
|
|
||||||
},
|
|
||||||
snapshots: "dashboard",
|
|
||||||
store: "dashboard",
|
|
||||||
system: "dashboard",
|
|
||||||
addon: {
|
|
||||||
tag: "hassio-addon-dashboard",
|
|
||||||
load: () =>
|
|
||||||
import(
|
|
||||||
/* webpackChunkName: "hassio-addon-dashboard" */ "./addon-view/hassio-addon-dashboard"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
ingress: {
|
|
||||||
tag: "hassio-ingress-view",
|
|
||||||
load: () =>
|
|
||||||
import(
|
|
||||||
/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
@internalProperty() private _supervisorInfo: HassioSupervisorInfo;
|
|
||||||
|
|
||||||
@internalProperty() private _hostInfo: HassioHostInfo;
|
|
||||||
|
|
||||||
@internalProperty() private _hassioInfo?: HassioInfo;
|
|
||||||
|
|
||||||
@internalProperty() private _hassOsInfo?: HassioHassOSInfo;
|
|
||||||
|
|
||||||
@internalProperty() private _hassInfo: HassioHomeAssistantInfo;
|
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
|
|
||||||
applyThemesOnElement(
|
this._applyTheme();
|
||||||
this.parentElement,
|
|
||||||
this.hass.themes,
|
|
||||||
this.hass.selectedTheme || this.hass.themes.default_theme
|
|
||||||
);
|
|
||||||
|
|
||||||
this.style.setProperty(
|
|
||||||
"--app-header-background-color",
|
|
||||||
"var(--sidebar-background-color)"
|
|
||||||
);
|
|
||||||
this.style.setProperty(
|
|
||||||
"--app-header-text-color",
|
|
||||||
"var(--sidebar-text-color)"
|
|
||||||
);
|
|
||||||
this.style.setProperty(
|
|
||||||
"--app-header-border-bottom",
|
|
||||||
"1px solid var(--divider-color)"
|
|
||||||
);
|
|
||||||
|
|
||||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
|
||||||
// Paulus - March 17, 2019
|
// Paulus - March 17, 2019
|
||||||
// We went to a single hass-toggle-menu event in HA 0.90. However, the
|
// We went to a single hass-toggle-menu event in HA 0.90. However, the
|
||||||
// supervisor UI can also run under older versions of Home Assistant.
|
// supervisor UI can also run under older versions of Home Assistant.
|
||||||
@@ -143,152 +62,59 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
makeDialogManager(this, document.body);
|
makeDialogManager(this, this.shadowRoot!);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updatePageEl(el) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
// the tabs page does its own routing so needs full route.
|
super.updated(changedProps);
|
||||||
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
if (!oldHass) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (oldHass.themes !== this.hass.themes) {
|
||||||
|
this._applyTheme();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ("setProperties" in el) {
|
protected render() {
|
||||||
// As long as we have Polymer pages
|
return html`
|
||||||
(el as PolymerElement).setProperties({
|
<hassio-router
|
||||||
hass: this.hass,
|
.hass=${this.hass}
|
||||||
narrow: this.narrow,
|
.route=${this.route}
|
||||||
supervisorInfo: this._supervisorInfo,
|
.panel=${this.panel}
|
||||||
hassioInfo: this._hassioInfo,
|
.narrow=${this.narrow}
|
||||||
hostInfo: this._hostInfo,
|
></hassio-router>
|
||||||
hassInfo: this._hassInfo,
|
`;
|
||||||
hassOsInfo: this._hassOsInfo,
|
}
|
||||||
route,
|
|
||||||
});
|
private _applyTheme() {
|
||||||
|
let themeName: string;
|
||||||
|
let options: Partial<HomeAssistant["selectedTheme"]> | undefined;
|
||||||
|
|
||||||
|
if (atLeastVersion(this.hass.config.version, 0, 114)) {
|
||||||
|
themeName =
|
||||||
|
this.hass.selectedTheme?.theme ||
|
||||||
|
(this.hass.themes.darkMode && this.hass.themes.default_dark_theme
|
||||||
|
? this.hass.themes.default_dark_theme!
|
||||||
|
: this.hass.themes.default_theme);
|
||||||
|
|
||||||
|
options = this.hass.selectedTheme;
|
||||||
|
if (themeName === "default" && options?.dark === undefined) {
|
||||||
|
options = {
|
||||||
|
...this.hass.selectedTheme,
|
||||||
|
dark: this.hass.themes.darkMode,
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
el.hass = this.hass;
|
themeName = (this.hass.selectedTheme as unknown) as string;
|
||||||
el.narrow = this.narrow;
|
|
||||||
el.supervisorInfo = this._supervisorInfo;
|
|
||||||
el.hassioInfo = this._hassioInfo;
|
|
||||||
el.hostInfo = this._hostInfo;
|
|
||||||
el.hassInfo = this._hassInfo;
|
|
||||||
el.hassOsInfo = this._hassOsInfo;
|
|
||||||
el.route = route;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _fetchData() {
|
|
||||||
if (this.panel.config && this.panel.config.ingress) {
|
|
||||||
await this._redirectIngress(this.panel.config.ingress);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [supervisorInfo, hostInfo, hassInfo, hassioInfo] = await Promise.all([
|
applyThemesOnElement(
|
||||||
fetchHassioSupervisorInfo(this.hass),
|
this.parentElement,
|
||||||
fetchHassioHostInfo(this.hass),
|
this.hass.themes,
|
||||||
fetchHassioHomeAssistantInfo(this.hass),
|
themeName,
|
||||||
fetchHassioInfo(this.hass),
|
options
|
||||||
]);
|
|
||||||
this._supervisorInfo = supervisorInfo;
|
|
||||||
this._hassioInfo = hassioInfo;
|
|
||||||
this._hostInfo = hostInfo;
|
|
||||||
this._hassInfo = hassInfo;
|
|
||||||
|
|
||||||
if (this._hostInfo.features && this._hostInfo.features.includes("hassos")) {
|
|
||||||
this._hassOsInfo = await fetchHassioHassOsInfo(this.hass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _redirectIngress(addonSlug: string) {
|
|
||||||
// When we trigger a navigation, we sleep to make sure we don't
|
|
||||||
// show the hassio dashboard before navigating away.
|
|
||||||
const awaitAlert = async (
|
|
||||||
alertParams: AlertDialogParams,
|
|
||||||
action: () => void
|
|
||||||
) => {
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
alertParams.confirm = resolve;
|
|
||||||
showAlertDialog(this, alertParams);
|
|
||||||
});
|
|
||||||
action();
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
||||||
};
|
|
||||||
|
|
||||||
const createSessionPromise = createHassioSession(this.hass).then(
|
|
||||||
() => true,
|
|
||||||
() => false
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let addon;
|
|
||||||
|
|
||||||
try {
|
|
||||||
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
|
|
||||||
} catch (err) {
|
|
||||||
await awaitAlert(
|
|
||||||
{
|
|
||||||
text: "Unable to fetch add-on info to start Ingress",
|
|
||||||
title: "Supervisor",
|
|
||||||
},
|
|
||||||
() => history.back()
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!addon.ingress_url) {
|
|
||||||
await awaitAlert(
|
|
||||||
{
|
|
||||||
text: "Add-on does not support Ingress",
|
|
||||||
title: addon.name,
|
|
||||||
},
|
|
||||||
() => history.back()
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addon.state !== "started") {
|
|
||||||
await awaitAlert(
|
|
||||||
{
|
|
||||||
text: "Add-on is not running. Please start it first",
|
|
||||||
title: addon.name,
|
|
||||||
},
|
|
||||||
() => navigate(this, `/hassio/addon/${addon.slug}/info`, true)
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await createSessionPromise)) {
|
|
||||||
await awaitAlert(
|
|
||||||
{
|
|
||||||
text: "Unable to create an Ingress session",
|
|
||||||
title: addon.name,
|
|
||||||
},
|
|
||||||
() => history.back()
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
location.assign(addon.ingress_url);
|
|
||||||
// await a promise that doesn't resolve, so we show the loading screen
|
|
||||||
// while we load the next page.
|
|
||||||
await new Promise(() => undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _apiCalled(ev) {
|
|
||||||
if (!ev.detail.success) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let tries = 1;
|
|
||||||
|
|
||||||
const tryUpdate = () => {
|
|
||||||
this._fetchData().catch(() => {
|
|
||||||
tries += 1;
|
|
||||||
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
tryUpdate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import {
|
|||||||
LitElement,
|
LitElement,
|
||||||
property,
|
property,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
|
import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
|
||||||
import {
|
import {
|
||||||
@@ -33,6 +35,9 @@ class HassioPanel extends LitElement {
|
|||||||
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
if (!this.supervisorInfo) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
return html`
|
return html`
|
||||||
<hassio-panel-router
|
<hassio-panel-router
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
@@ -46,6 +51,16 @@ class HassioPanel extends LitElement {
|
|||||||
></hassio-panel-router>
|
></hassio-panel-router>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
--app-header-background-color: var(--sidebar-background-color);
|
||||||
|
--app-header-text-color: var(--sidebar-text-color);
|
||||||
|
--app-header-border-bottom: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
150
hassio/src/hassio-router.ts
Normal file
150
hassio/src/hassio-router.ts
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
internalProperty,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit-element";
|
||||||
|
import {
|
||||||
|
fetchHassioHassOsInfo,
|
||||||
|
fetchHassioHostInfo,
|
||||||
|
HassioHassOSInfo,
|
||||||
|
HassioHostInfo,
|
||||||
|
} from "../../src/data/hassio/host";
|
||||||
|
import {
|
||||||
|
fetchHassioHomeAssistantInfo,
|
||||||
|
fetchHassioSupervisorInfo,
|
||||||
|
fetchHassioInfo,
|
||||||
|
HassioHomeAssistantInfo,
|
||||||
|
HassioInfo,
|
||||||
|
HassioPanelInfo,
|
||||||
|
HassioSupervisorInfo,
|
||||||
|
} from "../../src/data/hassio/supervisor";
|
||||||
|
import {
|
||||||
|
HassRouterPage,
|
||||||
|
RouterOptions,
|
||||||
|
} from "../../src/layouts/hass-router-page";
|
||||||
|
import "../../src/resources/ha-style";
|
||||||
|
import { HomeAssistant } from "../../src/types";
|
||||||
|
// Don't codesplit it, that way the dashboard always loads fast.
|
||||||
|
import "./hassio-panel";
|
||||||
|
|
||||||
|
@customElement("hassio-router")
|
||||||
|
class HassioRouter extends HassRouterPage {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public panel!: HassioPanelInfo;
|
||||||
|
|
||||||
|
@property() public narrow!: boolean;
|
||||||
|
|
||||||
|
protected routerOptions: RouterOptions = {
|
||||||
|
// Hass.io has a page with tabs, so we route all non-matching routes to it.
|
||||||
|
defaultPage: "dashboard",
|
||||||
|
initialLoad: () => this._fetchData(),
|
||||||
|
showLoading: true,
|
||||||
|
routes: {
|
||||||
|
dashboard: {
|
||||||
|
tag: "hassio-panel",
|
||||||
|
cache: true,
|
||||||
|
},
|
||||||
|
snapshots: "dashboard",
|
||||||
|
store: "dashboard",
|
||||||
|
system: "dashboard",
|
||||||
|
addon: {
|
||||||
|
tag: "hassio-addon-dashboard",
|
||||||
|
load: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "hassio-addon-dashboard" */ "./addon-view/hassio-addon-dashboard"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
ingress: {
|
||||||
|
tag: "hassio-ingress-view",
|
||||||
|
load: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
@internalProperty() private _supervisorInfo: HassioSupervisorInfo;
|
||||||
|
|
||||||
|
@internalProperty() private _hostInfo: HassioHostInfo;
|
||||||
|
|
||||||
|
@internalProperty() private _hassioInfo?: HassioInfo;
|
||||||
|
|
||||||
|
@internalProperty() private _hassOsInfo?: HassioHassOSInfo;
|
||||||
|
|
||||||
|
@internalProperty() private _hassInfo: HassioHomeAssistantInfo;
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updatePageEl(el) {
|
||||||
|
// the tabs page does its own routing so needs full route.
|
||||||
|
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
|
||||||
|
|
||||||
|
el.hass = this.hass;
|
||||||
|
el.narrow = this.narrow;
|
||||||
|
el.supervisorInfo = this._supervisorInfo;
|
||||||
|
el.hassioInfo = this._hassioInfo;
|
||||||
|
el.hostInfo = this._hostInfo;
|
||||||
|
el.hassInfo = this._hassInfo;
|
||||||
|
el.hassOsInfo = this._hassOsInfo;
|
||||||
|
el.route = route;
|
||||||
|
|
||||||
|
if (el.localName === "hassio-ingress-view") {
|
||||||
|
el.ingressPanel = this.panel.config && this.panel.config.ingress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchData() {
|
||||||
|
if (this.panel.config && this.panel.config.ingress) {
|
||||||
|
this._redirectIngress(this.panel.config.ingress);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [supervisorInfo, hostInfo, hassInfo, hassioInfo] = await Promise.all([
|
||||||
|
fetchHassioSupervisorInfo(this.hass),
|
||||||
|
fetchHassioHostInfo(this.hass),
|
||||||
|
fetchHassioHomeAssistantInfo(this.hass),
|
||||||
|
fetchHassioInfo(this.hass),
|
||||||
|
]);
|
||||||
|
this._supervisorInfo = supervisorInfo;
|
||||||
|
this._hassioInfo = hassioInfo;
|
||||||
|
this._hostInfo = hostInfo;
|
||||||
|
this._hassInfo = hassInfo;
|
||||||
|
|
||||||
|
if (this._hostInfo.features && this._hostInfo.features.includes("hassos")) {
|
||||||
|
this._hassOsInfo = await fetchHassioHassOsInfo(this.hass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _redirectIngress(addonSlug: string) {
|
||||||
|
this.route = { prefix: "/hassio", path: `/ingress/${addonSlug}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
private _apiCalled(ev) {
|
||||||
|
if (!ev.detail.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tries = 1;
|
||||||
|
|
||||||
|
const tryUpdate = () => {
|
||||||
|
this._fetchData().catch(() => {
|
||||||
|
tries += 1;
|
||||||
|
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
tryUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hassio-router": HassioRouter;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,10 @@ import { createHassioSession } from "../../../src/data/hassio/supervisor";
|
|||||||
import "../../../src/layouts/hass-loading-screen";
|
import "../../../src/layouts/hass-loading-screen";
|
||||||
import "../../../src/layouts/hass-subpage";
|
import "../../../src/layouts/hass-subpage";
|
||||||
import { HomeAssistant, Route } from "../../../src/types";
|
import { HomeAssistant, Route } from "../../../src/types";
|
||||||
|
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||||
|
import { navigate } from "../../../src/common/navigate";
|
||||||
|
import { mdiMenu } from "@mdi/js";
|
||||||
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
|
|
||||||
@customElement("hassio-ingress-view")
|
@customElement("hassio-ingress-view")
|
||||||
class HassioIngressView extends LitElement {
|
class HassioIngressView extends LitElement {
|
||||||
@@ -24,22 +28,45 @@ class HassioIngressView extends LitElement {
|
|||||||
|
|
||||||
@property() public route!: Route;
|
@property() public route!: Route;
|
||||||
|
|
||||||
|
@property() public ingressPanel = false;
|
||||||
|
|
||||||
@internalProperty() private _addon?: HassioAddonDetails;
|
@internalProperty() private _addon?: HassioAddonDetails;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public narrow = false;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this._addon) {
|
if (!this._addon) {
|
||||||
return html` <hass-loading-screen></hass-loading-screen> `;
|
return html` <hass-loading-screen></hass-loading-screen> `;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
const iframe = html`<iframe src=${this._addon.ingress_url!}></iframe>`;
|
||||||
<hass-subpage .header=${this._addon.name} hassio>
|
|
||||||
<iframe src=${this._addon.ingress_url}></iframe>
|
if (!this.ingressPanel) {
|
||||||
</hass-subpage>
|
return html`<hass-subpage
|
||||||
`;
|
.header=${this._addon.name}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
>
|
||||||
|
${iframe}
|
||||||
|
</hass-subpage>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`${this.narrow || this.hass.dockedSidebar === "always_hidden"
|
||||||
|
? html`<div class="header">
|
||||||
|
<mwc-icon-button
|
||||||
|
aria-label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
|
||||||
|
@click=${this._toggleMenu}
|
||||||
|
>
|
||||||
|
<ha-svg-icon path=${mdiMenu}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
<div class="main-title">${this._addon.name}</div>
|
||||||
|
</div>
|
||||||
|
${iframe}`
|
||||||
|
: iframe}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
super.firstUpdated(changedProps);
|
super.updated(changedProps);
|
||||||
|
|
||||||
if (!changedProps.has("route")) {
|
if (!changedProps.has("route")) {
|
||||||
return;
|
return;
|
||||||
@@ -56,27 +83,56 @@ class HassioIngressView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchData(addonSlug: string) {
|
private async _fetchData(addonSlug: string) {
|
||||||
|
const createSessionPromise = createHassioSession(this.hass).then(
|
||||||
|
() => true,
|
||||||
|
() => false
|
||||||
|
);
|
||||||
|
|
||||||
|
let addon;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [addon] = await Promise.all([
|
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
|
||||||
fetchHassioAddonInfo(this.hass, addonSlug).catch(() => {
|
|
||||||
throw new Error("Failed to fetch add-on info");
|
|
||||||
}),
|
|
||||||
createHassioSession(this.hass).catch(() => {
|
|
||||||
throw new Error("Failed to create an ingress session");
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!addon.ingress) {
|
|
||||||
throw new Error("This add-on does not support ingress");
|
|
||||||
}
|
|
||||||
|
|
||||||
this._addon = addon;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line
|
await showAlertDialog(this, {
|
||||||
console.error(err);
|
text: "Unable to fetch add-on info to start Ingress",
|
||||||
alert(err.message || "Unknown error starting ingress.");
|
title: "Supervisor",
|
||||||
|
});
|
||||||
history.back();
|
history.back();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!addon.ingress_url) {
|
||||||
|
await showAlertDialog(this, {
|
||||||
|
text: "Add-on does not support Ingress",
|
||||||
|
title: addon.name,
|
||||||
|
});
|
||||||
|
history.back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addon.state !== "started") {
|
||||||
|
await showAlertDialog(this, {
|
||||||
|
text: "Add-on is not running. Please start it first",
|
||||||
|
title: addon.name,
|
||||||
|
});
|
||||||
|
navigate(this, `/hassio/addon/${addon.slug}/info`, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await createSessionPromise)) {
|
||||||
|
await showAlertDialog(this, {
|
||||||
|
text: "Unable to create an Ingress session",
|
||||||
|
title: addon.name,
|
||||||
|
});
|
||||||
|
history.back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._addon = addon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toggleMenu(): void {
|
||||||
|
fireEvent(this, "hass-toggle-menu");
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
@@ -87,6 +143,41 @@ class HassioIngressView extends LitElement {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header + iframe {
|
||||||
|
height: calc(100% - 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 16px;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: var(--app-header-background-color);
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--app-header-text-color, white);
|
||||||
|
border-bottom: var(--app-header-border-bottom, none);
|
||||||
|
box-sizing: border-box;
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-title {
|
||||||
|
margin: 0 0 0 24px;
|
||||||
|
line-height: 20px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mwc-icon-button {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
hass-subpage {
|
||||||
|
--app-header-background-color: var(--sidebar-background-color);
|
||||||
|
--app-header-text-color: var(--sidebar-text-color);
|
||||||
|
--app-header-border-bottom: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,12 @@ import {
|
|||||||
setSupervisorOption,
|
setSupervisorOption,
|
||||||
SupervisorOptions,
|
SupervisorOptions,
|
||||||
} from "../../../src/data/hassio/supervisor";
|
} from "../../../src/data/hassio/supervisor";
|
||||||
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
import "../../../src/components/ha-switch";
|
||||||
|
import {
|
||||||
|
showConfirmationDialog,
|
||||||
|
showAlertDialog,
|
||||||
|
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||||
|
import "../../../src/components/ha-settings-row";
|
||||||
import { haStyle } from "../../../src/resources/styles";
|
import { haStyle } from "../../../src/resources/styles";
|
||||||
import { HomeAssistant } from "../../../src/types";
|
import { HomeAssistant } from "../../../src/types";
|
||||||
import { hassioStyle } from "../resources/hassio-style";
|
import { hassioStyle } from "../resources/hassio-style";
|
||||||
@@ -55,6 +60,26 @@ class HassioSupervisorInfo extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<div class="options">
|
||||||
|
<ha-settings-row>
|
||||||
|
<span slot="heading">
|
||||||
|
Share Diagnostics
|
||||||
|
</span>
|
||||||
|
<span slot="description">
|
||||||
|
Share crash reports and diagnostic information.
|
||||||
|
<button
|
||||||
|
class="link"
|
||||||
|
@click=${this._diagnosticsInformationDialog}
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
<ha-switch
|
||||||
|
.checked=${this.supervisorInfo.diagnostics}
|
||||||
|
@change=${this._toggleDiagnostics}
|
||||||
|
></ha-switch>
|
||||||
|
</ha-settings-row>
|
||||||
|
</div>
|
||||||
${this._errors
|
${this._errors
|
||||||
? html` <div class="errors">Error: ${this._errors}</div> `
|
? html` <div class="errors">Error: ${this._errors}</div> `
|
||||||
: ""}
|
: ""}
|
||||||
@@ -111,7 +136,8 @@ class HassioSupervisorInfo extends LitElement {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: calc(100% - 47px);
|
height: calc(100% - 47px);
|
||||||
}
|
}
|
||||||
.info {
|
.info,
|
||||||
|
.options {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.info td:nth-child(2) {
|
.info td:nth-child(2) {
|
||||||
@@ -121,6 +147,12 @@ class HassioSupervisorInfo extends LitElement {
|
|||||||
color: var(--error-color);
|
color: var(--error-color);
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
ha-settings-row {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
button.link {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -181,6 +213,40 @@ class HassioSupervisorInfo extends LitElement {
|
|||||||
this._errors = `Error joining beta channel, ${err.body?.message || err}`;
|
this._errors = `Error joining beta channel, ${err.body?.message || err}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _diagnosticsInformationDialog() {
|
||||||
|
await showAlertDialog(this, {
|
||||||
|
title: "Help Improve Home Assistant",
|
||||||
|
text: html`Would you want to automatically share crash reports and
|
||||||
|
diagnostic information when the supervisor encounters unexpected errors?
|
||||||
|
<br /><br />
|
||||||
|
This will allow us to fix the problems, the information is only
|
||||||
|
accessible to the Home Assistant Core team and will not be shared with
|
||||||
|
others.
|
||||||
|
<br /><br />
|
||||||
|
The data does not include any private/sensetive information and you can
|
||||||
|
disable this in settings at any time you want.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _toggleDiagnostics() {
|
||||||
|
try {
|
||||||
|
const data: SupervisorOptions = {
|
||||||
|
diagnostics: !this.supervisorInfo?.diagnostics,
|
||||||
|
};
|
||||||
|
await setSupervisorOption(this.hass, data);
|
||||||
|
const eventdata = {
|
||||||
|
success: true,
|
||||||
|
response: undefined,
|
||||||
|
path: "option",
|
||||||
|
};
|
||||||
|
fireEvent(this, "hass-api-called", eventdata);
|
||||||
|
} catch (err) {
|
||||||
|
this._errors = `Error changing supervisor setting, ${
|
||||||
|
err.body?.message || err
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
45
package.json
45
package.json
@@ -25,23 +25,24 @@
|
|||||||
"@formatjs/intl-pluralrules": "^1.5.8",
|
"@formatjs/intl-pluralrules": "^1.5.8",
|
||||||
"@fullcalendar/core": "^5.0.0-beta.2",
|
"@fullcalendar/core": "^5.0.0-beta.2",
|
||||||
"@fullcalendar/daygrid": "^5.0.0-beta.2",
|
"@fullcalendar/daygrid": "^5.0.0-beta.2",
|
||||||
"@material/chips": "=8.0.0-canary.a78ceb112.0",
|
"@material/chips": "=8.0.0-canary.096a7a066.0",
|
||||||
"@material/circular-progress": "=8.0.0-canary.a78ceb112.0",
|
"@material/circular-progress": "=8.0.0-canary.a78ceb112.0",
|
||||||
"@material/mwc-button": "^0.17.2",
|
"@material/mwc-button": "^0.18.0",
|
||||||
"@material/mwc-checkbox": "^0.17.2",
|
"@material/mwc-checkbox": "^0.18.0",
|
||||||
"@material/mwc-dialog": "^0.17.2",
|
"@material/mwc-dialog": "^0.18.0",
|
||||||
"@material/mwc-fab": "^0.17.2",
|
"@material/mwc-fab": "^0.18.0",
|
||||||
"@material/mwc-formfield": "^0.17.2",
|
"@material/mwc-formfield": "^0.18.0",
|
||||||
"@material/mwc-icon-button": "^0.17.2",
|
"@material/mwc-icon-button": "^0.18.0",
|
||||||
"@material/mwc-list": "^0.17.2",
|
"@material/mwc-list": "^0.18.0",
|
||||||
"@material/mwc-menu": "^0.17.2",
|
"@material/mwc-menu": "^0.18.0",
|
||||||
"@material/mwc-ripple": "^0.17.2",
|
"@material/mwc-radio": "^0.18.0",
|
||||||
"@material/mwc-switch": "^0.17.2",
|
"@material/mwc-ripple": "^0.18.0",
|
||||||
"@material/mwc-tab": "^0.17.2",
|
"@material/mwc-switch": "^0.18.0",
|
||||||
"@material/mwc-tab-bar": "^0.17.2",
|
"@material/mwc-tab": "^0.18.0",
|
||||||
"@material/top-app-bar": "=8.0.0-canary.a78ceb112.0",
|
"@material/mwc-tab-bar": "^0.18.0",
|
||||||
"@mdi/js": "5.3.45",
|
"@material/top-app-bar": "=8.0.0-canary.096a7a066.0",
|
||||||
"@mdi/svg": "5.3.45",
|
"@mdi/js": "5.4.55",
|
||||||
|
"@mdi/svg": "5.4.55",
|
||||||
"@polymer/app-layout": "^3.0.2",
|
"@polymer/app-layout": "^3.0.2",
|
||||||
"@polymer/app-route": "^3.0.2",
|
"@polymer/app-route": "^3.0.2",
|
||||||
"@polymer/app-storage": "^3.0.2",
|
"@polymer/app-storage": "^3.0.2",
|
||||||
@@ -100,11 +101,12 @@
|
|||||||
"lit-element": "^2.3.1",
|
"lit-element": "^2.3.1",
|
||||||
"lit-html": "^1.2.1",
|
"lit-html": "^1.2.1",
|
||||||
"lit-virtualizer": "^0.4.2",
|
"lit-virtualizer": "^0.4.2",
|
||||||
"marked": "^0.6.1",
|
"marked": "^1.1.1",
|
||||||
"mdn-polyfills": "^5.16.0",
|
"mdn-polyfills": "^5.16.0",
|
||||||
"memoize-one": "^5.0.2",
|
"memoize-one": "^5.0.2",
|
||||||
"node-vibrant": "^3.1.5",
|
"node-vibrant": "^3.1.5",
|
||||||
"proxy-polyfill": "^0.3.1",
|
"proxy-polyfill": "^0.3.1",
|
||||||
|
"punycode": "^2.1.1",
|
||||||
"regenerator-runtime": "^0.13.2",
|
"regenerator-runtime": "^0.13.2",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"roboto-fontface": "^0.10.0",
|
"roboto-fontface": "^0.10.0",
|
||||||
@@ -136,11 +138,12 @@
|
|||||||
"@rollup/plugin-replace": "^2.3.2",
|
"@rollup/plugin-replace": "^2.3.2",
|
||||||
"@types/chai": "^4.1.7",
|
"@types/chai": "^4.1.7",
|
||||||
"@types/chromecast-caf-receiver": "^3.0.12",
|
"@types/chromecast-caf-receiver": "^3.0.12",
|
||||||
"@types/codemirror": "^0.0.78",
|
"@types/codemirror": "^0.0.97",
|
||||||
"@types/hls.js": "^0.12.3",
|
"@types/hls.js": "^0.12.3",
|
||||||
"@types/js-yaml": "^3.12.1",
|
"@types/js-yaml": "^3.12.1",
|
||||||
"@types/leaflet": "^1.4.3",
|
"@types/leaflet": "^1.4.3",
|
||||||
"@types/leaflet-draw": "^1.0.1",
|
"@types/leaflet-draw": "^1.0.1",
|
||||||
|
"@types/marked": "^1.1.0",
|
||||||
"@types/memoize-one": "4.1.0",
|
"@types/memoize-one": "4.1.0",
|
||||||
"@types/mocha": "^5.2.6",
|
"@types/mocha": "^5.2.6",
|
||||||
"@types/resize-observer-browser": "^0.1.3",
|
"@types/resize-observer-browser": "^0.1.3",
|
||||||
@@ -210,7 +213,11 @@
|
|||||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||||
"@polymer/polymer": "3.1.0",
|
"@polymer/polymer": "3.1.0",
|
||||||
"lit-html": "1.2.1",
|
"lit-html": "1.2.1",
|
||||||
"lit-element": "2.3.1"
|
"lit-element": "2.3.1",
|
||||||
|
"@material/animation": "8.0.0-canary.096a7a066.0",
|
||||||
|
"@material/base": "8.0.0-canary.096a7a066.0",
|
||||||
|
"@material/feature-targeting": "8.0.0-canary.096a7a066.0",
|
||||||
|
"@material/theme": "8.0.0-canary.096a7a066.0"
|
||||||
},
|
},
|
||||||
"main": "src/home-assistant.js",
|
"main": "src/home-assistant.js",
|
||||||
"husky": {
|
"husky": {
|
||||||
|
|||||||
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="20200716.0",
|
version="20200807.1",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
|||||||
import { registerServiceWorker } from "../util/register-service-worker";
|
import { registerServiceWorker } from "../util/register-service-worker";
|
||||||
import "./ha-auth-flow";
|
import "./ha-auth-flow";
|
||||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||||
|
import punycode from "punycode";
|
||||||
|
|
||||||
import(/* webpackChunkName: "pick-auth-provider" */ "./ha-pick-auth-provider");
|
import(/* webpackChunkName: "pick-auth-provider" */ "./ha-pick-auth-provider");
|
||||||
|
|
||||||
@@ -75,7 +76,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
${this.localize(
|
${this.localize(
|
||||||
"ui.panel.page-authorize.authorizing_client",
|
"ui.panel.page-authorize.authorizing_client",
|
||||||
"clientId",
|
"clientId",
|
||||||
this.clientId
|
this.clientId ? punycode.toASCII(this.clientId) : this.clientId
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
${loggingInWith}
|
${loggingInWith}
|
||||||
|
|||||||
113
src/common/color/convert-color.ts
Normal file
113
src/common/color/convert-color.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
const expand_hex = (hex: string): string => {
|
||||||
|
let result = "";
|
||||||
|
for (const val of hex) {
|
||||||
|
result += val + val;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rgb_hex = (component: number): string => {
|
||||||
|
const hex = Math.round(Math.min(Math.max(component, 0), 255)).toString(16);
|
||||||
|
return hex.length === 1 ? `0${hex}` : hex;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Conversion between HEX and RGB
|
||||||
|
|
||||||
|
export const hex2rgb = (hex: string): [number, number, number] => {
|
||||||
|
hex = hex.replace("#", "");
|
||||||
|
if (hex.length === 3 || hex.length === 4) {
|
||||||
|
hex = expand_hex(hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
parseInt(hex.substring(0, 2), 16),
|
||||||
|
parseInt(hex.substring(2, 4), 16),
|
||||||
|
parseInt(hex.substring(4, 6), 16),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rgb2hex = (rgb: [number, number, number]): string => {
|
||||||
|
return `#${rgb_hex(rgb[0])}${rgb_hex(rgb[1])}${rgb_hex(rgb[2])}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Conversion between LAB, XYZ and RGB from https://github.com/gka/chroma.js
|
||||||
|
// Copyright (c) 2011-2019, Gregor Aisch
|
||||||
|
|
||||||
|
// Constants for XYZ and LAB conversion
|
||||||
|
const Xn = 0.95047;
|
||||||
|
const Yn = 1;
|
||||||
|
const Zn = 1.08883;
|
||||||
|
|
||||||
|
const t0 = 0.137931034; // 4 / 29
|
||||||
|
const t1 = 0.206896552; // 6 / 29
|
||||||
|
const t2 = 0.12841855; // 3 * t1 * t1
|
||||||
|
const t3 = 0.008856452; // t1 * t1 * t1
|
||||||
|
|
||||||
|
const rgb_xyz = (r: number) => {
|
||||||
|
r /= 255;
|
||||||
|
if (r <= 0.04045) {
|
||||||
|
return r / 12.92;
|
||||||
|
}
|
||||||
|
return ((r + 0.055) / 1.055) ** 2.4;
|
||||||
|
};
|
||||||
|
|
||||||
|
const xyz_lab = (t: number) => {
|
||||||
|
if (t > t3) {
|
||||||
|
return t ** (1 / 3);
|
||||||
|
}
|
||||||
|
return t / t2 + t0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const xyz_rgb = (r: number) => {
|
||||||
|
return 255 * (r <= 0.00304 ? 12.92 * r : 1.055 * r ** (1 / 2.4) - 0.055);
|
||||||
|
};
|
||||||
|
|
||||||
|
const lab_xyz = (t: number) => {
|
||||||
|
return t > t1 ? t * t * t : t2 * (t - t0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Conversions between RGB and LAB
|
||||||
|
|
||||||
|
const rgb2xyz = (rgb: [number, number, number]): [number, number, number] => {
|
||||||
|
let [r, g, b] = rgb;
|
||||||
|
r = rgb_xyz(r);
|
||||||
|
g = rgb_xyz(g);
|
||||||
|
b = rgb_xyz(b);
|
||||||
|
const x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / Xn);
|
||||||
|
const y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.072175 * b) / Yn);
|
||||||
|
const z = xyz_lab((0.0193339 * r + 0.119192 * g + 0.9503041 * b) / Zn);
|
||||||
|
return [x, y, z];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rgb2lab = (
|
||||||
|
rgb: [number, number, number]
|
||||||
|
): [number, number, number] => {
|
||||||
|
const [x, y, z] = rgb2xyz(rgb);
|
||||||
|
const l = 116 * y - 16;
|
||||||
|
return [l < 0 ? 0 : l, 500 * (x - y), 200 * (y - z)];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const lab2rgb = (
|
||||||
|
lab: [number, number, number]
|
||||||
|
): [number, number, number] => {
|
||||||
|
const [l, a, b] = lab;
|
||||||
|
|
||||||
|
let y = (l + 16) / 116;
|
||||||
|
let x = isNaN(a) ? y : y + a / 500;
|
||||||
|
let z = isNaN(b) ? y : y - b / 200;
|
||||||
|
|
||||||
|
y = Yn * lab_xyz(y);
|
||||||
|
x = Xn * lab_xyz(x);
|
||||||
|
z = Zn * lab_xyz(z);
|
||||||
|
|
||||||
|
const r = xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z); // D65 -> sRGB
|
||||||
|
const g = xyz_rgb(-0.969266 * x + 1.8760108 * y + 0.041556 * z);
|
||||||
|
const b_ = xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z);
|
||||||
|
|
||||||
|
return [r, g, b_];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const lab2hex = (lab: [number, number, number]): string => {
|
||||||
|
const rgb = lab2rgb(lab);
|
||||||
|
return rgb2hex(rgb);
|
||||||
|
};
|
||||||
16
src/common/color/lab.ts
Normal file
16
src/common/color/lab.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// From https://github.com/gka/chroma.js
|
||||||
|
// Copyright (c) 2011-2019, Gregor Aisch
|
||||||
|
|
||||||
|
export const labDarken = (
|
||||||
|
lab: [number, number, number],
|
||||||
|
amount = 1
|
||||||
|
): [number, number, number] => {
|
||||||
|
return [lab[0] - 18 * amount, lab[1], lab[2]];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const labBrighten = (
|
||||||
|
lab: [number, number, number],
|
||||||
|
amount = 1
|
||||||
|
): [number, number, number] => {
|
||||||
|
return labDarken(lab, -amount);
|
||||||
|
};
|
||||||
24
src/common/color/rgb.ts
Normal file
24
src/common/color/rgb.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const luminosity = (rgb: [number, number, number]): number => {
|
||||||
|
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||||
|
const lum: [number, number, number] = [0, 0, 0];
|
||||||
|
for (let i = 0; i < rgb.length; i++) {
|
||||||
|
const chan = rgb[i] / 255;
|
||||||
|
lum[i] = chan <= 0.03928 ? chan / 12.92 : ((chan + 0.055) / 1.055) ** 2.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rgbContrast = (
|
||||||
|
color1: [number, number, number],
|
||||||
|
color2: [number, number, number]
|
||||||
|
) => {
|
||||||
|
const lum1 = luminosity(color1);
|
||||||
|
const lum2 = luminosity(color2);
|
||||||
|
|
||||||
|
if (lum1 > lum2) {
|
||||||
|
return (lum1 + 0.05) / (lum2 + 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (lum2 + 0.05) / (lum1 + 0.05);
|
||||||
|
};
|
||||||
@@ -1,26 +1,20 @@
|
|||||||
import { derivedStyles } from "../../resources/styles";
|
import { derivedStyles, darkStyles } from "../../resources/styles";
|
||||||
import { HomeAssistant, Theme } from "../../types";
|
import { HomeAssistant, Theme } from "../../types";
|
||||||
|
import {
|
||||||
|
hex2rgb,
|
||||||
|
rgb2hex,
|
||||||
|
rgb2lab,
|
||||||
|
lab2rgb,
|
||||||
|
lab2hex,
|
||||||
|
} from "../color/convert-color";
|
||||||
|
import { rgbContrast } from "../color/rgb";
|
||||||
|
import { labDarken, labBrighten } from "../color/lab";
|
||||||
|
|
||||||
interface ProcessedTheme {
|
interface ProcessedTheme {
|
||||||
keys: { [key: string]: "" };
|
keys: { [key: string]: "" };
|
||||||
styles: { [key: string]: string };
|
styles: { [key: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
const hexToRgb = (hex: string): string | null => {
|
|
||||||
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
||||||
const checkHex = hex.replace(shorthandRegex, (_m, r, g, b) => {
|
|
||||||
return r + r + g + g + b + b;
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(checkHex);
|
|
||||||
return result
|
|
||||||
? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(
|
|
||||||
result[3],
|
|
||||||
16
|
|
||||||
)}`
|
|
||||||
: null;
|
|
||||||
};
|
|
||||||
|
|
||||||
let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
|
let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,17 +27,56 @@ let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
|
|||||||
export const applyThemesOnElement = (
|
export const applyThemesOnElement = (
|
||||||
element,
|
element,
|
||||||
themes: HomeAssistant["themes"],
|
themes: HomeAssistant["themes"],
|
||||||
selectedTheme?: string
|
selectedTheme?: string,
|
||||||
|
themeOptions?: Partial<HomeAssistant["selectedTheme"]>
|
||||||
) => {
|
) => {
|
||||||
const newTheme = selectedTheme
|
let cacheKey = selectedTheme;
|
||||||
? PROCESSED_THEMES[selectedTheme] || processTheme(selectedTheme, themes)
|
let themeRules: Partial<Theme> = {};
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (!element._themes && !newTheme) {
|
if (selectedTheme === "default" && themeOptions) {
|
||||||
|
if (themeOptions.dark) {
|
||||||
|
cacheKey = `${cacheKey}__dark`;
|
||||||
|
themeRules = darkStyles;
|
||||||
|
}
|
||||||
|
if (themeOptions.primaryColor) {
|
||||||
|
cacheKey = `${cacheKey}__primary_${themeOptions.primaryColor}`;
|
||||||
|
const rgbPrimaryColor = hex2rgb(themeOptions.primaryColor);
|
||||||
|
const labPrimaryColor = rgb2lab(rgbPrimaryColor);
|
||||||
|
themeRules["primary-color"] = themeOptions.primaryColor;
|
||||||
|
const rgbLigthPrimaryColor = lab2rgb(labBrighten(labPrimaryColor));
|
||||||
|
themeRules["light-primary-color"] = rgb2hex(rgbLigthPrimaryColor);
|
||||||
|
themeRules["dark-primary-color"] = lab2hex(labDarken(labPrimaryColor));
|
||||||
|
themeRules["text-primary-color"] =
|
||||||
|
rgbContrast(rgbPrimaryColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
|
||||||
|
themeRules["text-light-primary-color"] =
|
||||||
|
rgbContrast(rgbLigthPrimaryColor, [33, 33, 33]) < 6
|
||||||
|
? "#fff"
|
||||||
|
: "#212121";
|
||||||
|
themeRules["state-icon-color"] = themeRules["dark-primary-color"];
|
||||||
|
}
|
||||||
|
if (themeOptions.accentColor) {
|
||||||
|
cacheKey = `${cacheKey}__accent_${themeOptions.accentColor}`;
|
||||||
|
themeRules["accent-color"] = themeOptions.accentColor;
|
||||||
|
const rgbAccentColor = hex2rgb(themeOptions.accentColor);
|
||||||
|
themeRules["text-accent-color"] =
|
||||||
|
rgbContrast(rgbAccentColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedTheme && themes.themes[selectedTheme]) {
|
||||||
|
themeRules = themes.themes[selectedTheme];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!element._themes && !Object.keys(themeRules).length) {
|
||||||
// No styles to reset, and no styles to set
|
// No styles to reset, and no styles to set
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newTheme =
|
||||||
|
themeRules && cacheKey
|
||||||
|
? PROCESSED_THEMES[cacheKey] || processTheme(cacheKey, themeRules)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
// Add previous set keys to reset them, and new theme
|
// Add previous set keys to reset them, and new theme
|
||||||
const styles = { ...element._themes, ...newTheme?.styles };
|
const styles = { ...element._themes, ...newTheme?.styles };
|
||||||
element._themes = newTheme?.keys;
|
element._themes = newTheme?.keys;
|
||||||
@@ -58,42 +91,45 @@ export const applyThemesOnElement = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const processTheme = (
|
const processTheme = (
|
||||||
themeName: string,
|
cacheKey: string,
|
||||||
themes: HomeAssistant["themes"]
|
theme: Partial<Theme>
|
||||||
): ProcessedTheme | undefined => {
|
): ProcessedTheme | undefined => {
|
||||||
if (!themes.themes[themeName]) {
|
if (!theme || !Object.keys(theme).length) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const theme: Theme = {
|
const combinedTheme: Partial<Theme> = {
|
||||||
...derivedStyles,
|
...derivedStyles,
|
||||||
...themes.themes[themeName],
|
...theme,
|
||||||
};
|
};
|
||||||
const styles = {};
|
const styles = {};
|
||||||
const keys = {};
|
const keys = {};
|
||||||
for (const key of Object.keys(theme)) {
|
for (const key of Object.keys(combinedTheme)) {
|
||||||
const prefixedKey = `--${key}`;
|
const prefixedKey = `--${key}`;
|
||||||
const value = theme[key];
|
const value = combinedTheme[key]!;
|
||||||
styles[prefixedKey] = value;
|
styles[prefixedKey] = value;
|
||||||
keys[prefixedKey] = "";
|
keys[prefixedKey] = "";
|
||||||
|
|
||||||
// Try to create a rgb value for this key if it is a hex color
|
// Try to create a rgb value for this key if it is not a var
|
||||||
if (!value.startsWith("#")) {
|
if (!value.startsWith("#")) {
|
||||||
// Not a hex color
|
// Can't convert non hex value
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rgbKey = `rgb-${key}`;
|
const rgbKey = `rgb-${key}`;
|
||||||
if (theme[rgbKey] !== undefined) {
|
if (combinedTheme[rgbKey] !== undefined) {
|
||||||
// Theme has it's own rgb value
|
// Theme has it's own rgb value
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const rgbValue = hexToRgb(value);
|
try {
|
||||||
if (rgbValue !== null) {
|
const rgbValue = hex2rgb(value).join(",");
|
||||||
const prefixedRgbKey = `--${rgbKey}`;
|
const prefixedRgbKey = `--${rgbKey}`;
|
||||||
styles[prefixedRgbKey] = rgbValue;
|
styles[prefixedRgbKey] = rgbValue;
|
||||||
keys[prefixedRgbKey] = "";
|
keys[prefixedRgbKey] = "";
|
||||||
|
} catch (e) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PROCESSED_THEMES[themeName] = { styles, keys };
|
PROCESSED_THEMES[cacheKey] = { styles, keys };
|
||||||
return { styles, keys };
|
return { styles, keys };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Map } from "leaflet";
|
import type { Map, TileLayer } from "leaflet";
|
||||||
|
|
||||||
// Sets up a Leaflet map on the provided DOM element
|
// Sets up a Leaflet map on the provided DOM element
|
||||||
export type LeafletModuleType = typeof import("leaflet");
|
export type LeafletModuleType = typeof import("leaflet");
|
||||||
@@ -6,9 +6,9 @@ export type LeafletDrawModuleType = typeof import("leaflet-draw");
|
|||||||
|
|
||||||
export const setupLeafletMap = async (
|
export const setupLeafletMap = async (
|
||||||
mapElement: HTMLElement,
|
mapElement: HTMLElement,
|
||||||
darkMode = false,
|
darkMode?: boolean,
|
||||||
draw = false
|
draw = false
|
||||||
): Promise<[Map, LeafletModuleType]> => {
|
): Promise<[Map, LeafletModuleType, TileLayer]> => {
|
||||||
if (!mapElement.parentNode) {
|
if (!mapElement.parentNode) {
|
||||||
throw new Error("Cannot setup Leaflet map on disconnected element");
|
throw new Error("Cannot setup Leaflet map on disconnected element");
|
||||||
}
|
}
|
||||||
@@ -28,15 +28,28 @@ export const setupLeafletMap = async (
|
|||||||
style.setAttribute("rel", "stylesheet");
|
style.setAttribute("rel", "stylesheet");
|
||||||
mapElement.parentNode.appendChild(style);
|
mapElement.parentNode.appendChild(style);
|
||||||
map.setView([52.3731339, 4.8903147], 13);
|
map.setView([52.3731339, 4.8903147], 13);
|
||||||
createTileLayer(Leaflet, darkMode).addTo(map);
|
|
||||||
|
|
||||||
return [map, Leaflet];
|
const tileLayer = createTileLayer(Leaflet, Boolean(darkMode)).addTo(map);
|
||||||
|
|
||||||
|
return [map, Leaflet, tileLayer];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createTileLayer = (
|
export const replaceTileLayer = (
|
||||||
|
leaflet: LeafletModuleType,
|
||||||
|
map: Map,
|
||||||
|
tileLayer: TileLayer,
|
||||||
|
darkMode: boolean
|
||||||
|
): TileLayer => {
|
||||||
|
map.removeLayer(tileLayer);
|
||||||
|
tileLayer = createTileLayer(leaflet, darkMode);
|
||||||
|
tileLayer.addTo(map);
|
||||||
|
return tileLayer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createTileLayer = (
|
||||||
leaflet: LeafletModuleType,
|
leaflet: LeafletModuleType,
|
||||||
darkMode: boolean
|
darkMode: boolean
|
||||||
) => {
|
): TileLayer => {
|
||||||
return leaflet.tileLayer(
|
return leaflet.tileLayer(
|
||||||
`https://{s}.basemaps.cartocdn.com/${
|
`https://{s}.basemaps.cartocdn.com/${
|
||||||
darkMode ? "dark_all" : "light_all"
|
darkMode ? "dark_all" : "light_all"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const batteryIcon = (
|
|||||||
return "hass:battery-unknown";
|
return "hass:battery-unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
var icon = "hass:battery";
|
let icon = "hass:battery";
|
||||||
const batteryRound = Math.round(battery / 10) * 10;
|
const batteryRound = Math.round(battery / 10) * 10;
|
||||||
if (battery_charging && battery > 10) {
|
if (battery_charging && battery > 10) {
|
||||||
icon += `-charging-${batteryRound}`;
|
icon += `-charging-${batteryRound}`;
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ export const supportsFeature = (
|
|||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
feature: number
|
feature: number
|
||||||
): boolean => {
|
): boolean => {
|
||||||
// eslint-disable-next-line:no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
return (stateObj.attributes.supported_features! & feature) !== 0;
|
return (stateObj.attributes.supported_features! & feature) !== 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,11 +2,3 @@ const validEntityId = /^(\w+)\.(\w+)$/;
|
|||||||
|
|
||||||
export const isValidEntityId = (entityId: string) =>
|
export const isValidEntityId = (entityId: string) =>
|
||||||
validEntityId.test(entityId);
|
validEntityId.test(entityId);
|
||||||
|
|
||||||
export const createValidEntityId = (input: string) =>
|
|
||||||
input
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/\s|'|\./g, "_") // replace spaces, points and quotes with underscore
|
|
||||||
.replace(/\W/g, "") // remove not allowed chars
|
|
||||||
.replace(/_{2,}/g, "_") // replace multiple underscores with 1
|
|
||||||
.replace(/_$/, ""); // remove underscores at the end
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// https://gist.github.com/hagemann/382adfc57adbd5af078dc93feef01fe1
|
// https://gist.github.com/hagemann/382adfc57adbd5af078dc93feef01fe1
|
||||||
export const slugify = (value: string, delimiter = "-") => {
|
export const slugify = (value: string, delimiter = "_") => {
|
||||||
const a =
|
const a =
|
||||||
"àáäâãåăæąçćčđďèéěėëêęğǵḧìíïîįłḿǹńňñòóöôœøṕŕřßşśšșťțùúüûǘůűūųẃẍÿýźžż·/_,:;";
|
"àáäâãåăæąçćčđďèéěėëêęğǵḧìíïîįłḿǹńňñòóöôœøṕŕřßşśšșťțùúüûǘůűūųẃẍÿýźžż·/_,:;";
|
||||||
const b = `aaaaaaaaacccddeeeeeeegghiiiiilmnnnnooooooprrsssssttuuuuuuuuuwxyyzzz${delimiter}${delimiter}${delimiter}${delimiter}${delimiter}${delimiter}`;
|
const b = `aaaaaaaaacccddeeeeeeegghiiiiilmnnnnooooooprrsssssttuuuuuuuuuwxyyzzz${delimiter}${delimiter}${delimiter}${delimiter}${delimiter}${delimiter}`;
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ class HaCallServiceButton extends EventsMixin(PolymerElement) {
|
|||||||
callService() {
|
callService() {
|
||||||
this.progress = true;
|
this.progress = true;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
var el = this;
|
const el = this;
|
||||||
var eventData = {
|
const eventData = {
|
||||||
domain: this.domain,
|
domain: this.domain,
|
||||||
service: this.service,
|
service: this.service,
|
||||||
serviceData: this.serviceData,
|
serviceData: this.serviceData,
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class HaProgressButton extends PolymerElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tempClass(className) {
|
tempClass(className) {
|
||||||
var classList = this.$.container.classList;
|
const classList = this.$.container.classList;
|
||||||
classList.add(className);
|
classList.add(className);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
classList.remove(className);
|
classList.remove(className);
|
||||||
|
|||||||
@@ -541,7 +541,7 @@ export class HaDataTable extends LitElement {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: rgba(var(--rgb-primary-text-color), 0.12);
|
border-color: var(--divider-color);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -559,7 +559,7 @@ export class HaDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__row ~ .mdc-data-table__row {
|
.mdc-data-table__row ~ .mdc-data-table__row {
|
||||||
border-top: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
border-top: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__row:not(.mdc-data-table__row--selected):hover {
|
.mdc-data-table__row:not(.mdc-data-table__row--selected):hover {
|
||||||
@@ -578,7 +578,7 @@ export class HaDataTable extends LitElement {
|
|||||||
height: 56px;
|
height: 56px;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -831,7 +831,7 @@ export class HaDataTable extends LitElement {
|
|||||||
right: 12px;
|
right: 12px;
|
||||||
}
|
}
|
||||||
.table-header {
|
.table-header {
|
||||||
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
}
|
}
|
||||||
search-input {
|
search-input {
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ class DateRangePickerElement extends WrappedElement {
|
|||||||
}
|
}
|
||||||
.daterangepicker td.in-range {
|
.daterangepicker td.in-range {
|
||||||
background-color: var(--light-primary-color);
|
background-color: var(--light-primary-color);
|
||||||
color: var(--primary-text-color);
|
color: var(--text-light-primary-color, var(--primary-text-color));
|
||||||
}
|
}
|
||||||
.daterangepicker td.active,
|
.daterangepicker td.active,
|
||||||
.daterangepicker td.active:hover {
|
.daterangepicker td.active:hover {
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ export const HaIronFocusablesHelper = {
|
|||||||
* @return {!Array<!HTMLElement>}
|
* @return {!Array<!HTMLElement>}
|
||||||
*/
|
*/
|
||||||
getTabbableNodes: function (node) {
|
getTabbableNodes: function (node) {
|
||||||
var result = [];
|
const result = [];
|
||||||
// If there is at least one element with tabindex > 0, we need to sort
|
// If there is at least one element with tabindex > 0, we need to sort
|
||||||
// the final array by tabindex.
|
// the final array by tabindex.
|
||||||
var needsSortByTabIndex = this._collectTabbableNodes(node, result);
|
const needsSortByTabIndex = this._collectTabbableNodes(node, result);
|
||||||
if (needsSortByTabIndex) {
|
if (needsSortByTabIndex) {
|
||||||
return IronFocusablesHelper._sortByTabIndex(result);
|
return IronFocusablesHelper._sortByTabIndex(result);
|
||||||
}
|
}
|
||||||
@@ -50,9 +50,9 @@ export const HaIronFocusablesHelper = {
|
|||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var element = /** @type {!HTMLElement} */ (node);
|
const element = /** @type {!HTMLElement} */ (node);
|
||||||
var tabIndex = IronFocusablesHelper._normalizedTabIndex(element);
|
const tabIndex = IronFocusablesHelper._normalizedTabIndex(element);
|
||||||
var needsSort = tabIndex > 0;
|
let needsSort = tabIndex > 0;
|
||||||
if (tabIndex >= 0) {
|
if (tabIndex >= 0) {
|
||||||
result.push(element);
|
result.push(element);
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ export const HaIronFocusablesHelper = {
|
|||||||
// <input id="B" slot="b" tabindex="1">
|
// <input id="B" slot="b" tabindex="1">
|
||||||
// </div>
|
// </div>
|
||||||
// TODO(valdrin) support ShadowDOM v1 when upgrading to Polymer v2.0.
|
// TODO(valdrin) support ShadowDOM v1 when upgrading to Polymer v2.0.
|
||||||
var children;
|
let children;
|
||||||
if (element.localName === "content" || element.localName === "slot") {
|
if (element.localName === "content" || element.localName === "slot") {
|
||||||
children = dom(element).getDistributedNodes();
|
children = dom(element).getDistributedNodes();
|
||||||
} else {
|
} else {
|
||||||
@@ -80,7 +80,7 @@ export const HaIronFocusablesHelper = {
|
|||||||
children = dom(element.shadowRoot || element.root || element).children;
|
children = dom(element.shadowRoot || element.root || element).children;
|
||||||
// /////////////////////////
|
// /////////////////////////
|
||||||
}
|
}
|
||||||
for (var i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
// Ensure method is always invoked to collect tabbable children.
|
// Ensure method is always invoked to collect tabbable children.
|
||||||
needsSort = this._collectTabbableNodes(children[i], result) || needsSort;
|
needsSort = this._collectTabbableNodes(children[i], result) || needsSort;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
? ""
|
? ""
|
||||||
: this.image
|
: this.image
|
||||||
? this.image
|
? this.image
|
||||||
: state.attributes.entity_picture}"
|
: state.attributes.entity_picture_local || state.attributes.entity_picture}"
|
||||||
.label="${this._computeLabel(domain, state, this._timerTimeRemaining)}"
|
.label="${this._computeLabel(domain, state, this._timerTimeRemaining)}"
|
||||||
.description="${this.name ? this.name : computeStateName(state)}"
|
.description="${this.name ? this.name : computeStateName(state)}"
|
||||||
></ha-label-badge>
|
></ha-label-badge>
|
||||||
|
|||||||
@@ -73,10 +73,10 @@ export class StateBadge extends LitElement {
|
|||||||
if (stateObj) {
|
if (stateObj) {
|
||||||
// hide icon if we have entity picture
|
// hide icon if we have entity picture
|
||||||
if (
|
if (
|
||||||
(stateObj.attributes.entity_picture && !this.overrideIcon) ||
|
((stateObj.attributes.entity_picture_local || stateObj.attributes.entity_picture) && !this.overrideIcon) ||
|
||||||
this.overrideImage
|
this.overrideImage
|
||||||
) {
|
) {
|
||||||
let imageUrl = this.overrideImage || stateObj.attributes.entity_picture;
|
let imageUrl = this.overrideImage || stateObj.attributes.entity_picture_local || stateObj.attributes.entity_picture;
|
||||||
if (this.hass) {
|
if (this.hass) {
|
||||||
imageUrl = this.hass.hassUrl(imageUrl);
|
imageUrl = this.hass.hassUrl(imageUrl);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export class HaCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host ::slotted(.card-actions) {
|
:host ::slotted(.card-actions) {
|
||||||
border-top: 1px solid #e8e8e8;
|
border-top: 1px solid var(--divider-color, #e8e8e8);
|
||||||
padding: 5px 16px;
|
padding: 5px 16px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -188,10 +188,10 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
|
|||||||
// origin is wheel center
|
// origin is wheel center
|
||||||
// returns {x: X, y: Y} object
|
// returns {x: X, y: Y} object
|
||||||
convertToCanvasCoordinates(clientX, clientY) {
|
convertToCanvasCoordinates(clientX, clientY) {
|
||||||
var svgPoint = this.interactionLayer.createSVGPoint();
|
const svgPoint = this.interactionLayer.createSVGPoint();
|
||||||
svgPoint.x = clientX;
|
svgPoint.x = clientX;
|
||||||
svgPoint.y = clientY;
|
svgPoint.y = clientY;
|
||||||
var cc = svgPoint.matrixTransform(
|
const cc = svgPoint.matrixTransform(
|
||||||
this.interactionLayer.getScreenCTM().inverse()
|
this.interactionLayer.getScreenCTM().inverse()
|
||||||
);
|
);
|
||||||
return { x: cc.x, y: cc.y };
|
return { x: cc.x, y: cc.y };
|
||||||
@@ -225,7 +225,7 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
|
|||||||
// Touch events
|
// Touch events
|
||||||
|
|
||||||
onTouchStart(ev) {
|
onTouchStart(ev) {
|
||||||
var touch = ev.changedTouches[0];
|
const touch = ev.changedTouches[0];
|
||||||
const cc = this.convertToCanvasCoordinates(touch.clientX, touch.clientY);
|
const cc = this.convertToCanvasCoordinates(touch.clientX, touch.clientY);
|
||||||
// return if we're not on the wheel
|
// return if we're not on the wheel
|
||||||
if (!this.isInWheel(cc.x, cc.y)) {
|
if (!this.isInWheel(cc.x, cc.y)) {
|
||||||
@@ -275,8 +275,8 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
|
|||||||
|
|
||||||
// Process user input to color
|
// Process user input to color
|
||||||
processUserSelect(ev) {
|
processUserSelect(ev) {
|
||||||
var canvasXY = this.convertToCanvasCoordinates(ev.clientX, ev.clientY);
|
const canvasXY = this.convertToCanvasCoordinates(ev.clientX, ev.clientY);
|
||||||
var hs = this.getColor(canvasXY.x, canvasXY.y);
|
const hs = this.getColor(canvasXY.x, canvasXY.y);
|
||||||
this.onColorSelect(hs);
|
this.onColorSelect(hs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,11 +319,11 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
|
|||||||
|
|
||||||
// set marker position to the given color
|
// set marker position to the given color
|
||||||
setMarkerOnColor(hs) {
|
setMarkerOnColor(hs) {
|
||||||
var dist = hs.s * this.radius;
|
const dist = hs.s * this.radius;
|
||||||
var theta = ((hs.h - 180) / 180) * Math.PI;
|
const theta = ((hs.h - 180) / 180) * Math.PI;
|
||||||
var markerdX = -dist * Math.cos(theta);
|
const markerdX = -dist * Math.cos(theta);
|
||||||
var markerdY = -dist * Math.sin(theta);
|
const markerdY = -dist * Math.sin(theta);
|
||||||
var translateString = `translate(${markerdX},${markerdY})`;
|
const translateString = `translate(${markerdX},${markerdY})`;
|
||||||
this.marker.setAttribute("transform", translateString);
|
this.marker.setAttribute("transform", translateString);
|
||||||
this.tooltip.setAttribute("transform", translateString);
|
this.tooltip.setAttribute("transform", translateString);
|
||||||
}
|
}
|
||||||
@@ -358,8 +358,8 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
|
|||||||
|
|
||||||
// get angle (degrees)
|
// get angle (degrees)
|
||||||
getAngle(dX, dY) {
|
getAngle(dX, dY) {
|
||||||
var theta = Math.atan2(-dY, -dX); // radians from the left edge, clockwise = positive
|
const theta = Math.atan2(-dY, -dX); // radians from the left edge, clockwise = positive
|
||||||
var angle = (theta / Math.PI) * 180 + 180; // degrees, clockwise from right
|
const angle = (theta / Math.PI) * 180 + 180; // degrees, clockwise from right
|
||||||
return angle;
|
return angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,9 +378,9 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getColor(x, y) {
|
getColor(x, y) {
|
||||||
var hue = this.getAngle(x, y); // degrees, clockwise from right
|
const hue = this.getAngle(x, y); // degrees, clockwise from right
|
||||||
var relativeDistance = this.getDistance(x, y); // edge of radius = 1
|
const relativeDistance = this.getDistance(x, y); // edge of radius = 1
|
||||||
var sat = Math.min(relativeDistance, 1); // Distance from center
|
const sat = Math.min(relativeDistance, 1); // Distance from center
|
||||||
return { h: hue, s: sat };
|
return { h: hue, s: sat };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,9 +402,9 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
|
|||||||
if (this.saturationSegments === 1) {
|
if (this.saturationSegments === 1) {
|
||||||
hs.s = 1;
|
hs.s = 1;
|
||||||
} else {
|
} else {
|
||||||
var segmentSize = 1 / this.saturationSegments;
|
const segmentSize = 1 / this.saturationSegments;
|
||||||
var saturationStep = 1 / (this.saturationSegments - 1);
|
const saturationStep = 1 / (this.saturationSegments - 1);
|
||||||
var calculatedSat = Math.floor(hs.s / segmentSize) * saturationStep;
|
const calculatedSat = Math.floor(hs.s / segmentSize) * saturationStep;
|
||||||
hs.s = Math.min(calculatedSat, 1);
|
hs.s = Math.min(calculatedSat, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -477,9 +477,9 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
|
|||||||
hueSegments = hueSegments || 360; // reset 0 segments to 360
|
hueSegments = hueSegments || 360; // reset 0 segments to 360
|
||||||
const angleStep = 360 / hueSegments;
|
const angleStep = 360 / hueSegments;
|
||||||
const halfAngleStep = angleStep / 2; // center segments on color
|
const halfAngleStep = angleStep / 2; // center segments on color
|
||||||
for (var angle = 0; angle <= 360; angle += angleStep) {
|
for (let angle = 0; angle <= 360; angle += angleStep) {
|
||||||
var startAngle = (angle - halfAngleStep) * (Math.PI / 180);
|
const startAngle = (angle - halfAngleStep) * (Math.PI / 180);
|
||||||
var endAngle = (angle + halfAngleStep + 1) * (Math.PI / 180);
|
const endAngle = (angle + halfAngleStep + 1) * (Math.PI / 180);
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.moveTo(cX, cY);
|
context.moveTo(cX, cY);
|
||||||
context.arc(
|
context.arc(
|
||||||
@@ -492,7 +492,7 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
|
|||||||
);
|
);
|
||||||
context.closePath();
|
context.closePath();
|
||||||
// gradient
|
// gradient
|
||||||
var gradient = context.createRadialGradient(
|
const gradient = context.createRadialGradient(
|
||||||
cX,
|
cX,
|
||||||
cY,
|
cY,
|
||||||
0,
|
0,
|
||||||
@@ -507,8 +507,8 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
|
|||||||
if (saturationSegments > 0) {
|
if (saturationSegments > 0) {
|
||||||
const ratioStep = 1 / saturationSegments;
|
const ratioStep = 1 / saturationSegments;
|
||||||
let ratio = 0;
|
let ratio = 0;
|
||||||
for (var stop = 1; stop < saturationSegments; stop += 1) {
|
for (let stop = 1; stop < saturationSegments; stop += 1) {
|
||||||
var prevLighness = lightness;
|
const prevLighness = lightness;
|
||||||
ratio = stop * ratioStep;
|
ratio = stop * ratioStep;
|
||||||
lightness = 100 - 50 * ratio;
|
lightness = 100 - 50 * ratio;
|
||||||
gradient.addColorStop(
|
gradient.addColorStop(
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class HaCoverControls extends PolymerElement {
|
|||||||
if (stateObj.state === UNAVAILABLE) {
|
if (stateObj.state === UNAVAILABLE) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
var assumedState = stateObj.attributes.assumed_state === true;
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
return (entityObj.isFullyOpen || entityObj.isOpening) && !assumedState;
|
return (entityObj.isFullyOpen || entityObj.isOpening) && !assumedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ class HaCoverControls extends PolymerElement {
|
|||||||
if (stateObj.state === UNAVAILABLE) {
|
if (stateObj.state === UNAVAILABLE) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
var assumedState = stateObj.attributes.assumed_state === true;
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
return (entityObj.isFullyClosed || entityObj.isClosing) && !assumedState;
|
return (entityObj.isFullyClosed || entityObj.isClosing) && !assumedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class HaCoverTiltControls extends PolymerElement {
|
|||||||
if (stateObj.state === UNAVAILABLE) {
|
if (stateObj.state === UNAVAILABLE) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
var assumedState = stateObj.attributes.assumed_state === true;
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
return entityObj.isFullyOpenTilt && !assumedState;
|
return entityObj.isFullyOpenTilt && !assumedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ class HaCoverTiltControls extends PolymerElement {
|
|||||||
if (stateObj.state === UNAVAILABLE) {
|
if (stateObj.state === UNAVAILABLE) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
var assumedState = stateObj.attributes.assumed_state === true;
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
return entityObj.isFullyClosedTilt && !assumedState;
|
return entityObj.isFullyClosedTilt && !assumedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -163,13 +163,6 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
border-right: 1px solid var(--divider-color);
|
border-right: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
|
||||||
.date-range-ranges {
|
|
||||||
border-right: none;
|
|
||||||
border-bottom: 1px solid var(--divider-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-range-footer {
|
.date-range-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@@ -179,12 +172,30 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
|
|
||||||
paper-input {
|
paper-input {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
max-width: 200px;
|
max-width: 250px;
|
||||||
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-input:last-child {
|
paper-input:last-child {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
.date-range-ranges {
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 500px) {
|
||||||
|
paper-input {
|
||||||
|
min-width: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-svg-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,10 +34,12 @@ export class HaDialog extends MwcDialog {
|
|||||||
style,
|
style,
|
||||||
css`
|
css`
|
||||||
.mdc-dialog {
|
.mdc-dialog {
|
||||||
|
--mdc-dialog-scroll-divider-color: var(--divider-color);
|
||||||
z-index: var(--dialog-z-index, 7);
|
z-index: var(--dialog-z-index, 7);
|
||||||
}
|
}
|
||||||
.mdc-dialog__actions {
|
.mdc-dialog__actions {
|
||||||
justify-content: var(--justify-action-buttons, flex-end);
|
justify-content: var(--justify-action-buttons, flex-end);
|
||||||
|
padding-bottom: max(env(safe-area-inset-bottom), 8px);
|
||||||
}
|
}
|
||||||
.mdc-dialog__container {
|
.mdc-dialog__container {
|
||||||
align-items: var(--vertial-align-dialog, center);
|
align-items: var(--vertial-align-dialog, center);
|
||||||
@@ -50,6 +52,12 @@ export class HaDialog extends MwcDialog {
|
|||||||
position: var(--dialog-content-position, relative);
|
position: var(--dialog-content-position, relative);
|
||||||
padding: var(--dialog-content-padding, 20px 24px);
|
padding: var(--dialog-content-padding, 20px 24px);
|
||||||
}
|
}
|
||||||
|
:host([hideactions]) .mdc-dialog .mdc-dialog__content {
|
||||||
|
padding-bottom: max(
|
||||||
|
var(--dialog-content-padding, 20px),
|
||||||
|
env(safe-area-inset-bottom)
|
||||||
|
);
|
||||||
|
}
|
||||||
.mdc-dialog .mdc-dialog__surface {
|
.mdc-dialog .mdc-dialog__surface {
|
||||||
position: var(--dialog-surface-position, relative);
|
position: var(--dialog-surface-position, relative);
|
||||||
min-height: var(--mdc-dialog-min-height, auto);
|
min-height: var(--mdc-dialog-min-height, auto);
|
||||||
|
|||||||
@@ -37,6 +37,24 @@ export class HaFormString extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(): void {
|
||||||
|
if (this.schema.name.includes("password")) {
|
||||||
|
const stepInput = document.createElement("input");
|
||||||
|
stepInput.setAttribute("type", "password");
|
||||||
|
stepInput.setAttribute("name", "password");
|
||||||
|
stepInput.setAttribute("autocomplete", "on");
|
||||||
|
stepInput.onkeyup = (ev) => this._externalValueChanged(ev, this);
|
||||||
|
document.documentElement.appendChild(stepInput);
|
||||||
|
} else if (this.schema.name.includes("username")) {
|
||||||
|
const stepInput = document.createElement("input");
|
||||||
|
stepInput.setAttribute("type", "text");
|
||||||
|
stepInput.setAttribute("name", "username");
|
||||||
|
stepInput.setAttribute("autocomplete", "on");
|
||||||
|
stepInput.onkeyup = (ev) => this._externalValueChanged(ev, this);
|
||||||
|
document.documentElement.appendChild(stepInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return this.schema.name.includes("password")
|
return this.schema.name.includes("password")
|
||||||
? html`
|
? html`
|
||||||
@@ -81,11 +99,21 @@ export class HaFormString extends LitElement implements HaFormElement {
|
|||||||
if (this.data === value) {
|
if (this.data === value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _externalValueChanged(ev: Event, el): void {
|
||||||
|
const value = (ev.target as PaperInputElement).value;
|
||||||
|
if (this.data === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
el.shadowRoot!.querySelector("paper-input").value = value;
|
||||||
|
}
|
||||||
|
|
||||||
private get _stringType(): string {
|
private get _stringType(): string {
|
||||||
if (this.schema.format) {
|
if (this.schema.format) {
|
||||||
if (["email", "url"].includes(this.schema.format)) {
|
if (["email", "url"].includes(this.schema.format)) {
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export interface HaFormTimeData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface HaFormElement extends LitElement {
|
export interface HaFormElement extends LitElement {
|
||||||
schema: HaFormSchema;
|
schema: HaFormSchema | HaFormSchema[];
|
||||||
data?: HaFormDataContainer | HaFormData;
|
data?: HaFormDataContainer | HaFormData;
|
||||||
label?: string;
|
label?: string;
|
||||||
suffix?: string;
|
suffix?: string;
|
||||||
@@ -110,7 +110,7 @@ export interface HaFormElement extends LitElement {
|
|||||||
export class HaForm extends LitElement implements HaFormElement {
|
export class HaForm extends LitElement implements HaFormElement {
|
||||||
@property() public data!: HaFormDataContainer | HaFormData;
|
@property() public data!: HaFormDataContainer | HaFormData;
|
||||||
|
|
||||||
@property() public schema!: HaFormSchema;
|
@property() public schema!: HaFormSchema | HaFormSchema[];
|
||||||
|
|
||||||
@property() public error;
|
@property() public error;
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeError(error, schema: HaFormSchema) {
|
private _computeError(error, schema: HaFormSchema | HaFormSchema[]) {
|
||||||
return this.computeError ? this.computeError(error, schema) : error;
|
return this.computeError ? this.computeError(error, schema) : error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
private _valueChanged(ev: CustomEvent) {
|
private _valueChanged(ev: CustomEvent) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const schema = (ev.target as HaFormElement).schema;
|
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
|
||||||
const data = this.data as HaFormDataContainer;
|
const data = this.data as HaFormDataContainer;
|
||||||
data[schema.name] = ev.detail.value;
|
data[schema.name] = ev.detail.value;
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { styleMap } from "lit-html/directives/style-map";
|
import { styleMap } from "lit-html/directives/style-map";
|
||||||
import { afterNextRender } from "../common/util/render-status";
|
import { afterNextRender } from "../common/util/render-status";
|
||||||
|
import { ifDefined } from "lit-html/directives/if-defined";
|
||||||
|
|
||||||
const getAngle = (value: number, min: number, max: number) => {
|
const getAngle = (value: number, min: number, max: number) => {
|
||||||
const percentage = getValueInPercentage(normalize(value, min, max), min, max);
|
const percentage = getValueInPercentage(normalize(value, min, max), min, max);
|
||||||
@@ -27,6 +28,9 @@ const getValueInPercentage = (value: number, min: number, max: number) => {
|
|||||||
return (100 * newVal) / newMax;
|
return (100 * newVal) / newMax;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Workaround for https://github.com/home-assistant/frontend/issues/6467
|
||||||
|
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||||
|
|
||||||
@customElement("ha-gauge")
|
@customElement("ha-gauge")
|
||||||
export class Gauge extends LitElement {
|
export class Gauge extends LitElement {
|
||||||
@property({ type: Number }) public min = 0;
|
@property({ type: Number }) public min = 0;
|
||||||
@@ -69,9 +73,28 @@ export class Gauge extends LitElement {
|
|||||||
></path>
|
></path>
|
||||||
<path
|
<path
|
||||||
class="value"
|
class="value"
|
||||||
style=${styleMap({ transform: `rotate(${this._angle}deg)` })}
|
|
||||||
d="M 90 50.001 A 40 40 0 0 1 10 50"
|
d="M 90 50.001 A 40 40 0 0 1 10 50"
|
||||||
></path>
|
style=${ifDefined(
|
||||||
|
!isSafari
|
||||||
|
? styleMap({ transform: `rotate(${this._angle}deg)` })
|
||||||
|
: undefined
|
||||||
|
)}
|
||||||
|
transform=${ifDefined(
|
||||||
|
isSafari ? `rotate(${this._angle} 50 50)` : undefined
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
${
|
||||||
|
isSafari
|
||||||
|
? svg`<animateTransform
|
||||||
|
attributeName="transform"
|
||||||
|
type="rotate"
|
||||||
|
from="0 50 50"
|
||||||
|
to="${this._angle} 50 50"
|
||||||
|
dur="1s"
|
||||||
|
/>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
<svg class="text">
|
<svg class="text">
|
||||||
<text class="value-text">
|
<text class="value-text">
|
||||||
@@ -106,8 +129,8 @@ export class Gauge extends LitElement {
|
|||||||
fill: none;
|
fill: none;
|
||||||
stroke-width: 15;
|
stroke-width: 15;
|
||||||
stroke: var(--gauge-color);
|
stroke: var(--gauge-color);
|
||||||
transition: all 1000ms ease 0s;
|
|
||||||
transform-origin: 50% 100%;
|
transform-origin: 50% 100%;
|
||||||
|
transition: all 1s ease 0s;
|
||||||
}
|
}
|
||||||
.gauge {
|
.gauge {
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ const mdiRemovedIcons = new Set([
|
|||||||
"medium",
|
"medium",
|
||||||
"meetup",
|
"meetup",
|
||||||
"mixcloud",
|
"mixcloud",
|
||||||
|
"mixer",
|
||||||
"nfc-off",
|
"nfc-off",
|
||||||
"npm-variant",
|
"npm-variant",
|
||||||
"npm-variant-outline",
|
"npm-variant-outline",
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ class HaMarkdownElement extends UpdatingElement {
|
|||||||
{
|
{
|
||||||
breaks: this.breaks,
|
breaks: this.breaks,
|
||||||
gfm: true,
|
gfm: true,
|
||||||
tables: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
allowSvg: this.allowSvg,
|
allowSvg: this.allowSvg,
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ class HaMarkdown extends LitElement {
|
|||||||
background-color: var(--markdown-code-background-color, none);
|
background-color: var(--markdown-code-background-color, none);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
ha-markdown-element svg {
|
||||||
|
background-color: var(--markdown-svg-background-color, none);
|
||||||
|
color: var(--markdown-svg-color, none);
|
||||||
|
}
|
||||||
ha-markdown-element code {
|
ha-markdown-element code {
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
@@ -70,8 +74,8 @@ class HaMarkdown extends LitElement {
|
|||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
ha-markdown-element h2 {
|
ha-markdown-element h2 {
|
||||||
font-size: 1.5em !important;
|
font-size: 1.5em;
|
||||||
font-weight: bold !important;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/components/ha-radio.ts
Normal file
20
src/components/ha-radio.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import "@material/mwc-radio";
|
||||||
|
import type { Radio } from "@material/mwc-radio";
|
||||||
|
import { customElement } from "lit-element";
|
||||||
|
import type { Constructor } from "../types";
|
||||||
|
|
||||||
|
const MwcRadio = customElements.get("mwc-radio") as Constructor<Radio>;
|
||||||
|
|
||||||
|
@customElement("ha-radio")
|
||||||
|
export class HaRadio extends MwcRadio {
|
||||||
|
public firstUpdated() {
|
||||||
|
super.firstUpdated();
|
||||||
|
this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-radio": HaRadio;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,6 +97,7 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
|||||||
</h3>
|
</h3>
|
||||||
<a
|
<a
|
||||||
href=${`/config/integrations#config_entry=${relatedConfigEntryId}`}
|
href=${`/config/integrations#config_entry=${relatedConfigEntryId}`}
|
||||||
|
@click=${this._navigateAwayClose}
|
||||||
>
|
>
|
||||||
${this.hass.localize(`component.${entry.domain}.title`)}:
|
${this.hass.localize(`component.${entry.domain}.title`)}:
|
||||||
${entry.title}
|
${entry.title}
|
||||||
@@ -116,7 +117,10 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
|||||||
<h3>
|
<h3>
|
||||||
${this.hass.localize("ui.components.related-items.device")}:
|
${this.hass.localize("ui.components.related-items.device")}:
|
||||||
</h3>
|
</h3>
|
||||||
<a href="/config/devices/device/${relatedDeviceId}">
|
<a
|
||||||
|
href="/config/devices/device/${relatedDeviceId}"
|
||||||
|
@click=${this._navigateAwayClose}
|
||||||
|
>
|
||||||
${device.name_by_user || device.name}
|
${device.name_by_user || device.name}
|
||||||
</a>
|
</a>
|
||||||
`;
|
`;
|
||||||
@@ -134,7 +138,10 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
|||||||
<h3>
|
<h3>
|
||||||
${this.hass.localize("ui.components.related-items.area")}:
|
${this.hass.localize("ui.components.related-items.area")}:
|
||||||
</h3>
|
</h3>
|
||||||
<a href="/config/areas/area/${relatedAreaId}">
|
<a
|
||||||
|
href="/config/areas/area/${relatedAreaId}"
|
||||||
|
@click=${this._navigateAwayClose}
|
||||||
|
>
|
||||||
${area.name}
|
${area.name}
|
||||||
</a>
|
</a>
|
||||||
`;
|
`;
|
||||||
@@ -278,6 +285,12 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _navigateAwayClose() {
|
||||||
|
// allow new page to open before closing dialog
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
fireEvent(this, "close-dialog");
|
||||||
|
}
|
||||||
|
|
||||||
private async _findRelated() {
|
private async _findRelated() {
|
||||||
this._related = await findRelated(this.hass, this.itemType, this.itemId);
|
this._related = await findRelated(this.hass, this.itemType, this.itemId);
|
||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ class HaServiceDescription extends PolymerElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getDescription(hass, domain, service) {
|
_getDescription(hass, domain, service) {
|
||||||
var domainServices = hass.services[domain];
|
const domainServices = hass.services[domain];
|
||||||
if (!domainServices) return "";
|
if (!domainServices) return "";
|
||||||
var serviceObject = domainServices[service];
|
const serviceObject = domainServices[service];
|
||||||
if (!serviceObject) return "";
|
if (!serviceObject) return "";
|
||||||
return serviceObject.description;
|
return serviceObject.description;
|
||||||
}
|
}
|
||||||
|
|||||||
59
src/components/ha-settings-row.ts
Normal file
59
src/components/ha-settings-row.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
SVGTemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
|
||||||
|
@customElement("ha-settings-row")
|
||||||
|
export class HaSettingsRow extends LitElement {
|
||||||
|
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "three-line" })
|
||||||
|
public threeLine = false;
|
||||||
|
|
||||||
|
protected render(): SVGTemplateResult {
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
paper-item-body {
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<paper-item-body
|
||||||
|
?two-line=${!this.threeLine}
|
||||||
|
?three-line=${!this.threeLine}
|
||||||
|
>
|
||||||
|
<slot name="heading"></slot>
|
||||||
|
<div secondary><slot name="description"></slot></div>
|
||||||
|
</paper-item-body>
|
||||||
|
<slot></slot>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 16px;
|
||||||
|
align-content: normal;
|
||||||
|
align-self: auto;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
:host([narrow]) {
|
||||||
|
align-items: normal;
|
||||||
|
flex-direction: column;
|
||||||
|
border-top: 1px solid var(--divider-color);
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-settings-row": HaSettingsRow;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -126,7 +126,7 @@ class HaSidebar extends LitElement {
|
|||||||
|
|
||||||
// property used only in css
|
// property used only in css
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@property({ type: Boolean, reflect: true }) private _rtl = false;
|
@property({ type: Boolean, reflect: true }) public rtl = false;
|
||||||
|
|
||||||
private _mouseLeaveTimeout?: number;
|
private _mouseLeaveTimeout?: number;
|
||||||
|
|
||||||
@@ -312,6 +312,7 @@ class HaSidebar extends LitElement {
|
|||||||
hass.panelUrl !== oldHass.panelUrl ||
|
hass.panelUrl !== oldHass.panelUrl ||
|
||||||
hass.user !== oldHass.user ||
|
hass.user !== oldHass.user ||
|
||||||
hass.localize !== oldHass.localize ||
|
hass.localize !== oldHass.localize ||
|
||||||
|
hass.language !== oldHass.language ||
|
||||||
hass.states !== oldHass.states ||
|
hass.states !== oldHass.states ||
|
||||||
hass.defaultPanel !== oldHass.defaultPanel
|
hass.defaultPanel !== oldHass.defaultPanel
|
||||||
);
|
);
|
||||||
@@ -339,12 +340,14 @@ class HaSidebar extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._rtl = computeRTL(this.hass);
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
if (!oldHass || oldHass.language !== this.hass.language) {
|
||||||
|
this.rtl = computeRTL(this.hass);
|
||||||
|
}
|
||||||
|
|
||||||
if (!SUPPORT_SCROLL_IF_NEEDED) {
|
if (!SUPPORT_SCROLL_IF_NEEDED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
|
||||||
if (!oldHass || oldHass.panelUrl !== this.hass.panelUrl) {
|
if (!oldHass || oldHass.panelUrl !== this.hass.panelUrl) {
|
||||||
const selectedEl = this.shadowRoot!.querySelector(".iron-selected");
|
const selectedEl = this.shadowRoot!.querySelector(".iron-selected");
|
||||||
if (selectedEl) {
|
if (selectedEl) {
|
||||||
@@ -496,9 +499,12 @@ class HaSidebar extends LitElement {
|
|||||||
width: 64px;
|
width: 64px;
|
||||||
}
|
}
|
||||||
:host([expanded]) {
|
:host([expanded]) {
|
||||||
width: 256px;
|
width: calc(256px + env(safe-area-inset-left));
|
||||||
|
}
|
||||||
|
:host([rtl]) {
|
||||||
|
border-right: 0;
|
||||||
|
border-left: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 65px;
|
height: 65px;
|
||||||
@@ -512,18 +518,25 @@ class HaSidebar extends LitElement {
|
|||||||
background-color: var(--primary-background-color);
|
background-color: var(--primary-background-color);
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding-left: calc(8.5px + env(safe-area-inset-left));
|
||||||
|
}
|
||||||
|
:host([rtl]) .menu {
|
||||||
|
padding-left: 8.5px;
|
||||||
|
padding-right: calc(8.5px + env(safe-area-inset-right));
|
||||||
}
|
}
|
||||||
:host([expanded]) .menu {
|
:host([expanded]) .menu {
|
||||||
width: 256px;
|
width: calc(256px + env(safe-area-inset-left));
|
||||||
|
}
|
||||||
|
:host([rtl][expanded]) .menu {
|
||||||
|
width: calc(256px + env(safe-area-inset-right));
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu mwc-icon-button {
|
.menu mwc-icon-button {
|
||||||
color: var(--sidebar-icon-color);
|
color: var(--sidebar-icon-color);
|
||||||
}
|
}
|
||||||
:host([expanded]) .menu mwc-icon-button {
|
:host([expanded]) .menu mwc-icon-button {
|
||||||
margin-right: 23px;
|
margin-right: 23px;
|
||||||
}
|
}
|
||||||
:host([expanded][_rtl]) .menu mwc-icon-button {
|
:host([expanded][rtl]) .menu mwc-icon-button {
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
margin-left: 23px;
|
margin-left: 23px;
|
||||||
}
|
}
|
||||||
@@ -551,12 +564,18 @@ class HaSidebar extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: calc(100% - 196px);
|
height: calc(100% - 196px - env(safe-area-inset-bottom));
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
scrollbar-color: var(--scrollbar-thumb-color) transparent;
|
scrollbar-color: var(--scrollbar-thumb-color) transparent;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
background: none;
|
background: none;
|
||||||
|
margin-left: env(safe-area-inset-left);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([rtl]) paper-listbox {
|
||||||
|
margin-left: initial;
|
||||||
|
margin-right: env(safe-area-inset-right);
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@@ -580,7 +599,7 @@ class HaSidebar extends LitElement {
|
|||||||
:host([expanded]) paper-icon-item {
|
:host([expanded]) paper-icon-item {
|
||||||
width: 240px;
|
width: 240px;
|
||||||
}
|
}
|
||||||
:host([_rtl]) paper-icon-item {
|
:host([rtl]) paper-icon-item {
|
||||||
padding-left: auto;
|
padding-left: auto;
|
||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
}
|
}
|
||||||
@@ -656,6 +675,11 @@ class HaSidebar extends LitElement {
|
|||||||
}
|
}
|
||||||
.notifications-container {
|
.notifications-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
margin-left: env(safe-area-inset-left);
|
||||||
|
}
|
||||||
|
:host([rtl]) .notifications-container {
|
||||||
|
margin-left: initial;
|
||||||
|
margin-right: env(safe-area-inset-right);
|
||||||
}
|
}
|
||||||
.notifications {
|
.notifications {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -664,18 +688,23 @@ class HaSidebar extends LitElement {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
.profile {
|
.profile {
|
||||||
|
margin-left: env(safe-area-inset-left);
|
||||||
|
}
|
||||||
|
:host([rtl]) .profile {
|
||||||
|
margin-left: initial;
|
||||||
|
margin-right: env(safe-area-inset-right);
|
||||||
}
|
}
|
||||||
.profile paper-icon-item {
|
.profile paper-icon-item {
|
||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
}
|
}
|
||||||
:host([_rtl]) .profile paper-icon-item {
|
:host([rtl]) .profile paper-icon-item {
|
||||||
padding-left: auto;
|
padding-left: auto;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
.profile .item-text {
|
.profile .item-text {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
:host([_rtl]) .profile .item-text {
|
:host([rtl]) .profile .item-text {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -688,7 +717,7 @@ class HaSidebar extends LitElement {
|
|||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0px 6px;
|
padding: 0px 6px;
|
||||||
color: var(--text-primary-color);
|
color: var(--text-accent-color, var(--text-primary-color));
|
||||||
}
|
}
|
||||||
ha-svg-icon + .notification-badge {
|
ha-svg-icon + .notification-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -735,7 +764,7 @@ class HaSidebar extends LitElement {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([_rtl]) .menu mwc-icon-button {
|
:host([rtl]) .menu mwc-icon-button {
|
||||||
-webkit-transform: scaleX(-1);
|
-webkit-transform: scaleX(-1);
|
||||||
transform: scaleX(-1);
|
transform: scaleX(-1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ export class HaYamlEditor extends LitElement {
|
|||||||
try {
|
try {
|
||||||
this._yaml = value && !isEmpty(value) ? safeDump(value) : "";
|
this._yaml = value && !isEmpty(value) ? safeDump(value) : "";
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(err);
|
||||||
alert(`There was an error converting to YAML: ${err}`);
|
alert(`There was an error converting to YAML: ${err}`);
|
||||||
}
|
}
|
||||||
afterNextRender(() => {
|
afterNextRender(() => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
LeafletMouseEvent,
|
LeafletMouseEvent,
|
||||||
Map,
|
Map,
|
||||||
Marker,
|
Marker,
|
||||||
|
TileLayer,
|
||||||
} from "leaflet";
|
} from "leaflet";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@@ -21,15 +22,19 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import {
|
import {
|
||||||
LeafletModuleType,
|
LeafletModuleType,
|
||||||
setupLeafletMap,
|
setupLeafletMap,
|
||||||
|
replaceTileLayer,
|
||||||
} from "../../common/dom/setup-leaflet-map";
|
} from "../../common/dom/setup-leaflet-map";
|
||||||
import { nextRender } from "../../common/util/render-status";
|
import { nextRender } from "../../common/util/render-status";
|
||||||
import { defaultRadiusColor } from "../../data/zone";
|
import { defaultRadiusColor } from "../../data/zone";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
@customElement("ha-location-editor")
|
@customElement("ha-location-editor")
|
||||||
class LocationEditor extends LitElement {
|
class LocationEditor extends LitElement {
|
||||||
@property() public location?: [number, number];
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public radius?: number;
|
@property({ type: Array }) public location?: [number, number];
|
||||||
|
|
||||||
|
@property({ type: Number }) public radius?: number;
|
||||||
|
|
||||||
@property() public radiusColor?: string;
|
@property() public radiusColor?: string;
|
||||||
|
|
||||||
@@ -46,6 +51,8 @@ class LocationEditor extends LitElement {
|
|||||||
|
|
||||||
private _leafletMap?: Map;
|
private _leafletMap?: Map;
|
||||||
|
|
||||||
|
private _tileLayer?: TileLayer;
|
||||||
|
|
||||||
private _locationMarker?: Marker | Circle;
|
private _locationMarker?: Marker | Circle;
|
||||||
|
|
||||||
public fitMap(): void {
|
public fitMap(): void {
|
||||||
@@ -97,6 +104,22 @@ class LocationEditor extends LitElement {
|
|||||||
if (changedProps.has("icon")) {
|
if (changedProps.has("icon")) {
|
||||||
this._updateIcon();
|
this._updateIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changedProps.has("hass")) {
|
||||||
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
if (!oldHass || oldHass.themes?.darkMode === this.hass.themes?.darkMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._leafletMap || !this._tileLayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._tileLayer = replaceTileLayer(
|
||||||
|
this.Leaflet,
|
||||||
|
this._leafletMap,
|
||||||
|
this._tileLayer,
|
||||||
|
this.hass.themes?.darkMode
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _mapEl(): HTMLDivElement {
|
private get _mapEl(): HTMLDivElement {
|
||||||
@@ -104,9 +127,9 @@ class LocationEditor extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _initMap(): Promise<void> {
|
private async _initMap(): Promise<void> {
|
||||||
[this._leafletMap, this.Leaflet] = await setupLeafletMap(
|
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
|
||||||
this._mapEl,
|
this._mapEl,
|
||||||
false,
|
this.hass.themes?.darkMode,
|
||||||
Boolean(this.radius)
|
Boolean(this.radius)
|
||||||
);
|
);
|
||||||
this._leafletMap.addEventListener(
|
this._leafletMap.addEventListener(
|
||||||
@@ -255,9 +278,6 @@ class LocationEditor extends LitElement {
|
|||||||
#map {
|
#map {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.light {
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
.leaflet-edit-move {
|
.leaflet-edit-move {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
cursor: move !important;
|
cursor: move !important;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Map,
|
Map,
|
||||||
Marker,
|
Marker,
|
||||||
MarkerOptions,
|
MarkerOptions,
|
||||||
|
TileLayer,
|
||||||
} from "leaflet";
|
} from "leaflet";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@@ -21,8 +22,10 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import {
|
import {
|
||||||
LeafletModuleType,
|
LeafletModuleType,
|
||||||
setupLeafletMap,
|
setupLeafletMap,
|
||||||
|
replaceTileLayer,
|
||||||
} from "../../common/dom/setup-leaflet-map";
|
} from "../../common/dom/setup-leaflet-map";
|
||||||
import { defaultRadiusColor } from "../../data/zone";
|
import { defaultRadiusColor } from "../../data/zone";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@@ -47,6 +50,8 @@ export interface MarkerLocation {
|
|||||||
|
|
||||||
@customElement("ha-locations-editor")
|
@customElement("ha-locations-editor")
|
||||||
export class HaLocationsEditor extends LitElement {
|
export class HaLocationsEditor extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public locations?: MarkerLocation[];
|
@property() public locations?: MarkerLocation[];
|
||||||
|
|
||||||
public fitZoom = 16;
|
public fitZoom = 16;
|
||||||
@@ -57,6 +62,8 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
private _leafletMap?: Map;
|
private _leafletMap?: Map;
|
||||||
|
|
||||||
|
private _tileLayer?: TileLayer;
|
||||||
|
|
||||||
private _locationMarkers?: { [key: string]: Marker | Circle };
|
private _locationMarkers?: { [key: string]: Marker | Circle };
|
||||||
|
|
||||||
private _circles: { [key: string]: Circle } = {};
|
private _circles: { [key: string]: Circle } = {};
|
||||||
@@ -116,6 +123,22 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
if (changedProps.has("locations")) {
|
if (changedProps.has("locations")) {
|
||||||
this._updateMarkers();
|
this._updateMarkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changedProps.has("hass")) {
|
||||||
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
if (!oldHass || oldHass.themes.darkMode === this.hass.themes.darkMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._leafletMap || !this._tileLayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._tileLayer = replaceTileLayer(
|
||||||
|
this.Leaflet,
|
||||||
|
this._leafletMap,
|
||||||
|
this._tileLayer,
|
||||||
|
this.hass.themes.darkMode
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _mapEl(): HTMLDivElement {
|
private get _mapEl(): HTMLDivElement {
|
||||||
@@ -123,9 +146,9 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _initMap(): Promise<void> {
|
private async _initMap(): Promise<void> {
|
||||||
[this._leafletMap, this.Leaflet] = await setupLeafletMap(
|
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
|
||||||
this._mapEl,
|
this._mapEl,
|
||||||
false,
|
this.hass.themes.darkMode,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
this._updateMarkers();
|
this._updateMarkers();
|
||||||
@@ -290,9 +313,6 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
#map {
|
#map {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.light {
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
.leaflet-marker-draggable {
|
.leaflet-marker-draggable {
|
||||||
cursor: move !important;
|
cursor: move !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import "../ha-icon-button";
|
import "../ha-icon-button";
|
||||||
import { Circle, Layer, Map, Marker } from "leaflet";
|
import { Circle, Layer, Map, Marker, TileLayer } from "leaflet";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
LeafletModuleType,
|
LeafletModuleType,
|
||||||
setupLeafletMap,
|
setupLeafletMap,
|
||||||
|
replaceTileLayer,
|
||||||
} from "../../common/dom/setup-leaflet-map";
|
} from "../../common/dom/setup-leaflet-map";
|
||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
@@ -22,11 +23,11 @@ import { HomeAssistant } from "../../types";
|
|||||||
|
|
||||||
@customElement("ha-map")
|
@customElement("ha-map")
|
||||||
class HaMap extends LitElement {
|
class HaMap extends LitElement {
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public entities?: string[];
|
@property() public entities?: string[];
|
||||||
|
|
||||||
@property() public darkMode = false;
|
@property() public darkMode?: boolean;
|
||||||
|
|
||||||
@property() public zoom?: number;
|
@property() public zoom?: number;
|
||||||
|
|
||||||
@@ -35,6 +36,8 @@ class HaMap extends LitElement {
|
|||||||
|
|
||||||
private _leafletMap?: Map;
|
private _leafletMap?: Map;
|
||||||
|
|
||||||
|
private _tileLayer?: TileLayer;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
private _resizeObserver?: ResizeObserver;
|
private _resizeObserver?: ResizeObserver;
|
||||||
|
|
||||||
@@ -122,6 +125,20 @@ class HaMap extends LitElement {
|
|||||||
if (changedProps.has("hass")) {
|
if (changedProps.has("hass")) {
|
||||||
this._drawEntities();
|
this._drawEntities();
|
||||||
this._fitMap();
|
this._fitMap();
|
||||||
|
|
||||||
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
if (!oldHass || oldHass.themes.darkMode === this.hass.themes.darkMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.Leaflet || !this._leafletMap || !this._tileLayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._tileLayer = replaceTileLayer(
|
||||||
|
this.Leaflet,
|
||||||
|
this._leafletMap,
|
||||||
|
this._tileLayer,
|
||||||
|
this.hass.themes.darkMode
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,9 +147,9 @@ class HaMap extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async loadMap(): Promise<void> {
|
private async loadMap(): Promise<void> {
|
||||||
[this._leafletMap, this.Leaflet] = await setupLeafletMap(
|
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
|
||||||
this._mapEl,
|
this._mapEl,
|
||||||
this.darkMode
|
this.darkMode ?? this.hass.themes.darkMode
|
||||||
);
|
);
|
||||||
this._drawEntities();
|
this._drawEntities();
|
||||||
this._leafletMap.invalidateSize();
|
this._leafletMap.invalidateSize();
|
||||||
@@ -229,7 +246,8 @@ class HaMap extends LitElement {
|
|||||||
icon: Leaflet.divIcon({
|
icon: Leaflet.divIcon({
|
||||||
html: iconHTML,
|
html: iconHTML,
|
||||||
iconSize: [24, 24],
|
iconSize: [24, 24],
|
||||||
className: this.darkMode ? "dark" : "light",
|
className:
|
||||||
|
this.darkMode ?? this.hass.themes.darkMode ? "dark" : "light",
|
||||||
}),
|
}),
|
||||||
interactive: false,
|
interactive: false,
|
||||||
title,
|
title,
|
||||||
|
|||||||
@@ -322,9 +322,9 @@ export class PaperTimeInput extends PolymerElement {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
validate() {
|
validate() {
|
||||||
var valid = true;
|
let valid = true;
|
||||||
// Validate hour & min fields
|
// Validate hour & min fields
|
||||||
if (!this.$.hour.validate() | !this.$.min.validate()) {
|
if (!this.$.hour.validate() || !this.$.min.validate()) {
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
// Validate second field
|
// Validate second field
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class StateBadge extends LitElement {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: var(--light-primary-color);
|
background-color: var(--light-primary-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--primary-text-color);
|
color: var(--text-light-primary-color, var(--primary-text-color));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { computeStateName } from "../common/entity/compute_state_name";
|
import { computeStateName } from "../common/entity/compute_state_name";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
import { HaFormSchema } from "../components/ha-form/ha-form";
|
||||||
|
|
||||||
export interface DeviceAutomation {
|
export interface DeviceAutomation {
|
||||||
device_id: string;
|
device_id: string;
|
||||||
@@ -20,6 +21,10 @@ export interface DeviceTrigger extends DeviceAutomation {
|
|||||||
platform: "device";
|
platform: "device";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeviceCapabilities {
|
||||||
|
extra_fields: HaFormSchema[];
|
||||||
|
}
|
||||||
|
|
||||||
export const fetchDeviceActions = (hass: HomeAssistant, deviceId: string) =>
|
export const fetchDeviceActions = (hass: HomeAssistant, deviceId: string) =>
|
||||||
hass.callWS<DeviceAction[]>({
|
hass.callWS<DeviceAction[]>({
|
||||||
type: "device_automation/action/list",
|
type: "device_automation/action/list",
|
||||||
@@ -42,7 +47,7 @@ export const fetchDeviceActionCapabilities = (
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
action: DeviceAction
|
action: DeviceAction
|
||||||
) =>
|
) =>
|
||||||
hass.callWS<DeviceAction[]>({
|
hass.callWS<DeviceCapabilities>({
|
||||||
type: "device_automation/action/capabilities",
|
type: "device_automation/action/capabilities",
|
||||||
action,
|
action,
|
||||||
});
|
});
|
||||||
@@ -51,7 +56,7 @@ export const fetchDeviceConditionCapabilities = (
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
condition: DeviceCondition
|
condition: DeviceCondition
|
||||||
) =>
|
) =>
|
||||||
hass.callWS<DeviceCondition[]>({
|
hass.callWS<DeviceCapabilities>({
|
||||||
type: "device_automation/condition/capabilities",
|
type: "device_automation/condition/capabilities",
|
||||||
condition,
|
condition,
|
||||||
});
|
});
|
||||||
@@ -60,7 +65,7 @@ export const fetchDeviceTriggerCapabilities = (
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
trigger: DeviceTrigger
|
trigger: DeviceTrigger
|
||||||
) =>
|
) =>
|
||||||
hass.callWS<DeviceTrigger[]>({
|
hass.callWS<DeviceCapabilities>({
|
||||||
type: "device_automation/trigger/capabilities",
|
type: "device_automation/trigger/capabilities",
|
||||||
trigger,
|
trigger,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export interface DeviceRegistryEntry {
|
|||||||
id: string;
|
id: string;
|
||||||
config_entries: string[];
|
config_entries: string[];
|
||||||
connections: Array<[string, string]>;
|
connections: Array<[string, string]>;
|
||||||
|
identifiers: Array<[string, string]>;
|
||||||
manufacturer: string;
|
manufacturer: string;
|
||||||
model?: string;
|
model?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export interface CreateSessionResponse {
|
|||||||
|
|
||||||
export interface SupervisorOptions {
|
export interface SupervisorOptions {
|
||||||
channel?: "beta" | "dev" | "stable";
|
channel?: "beta" | "dev" | "stable";
|
||||||
|
diagnostics?: boolean;
|
||||||
addons_repositories?: string[];
|
addons_repositories?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
21
src/data/ozw.ts
Normal file
21
src/data/ozw.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export interface OZWDevice {
|
||||||
|
node_id: number;
|
||||||
|
node_query_stage: string;
|
||||||
|
is_awake: boolean;
|
||||||
|
is_failed: boolean;
|
||||||
|
is_zwave_plus: boolean;
|
||||||
|
ozw_instance: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchOZWNodeStatus = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
ozw_instance: string,
|
||||||
|
node_id: string
|
||||||
|
): Promise<OZWDevice> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "ozw/node_status",
|
||||||
|
ozw_instance: ozw_instance,
|
||||||
|
node_id: node_id,
|
||||||
|
});
|
||||||
@@ -58,6 +58,31 @@ export interface WaitAction {
|
|||||||
timeout?: number;
|
timeout?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RepeatAction {
|
||||||
|
repeat: CountRepeat | WhileRepeat | UntilRepeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseRepeat {
|
||||||
|
sequence: Action[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CountRepeat extends BaseRepeat {
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WhileRepeat extends BaseRepeat {
|
||||||
|
while: Condition[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UntilRepeat extends BaseRepeat {
|
||||||
|
until: Condition[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChooseAction {
|
||||||
|
choose: [{ conditions: Condition[]; sequence: Action[] }];
|
||||||
|
default?: Action[];
|
||||||
|
}
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
| EventAction
|
| EventAction
|
||||||
| DeviceAction
|
| DeviceAction
|
||||||
@@ -65,7 +90,9 @@ export type Action =
|
|||||||
| Condition
|
| Condition
|
||||||
| DelayAction
|
| DelayAction
|
||||||
| SceneAction
|
| SceneAction
|
||||||
| WaitAction;
|
| WaitAction
|
||||||
|
| RepeatAction
|
||||||
|
| ChooseAction;
|
||||||
|
|
||||||
export const triggerScript = (
|
export const triggerScript = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
|||||||
@@ -165,7 +165,6 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
|||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
ha-paper-dialog {
|
ha-paper-dialog {
|
||||||
min-width: 400px;
|
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
.init-spinner {
|
.init-spinner {
|
||||||
|
|||||||
@@ -123,12 +123,12 @@ class MoreInfoConfigurator extends PolymerElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fieldChanged(ev) {
|
fieldChanged(ev) {
|
||||||
var el = ev.target;
|
const el = ev.target;
|
||||||
this.fieldInput[el.name] = el.value;
|
this.fieldInput[el.name] = el.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
submitClicked() {
|
submitClicked() {
|
||||||
var data = {
|
const data = {
|
||||||
configure_id: this.stateObj.attributes.configure_id,
|
configure_id: this.stateObj.attributes.configure_id,
|
||||||
fields: this.fieldInput,
|
fields: this.fieldInput,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class MoreInfoCover extends LocalizeMixin(PolymerElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
computeClassNames(stateObj) {
|
computeClassNames(stateObj) {
|
||||||
var classes = [
|
const classes = [
|
||||||
attributeClassNames(stateObj, [
|
attributeClassNames(stateObj, [
|
||||||
"current_position",
|
"current_position",
|
||||||
"current_tilt_position",
|
"current_tilt_position",
|
||||||
|
|||||||
@@ -140,8 +140,8 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
speedChanged(ev) {
|
speedChanged(ev) {
|
||||||
var oldVal = this.stateObj.attributes.speed;
|
const oldVal = this.stateObj.attributes.speed;
|
||||||
var newVal = ev.detail.value;
|
const newVal = ev.detail.value;
|
||||||
|
|
||||||
if (!newVal || oldVal === newVal) return;
|
if (!newVal || oldVal === newVal) return;
|
||||||
|
|
||||||
@@ -152,8 +152,8 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
oscillationToggleChanged(ev) {
|
oscillationToggleChanged(ev) {
|
||||||
var oldVal = this.stateObj.attributes.oscillating;
|
const oldVal = this.stateObj.attributes.oscillating;
|
||||||
var newVal = ev.target.checked;
|
const newVal = ev.target.checked;
|
||||||
|
|
||||||
if (oldVal === newVal) return;
|
if (oldVal === newVal) return;
|
||||||
|
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ class MoreInfoGroup extends PolymerElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
computeStates(stateObj, hass) {
|
computeStates(stateObj, hass) {
|
||||||
var states = [];
|
const states = [];
|
||||||
var entIds = stateObj.attributes.entity_id || [];
|
const entIds = stateObj.attributes.entity_id || [];
|
||||||
|
|
||||||
for (var i = 0; i < entIds.length; i++) {
|
for (let i = 0; i < entIds.length; i++) {
|
||||||
var state = hass.states[entIds[i]];
|
const state = hass.states[entIds[i]];
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
states.push(state);
|
states.push(state);
|
||||||
|
|||||||
@@ -78,14 +78,14 @@ class DatetimeInput extends PolymerElement {
|
|||||||
if (stateObj.state === "unknown") {
|
if (stateObj.state === "unknown") {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
var monthFiller;
|
let monthFiller;
|
||||||
if (stateObj.attributes.month < 10) {
|
if (stateObj.attributes.month < 10) {
|
||||||
monthFiller = "0";
|
monthFiller = "0";
|
||||||
} else {
|
} else {
|
||||||
monthFiller = "";
|
monthFiller = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
var dayFiller;
|
let dayFiller;
|
||||||
if (stateObj.attributes.day < 10) {
|
if (stateObj.attributes.day < 10) {
|
||||||
dayFiller = "0";
|
dayFiller = "0";
|
||||||
} else {
|
} else {
|
||||||
@@ -119,15 +119,18 @@ class DatetimeInput extends PolymerElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.stateObj.attributes.has_time) {
|
if (this.stateObj.attributes.has_time) {
|
||||||
changed |=
|
changed =
|
||||||
|
changed ||
|
||||||
parseInt(this.selectedMinute) !== this.stateObj.attributes.minute;
|
parseInt(this.selectedMinute) !== this.stateObj.attributes.minute;
|
||||||
changed |= parseInt(this.selectedHour) !== this.stateObj.attributes.hour;
|
changed =
|
||||||
|
changed ||
|
||||||
|
parseInt(this.selectedHour) !== this.stateObj.attributes.hour;
|
||||||
if (this.selectedMinute < 10) {
|
if (this.selectedMinute < 10) {
|
||||||
minuteFiller = "0";
|
minuteFiller = "0";
|
||||||
} else {
|
} else {
|
||||||
minuteFiller = "";
|
minuteFiller = "";
|
||||||
}
|
}
|
||||||
var timeStr =
|
const timeStr =
|
||||||
this.selectedHour + ":" + minuteFiller + this.selectedMinute;
|
this.selectedHour + ":" + minuteFiller + this.selectedMinute;
|
||||||
serviceData.time = timeStr;
|
serviceData.time = timeStr;
|
||||||
}
|
}
|
||||||
@@ -144,7 +147,7 @@ class DatetimeInput extends PolymerElement {
|
|||||||
this.stateObj.attributes.day
|
this.stateObj.attributes.day
|
||||||
);
|
);
|
||||||
|
|
||||||
changed |= dateValState !== dateValInput;
|
changed = changed || dateValState !== dateValInput;
|
||||||
|
|
||||||
serviceData.date = this.selectedDate;
|
serviceData.date = this.selectedDate;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -290,8 +290,8 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
effectChanged(ev) {
|
effectChanged(ev) {
|
||||||
var oldVal = this.stateObj.attributes.effect;
|
const oldVal = this.stateObj.attributes.effect;
|
||||||
var newVal = ev.detail.value;
|
const newVal = ev.detail.value;
|
||||||
|
|
||||||
if (!newVal || oldVal === newVal) return;
|
if (!newVal || oldVal === newVal) return;
|
||||||
|
|
||||||
@@ -302,7 +302,7 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
brightnessSliderChanged(ev) {
|
brightnessSliderChanged(ev) {
|
||||||
var bri = parseInt(ev.target.value, 10);
|
const bri = parseInt(ev.target.value, 10);
|
||||||
|
|
||||||
if (isNaN(bri)) return;
|
if (isNaN(bri)) return;
|
||||||
|
|
||||||
@@ -313,7 +313,7 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctSliderChanged(ev) {
|
ctSliderChanged(ev) {
|
||||||
var ct = parseInt(ev.target.value, 10);
|
const ct = parseInt(ev.target.value, 10);
|
||||||
|
|
||||||
if (isNaN(ct)) return;
|
if (isNaN(ct)) return;
|
||||||
|
|
||||||
@@ -324,7 +324,7 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wvSliderChanged(ev) {
|
wvSliderChanged(ev) {
|
||||||
var wv = parseInt(ev.target.value, 10);
|
const wv = parseInt(ev.target.value, 10);
|
||||||
|
|
||||||
if (isNaN(wv)) return;
|
if (isNaN(wv)) return;
|
||||||
|
|
||||||
|
|||||||
@@ -329,8 +329,8 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
handleSourceChanged(ev) {
|
handleSourceChanged(ev) {
|
||||||
if (!this.playerObj) return;
|
if (!this.playerObj) return;
|
||||||
|
|
||||||
var oldVal = this.playerObj.source;
|
const oldVal = this.playerObj.source;
|
||||||
var newVal = ev.detail.value;
|
const newVal = ev.detail.value;
|
||||||
|
|
||||||
if (!newVal || oldVal === newVal) return;
|
if (!newVal || oldVal === newVal) return;
|
||||||
|
|
||||||
@@ -340,8 +340,8 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
handleSoundModeChanged(ev) {
|
handleSoundModeChanged(ev) {
|
||||||
if (!this.playerObj) return;
|
if (!this.playerObj) return;
|
||||||
|
|
||||||
var oldVal = this.playerObj.soundMode;
|
const oldVal = this.playerObj.soundMode;
|
||||||
var newVal = ev.detail.value;
|
const newVal = ev.detail.value;
|
||||||
|
|
||||||
if (!newVal || oldVal === newVal) return;
|
if (!newVal || oldVal === newVal) return;
|
||||||
|
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ class MoreInfoWaterHeater extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
4: "has-away_mode",
|
4: "has-away_mode",
|
||||||
};
|
};
|
||||||
|
|
||||||
var classes = [featureClassNames(stateObj, _featureClassNames)];
|
const classes = [featureClassNames(stateObj, _featureClassNames)];
|
||||||
|
|
||||||
classes.push("more-info-water_heater");
|
classes.push("more-info-water_heater");
|
||||||
|
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
|
|
||||||
ha-header-bar {
|
ha-header-bar {
|
||||||
--mdc-theme-on-primary: var(--primary-text-color);
|
--mdc-theme-on-primary: var(--primary-text-color);
|
||||||
--mdc-theme-primary: var(--card-background-color);
|
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-bottom: 1px solid
|
border-bottom: 1px solid
|
||||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||||
|
|||||||
@@ -29,9 +29,17 @@ export class HuiNotificationDrawer extends EventsMixin(
|
|||||||
width: calc(100% - 32px);
|
width: calc(100% - 32px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div[main-title] {
|
||||||
|
padding-left: env(safe-area-inset-left);
|
||||||
|
padding-right: env(safe-area-inset-right);
|
||||||
|
}
|
||||||
|
|
||||||
.notifications {
|
.notifications {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
|
padding-left: env(safe-area-inset-left);
|
||||||
|
padding-right: env(safe-area-inset-right);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
height: calc(100% - 65px);
|
height: calc(100% - 65px);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: var(--primary-background-color);
|
background-color: var(--primary-background-color);
|
||||||
|
|||||||
@@ -426,7 +426,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
border-bottom-right-radius: 0px;
|
border-bottom-right-radius: 0px;
|
||||||
background-color: var(--light-primary-color);
|
background-color: var(--light-primary-color);
|
||||||
color: var(--primary-text-color);
|
color: var(--text-light-primary-color, var(--primary-text-color));
|
||||||
}
|
}
|
||||||
|
|
||||||
.message.hass {
|
.message.hass {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ function initPushNotifications() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.addEventListener("push", function (event) {
|
self.addEventListener("push", function (event) {
|
||||||
var data;
|
let data;
|
||||||
if (event.data) {
|
if (event.data) {
|
||||||
data = event.data.json();
|
data = event.data.json();
|
||||||
if (data.dismiss) {
|
if (data.dismiss) {
|
||||||
@@ -113,8 +113,6 @@ function initPushNotifications() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener("notificationclick", function (event) {
|
self.addEventListener("notificationclick", function (event) {
|
||||||
var url;
|
|
||||||
|
|
||||||
notificationEventCallback("clicked", event);
|
notificationEventCallback("clicked", event);
|
||||||
|
|
||||||
event.notification.close();
|
event.notification.close();
|
||||||
@@ -127,7 +125,7 @@ function initPushNotifications() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
url = event.notification.data.url;
|
const url = event.notification.data.url;
|
||||||
|
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
|
|
||||||
@@ -137,8 +135,8 @@ function initPushNotifications() {
|
|||||||
type: "window",
|
type: "window",
|
||||||
})
|
})
|
||||||
.then(function (windowClients) {
|
.then(function (windowClients) {
|
||||||
var i;
|
let i;
|
||||||
var client;
|
let client;
|
||||||
for (i = 0; i < windowClients.length; i++) {
|
for (i = 0; i < windowClients.length; i++) {
|
||||||
client = windowClients[i];
|
client = windowClients[i];
|
||||||
if (client.url === url && "focus" in client) {
|
if (client.url === url && "focus" in client) {
|
||||||
|
|||||||
@@ -180,7 +180,9 @@ export const provideHass = (
|
|||||||
config: demoConfig,
|
config: demoConfig,
|
||||||
themes: {
|
themes: {
|
||||||
default_theme: "default",
|
default_theme: "default",
|
||||||
|
default_dark_theme: null,
|
||||||
themes: {},
|
themes: {},
|
||||||
|
darkMode: false,
|
||||||
},
|
},
|
||||||
panels: demoPanels,
|
panels: demoPanels,
|
||||||
services: demoServices,
|
services: demoServices,
|
||||||
@@ -253,7 +255,7 @@ export const provideHass = (
|
|||||||
mockTheme(theme) {
|
mockTheme(theme) {
|
||||||
invalidateThemeCache();
|
invalidateThemeCache();
|
||||||
hass().updateHass({
|
hass().updateHass({
|
||||||
selectedTheme: theme ? "mock" : "default",
|
selectedTheme: { theme: theme ? "mock" : "default" },
|
||||||
themes: {
|
themes: {
|
||||||
...hass().themes,
|
...hass().themes,
|
||||||
themes: {
|
themes: {
|
||||||
@@ -265,7 +267,7 @@ export const provideHass = (
|
|||||||
applyThemesOnElement(
|
applyThemesOnElement(
|
||||||
document.documentElement,
|
document.documentElement,
|
||||||
themes,
|
themes,
|
||||||
selectedTheme as string
|
selectedTheme!.theme
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<meta name='viewport' content='width=device-width, user-scalable=no'>
|
<meta name='viewport' content='width=device-width, user-scalable=no, viewport-fit=cover'>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Roboto, sans-serif;
|
font-family: Roboto, sans-serif;
|
||||||
@@ -7,6 +7,6 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,14 +5,16 @@
|
|||||||
<link rel="preload" href="<%= latestAppJS %>" as="script" crossorigin="use-credentials" />
|
<link rel="preload" href="<%= latestAppJS %>" as="script" crossorigin="use-credentials" />
|
||||||
<%= renderTemplate('_header') %>
|
<%= renderTemplate('_header') %>
|
||||||
<title>Home Assistant</title>
|
<title>Home Assistant</title>
|
||||||
|
<link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#03a9f4" />
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-icon"
|
rel="apple-touch-icon"
|
||||||
sizes="180x180"
|
sizes="180x180"
|
||||||
href="/static/icons/favicon-apple-180x180.png"
|
href="/static/icons/favicon-apple-180x180.png"
|
||||||
/>
|
/>
|
||||||
<link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#03a9f4" />
|
|
||||||
<meta name="apple-itunes-app" content="app-id=1099568401" />
|
<meta name="apple-itunes-app" content="app-id=1099568401" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Home Assistant">
|
||||||
<meta
|
<meta
|
||||||
name="msapplication-square70x70logo"
|
name="msapplication-square70x70logo"
|
||||||
content="/static/icons/tile-win-70x70.png"
|
content="/static/icons/tile-win-70x70.png"
|
||||||
@@ -33,6 +35,7 @@
|
|||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta name="referrer" content="same-origin" />
|
<meta name="referrer" content="same-origin" />
|
||||||
<meta name="theme-color" content="#THEMEC" />
|
<meta name="theme-color" content="#THEMEC" />
|
||||||
|
<meta name="color-scheme" content="dark light" />
|
||||||
<style>
|
<style>
|
||||||
#ha-init-skeleton::before {
|
#ha-init-skeleton::before {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -41,7 +44,15 @@
|
|||||||
background-color: #THEMEC;
|
background-color: #THEMEC;
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
background-color: var(--primary-background-color, #fafafa);
|
background-color: var(--primary-background-color);
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
html {
|
||||||
|
background-color: #111111;
|
||||||
|
}
|
||||||
|
#ha-init-skeleton::before {
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ class HaAppLayout extends customElements.get("app-header-layout") {
|
|||||||
/* Using 'transform' will cause 'position: fixed' elements to behave like
|
/* Using 'transform' will cause 'position: fixed' elements to behave like
|
||||||
'position: absolute' relative to this element. */
|
'position: absolute' relative to this element. */
|
||||||
transform: translate(0);
|
transform: translate(0);
|
||||||
|
margin-left: env(safe-area-inset-left);
|
||||||
|
margin-right: env(safe-area-inset-right);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class HaInitPage extends LitElement {
|
|||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
max-width: 350px;
|
max-width: 350px;
|
||||||
|
color: var(--text-primary-color);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ class HassSubpage extends LitElement {
|
|||||||
background-color: var(--primary-background-color);
|
background-color: var(--primary-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host([narrow]) {
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ import { computeRTLDirection } from "../common/util/compute_rtl";
|
|||||||
export class HaTabsSubpageDataTable extends LitElement {
|
export class HaTabsSubpageDataTable extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public isWide!: boolean;
|
@property({ type: Boolean }) public isWide = false;
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object with the columns.
|
* Object with the columns.
|
||||||
@@ -110,6 +110,7 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
<hass-tabs-subpage
|
<hass-tabs-subpage
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
|
.isWide=${this.isWide}
|
||||||
.backPath=${this.backPath}
|
.backPath=${this.backPath}
|
||||||
.backCallback=${this.backCallback}
|
.backCallback=${this.backCallback}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
@@ -168,38 +169,37 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<div slot="header">
|
<div slot="header">
|
||||||
<slot name="header">
|
<slot name="header">
|
||||||
<slot name="header">
|
<div class="table-header">
|
||||||
<div class="table-header">
|
<search-input
|
||||||
<search-input
|
.filter=${this.filter}
|
||||||
.filter=${this.filter}
|
no-label-float
|
||||||
no-label-float
|
no-underline
|
||||||
no-underline
|
@value-changed=${this._handleSearchChange}
|
||||||
@value-changed=${this._handleSearchChange}
|
.label=${this.hass.localize(
|
||||||
.label=${this.hass.localize(
|
"ui.components.data-table.search"
|
||||||
"ui.components.data-table.search"
|
)}
|
||||||
)}
|
>
|
||||||
>
|
</search-input>
|
||||||
</search-input>
|
${this.activeFilters
|
||||||
${this.activeFilters
|
? html`<div class="active-filters">
|
||||||
? html`<div class="active-filters">
|
${this.hass.localize(
|
||||||
${this.hass.localize(
|
"ui.panel.config.filtering.filtering_by"
|
||||||
"ui.panel.config.filtering.filtering_by"
|
)}
|
||||||
)}
|
${this.activeFilters.join(", ")}
|
||||||
${this.activeFilters.join(", ")}
|
<mwc-button @click=${this._clearFilter}
|
||||||
<mwc-button @click=${this._clearFilter}
|
>${this.hass.localize(
|
||||||
>${this.hass.localize(
|
"ui.panel.config.filtering.clear"
|
||||||
"ui.panel.config.filtering.clear"
|
)}</mwc-button
|
||||||
)}</mwc-button
|
>
|
||||||
>
|
</div>`
|
||||||
</div>`
|
: ""}
|
||||||
: ""}
|
</div>
|
||||||
</div></slot
|
</slot>
|
||||||
></slot
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: html` <div slot="header"></div> `}
|
: html` <div slot="header"></div> `}
|
||||||
</ha-data-table>
|
</ha-data-table>
|
||||||
|
<div slot="fab"><slot name="fab"></slot></div>
|
||||||
</hass-tabs-subpage>
|
</hass-tabs-subpage>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import "../components/ha-svg-icon";
|
|||||||
import "../components/ha-icon";
|
import "../components/ha-icon";
|
||||||
import "../components/ha-tab";
|
import "../components/ha-tab";
|
||||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||||
|
import { computeRTL } from "../common/util/compute_rtl";
|
||||||
|
|
||||||
export interface PageNavigation {
|
export interface PageNavigation {
|
||||||
path: string;
|
path: string;
|
||||||
@@ -53,6 +54,11 @@ class HassTabsSubpage extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true, attribute: "is-wide" })
|
||||||
|
public isWide = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public rtl = false;
|
||||||
|
|
||||||
@internalProperty() private _activeTab?: PageNavigation;
|
@internalProperty() private _activeTab?: PageNavigation;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -107,6 +113,14 @@ class HassTabsSubpage extends LitElement {
|
|||||||
`${this.route.prefix}${this.route.path}`.includes(tab.path)
|
`${this.route.prefix}${this.route.path}`.includes(tab.path)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (changedProperties.has("hass")) {
|
||||||
|
const oldHass = changedProperties.get("hass") as
|
||||||
|
| HomeAssistant
|
||||||
|
| undefined;
|
||||||
|
if (!oldHass || oldHass.language !== this.hass.language) {
|
||||||
|
this.rtl = computeRTL(this.hass);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
@@ -152,6 +166,7 @@ class HassTabsSubpage extends LitElement {
|
|||||||
<div class="content" @scroll=${this._saveScrollPos}>
|
<div class="content" @scroll=${this._saveScrollPos}>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="fab"><slot name="fab"></slot></div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,6 +199,11 @@ class HassTabsSubpage extends LitElement {
|
|||||||
background-color: var(--primary-background-color);
|
background-color: var(--primary-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host([narrow]) {
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
ha-menu-button {
|
ha-menu-button {
|
||||||
margin-right: 24px;
|
margin-right: 24px;
|
||||||
}
|
}
|
||||||
@@ -215,9 +235,10 @@ class HassTabsSubpage extends LitElement {
|
|||||||
background-color: var(--sidebar-background-color);
|
background-color: var(--sidebar-background-color);
|
||||||
border-top: 1px solid var(--divider-color);
|
border-top: 1px solid var(--divider-color);
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
z-index: 1;
|
z-index: 2;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
#tabbar:not(.bottom-bar) {
|
#tabbar:not(.bottom-bar) {
|
||||||
@@ -247,7 +268,11 @@ class HassTabsSubpage extends LitElement {
|
|||||||
|
|
||||||
.content {
|
.content {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: calc(
|
||||||
|
100% - env(safe-area-inset-left) - env(safe-area-inset-right)
|
||||||
|
);
|
||||||
|
margin-left: env(safe-area-inset-left);
|
||||||
|
margin-right: env(safe-area-inset-right);
|
||||||
height: calc(100% - 65px);
|
height: calc(100% - 65px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@@ -256,6 +281,30 @@ class HassTabsSubpage extends LitElement {
|
|||||||
|
|
||||||
:host([narrow]) .content {
|
:host([narrow]) .content {
|
||||||
height: calc(100% - 128px);
|
height: calc(100% - 128px);
|
||||||
|
height: calc(100% - 128px - env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
#fab {
|
||||||
|
position: fixed;
|
||||||
|
right: calc(16px + env(safe-area-inset-right));
|
||||||
|
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
:host([narrow]) #fab {
|
||||||
|
bottom: calc(84px + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
#fab[is-wide] {
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
}
|
||||||
|
:host([rtl]) #fab {
|
||||||
|
right: auto;
|
||||||
|
left: calc(16px + env(safe-area-inset-left));
|
||||||
|
}
|
||||||
|
:host([rtl][is-wide]) #fab {
|
||||||
|
bottom: 24px;
|
||||||
|
left: 24px;
|
||||||
|
right: auto;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,16 +152,13 @@ class HomeAssistantMain extends LitElement {
|
|||||||
--app-drawer-width: 64px;
|
--app-drawer-width: 64px;
|
||||||
}
|
}
|
||||||
:host([expanded]) {
|
:host([expanded]) {
|
||||||
--app-drawer-width: 256px;
|
--app-drawer-width: calc(256px + env(safe-area-inset-left));
|
||||||
}
|
}
|
||||||
partial-panel-resolver,
|
partial-panel-resolver,
|
||||||
ha-sidebar {
|
ha-sidebar {
|
||||||
/* allow a light tap highlight on the actual interface elements */
|
/* allow a light tap highlight on the actual interface elements */
|
||||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
partial-panel-resolver {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,9 +62,6 @@ class IntegrationBadge extends LitElement {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([clickable]) .icon {
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -72,9 +69,8 @@ class IntegrationBadge extends LitElement {
|
|||||||
right: -10px;
|
right: -10px;
|
||||||
background-color: var(--label-badge-green);
|
background-color: var(--label-badge-green);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 18px;
|
|
||||||
display: block;
|
display: block;
|
||||||
height: 18px;
|
--mdc-icon-size: 18px;
|
||||||
border: 2px solid white;
|
border: 2px solid white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ class OnboardingCoreConfig extends LitElement {
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<ha-location-editor
|
<ha-location-editor
|
||||||
class="flex"
|
class="flex"
|
||||||
|
.hass=${this.hass}
|
||||||
.location=${this._locationValue}
|
.location=${this._locationValue}
|
||||||
.fitZoom=${14}
|
.fitZoom=${14}
|
||||||
@change=${this._locationChanged}
|
@change=${this._locationChanged}
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ class HAFullCalendar extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
--fc-theme-standard-border-color: var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
@@ -234,6 +235,10 @@ class HAFullCalendar extends LitElement {
|
|||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -259,6 +264,10 @@ class HAFullCalendar extends LitElement {
|
|||||||
background-color: var(--card-background-color);
|
background-color: var(--card-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fc-theme-standard .fc-scrollgrid {
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
|
||||||
.fc-scrollgrid-section-header td {
|
.fc-scrollgrid-section-header td {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
@@ -293,14 +302,15 @@ class HAFullCalendar extends LitElement {
|
|||||||
|
|
||||||
td.fc-day-today .fc-daygrid-day-number {
|
td.fc-day-today .fc-daygrid-day-number {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
color: #fff;
|
color: var(--text-primary-color);
|
||||||
background-color: #1a73e8;
|
background-color: var(--primary-color);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
min-width: 24px;
|
min-width: 24px;
|
||||||
|
line-height: 140%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-daygrid-day-events {
|
.fc-daygrid-day-events {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { styleMap } from "lit-html/directives/style-map";
|
import { styleMap } from "lit-html/directives/style-map";
|
||||||
|
|
||||||
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
import "../../layouts/ha-app-layout";
|
||||||
import "@polymer/app-layout/app-header/app-header";
|
import "@polymer/app-layout/app-header/app-header";
|
||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||||
import "@material/mwc-checkbox";
|
import "@material/mwc-checkbox";
|
||||||
@@ -63,7 +63,7 @@ class PanelCalendar extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<app-header-layout>
|
<ha-app-layout>
|
||||||
<app-header fixed slot="header">
|
<app-header fixed slot="header">
|
||||||
<app-toolbar>
|
<app-toolbar>
|
||||||
<ha-menu-button
|
<ha-menu-button
|
||||||
@@ -106,7 +106,7 @@ class PanelCalendar extends LitElement {
|
|||||||
@view-changed=${this._handleViewChanged}
|
@view-changed=${this._handleViewChanged}
|
||||||
></ha-full-calendar>
|
></ha-full-calendar>
|
||||||
</div>
|
</div>
|
||||||
</app-header-layout>
|
</ha-app-layout>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import {
|
|||||||
showAreaRegistryDetailDialog,
|
showAreaRegistryDetailDialog,
|
||||||
} from "./show-dialog-area-registry-detail";
|
} from "./show-dialog-area-registry-detail";
|
||||||
import { mdiPlus } from "@mdi/js";
|
import { mdiPlus } from "@mdi/js";
|
||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
|
||||||
|
|
||||||
@customElement("ha-config-areas-dashboard")
|
@customElement("ha-config-areas-dashboard")
|
||||||
export class HaConfigAreasDashboard extends LitElement {
|
export class HaConfigAreasDashboard extends LitElement {
|
||||||
@@ -106,6 +105,7 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
<hass-tabs-subpage-data-table
|
<hass-tabs-subpage-data-table
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
|
.isWide=${this.isWide}
|
||||||
back-path="/config"
|
back-path="/config"
|
||||||
.tabs=${configSections.integrations}
|
.tabs=${configSections.integrations}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
@@ -123,18 +123,16 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
icon="hass:help-circle"
|
icon="hass:help-circle"
|
||||||
@click=${this._showHelp}
|
@click=${this._showHelp}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
|
<mwc-fab
|
||||||
|
slot="fab"
|
||||||
|
title="${this.hass.localize(
|
||||||
|
"ui.panel.config.areas.picker.create_area"
|
||||||
|
)}"
|
||||||
|
@click=${this._createArea}
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
|
||||||
|
</mwc-fab>
|
||||||
</hass-tabs-subpage-data-table>
|
</hass-tabs-subpage-data-table>
|
||||||
<mwc-fab
|
|
||||||
?is-wide=${this.isWide}
|
|
||||||
?narrow=${this.narrow}
|
|
||||||
?rtl=${computeRTL(this.hass!)}
|
|
||||||
title="${this.hass.localize(
|
|
||||||
"ui.panel.config.areas.picker.create_area"
|
|
||||||
)}"
|
|
||||||
@click=${this._createArea}
|
|
||||||
>
|
|
||||||
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
|
|
||||||
</mwc-fab>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,28 +181,6 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
--app-header-background-color: var(--sidebar-background-color);
|
--app-header-background-color: var(--sidebar-background-color);
|
||||||
--app-header-text-color: var(--sidebar-text-color);
|
--app-header-text-color: var(--sidebar-text-color);
|
||||||
}
|
}
|
||||||
mwc-fab {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 16px;
|
|
||||||
right: 16px;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
mwc-fab[is-wide] {
|
|
||||||
bottom: 24px;
|
|
||||||
right: 24px;
|
|
||||||
}
|
|
||||||
mwc-fab[narrow] {
|
|
||||||
bottom: 84px;
|
|
||||||
}
|
|
||||||
mwc-fab[rtl] {
|
|
||||||
right: auto;
|
|
||||||
left: 16px;
|
|
||||||
}
|
|
||||||
mwc-fab[is-wide][rtl] {
|
|
||||||
bottom: 24px;
|
|
||||||
left: 24px;
|
|
||||||
right: auto;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
LitElement,
|
LitElement,
|
||||||
property,
|
property,
|
||||||
internalProperty,
|
internalProperty,
|
||||||
|
PropertyValues,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
@@ -29,6 +30,8 @@ import "./types/ha-automation-action-event";
|
|||||||
import "./types/ha-automation-action-scene";
|
import "./types/ha-automation-action-scene";
|
||||||
import "./types/ha-automation-action-service";
|
import "./types/ha-automation-action-service";
|
||||||
import "./types/ha-automation-action-wait_template";
|
import "./types/ha-automation-action-wait_template";
|
||||||
|
import "./types/ha-automation-action-repeat";
|
||||||
|
import "./types/ha-automation-action-choose";
|
||||||
import { handleStructError } from "../../../lovelace/common/structs/handle-errors";
|
import { handleStructError } from "../../../lovelace/common/structs/handle-errors";
|
||||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../../resources/styles";
|
||||||
@@ -41,6 +44,8 @@ const OPTIONS = [
|
|||||||
"scene",
|
"scene",
|
||||||
"service",
|
"service",
|
||||||
"wait_template",
|
"wait_template",
|
||||||
|
"repeat",
|
||||||
|
"choose",
|
||||||
];
|
];
|
||||||
|
|
||||||
const getType = (action: Action) => {
|
const getType = (action: Action) => {
|
||||||
@@ -96,6 +101,16 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
|
|
||||||
@internalProperty() private _yamlMode = false;
|
@internalProperty() private _yamlMode = false;
|
||||||
|
|
||||||
|
protected updated(changedProperties: PropertyValues) {
|
||||||
|
if (!changedProperties.has("action")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._uiModeAvailable = Boolean(getType(this.action));
|
||||||
|
if (!this._uiModeAvailable && !this._yamlMode) {
|
||||||
|
this._yamlMode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const type = getType(this.action);
|
const type = getType(this.action);
|
||||||
const selected = type ? OPTIONS.indexOf(type) : -1;
|
const selected = type ? OPTIONS.indexOf(type) : -1;
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
} from "lit-element";
|
||||||
|
import { html } from "lit-html";
|
||||||
|
import { Action, ChooseAction } from "../../../../../data/script";
|
||||||
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import { ActionElement } from "../ha-automation-action-row";
|
||||||
|
import "../../condition/ha-automation-condition-editor";
|
||||||
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import "../ha-automation-action";
|
||||||
|
import { Condition } from "../../../../../data/automation";
|
||||||
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
|
import { mdiDelete } from "@mdi/js";
|
||||||
|
|
||||||
|
@customElement("ha-automation-action-choose")
|
||||||
|
export class HaChooseAction extends LitElement implements ActionElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public action!: ChooseAction;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return { choose: [{ conditions: [], sequence: [] }], default: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const action = this.action;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${action.choose.map(
|
||||||
|
(option, idx) => html`<ha-card>
|
||||||
|
<mwc-icon-button
|
||||||
|
.idx=${idx}
|
||||||
|
@click=${this._removeOption}
|
||||||
|
title=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-svg-icon path=${mdiDelete}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
<div class="card-content">
|
||||||
|
<h2>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.option",
|
||||||
|
"number",
|
||||||
|
idx + 1
|
||||||
|
)}:
|
||||||
|
</h2>
|
||||||
|
<h3>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.conditions"
|
||||||
|
)}:
|
||||||
|
</h3>
|
||||||
|
<ha-automation-condition
|
||||||
|
.conditions=${option.conditions}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.idx=${idx}
|
||||||
|
@value-changed=${this._conditionChanged}
|
||||||
|
></ha-automation-condition>
|
||||||
|
<h3>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.sequence"
|
||||||
|
)}:
|
||||||
|
</h3>
|
||||||
|
<ha-automation-action
|
||||||
|
.actions=${option.sequence}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.idx=${idx}
|
||||||
|
@value-changed=${this._actionChanged}
|
||||||
|
></ha-automation-action>
|
||||||
|
</div>
|
||||||
|
</ha-card>`
|
||||||
|
)}
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-actions add-card">
|
||||||
|
<mwc-button @click=${this._addOption}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.add_option"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
<h2>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.choose.default"
|
||||||
|
)}:
|
||||||
|
</h2>
|
||||||
|
<ha-automation-action
|
||||||
|
.actions=${action.default || []}
|
||||||
|
@value-changed=${this._defaultChanged}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-automation-action>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _conditionChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const value = ev.detail.value as Condition[];
|
||||||
|
const index = (ev.target as any).idx;
|
||||||
|
const choose = [...this.action.choose];
|
||||||
|
choose[index].conditions = value;
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.action, choose },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _actionChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const value = ev.detail.value as Action[];
|
||||||
|
const index = (ev.target as any).idx;
|
||||||
|
const choose = [...this.action.choose];
|
||||||
|
choose[index].sequence = value;
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.action, choose },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addOption() {
|
||||||
|
const choose = [...this.action.choose];
|
||||||
|
choose.push({ conditions: [], sequence: [] });
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.action, choose },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _removeOption(ev: CustomEvent) {
|
||||||
|
const index = (ev.currentTarget as any).idx;
|
||||||
|
const choose = [...this.action.choose];
|
||||||
|
choose.splice(index, 1);
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.action, choose },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _defaultChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const value = ev.detail.value as Action[];
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.action,
|
||||||
|
default: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
ha-card {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.add-card mwc-button {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
mwc-icon-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-action-choose": HaChooseAction;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
DeviceAction,
|
DeviceAction,
|
||||||
deviceAutomationsEqual,
|
deviceAutomationsEqual,
|
||||||
fetchDeviceActionCapabilities,
|
fetchDeviceActionCapabilities,
|
||||||
|
DeviceCapabilities,
|
||||||
} from "../../../../../data/device_automation";
|
} from "../../../../../data/device_automation";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
|
||||||
@@ -21,11 +22,11 @@ import { HomeAssistant } from "../../../../../types";
|
|||||||
export class HaDeviceAction extends LitElement {
|
export class HaDeviceAction extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public action!: DeviceAction;
|
@property({ type: Object }) public action!: DeviceAction;
|
||||||
|
|
||||||
@internalProperty() private _deviceId?: string;
|
@internalProperty() private _deviceId?: string;
|
||||||
|
|
||||||
@internalProperty() private _capabilities?;
|
@internalProperty() private _capabilities?: DeviceCapabilities;
|
||||||
|
|
||||||
private _origAction?: DeviceAction;
|
private _origAction?: DeviceAction;
|
||||||
|
|
||||||
@@ -37,20 +38,20 @@ export class HaDeviceAction extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _extraFieldsData = memoizeOne((capabilities, action: DeviceAction) =>
|
private _extraFieldsData = memoizeOne(
|
||||||
capabilities && capabilities.extra_fields
|
(action: DeviceAction, capabilities: DeviceCapabilities) => {
|
||||||
? capabilities.extra_fields.map((item) => {
|
const extraFieldsData: { [key: string]: any } = {};
|
||||||
return { [item.name]: action[item.name] };
|
capabilities.extra_fields.forEach((item) => {
|
||||||
})
|
if (action[item.name] !== undefined) {
|
||||||
: undefined
|
extraFieldsData![item.name] = action[item.name];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return extraFieldsData;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const deviceId = this._deviceId || this.action.device_id;
|
const deviceId = this._deviceId || this.action.device_id;
|
||||||
const extraFieldsData = this._extraFieldsData(
|
|
||||||
this._capabilities,
|
|
||||||
this.action
|
|
||||||
);
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-device-picker
|
<ha-device-picker
|
||||||
@@ -70,10 +71,10 @@ export class HaDeviceAction extends LitElement {
|
|||||||
"ui.panel.config.automation.editor.actions.type.device_id.action"
|
"ui.panel.config.automation.editor.actions.type.device_id.action"
|
||||||
)}
|
)}
|
||||||
></ha-device-action-picker>
|
></ha-device-action-picker>
|
||||||
${extraFieldsData
|
${this._capabilities?.extra_fields
|
||||||
? html`
|
? html`
|
||||||
<ha-form
|
<ha-form
|
||||||
.data=${Object.assign({}, ...extraFieldsData)}
|
.data=${this._extraFieldsData(this.action, this._capabilities)}
|
||||||
.schema=${this._capabilities.extra_fields}
|
.schema=${this._capabilities.extra_fields}
|
||||||
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
||||||
this.hass.localize
|
this.hass.localize
|
||||||
@@ -105,7 +106,7 @@ export class HaDeviceAction extends LitElement {
|
|||||||
private async _getCapabilities() {
|
private async _getCapabilities() {
|
||||||
this._capabilities = this.action.domain
|
this._capabilities = this.action.domain
|
||||||
? await fetchDeviceActionCapabilities(this.hass, this.action)
|
? await fetchDeviceActionCapabilities(this.hass, this.action)
|
||||||
: null;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _devicePicked(ev) {
|
private _devicePicked(ev) {
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import { customElement, LitElement, property, CSSResult } from "lit-element";
|
||||||
|
import { html } from "lit-html";
|
||||||
|
import {
|
||||||
|
RepeatAction,
|
||||||
|
Action,
|
||||||
|
CountRepeat,
|
||||||
|
WhileRepeat,
|
||||||
|
UntilRepeat,
|
||||||
|
} from "../../../../../data/script";
|
||||||
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import { ActionElement } from "../ha-automation-action-row";
|
||||||
|
import "../../condition/ha-automation-condition-editor";
|
||||||
|
import type { PaperListboxElement } from "@polymer/paper-listbox";
|
||||||
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import "../ha-automation-action";
|
||||||
|
import { Condition } from "../../../../lovelace/common/validate-condition";
|
||||||
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
|
|
||||||
|
const OPTIONS = ["count", "while", "until"];
|
||||||
|
|
||||||
|
const getType = (action) => {
|
||||||
|
return OPTIONS.find((option) => option in action);
|
||||||
|
};
|
||||||
|
|
||||||
|
@customElement("ha-automation-action-repeat")
|
||||||
|
export class HaRepeatAction extends LitElement implements ActionElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public action!: RepeatAction;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return { repeat: { count: 2, sequence: [] } };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const action = this.action.repeat;
|
||||||
|
|
||||||
|
const type = getType(action);
|
||||||
|
const selected = type ? OPTIONS.indexOf(type) : -1;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<paper-dropdown-menu-light
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.repeat.type_select"
|
||||||
|
)}
|
||||||
|
no-animations
|
||||||
|
>
|
||||||
|
<paper-listbox
|
||||||
|
slot="dropdown-content"
|
||||||
|
.selected=${selected}
|
||||||
|
@iron-select=${this._typeChanged}
|
||||||
|
>
|
||||||
|
${OPTIONS.map(
|
||||||
|
(opt) => html`
|
||||||
|
<paper-item .action=${opt}>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.actions.type.repeat.type.${opt}.label`
|
||||||
|
)}
|
||||||
|
</paper-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-dropdown-menu-light>
|
||||||
|
${type === "count"
|
||||||
|
? html`<paper-input
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.repeat.type.count.label"
|
||||||
|
)}
|
||||||
|
name="count"
|
||||||
|
.value=${(action as CountRepeat).count || "0"}
|
||||||
|
@value-changed=${this._countChanged}
|
||||||
|
></paper-input>`
|
||||||
|
: ""}
|
||||||
|
${type === "while"
|
||||||
|
? html` <h3>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.actions.type.repeat.type.while.conditions`
|
||||||
|
)}:
|
||||||
|
</h3>
|
||||||
|
<ha-automation-condition
|
||||||
|
.conditions=${(action as WhileRepeat).while || []}
|
||||||
|
.hass=${this.hass}
|
||||||
|
@value-changed=${this._conditionChanged}
|
||||||
|
></ha-automation-condition>`
|
||||||
|
: ""}
|
||||||
|
${type === "until"
|
||||||
|
? html` <h3>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.actions.type.repeat.type.until.conditions`
|
||||||
|
)}:
|
||||||
|
</h3>
|
||||||
|
<ha-automation-condition
|
||||||
|
.conditions=${(action as UntilRepeat).until || []}
|
||||||
|
.hass=${this.hass}
|
||||||
|
@value-changed=${this._conditionChanged}
|
||||||
|
></ha-automation-condition>`
|
||||||
|
: ""}
|
||||||
|
<h3>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.repeat.sequence"
|
||||||
|
)}:
|
||||||
|
</h3>
|
||||||
|
<ha-automation-action
|
||||||
|
.actions=${action.sequence}
|
||||||
|
@value-changed=${this._actionChanged}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-automation-action>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _typeChanged(ev: CustomEvent) {
|
||||||
|
const type = ((ev.target as PaperListboxElement)?.selectedItem as any)
|
||||||
|
?.action;
|
||||||
|
|
||||||
|
if (!type || type === getType(this.action.repeat)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = type === "count" ? 2 : [];
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
repeat: { [type]: value, sequence: this.action.repeat.sequence },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _conditionChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const value = ev.detail.value as Condition[];
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
repeat: {
|
||||||
|
...this.action.repeat,
|
||||||
|
[getType(this.action.repeat)!]: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _actionChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const value = ev.detail.value as Action[];
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
repeat: {
|
||||||
|
...this.action.repeat,
|
||||||
|
sequence: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _countChanged(ev: CustomEvent): void {
|
||||||
|
const newVal = ev.detail.value;
|
||||||
|
if ((this.action.repeat as CountRepeat).count === newVal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
repeat: {
|
||||||
|
...this.action.repeat,
|
||||||
|
count: newVal,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return haStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-action-repeat": HaRepeatAction;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,18 +13,20 @@ import {
|
|||||||
deviceAutomationsEqual,
|
deviceAutomationsEqual,
|
||||||
DeviceCondition,
|
DeviceCondition,
|
||||||
fetchDeviceConditionCapabilities,
|
fetchDeviceConditionCapabilities,
|
||||||
|
DeviceCapabilities,
|
||||||
} from "../../../../../data/device_automation";
|
} from "../../../../../data/device_automation";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
|
||||||
@customElement("ha-automation-condition-device")
|
@customElement("ha-automation-condition-device")
|
||||||
export class HaDeviceCondition extends LitElement {
|
export class HaDeviceCondition extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public condition!: DeviceCondition;
|
@property({ type: Object }) public condition!: DeviceCondition;
|
||||||
|
|
||||||
@internalProperty() private _deviceId?: string;
|
@internalProperty() private _deviceId?: string;
|
||||||
|
|
||||||
@internalProperty() private _capabilities?;
|
@internalProperty() private _capabilities?: DeviceCapabilities;
|
||||||
|
|
||||||
private _origCondition?: DeviceCondition;
|
private _origCondition?: DeviceCondition;
|
||||||
|
|
||||||
@@ -36,16 +38,21 @@ export class HaDeviceCondition extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _extraFieldsData = memoizeOne(
|
||||||
|
(condition: DeviceCondition, capabilities: DeviceCapabilities) => {
|
||||||
|
const extraFieldsData: { [key: string]: any } = {};
|
||||||
|
capabilities.extra_fields.forEach((item) => {
|
||||||
|
if (condition[item.name] !== undefined) {
|
||||||
|
extraFieldsData![item.name] = condition[item.name];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return extraFieldsData;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const deviceId = this._deviceId || this.condition.device_id;
|
const deviceId = this._deviceId || this.condition.device_id;
|
||||||
|
|
||||||
const extraFieldsData =
|
|
||||||
this._capabilities && this._capabilities.extra_fields
|
|
||||||
? this._capabilities.extra_fields.map((item) => {
|
|
||||||
return { [item.name]: this.condition[item.name] };
|
|
||||||
})
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-device-picker
|
<ha-device-picker
|
||||||
.value=${deviceId}
|
.value=${deviceId}
|
||||||
@@ -64,10 +71,10 @@ export class HaDeviceCondition extends LitElement {
|
|||||||
"ui.panel.config.automation.editor.conditions.type.device.condition"
|
"ui.panel.config.automation.editor.conditions.type.device.condition"
|
||||||
)}
|
)}
|
||||||
></ha-device-condition-picker>
|
></ha-device-condition-picker>
|
||||||
${extraFieldsData
|
${this._capabilities?.extra_fields
|
||||||
? html`
|
? html`
|
||||||
<ha-form
|
<ha-form
|
||||||
.data=${Object.assign({}, ...extraFieldsData)}
|
.data=${this._extraFieldsData(this.condition, this._capabilities)}
|
||||||
.schema=${this._capabilities.extra_fields}
|
.schema=${this._capabilities.extra_fields}
|
||||||
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
||||||
this.hass.localize
|
this.hass.localize
|
||||||
@@ -103,7 +110,7 @@ export class HaDeviceCondition extends LitElement {
|
|||||||
|
|
||||||
this._capabilities = condition.domain
|
this._capabilities = condition.domain
|
||||||
? await fetchDeviceConditionCapabilities(this.hass, condition)
|
? await fetchDeviceConditionCapabilities(this.hass, condition)
|
||||||
: null;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _devicePicked(ev) {
|
private _devicePicked(ev) {
|
||||||
@@ -141,3 +148,9 @@ export class HaDeviceCondition extends LitElement {
|
|||||||
) || schema.name;
|
) || schema.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-condition-device": HaDeviceCondition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import "@material/mwc-fab";
|
import "@material/mwc-fab";
|
||||||
@@ -46,6 +44,7 @@ import "./trigger/ha-automation-trigger";
|
|||||||
import { HaDeviceTrigger } from "./trigger/types/ha-automation-trigger-device";
|
import { HaDeviceTrigger } from "./trigger/types/ha-automation-trigger-device";
|
||||||
import { mdiContentSave } from "@mdi/js";
|
import { mdiContentSave } from "@mdi/js";
|
||||||
import { PaperListboxElement } from "@polymer/paper-listbox";
|
import { PaperListboxElement } from "@polymer/paper-listbox";
|
||||||
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
|
||||||
const MODES = ["single", "restart", "queued", "parallel"];
|
const MODES = ["single", "restart", "queued", "parallel"];
|
||||||
const MODES_MAX = ["queued", "parallel"];
|
const MODES_MAX = ["queued", "parallel"];
|
||||||
@@ -72,7 +71,7 @@ export class HaAutomationEditor extends LitElement {
|
|||||||
|
|
||||||
@internalProperty() private _config?: AutomationConfig;
|
@internalProperty() private _config?: AutomationConfig;
|
||||||
|
|
||||||
@internalProperty() private _dirty?: boolean;
|
@internalProperty() private _dirty = false;
|
||||||
|
|
||||||
@internalProperty() private _errors?: string;
|
@internalProperty() private _errors?: string;
|
||||||
|
|
||||||
@@ -312,16 +311,10 @@ export class HaAutomationEditor extends LitElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<mwc-fab
|
<mwc-fab
|
||||||
?is-wide="${this.isWide}"
|
slot="fab"
|
||||||
?narrow="${this.narrow}"
|
class=${classMap({ dirty: this._dirty })}
|
||||||
?dirty="${this._dirty}"
|
.title=${this.hass.localize("ui.panel.config.automation.editor.save")}
|
||||||
.title="${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.save"
|
|
||||||
)}"
|
|
||||||
@click=${this._saveAutomation}
|
@click=${this._saveAutomation}
|
||||||
class="${classMap({
|
|
||||||
rtl: computeRTL(this.hass),
|
|
||||||
})}"
|
|
||||||
>
|
>
|
||||||
<ha-svg-icon slot="icon" path=${mdiContentSave}></ha-svg-icon>
|
<ha-svg-icon slot="icon" path=${mdiContentSave}></ha-svg-icon>
|
||||||
</mwc-fab>
|
</mwc-fab>
|
||||||
@@ -411,6 +404,10 @@ export class HaAutomationEditor extends LitElement {
|
|||||||
const mode = ((ev.target as PaperListboxElement)?.selectedItem as any)
|
const mode = ((ev.target as PaperListboxElement)?.selectedItem as any)
|
||||||
?.mode;
|
?.mode;
|
||||||
|
|
||||||
|
if (mode === this._config!.mode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._config = { ...this._config!, mode };
|
this._config = { ...this._config!, mode };
|
||||||
if (!MODES_MAX.includes(mode)) {
|
if (!MODES_MAX.includes(mode)) {
|
||||||
delete this._config.max;
|
delete this._config.max;
|
||||||
@@ -538,35 +535,12 @@ export class HaAutomationEditor extends LitElement {
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
mwc-fab {
|
mwc-fab {
|
||||||
position: fixed;
|
position: relative;
|
||||||
bottom: 16px;
|
bottom: calc(-80px - env(safe-area-inset-bottom));
|
||||||
right: 16px;
|
transition: bottom 0.3s;
|
||||||
z-index: 3;
|
|
||||||
margin-bottom: -80px;
|
|
||||||
transition: margin-bottom 0.3s;
|
|
||||||
}
|
}
|
||||||
|
mwc-fab.dirty {
|
||||||
mwc-fab[is-wide] {
|
bottom: 0;
|
||||||
bottom: 24px;
|
|
||||||
right: 24px;
|
|
||||||
}
|
|
||||||
mwc-fab[narrow] {
|
|
||||||
bottom: 84px;
|
|
||||||
margin-bottom: -140px;
|
|
||||||
}
|
|
||||||
mwc-fab[dirty] {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
mwc-fab.rtl {
|
|
||||||
right: auto;
|
|
||||||
left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
mwc-fab[is-wide].rtl {
|
|
||||||
bottom: 24px;
|
|
||||||
right: auto;
|
|
||||||
left: 24px;
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import {
|
import {
|
||||||
css,
|
|
||||||
CSSResultArray,
|
|
||||||
customElement,
|
customElement,
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
property,
|
property,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { ifDefined } from "lit-html/directives/if-defined";
|
import { ifDefined } from "lit-html/directives/if-defined";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@@ -15,7 +14,6 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
|||||||
import { formatDateTime } from "../../../common/datetime/format_date_time";
|
import { formatDateTime } from "../../../common/datetime/format_date_time";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
|
||||||
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||||
import "../../../components/entity/ha-entity-toggle";
|
import "../../../components/entity/ha-entity-toggle";
|
||||||
import "@material/mwc-fab";
|
import "@material/mwc-fab";
|
||||||
@@ -169,19 +167,16 @@ class HaAutomationPicker extends LitElement {
|
|||||||
)}
|
)}
|
||||||
hasFab
|
hasFab
|
||||||
>
|
>
|
||||||
|
<mwc-fab
|
||||||
|
slot="fab"
|
||||||
|
title=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.picker.add_automation"
|
||||||
|
)}
|
||||||
|
@click=${this._createNew}
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
|
||||||
|
</mwc-fab>
|
||||||
</hass-tabs-subpage-data-table>
|
</hass-tabs-subpage-data-table>
|
||||||
<mwc-fab
|
|
||||||
slot="fab"
|
|
||||||
?is-wide=${this.isWide}
|
|
||||||
?narrow=${this.narrow}
|
|
||||||
title=${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.picker.add_automation"
|
|
||||||
)}
|
|
||||||
?rtl=${computeRTL(this.hass)}
|
|
||||||
@click=${this._createNew}
|
|
||||||
>
|
|
||||||
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
|
|
||||||
</mwc-fab>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,37 +202,8 @@ class HaAutomationPicker extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultArray {
|
static get styles(): CSSResult {
|
||||||
return [
|
return haStyle;
|
||||||
haStyle,
|
|
||||||
css`
|
|
||||||
mwc-fab {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 16px;
|
|
||||||
right: 16px;
|
|
||||||
z-index: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
mwc-fab[is-wide] {
|
|
||||||
bottom: 24px;
|
|
||||||
right: 24px;
|
|
||||||
}
|
|
||||||
mwc-fab[narrow] {
|
|
||||||
bottom: 84px;
|
|
||||||
}
|
|
||||||
mwc-fab[rtl] {
|
|
||||||
right: auto;
|
|
||||||
left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
mwc-fab[rtl][is-wide] {
|
|
||||||
bottom: 24px;
|
|
||||||
right: auto;
|
|
||||||
left: 24px;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,18 +13,20 @@ import {
|
|||||||
deviceAutomationsEqual,
|
deviceAutomationsEqual,
|
||||||
DeviceTrigger,
|
DeviceTrigger,
|
||||||
fetchDeviceTriggerCapabilities,
|
fetchDeviceTriggerCapabilities,
|
||||||
|
DeviceCapabilities,
|
||||||
} from "../../../../../data/device_automation";
|
} from "../../../../../data/device_automation";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-device")
|
@customElement("ha-automation-trigger-device")
|
||||||
export class HaDeviceTrigger extends LitElement {
|
export class HaDeviceTrigger extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public trigger!: DeviceTrigger;
|
@property({ type: Object }) public trigger!: DeviceTrigger;
|
||||||
|
|
||||||
@internalProperty() private _deviceId?: string;
|
@internalProperty() private _deviceId?: string;
|
||||||
|
|
||||||
@internalProperty() private _capabilities?;
|
@internalProperty() private _capabilities?: DeviceCapabilities;
|
||||||
|
|
||||||
private _origTrigger?: DeviceTrigger;
|
private _origTrigger?: DeviceTrigger;
|
||||||
|
|
||||||
@@ -36,16 +38,21 @@ export class HaDeviceTrigger extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _extraFieldsData = memoizeOne(
|
||||||
|
(trigger: DeviceTrigger, capabilities: DeviceCapabilities) => {
|
||||||
|
const extraFieldsData: { [key: string]: any } = {};
|
||||||
|
capabilities.extra_fields.forEach((item) => {
|
||||||
|
if (trigger[item.name] !== undefined) {
|
||||||
|
extraFieldsData![item.name] = trigger[item.name];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return extraFieldsData;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const deviceId = this._deviceId || this.trigger.device_id;
|
const deviceId = this._deviceId || this.trigger.device_id;
|
||||||
|
|
||||||
const extraFieldsData =
|
|
||||||
this._capabilities && this._capabilities.extra_fields
|
|
||||||
? this._capabilities.extra_fields.map((item) => {
|
|
||||||
return { [item.name]: this.trigger[item.name] };
|
|
||||||
})
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-device-picker
|
<ha-device-picker
|
||||||
.value=${deviceId}
|
.value=${deviceId}
|
||||||
@@ -64,10 +71,10 @@ export class HaDeviceTrigger extends LitElement {
|
|||||||
"ui.panel.config.automation.editor.triggers.type.device.trigger"
|
"ui.panel.config.automation.editor.triggers.type.device.trigger"
|
||||||
)}
|
)}
|
||||||
></ha-device-trigger-picker>
|
></ha-device-trigger-picker>
|
||||||
${extraFieldsData
|
${this._capabilities?.extra_fields
|
||||||
? html`
|
? html`
|
||||||
<ha-form
|
<ha-form
|
||||||
.data=${Object.assign({}, ...extraFieldsData)}
|
.data=${this._extraFieldsData(this.trigger, this._capabilities)}
|
||||||
.schema=${this._capabilities.extra_fields}
|
.schema=${this._capabilities.extra_fields}
|
||||||
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
||||||
this.hass.localize
|
this.hass.localize
|
||||||
@@ -100,7 +107,7 @@ export class HaDeviceTrigger extends LitElement {
|
|||||||
|
|
||||||
this._capabilities = trigger.domain
|
this._capabilities = trigger.domain
|
||||||
? await fetchDeviceTriggerCapabilities(this.hass, trigger)
|
? await fetchDeviceTriggerCapabilities(this.hass, trigger)
|
||||||
: null;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _devicePicked(ev) {
|
private _devicePicked(ev) {
|
||||||
@@ -120,7 +127,7 @@ export class HaDeviceTrigger extends LitElement {
|
|||||||
fireEvent(this, "value-changed", { value: trigger });
|
fireEvent(this, "value-changed", { value: trigger });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _extraFieldsChanged(ev) {
|
private _extraFieldsChanged(ev: CustomEvent) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: {
|
value: {
|
||||||
@@ -138,3 +145,9 @@ export class HaDeviceTrigger extends LitElement {
|
|||||||
) || schema.name;
|
) || schema.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-trigger-device": HaDeviceTrigger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class ConfigCoreForm extends LitElement {
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<ha-location-editor
|
<ha-location-editor
|
||||||
class="flex"
|
class="flex"
|
||||||
|
.hass=${this.hass}
|
||||||
.location=${this._locationValue}
|
.location=${this._locationValue}
|
||||||
@change=${this._locationChanged}
|
@change=${this._locationChanged}
|
||||||
></ha-location-editor>
|
></ha-location-editor>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
import "../../../layouts/ha-app-layout";
|
||||||
import "@polymer/app-layout/app-header/app-header";
|
import "@polymer/app-layout/app-header/app-header";
|
||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||||
import {
|
import {
|
||||||
@@ -127,7 +127,7 @@ class HaConfigDashboard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<app-header-layout>
|
<ha-app-layout>
|
||||||
<app-header fixed slot="header">
|
<app-header fixed slot="header">
|
||||||
<app-toolbar>
|
<app-toolbar>
|
||||||
<ha-menu-button
|
<ha-menu-button
|
||||||
@@ -138,7 +138,7 @@ class HaConfigDashboard extends LitElement {
|
|||||||
</app-header>
|
</app-header>
|
||||||
|
|
||||||
${content}
|
${content}
|
||||||
</app-header-layout>
|
</ha-app-layout>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import {
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
internalProperty,
|
||||||
|
TemplateResult,
|
||||||
|
css,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit-element";
|
||||||
|
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||||
|
import { haStyle } from "../../../../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../../../../types";
|
||||||
|
import { OZWDevice, fetchOZWNodeStatus } from "../../../../../../data/ozw";
|
||||||
|
|
||||||
|
@customElement("ha-device-info-ozw")
|
||||||
|
export class HaDeviceInfoOzw extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public device!: DeviceRegistryEntry;
|
||||||
|
|
||||||
|
@internalProperty() private _ozwDevice?: OZWDevice;
|
||||||
|
|
||||||
|
protected updated(changedProperties: PropertyValues) {
|
||||||
|
if (changedProperties.has("device")) {
|
||||||
|
this._fetchNodeDetails(this.device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _fetchNodeDetails(device) {
|
||||||
|
const ozwIdentifier = device.identifiers.find(
|
||||||
|
(identifier) => identifier[0] === "ozw"
|
||||||
|
);
|
||||||
|
if (!ozwIdentifier) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const identifiers = ozwIdentifier[1].split(".");
|
||||||
|
this._ozwDevice = await fetchOZWNodeStatus(
|
||||||
|
this.hass,
|
||||||
|
identifiers[0],
|
||||||
|
identifiers[1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._ozwDevice) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<h4>
|
||||||
|
${this.hass.localize("ui.panel.config.ozw.device_info.zwave_info")}
|
||||||
|
</h4>
|
||||||
|
<div>
|
||||||
|
${this.hass.localize("ui.panel.config.ozw.common.node_id")}:
|
||||||
|
${this._ozwDevice.node_id}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
${this.hass.localize("ui.panel.config.ozw.device_info.stage")}:
|
||||||
|
${this._ozwDevice.node_query_stage}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
${this.hass.localize("ui.panel.config.ozw.common.ozw_instance")}:
|
||||||
|
${this._ozwDevice.ozw_instance}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
${this.hass.localize("ui.panel.config.ozw.device_info.node_failed")}:
|
||||||
|
${this._ozwDevice.is_failed
|
||||||
|
? this.hass.localize("ui.common.yes")
|
||||||
|
: this.hass.localize("ui.common.no")}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
h4 {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
word-break: break-all;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ import { ifDefined } from "lit-html/directives/if-defined";
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import { createValidEntityId } from "../../../common/entity/valid_entity_id";
|
|
||||||
import { compare } from "../../../common/string/compare";
|
import { compare } from "../../../common/string/compare";
|
||||||
import "../../../components/entity/ha-battery-icon";
|
import "../../../components/entity/ha-battery-icon";
|
||||||
import "../../../components/ha-icon-next";
|
import "../../../components/ha-icon-next";
|
||||||
@@ -44,6 +43,7 @@ import { configSections } from "../ha-panel-config";
|
|||||||
import "./device-detail/ha-device-entities-card";
|
import "./device-detail/ha-device-entities-card";
|
||||||
import "./device-detail/ha-device-info-card";
|
import "./device-detail/ha-device-info-card";
|
||||||
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
|
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
|
||||||
|
import { slugify } from "../../../common/string/slugify";
|
||||||
|
|
||||||
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
||||||
stateName?: string | null;
|
stateName?: string | null;
|
||||||
@@ -501,6 +501,15 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
if (integrations.includes("ozw")) {
|
||||||
|
import("./device-detail/integration-elements/ozw/ha-device-info-ozw");
|
||||||
|
templates.push(html`
|
||||||
|
<ha-device-info-ozw
|
||||||
|
.hass=${this.hass}
|
||||||
|
.device=${device}
|
||||||
|
></ha-device-info-ozw>
|
||||||
|
`);
|
||||||
|
}
|
||||||
if (integrations.includes("zha")) {
|
if (integrations.includes("zha")) {
|
||||||
import("./device-detail/integration-elements/zha/ha-device-actions-zha");
|
import("./device-detail/integration-elements/zha/ha-device-actions-zha");
|
||||||
import("./device-detail/integration-elements/zha/ha-device-info-zha");
|
import("./device-detail/integration-elements/zha/ha-device-info-zha");
|
||||||
@@ -556,11 +565,11 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (renameEntityid) {
|
if (renameEntityid) {
|
||||||
const oldSearch = createValidEntityId(oldDeviceName);
|
const oldSearch = slugify(oldDeviceName);
|
||||||
if (entity.entity_id.includes(oldSearch)) {
|
if (entity.entity_id.includes(oldSearch)) {
|
||||||
newEntityId = entity.entity_id.replace(
|
newEntityId = entity.entity_id.replace(
|
||||||
oldSearch,
|
oldSearch,
|
||||||
createValidEntityId(newDeviceName)
|
slugify(newDeviceName)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -571,7 +580,6 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
|
|
||||||
return updateEntityRegistryEntry(this.hass!, entity.entity_id, {
|
return updateEntityRegistryEntry(this.hass!, entity.entity_id, {
|
||||||
name: newName || name,
|
name: newName || name,
|
||||||
disabled_by: entity.disabled_by,
|
|
||||||
new_entity_id: newEntityId || entity.entity_id,
|
new_entity_id: newEntityId || entity.entity_id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ export class DialogEntityEditor extends LitElement {
|
|||||||
css`
|
css`
|
||||||
ha-header-bar {
|
ha-header-bar {
|
||||||
--mdc-theme-on-primary: var(--primary-text-color);
|
--mdc-theme-on-primary: var(--primary-text-color);
|
||||||
--mdc-theme-primary: var(--card-background-color);
|
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +247,7 @@ export class DialogEntityEditor extends LitElement {
|
|||||||
ha-dialog {
|
ha-dialog {
|
||||||
--dialog-content-position: static;
|
--dialog-content-position: static;
|
||||||
--dialog-content-padding: 0;
|
--dialog-content-padding: 0;
|
||||||
|
--dialog-z-index: 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (min-width: 451px) and (min-height: 501px) {
|
@media all and (min-width: 451px) and (min-height: 501px) {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export class EntityRegistrySettings extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
${!stateObj
|
${!stateObj
|
||||||
? html`
|
? html`
|
||||||
<div>
|
<div class="container">
|
||||||
${this.hass!.localize(
|
${this.hass!.localize(
|
||||||
"ui.dialogs.entity_registry.editor.unavailable"
|
"ui.dialogs.entity_registry.editor.unavailable"
|
||||||
)}
|
)}
|
||||||
@@ -81,7 +81,7 @@ export class EntityRegistrySettings extends LitElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||||
<div class="form">
|
<div class="form container">
|
||||||
<paper-input
|
<paper-input
|
||||||
.value=${this._name}
|
.value=${this._name}
|
||||||
@value-changed=${this._nameChanged}
|
@value-changed=${this._nameChanged}
|
||||||
@@ -227,8 +227,10 @@ export class EntityRegistrySettings extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.form {
|
.container {
|
||||||
padding: 20px 24px;
|
padding: 20px 24px;
|
||||||
|
}
|
||||||
|
.form {
|
||||||
margin-bottom: 53px;
|
margin-bottom: 53px;
|
||||||
}
|
}
|
||||||
.buttons {
|
.buttons {
|
||||||
@@ -241,6 +243,7 @@ export class EntityRegistrySettings extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
padding-bottom: max(env(safe-area-inset-bottom), 8px);
|
||||||
background-color: var(--mdc-theme-surface, #fff);
|
background-color: var(--mdc-theme-surface, #fff);
|
||||||
}
|
}
|
||||||
ha-switch {
|
ha-switch {
|
||||||
|
|||||||
@@ -182,9 +182,9 @@ class HaEntityConfig extends PolymerElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldEntityId = oldEntities[this.selectedEntity].entity_id;
|
const oldEntityId = oldEntities[this.selectedEntity].entity_id;
|
||||||
|
|
||||||
var newIndex = entities.findIndex(function (ent) {
|
const newIndex = entities.findIndex(function (ent) {
|
||||||
return ent.entity_id === oldEntityId;
|
return ent.entity_id === oldEntityId;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -198,12 +198,12 @@ class HaEntityConfig extends PolymerElement {
|
|||||||
|
|
||||||
entityChanged(index) {
|
entityChanged(index) {
|
||||||
if (!this.entities || !this.formEl) return;
|
if (!this.entities || !this.formEl) return;
|
||||||
var entity = this.entities[index];
|
const entity = this.entities[index];
|
||||||
if (!entity) return;
|
if (!entity) return;
|
||||||
|
|
||||||
this.formState = "loading";
|
this.formState = "loading";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
var el = this;
|
const el = this;
|
||||||
this.formEl.loadEntity(entity).then(function () {
|
this.formEl.loadEntity(entity).then(function () {
|
||||||
el.formState = "editing";
|
el.formState = "editing";
|
||||||
});
|
});
|
||||||
@@ -212,7 +212,7 @@ class HaEntityConfig extends PolymerElement {
|
|||||||
saveEntity() {
|
saveEntity() {
|
||||||
this.formState = "saving";
|
this.formState = "saving";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
var el = this;
|
const el = this;
|
||||||
this.formEl.saveEntity().then(function () {
|
this.formEl.saveEntity().then(function () {
|
||||||
el.formState = "editing";
|
el.formState = "editing";
|
||||||
});
|
});
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user