mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-03 16:59:42 +00:00
Compare commits
68 Commits
20200930.0
...
auth-onboa
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3717e94814 | ||
![]() |
993d73c359 | ||
![]() |
97ca0b818e | ||
![]() |
44166f76d4 | ||
![]() |
557d6d37a1 | ||
![]() |
d3ad56a307 | ||
![]() |
0641022ec5 | ||
![]() |
80c7a8473a | ||
![]() |
d9a954ca91 | ||
![]() |
c219f64322 | ||
![]() |
f7a9ecff21 | ||
![]() |
2b3126ae04 | ||
![]() |
934c227545 | ||
![]() |
cc0515c217 | ||
![]() |
55ba75f2bc | ||
![]() |
c220228566 | ||
![]() |
26b476ab3c | ||
![]() |
b8a67d530f | ||
![]() |
b08c96d2db | ||
![]() |
4773c39a57 | ||
![]() |
892843b290 | ||
![]() |
733244531e | ||
![]() |
66633273e2 | ||
![]() |
0405adcd16 | ||
![]() |
426a7ac8dd | ||
![]() |
3bf6205ff7 | ||
![]() |
c7f4986e61 | ||
![]() |
0f0a3fdaf7 | ||
![]() |
7d6911b140 | ||
![]() |
b8777539d7 | ||
![]() |
5fc0eaef1a | ||
![]() |
113718c3c1 | ||
![]() |
701bea6cae | ||
![]() |
8d516ed12a | ||
![]() |
667c5744f2 | ||
![]() |
80b7c840e2 | ||
![]() |
919c86796f | ||
![]() |
c90c88ecbf | ||
![]() |
d9ba0e2c46 | ||
![]() |
45b2fc590b | ||
![]() |
17ffdb0247 | ||
![]() |
c2fba15fc6 | ||
![]() |
5937be695f | ||
![]() |
a076fcde84 | ||
![]() |
ede9931903 | ||
![]() |
722e01608c | ||
![]() |
af926370d6 | ||
![]() |
5971aee02e | ||
![]() |
3940606167 | ||
![]() |
da9faccada | ||
![]() |
7e708b3bf7 | ||
![]() |
05630c9896 | ||
![]() |
369c56db73 | ||
![]() |
9873459169 | ||
![]() |
7776b3766b | ||
![]() |
29c9004654 | ||
![]() |
601c909004 | ||
![]() |
72aa9a3b62 | ||
![]() |
2ecf7bca97 | ||
![]() |
cbdfaccdb2 | ||
![]() |
93d1b9a2d5 | ||
![]() |
bfb5ee794e | ||
![]() |
9ae8bd238b | ||
![]() |
0171f3aec7 | ||
![]() |
2c827bab9a | ||
![]() |
ec920093d4 | ||
![]() |
4289ff6652 | ||
![]() |
cd32ef60da |
@@ -76,12 +76,13 @@
|
||||
"default-case": 0,
|
||||
"wc/no-self-class": 0,
|
||||
"@typescript-eslint/camelcase": 0,
|
||||
"@typescript-eslint/ban-ts-ignore": 0,
|
||||
"@typescript-eslint/ban-ts-comment": 0,
|
||||
"@typescript-eslint/no-use-before-define": 0,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/no-unused-vars": 0,
|
||||
"@typescript-eslint/explicit-function-return-type": 0
|
||||
"@typescript-eslint/explicit-function-return-type": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types": 0
|
||||
},
|
||||
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
|
||||
"processor": "disable/disable"
|
||||
|
@@ -52,7 +52,10 @@ module.exports.terserOptions = (latestBuild) => ({
|
||||
module.exports.babelOptions = ({ latestBuild }) => ({
|
||||
babelrc: false,
|
||||
presets: [
|
||||
!latestBuild && [require("@babel/preset-env").default, { modules: false }],
|
||||
!latestBuild && [
|
||||
require("@babel/preset-env").default,
|
||||
{ modules: false, useBuiltIns: "entry", corejs: 3 },
|
||||
],
|
||||
require("@babel/preset-typescript").default,
|
||||
].filter(Boolean),
|
||||
plugins: [
|
||||
@@ -62,6 +65,7 @@ module.exports.babelOptions = ({ latestBuild }) => ({
|
||||
{ loose: true, useBuiltIns: true },
|
||||
],
|
||||
// Only support the syntax, Webpack will handle it.
|
||||
"@babel/plugin-syntax-import-meta",
|
||||
"@babel/syntax-dynamic-import",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
|
@@ -2,7 +2,6 @@ const webpack = require("webpack");
|
||||
const path = require("path");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const ManifestPlugin = require("webpack-manifest-plugin");
|
||||
const WorkerPlugin = require("worker-plugin");
|
||||
const paths = require("./paths.js");
|
||||
const bundle = require("./bundle");
|
||||
|
||||
@@ -55,7 +54,6 @@ const createWebpackConfig = ({
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new WorkerPlugin(),
|
||||
new ManifestPlugin({
|
||||
// Only include the JS of entrypoints
|
||||
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
|
||||
|
@@ -45,6 +45,8 @@ const showMediaPlayer = () => {
|
||||
style.innerHTML = `
|
||||
body {
|
||||
--logo-image: url('https://www.home-assistant.io/images/home-assistant-logo.svg');
|
||||
--logo-repeat: no-repeat;
|
||||
--playback-logo-image: url('https://www.home-assistant.io/images/home-assistant-logo.svg');
|
||||
--theme-hue: 200;
|
||||
--progress-color: #03a9f4;
|
||||
--splash-image: url('https://home-assistant.io/images/cast/splash.png');
|
||||
|
@@ -49,7 +49,6 @@ class HcLovelace extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.lovelace=${lovelace}
|
||||
.index=${index}
|
||||
columns="2"
|
||||
></hui-view>
|
||||
`;
|
||||
}
|
||||
@@ -67,7 +66,7 @@ class HcLovelace extends LitElement {
|
||||
|
||||
if (configBackground) {
|
||||
(this.shadowRoot!.querySelector(
|
||||
"hui-view, hui-panel-view"
|
||||
"hui-view"
|
||||
) as HTMLElement)!.style.setProperty(
|
||||
"--lovelace-background",
|
||||
configBackground
|
||||
|
@@ -98,14 +98,14 @@ class HassioAddonStore extends LitElement {
|
||||
main-page
|
||||
.tabs=${supervisorTabs}
|
||||
>
|
||||
<span slot="header">Add-on store</span>
|
||||
<span slot="header">Add-on Store</span>
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
slot="toolbar-icon"
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<mwc-icon-button slot="trigger" alt="menu">
|
||||
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item>
|
||||
Repositories
|
||||
|
@@ -39,7 +39,7 @@ class HassioAddonConfig extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) private _configHasChanged = false;
|
||||
|
||||
@query("ha-yaml-editor") private _editor!: HaYamlEditor;
|
||||
@query("ha-yaml-editor", true) private _editor!: HaYamlEditor;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const editor = this._editor;
|
||||
|
@@ -69,7 +69,7 @@ const STAGE_ICON = {
|
||||
const PERMIS_DESC = {
|
||||
stage: {
|
||||
title: "Add-on Stage",
|
||||
description: `Add-ons can have one of three stages:\n\n<ha-svg-icon path='${STAGE_ICON.stable}'></ha-svg-icon> **Stable**: These are add-ons ready to be used in production.\n\n<ha-svg-icon path='${STAGE_ICON.experimental}'></ha-svg-icon> **Experimental**: These may contain bugs, and may be unfinished.\n\n<ha-svg-icon path='${STAGE_ICON.deprecated}'></ha-svg-icon> **Deprecated**: These add-ons will no longer receive any updates.`,
|
||||
description: `Add-ons can have one of three stages:\n\n<ha-svg-icon .path='${STAGE_ICON.stable}'></ha-svg-icon> **Stable**: These are add-ons ready to be used in production.\n\n<ha-svg-icon .path='${STAGE_ICON.experimental}'></ha-svg-icon> **Experimental**: These may contain bugs, and may be unfinished.\n\n<ha-svg-icon .path='${STAGE_ICON.deprecated}'></ha-svg-icon> **Deprecated**: These add-ons will no longer receive any updates.`,
|
||||
},
|
||||
rating: {
|
||||
title: "Add-on Security Rating",
|
||||
@@ -202,14 +202,14 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-svg-icon
|
||||
title="Add-on is running"
|
||||
class="running"
|
||||
path=${mdiCircle}
|
||||
.path=${mdiCircle}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: html`
|
||||
<ha-svg-icon
|
||||
title="Add-on is stopped"
|
||||
class="stopped"
|
||||
path=${mdiCircle}
|
||||
.path=${mdiCircle}
|
||||
></ha-svg-icon>
|
||||
`}
|
||||
`
|
||||
@@ -283,7 +283,7 @@ class HassioAddonInfo extends LitElement {
|
||||
label="host"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon path=${mdiNetwork}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiNetwork}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
@@ -295,7 +295,7 @@ class HassioAddonInfo extends LitElement {
|
||||
label="hardware"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon path=${mdiChip}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiChip}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
@@ -307,7 +307,7 @@ class HassioAddonInfo extends LitElement {
|
||||
label="hass"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
@@ -319,7 +319,7 @@ class HassioAddonInfo extends LitElement {
|
||||
label="hassio"
|
||||
.description=${this.addon.hassio_role}
|
||||
>
|
||||
<ha-svg-icon path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
@@ -331,7 +331,7 @@ class HassioAddonInfo extends LitElement {
|
||||
label="docker"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon path=${mdiDocker}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDocker}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
@@ -343,7 +343,7 @@ class HassioAddonInfo extends LitElement {
|
||||
label="host pid"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon path=${mdiPound}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiPound}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
@@ -356,7 +356,7 @@ class HassioAddonInfo extends LitElement {
|
||||
label="apparmor"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon path=${mdiShield}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiShield}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
@@ -368,7 +368,7 @@ class HassioAddonInfo extends LitElement {
|
||||
label="auth"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon path=${mdiKey}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiKey}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
@@ -381,7 +381,7 @@ class HassioAddonInfo extends LitElement {
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon
|
||||
path=${mdiCursorDefaultClickOutline}
|
||||
.path=${mdiCursorDefaultClickOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
@@ -798,10 +798,10 @@ class HassioAddonInfo extends LitElement {
|
||||
);
|
||||
if (!validate.data.valid) {
|
||||
await showConfirmationDialog(this, {
|
||||
title: "Failed to start addon - configruation validation faled!",
|
||||
title: "Failed to start addon - configuration validation failed!",
|
||||
text: validate.data.message.split(" Got ")[0],
|
||||
confirm: () => this._openConfiguration(),
|
||||
confirmText: "Go to configruation",
|
||||
confirmText: "Go to configuration",
|
||||
dismissText: "Cancel",
|
||||
});
|
||||
button.progress = false;
|
||||
|
@@ -39,7 +39,8 @@ import type { HomeAssistant } from "../../../../src/types";
|
||||
import { HassioNetworkDialogParams } from "./show-dialog-network";
|
||||
|
||||
@customElement("dialog-hassio-network")
|
||||
export class DialogHassioNetwork extends LitElement implements HassDialog {
|
||||
export class DialogHassioNetwork extends LitElement
|
||||
implements HassDialog<HassioNetworkDialogParams> {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _prosessing = false;
|
||||
|
@@ -39,7 +39,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
@property({ attribute: false })
|
||||
private _dialogParams?: HassioRepositoryDialogParams;
|
||||
|
||||
@query("#repository_input") private _optionInput?: PaperInputElement;
|
||||
@query("#repository_input", true) private _optionInput?: PaperInputElement;
|
||||
|
||||
@internalProperty() private _opened = false;
|
||||
|
||||
@@ -91,7 +91,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
title="Remove"
|
||||
@click=${this._removeRepository}
|
||||
>
|
||||
<ha-svg-icon path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</paper-item>
|
||||
`;
|
||||
|
@@ -19,7 +19,7 @@ import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload"
|
||||
|
||||
@customElement("dialog-hassio-snapshot-upload")
|
||||
export class DialogHassioSnapshotUpload extends LitElement
|
||||
implements HassDialog {
|
||||
implements HassDialog<HassioSnapshotUploadDialogParams> {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _params?: HassioSnapshotUploadDialogParams;
|
||||
|
@@ -196,7 +196,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
@click=${this._downloadClicked}
|
||||
slot="primaryAction"
|
||||
>
|
||||
<ha-svg-icon path=${mdiDownload} class="icon"></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDownload} class="icon"></ha-svg-icon>
|
||||
Download Snapshot
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
@@ -205,7 +205,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
@click=${this._partialRestoreClicked}
|
||||
slot="secondaryAction"
|
||||
>
|
||||
<ha-svg-icon path=${mdiHistory} class="icon"></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon>
|
||||
Restore Selected
|
||||
</mwc-button>
|
||||
${this._snapshot.type === "full"
|
||||
@@ -214,7 +214,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
@click=${this._fullRestoreClicked}
|
||||
slot="secondaryAction"
|
||||
>
|
||||
<ha-svg-icon path=${mdiHistory} class="icon"></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon>
|
||||
Wipe & restore
|
||||
</mwc-button>
|
||||
`
|
||||
@@ -224,7 +224,10 @@ class HassioSnapshotDialog extends LitElement {
|
||||
@click=${this._deleteClicked}
|
||||
slot="secondaryAction"
|
||||
>
|
||||
<ha-svg-icon path=${mdiDelete} class="icon warning"></ha-svg-icon>
|
||||
<ha-svg-icon
|
||||
.path=${mdiDelete}
|
||||
class="icon warning"
|
||||
></ha-svg-icon>
|
||||
<span class="warning">Delete Snapshot</span>
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
@@ -440,6 +443,19 @@ class HassioSnapshotDialog extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.location.href.includes("ui.nabu.casa")) {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
title: "Potential slow download",
|
||||
text:
|
||||
"Downloading snapshots over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?",
|
||||
confirmText: "continue",
|
||||
dismissText: "cancel",
|
||||
});
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const name = this._computeName.replace(/[^a-z0-9]+/gi, "_");
|
||||
const a = document.createElement("a");
|
||||
a.href = signedPath.path;
|
||||
|
@@ -8,7 +8,7 @@ export const supervisorTabs: PageNavigation[] = [
|
||||
iconPath: mdiViewDashboard,
|
||||
},
|
||||
{
|
||||
name: "Add-on store",
|
||||
name: "Add-on Store",
|
||||
path: `/hassio/store`,
|
||||
iconPath: mdiStore,
|
||||
},
|
||||
|
@@ -57,7 +57,7 @@ class HassioIngressView extends LitElement {
|
||||
aria-label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
|
||||
@click=${this._toggleMenu}
|
||||
>
|
||||
<ha-svg-icon path=${mdiMenu}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiMenu}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<div class="main-title">${this._addon.name}</div>
|
||||
</div>
|
||||
|
@@ -117,7 +117,7 @@ class HassioSnapshots extends LitElement {
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<mwc-icon-button slot="trigger" alt="menu">
|
||||
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item>
|
||||
Reload
|
||||
@@ -131,7 +131,7 @@ class HassioSnapshots extends LitElement {
|
||||
|
||||
<div class="content">
|
||||
<h1>
|
||||
Create snapshot
|
||||
Create Snapshot
|
||||
</h1>
|
||||
<p class="description">
|
||||
Snapshots allow you to easily backup and restore all data of your
|
||||
@@ -219,7 +219,7 @@ class HassioSnapshots extends LitElement {
|
||||
</ha-card>
|
||||
</div>
|
||||
|
||||
<h1>Available snapshots</h1>
|
||||
<h1>Available Snapshots</h1>
|
||||
<div class="card-group">
|
||||
${this._snapshots === undefined
|
||||
? undefined
|
||||
|
@@ -87,7 +87,7 @@ class HassioHostInfo extends LitElement {
|
||||
${this.hostInfo.features.includes("network")
|
||||
? html` <ha-settings-row>
|
||||
<span slot="heading">
|
||||
IP address
|
||||
IP Address
|
||||
</span>
|
||||
<span slot="description">
|
||||
${primaryIpAddress}
|
||||
@@ -103,7 +103,7 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Operating system
|
||||
Operating System
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.hostInfo.operating_system}
|
||||
@@ -221,7 +221,7 @@ class HassioHostInfo extends LitElement {
|
||||
});
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to get Hardware list",
|
||||
title: "Failed to get hardware list",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -324,7 +324,7 @@ class HassioHostInfo extends LitElement {
|
||||
private async _changeHostnameClicked(): Promise<void> {
|
||||
const curHostname: string = this.hostInfo.hostname;
|
||||
const hostname = await showPromptDialog(this, {
|
||||
title: "Change hostname",
|
||||
title: "Change Hostname",
|
||||
inputLabel: "Please enter a new hostname:",
|
||||
inputType: "string",
|
||||
defaultValue: curHostname,
|
||||
|
@@ -51,7 +51,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Newest version
|
||||
Newest Version
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisorInfo.version_latest}
|
||||
@@ -98,7 +98,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
${this.supervisorInfo?.supported
|
||||
? html` <ha-settings-row three-line>
|
||||
<span slot="heading">
|
||||
Share diagnostics
|
||||
Share Diagnostics
|
||||
</span>
|
||||
<div slot="description" class="diagnostics-description">
|
||||
Share crash reports and diagnostic information.
|
||||
@@ -135,7 +135,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
@click=${this._supervisorReload}
|
||||
title="Reload parts of the supervisor."
|
||||
title="Reload parts of the supervisor"
|
||||
>
|
||||
Reload
|
||||
</ha-progress-button>
|
||||
@@ -212,7 +212,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Update supervisor",
|
||||
title: "Update Supervisor",
|
||||
text: `Are you sure you want to update supervisor to version ${this.supervisorInfo.version_latest}?`,
|
||||
confirmText: "update",
|
||||
dismissText: "cancel",
|
||||
|
@@ -76,7 +76,7 @@ class HassioSupervisorLog extends LitElement {
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<paper-dropdown-menu
|
||||
label="Log provider"
|
||||
label="Log Provider"
|
||||
@iron-select=${this._setLogProvider}
|
||||
>
|
||||
<paper-listbox
|
||||
|
@@ -25,6 +25,7 @@ import {
|
||||
getValueInPercentage,
|
||||
roundWithOneDecimal,
|
||||
} from "../../../src/util/calculate";
|
||||
import { bytesToString } from "../../../src/util/bytes-to-string";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-system-metrics")
|
||||
@@ -38,35 +39,47 @@ class HassioSystemMetrics extends LitElement {
|
||||
@internalProperty() private _coreMetrics?: HassioStats;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
const usedSpace = this._getUsedSpace(this.hostInfo);
|
||||
const metrics = [
|
||||
{
|
||||
description: "Core CPU usage",
|
||||
description: "Core CPU Usage",
|
||||
value: this._coreMetrics?.cpu_percent,
|
||||
},
|
||||
{
|
||||
description: "Core RAM usage",
|
||||
description: "Core RAM Usage",
|
||||
value: this._coreMetrics?.memory_percent,
|
||||
tooltip: `${bytesToString(
|
||||
this._coreMetrics?.memory_usage
|
||||
)}/${bytesToString(this._coreMetrics?.memory_limit)}`,
|
||||
},
|
||||
{
|
||||
description: "Supervisor CPU usage",
|
||||
description: "Supervisor CPU Usage",
|
||||
value: this._supervisorMetrics?.cpu_percent,
|
||||
},
|
||||
{
|
||||
description: "Supervisor RAM usage",
|
||||
description: "Supervisor RAM Usage",
|
||||
value: this._supervisorMetrics?.memory_percent,
|
||||
tooltip: `${bytesToString(
|
||||
this._supervisorMetrics?.memory_usage
|
||||
)}/${bytesToString(this._supervisorMetrics?.memory_limit)}`,
|
||||
},
|
||||
{
|
||||
description: "Used space",
|
||||
value: usedSpace,
|
||||
description: "Used Space",
|
||||
value: this._getUsedSpace(this.hostInfo),
|
||||
tooltip: `${
|
||||
this.hostInfo.disk_used
|
||||
} GB/${this.hostInfo.disk_total} GB`,
|
||||
},
|
||||
];
|
||||
|
||||
return html`
|
||||
<ha-card header="System metrics">
|
||||
<ha-card header="System Metrics">
|
||||
<div class="card-content">
|
||||
${metrics.map((metric) =>
|
||||
this._renderMetric(metric.description, metric.value ?? 0)
|
||||
this._renderMetric(
|
||||
metric.description,
|
||||
metric.value ?? 0,
|
||||
metric.tooltip
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -77,13 +90,17 @@ class HassioSystemMetrics extends LitElement {
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private _renderMetric(description: string, value: number): TemplateResult {
|
||||
private _renderMetric(
|
||||
description: string,
|
||||
value: number,
|
||||
tooltip?: string
|
||||
): TemplateResult {
|
||||
const roundedValue = roundWithOneDecimal(value);
|
||||
return html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
${description}
|
||||
</span>
|
||||
<div slot="description">
|
||||
<div slot="description" title="${tooltip ?? ""}">
|
||||
<span class="value">
|
||||
${roundedValue}%
|
||||
</span>
|
||||
@@ -155,6 +172,7 @@ class HassioSystemMetrics extends LitElement {
|
||||
}
|
||||
.value {
|
||||
width: 42px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
85
package.json
85
package.json
@@ -28,22 +28,22 @@
|
||||
"@fullcalendar/daygrid": "5.1.0",
|
||||
"@fullcalendar/interaction": "5.1.0",
|
||||
"@fullcalendar/list": "5.1.0",
|
||||
"@material/chips": "=8.0.0-canary.096a7a066.0",
|
||||
"@material/circular-progress": "=8.0.0-canary.a78ceb112.0",
|
||||
"@material/mwc-button": "^0.18.0",
|
||||
"@material/mwc-checkbox": "^0.18.0",
|
||||
"@material/mwc-dialog": "^0.18.0",
|
||||
"@material/mwc-fab": "^0.18.0",
|
||||
"@material/mwc-formfield": "^0.18.0",
|
||||
"@material/mwc-icon-button": "^0.18.0",
|
||||
"@material/mwc-list": "^0.18.0",
|
||||
"@material/mwc-menu": "^0.18.0",
|
||||
"@material/mwc-radio": "^0.18.0",
|
||||
"@material/mwc-ripple": "^0.18.0",
|
||||
"@material/mwc-switch": "^0.18.0",
|
||||
"@material/mwc-tab": "^0.18.0",
|
||||
"@material/mwc-tab-bar": "^0.18.0",
|
||||
"@material/top-app-bar": "=8.0.0-canary.096a7a066.0",
|
||||
"@material/chips": "=8.0.0-canary.774dcfc8e.0",
|
||||
"@material/mwc-button": "^0.19.0",
|
||||
"@material/mwc-checkbox": "^0.19.0",
|
||||
"@material/mwc-circular-progress": "^0.19.0",
|
||||
"@material/mwc-dialog": "^0.19.0",
|
||||
"@material/mwc-fab": "^0.19.0",
|
||||
"@material/mwc-formfield": "^0.19.0",
|
||||
"@material/mwc-icon-button": "^0.19.0",
|
||||
"@material/mwc-list": "^0.19.0",
|
||||
"@material/mwc-menu": "^0.19.0",
|
||||
"@material/mwc-radio": "^0.19.0",
|
||||
"@material/mwc-ripple": "^0.19.0",
|
||||
"@material/mwc-switch": "^0.19.0",
|
||||
"@material/mwc-tab": "^0.19.0",
|
||||
"@material/mwc-tab-bar": "^0.19.0",
|
||||
"@material/top-app-bar": "=8.0.0-canary.774dcfc8e.0",
|
||||
"@mdi/js": "5.6.55",
|
||||
"@mdi/svg": "5.6.55",
|
||||
"@polymer/app-layout": "^3.0.2",
|
||||
@@ -88,11 +88,11 @@
|
||||
"chartjs-chart-timeline": "^0.3.0",
|
||||
"codemirror": "^5.49.0",
|
||||
"comlink": "^4.3.0",
|
||||
"core-js": "^3.6.5",
|
||||
"cpx": "^1.5.0",
|
||||
"cropperjs": "^1.5.7",
|
||||
"deep-clone-simple": "^1.1.1",
|
||||
"deep-freeze": "^0.0.1",
|
||||
"es6-object-assign": "^1.1.0",
|
||||
"fecha": "^4.2.0",
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
@@ -103,8 +103,8 @@
|
||||
"js-yaml": "^3.13.1",
|
||||
"leaflet": "^1.4.0",
|
||||
"leaflet-draw": "^1.0.4",
|
||||
"lit-element": "^2.3.1",
|
||||
"lit-html": "^1.2.1",
|
||||
"lit-element": "^2.4.0",
|
||||
"lit-html": "^1.3.0",
|
||||
"lit-virtualizer": "^0.4.2",
|
||||
"marked": "^1.1.1",
|
||||
"mdn-polyfills": "^5.16.0",
|
||||
@@ -112,6 +112,7 @@
|
||||
"node-vibrant": "^3.1.5",
|
||||
"proxy-polyfill": "^0.3.1",
|
||||
"punycode": "^2.1.1",
|
||||
"qrcode": "^1.4.4",
|
||||
"regenerator-runtime": "^0.13.2",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
@@ -128,16 +129,17 @@
|
||||
"xss": "^1.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/plugin-external-helpers": "^7.8.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-decorators": "^7.8.3",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.9.5",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
||||
"@babel/core": "^7.11.6",
|
||||
"@babel/plugin-external-helpers": "^7.10.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.10.5",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.11.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.11.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/preset-env": "^7.9.5",
|
||||
"@babel/preset-typescript": "^7.9.0",
|
||||
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
||||
"@babel/preset-env": "^7.11.5",
|
||||
"@babel/preset-typescript": "^7.10.4",
|
||||
"@rollup/plugin-commonjs": "^11.1.0",
|
||||
"@rollup/plugin-json": "^4.0.3",
|
||||
"@rollup/plugin-node-resolve": "^7.1.3",
|
||||
@@ -154,8 +156,8 @@
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/resize-observer-browser": "^0.1.3",
|
||||
"@types/webspeechapi": "^0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "^2.28.0",
|
||||
"@typescript-eslint/parser": "^2.28.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.4.0",
|
||||
"@typescript-eslint/parser": "^4.4.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"chai": "^4.2.0",
|
||||
"del": "^4.0.0",
|
||||
@@ -180,7 +182,7 @@
|
||||
"html-minifier": "^4.0.0",
|
||||
"husky": "^1.3.1",
|
||||
"lint-staged": "^8.1.5",
|
||||
"lit-analyzer": "^1.2.0",
|
||||
"lit-analyzer": "^1.2.1",
|
||||
"lodash.template": "^4.5.0",
|
||||
"magic-string": "^0.25.7",
|
||||
"map-stream": "^0.0.7",
|
||||
@@ -201,29 +203,24 @@
|
||||
"source-map-url": "^0.4.0",
|
||||
"systemjs": "^6.3.2",
|
||||
"terser-webpack-plugin": "^3.0.6",
|
||||
"ts-lit-plugin": "^1.2.0",
|
||||
"ts-lit-plugin": "^1.2.1",
|
||||
"ts-mocha": "^7.0.0",
|
||||
"typescript": "^3.8.3",
|
||||
"typescript": "^4.0.3",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"webpack": "^4.40.2",
|
||||
"webpack-cli": "^3.3.9",
|
||||
"webpack": "5.0.0-rc.3",
|
||||
"webpack-cli": "4.0.0-rc.0",
|
||||
"webpack-dev-server": "^3.10.3",
|
||||
"webpack-manifest-plugin": "^2.0.4",
|
||||
"workbox-build": "^5.1.3",
|
||||
"worker-plugin": "^4.0.3"
|
||||
"webpack-manifest-plugin": "3.0.0-rc.0",
|
||||
"workbox-build": "^5.1.3"
|
||||
},
|
||||
"_comment": "Polymer fixed to 3.1 because 3.2 throws on logbook page",
|
||||
"_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569",
|
||||
"resolutions": {
|
||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"lit-html": "1.2.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"
|
||||
"lit-html": "1.3.0",
|
||||
"lit-element": "2.4.0"
|
||||
},
|
||||
"main": "src/home-assistant.js",
|
||||
"husky": {
|
||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200930.0",
|
||||
version="20201001.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@@ -200,7 +200,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
|
||||
private _redirect(authCode: string) {
|
||||
// OAuth 2: 3.1.2 we need to retain query component of a redirect URI
|
||||
let url = this.redirectUri!!;
|
||||
let url = this.redirectUri!;
|
||||
if (!url.includes("?")) {
|
||||
url += "?";
|
||||
} else if (!url.endsWith("&")) {
|
||||
|
@@ -38,13 +38,11 @@ export default function relativeTime(
|
||||
roundedDelta = Math.round(delta);
|
||||
}
|
||||
|
||||
const timeDesc = localize(
|
||||
`ui.components.relative_time.duration.${unit}`,
|
||||
return localize(
|
||||
options.includeTense === false
|
||||
? `ui.components.relative_time.duration.${unit}`
|
||||
: `ui.components.relative_time.${tense}_duration.${unit}`,
|
||||
"count",
|
||||
roundedDelta
|
||||
);
|
||||
|
||||
return options.includeTense === false
|
||||
? timeDesc
|
||||
: localize(`ui.components.relative_time.${tense}`, "time", timeDesc);
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
||||
case "garage_door":
|
||||
return is_off ? "hass:garage" : "hass:garage-open";
|
||||
case "power":
|
||||
return is_off ? "hass:power-off" : "hass:power-on";
|
||||
return is_off ? "hass:power-plug-off" : "hass:power-plug";
|
||||
case "gas":
|
||||
case "problem":
|
||||
case "safety":
|
||||
|
@@ -52,7 +52,7 @@ class SearchInput extends LitElement {
|
||||
.noLabelFloat=${this.noLabelFloat}
|
||||
>
|
||||
<ha-svg-icon
|
||||
path=${mdiMagnify}
|
||||
.path=${mdiMagnify}
|
||||
slot="prefix"
|
||||
class="prefix"
|
||||
></ha-svg-icon>
|
||||
@@ -65,7 +65,7 @@ class SearchInput extends LitElement {
|
||||
alt="Clear"
|
||||
title="Clear"
|
||||
>
|
||||
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`}
|
||||
</paper-input>
|
||||
|
29
src/common/string/sequence_matching.ts
Normal file
29
src/common/string/sequence_matching.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Determine whether a sequence of letters exists in another string,
|
||||
* in that order, allowing for skipping. Ex: "chdr" exists in "chandelier")
|
||||
*
|
||||
* filter => sequence of letters
|
||||
* word => Word to check for sequence
|
||||
*
|
||||
* return true if word contains sequence. Otherwise false.
|
||||
*/
|
||||
export const fuzzySequentialMatch = (filter: string, word: string) => {
|
||||
if (filter === "") {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (let i = 0; i <= filter.length; i++) {
|
||||
const pos = word.indexOf(filter[0]);
|
||||
|
||||
if (pos < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const newWord = word.substring(pos + 1);
|
||||
const newFilter = filter.substring(1);
|
||||
|
||||
return fuzzySequentialMatch(newFilter, newWord);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
@@ -5,7 +5,7 @@
|
||||
// N milliseconds. If `immediate` is passed, trigger the function on the
|
||||
// leading edge, instead of the trailing.
|
||||
// eslint-disable-next-line: ban-types
|
||||
export const debounce = <T extends Function>(
|
||||
export const debounce = <T extends (...args) => unknown>(
|
||||
func: T,
|
||||
wait,
|
||||
immediate = false
|
||||
|
@@ -2,7 +2,10 @@ import { Connection, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
|
||||
export const subscribeOne = async <T>(
|
||||
conn: Connection,
|
||||
subscribe: (conn: Connection, onChange: (items: T) => void) => UnsubscribeFunc
|
||||
subscribe: (
|
||||
conn2: Connection,
|
||||
onChange: (items: T) => void
|
||||
) => UnsubscribeFunc
|
||||
) =>
|
||||
new Promise<T>((resolve) => {
|
||||
const unsub = subscribe(conn, (items) => {
|
||||
|
@@ -5,7 +5,7 @@
|
||||
// as much as it can, without ever going more than once per `wait` duration;
|
||||
// but if you'd like to disable the execution on the leading edge, pass
|
||||
// `false for leading`. To disable execution on the trailing edge, ditto.
|
||||
export const throttle = <T extends Function>(
|
||||
export const throttle = <T extends (...args) => unknown>(
|
||||
func: T,
|
||||
wait: number,
|
||||
leading = true,
|
||||
|
@@ -21,7 +21,7 @@ class HaProgressButton extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public raised = false;
|
||||
|
||||
@query("mwc-button") private _button?: Button;
|
||||
@query("mwc-button", true) private _button?: Button;
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
|
@@ -73,13 +73,17 @@ export interface DataTableColumnData extends DataTableSortColumnData {
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
type ClonedDataTableColumnData = Omit<DataTableColumnData, "title"> & {
|
||||
title?: string;
|
||||
};
|
||||
|
||||
export interface DataTableRowData {
|
||||
[key: string]: any;
|
||||
selectable?: boolean;
|
||||
}
|
||||
|
||||
export interface SortableColumnContainer {
|
||||
[key: string]: DataTableSortColumnData;
|
||||
[key: string]: ClonedDataTableColumnData;
|
||||
}
|
||||
|
||||
@customElement("ha-data-table")
|
||||
@@ -101,6 +105,9 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
@property({ type: String }) public searchLabel?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-label-float" })
|
||||
public noLabelFloat? = false;
|
||||
|
||||
@property({ type: String }) public filter = "";
|
||||
|
||||
@internalProperty() private _filterable = false;
|
||||
@@ -113,9 +120,9 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
@internalProperty() private _filteredData: DataTableRowData[] = [];
|
||||
|
||||
@query("slot[name='header']") private _header!: HTMLSlotElement;
|
||||
@internalProperty() private _headerHeight = 0;
|
||||
|
||||
@query(".mdc-data-table__table") private _table!: HTMLDivElement;
|
||||
@query("slot[name='header']") private _header!: HTMLSlotElement;
|
||||
|
||||
private _checkableRowsCount?: number;
|
||||
|
||||
@@ -166,11 +173,13 @@ export class HaDataTable extends LitElement {
|
||||
}
|
||||
|
||||
const clonedColumns: DataTableColumnContainer = deepClone(this.columns);
|
||||
Object.values(clonedColumns).forEach((column: DataTableColumnData) => {
|
||||
delete column.title;
|
||||
delete column.type;
|
||||
delete column.template;
|
||||
});
|
||||
Object.values(clonedColumns).forEach(
|
||||
(column: ClonedDataTableColumnData) => {
|
||||
delete column.title;
|
||||
delete column.type;
|
||||
delete column.template;
|
||||
}
|
||||
);
|
||||
|
||||
this._sortColumns = clonedColumns;
|
||||
}
|
||||
@@ -206,6 +215,7 @@ export class HaDataTable extends LitElement {
|
||||
<search-input
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.label=${this.searchLabel}
|
||||
.noLabelFloat=${this.noLabelFloat}
|
||||
></search-input>
|
||||
</div>
|
||||
`
|
||||
@@ -220,7 +230,7 @@ export class HaDataTable extends LitElement {
|
||||
style=${styleMap({
|
||||
height: this.autoHeight
|
||||
? `${(this._filteredData.length || 1) * 53 + 57}px`
|
||||
: `calc(100% - ${this._header?.clientHeight}px)`,
|
||||
: `calc(100% - ${this._headerHeight}px)`,
|
||||
})}
|
||||
>
|
||||
<div class="mdc-data-table__header-row" role="row">
|
||||
@@ -523,7 +533,7 @@ export class HaDataTable extends LitElement {
|
||||
return;
|
||||
}
|
||||
await this.updateComplete;
|
||||
this._table.style.height = `calc(100% - ${this._header.clientHeight}px)`;
|
||||
this._headerHeight = this._header.clientHeight;
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
|
@@ -16,7 +16,7 @@ export const filterData = async (
|
||||
filter: FilterDataParamTypes[2]
|
||||
): Promise<ReturnType<FilterDataType>> => {
|
||||
if (!worker) {
|
||||
worker = wrap(new Worker("./sort_filter_worker", { type: "module" }));
|
||||
worker = wrap(new Worker(new URL("./sort_filter_worker", import.meta.url)));
|
||||
}
|
||||
|
||||
return await worker.filterData(data, columns, filter);
|
||||
@@ -29,7 +29,7 @@ export const sortData = async (
|
||||
sortColumn: SortDataParamTypes[3]
|
||||
): Promise<ReturnType<SortDataType>> => {
|
||||
if (!worker) {
|
||||
worker = wrap(new Worker("./sort_filter_worker", { type: "module" }));
|
||||
worker = wrap(new Worker(new URL("./sort_filter_worker", import.meta.url)));
|
||||
}
|
||||
|
||||
return await worker.sortData(data, columns, direction, sortColumn);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import Vue from "vue";
|
||||
import wrap from "@vue/web-component-wrapper";
|
||||
import DateRangePicker from "vue2-daterange-picker";
|
||||
// @ts-ignore
|
||||
import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { Constructor } from "../types";
|
||||
@@ -35,7 +35,6 @@ const Component = Vue.extend({
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
// @ts-ignore
|
||||
return createElement(DateRangePicker, {
|
||||
props: {
|
||||
"time-picker": true,
|
||||
@@ -52,7 +51,6 @@ const Component = Vue.extend({
|
||||
endDate: this.endDate,
|
||||
},
|
||||
callback: (value) => {
|
||||
// @ts-ignore
|
||||
fireEvent(this.$el as HTMLElement, "change", value);
|
||||
},
|
||||
expression: "dateRange",
|
||||
|
@@ -71,14 +71,24 @@ class HaChartBase extends mixinBehaviors(
|
||||
margin: 5px 0 0 0;
|
||||
width: 100%;
|
||||
}
|
||||
.chartTooltip ul {
|
||||
margin: 0 3px;
|
||||
}
|
||||
.chartTooltip li {
|
||||
display: block;
|
||||
white-space: pre-line;
|
||||
}
|
||||
.chartTooltip li::first-line {
|
||||
line-height: 0;
|
||||
}
|
||||
.chartTooltip .title {
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
.chartTooltip .beforeBody {
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
}
|
||||
.chartLegend li {
|
||||
display: inline-block;
|
||||
padding: 0 6px;
|
||||
@@ -133,6 +143,9 @@ class HaChartBase extends mixinBehaviors(
|
||||
style$="opacity:[[tooltip.opacity]]; top:[[tooltip.top]]; left:[[tooltip.left]]; padding:[[tooltip.yPadding]]px [[tooltip.xPadding]]px"
|
||||
>
|
||||
<div class="title">[[tooltip.title]]</div>
|
||||
<template is="dom-if" if="[[tooltip.beforeBody]]">
|
||||
<div class="beforeBody">[[tooltip.beforeBody]]</div>
|
||||
</template>
|
||||
<div>
|
||||
<ul>
|
||||
<template is="dom-repeat" items="[[tooltip.lines]]">
|
||||
@@ -264,6 +277,10 @@ class HaChartBase extends mixinBehaviors(
|
||||
const title = tooltip.title ? tooltip.title[0] || "" : "";
|
||||
this.set(["tooltip", "title"], title);
|
||||
|
||||
if (tooltip.beforeBody) {
|
||||
this.set(["tooltip", "beforeBody"], tooltip.beforeBody.join("\n"));
|
||||
}
|
||||
|
||||
const bodyLines = tooltip.body.map((n) => n.lines);
|
||||
|
||||
// Set Text
|
||||
|
@@ -55,7 +55,7 @@ class HaEntityAttributePicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) private _opened = false;
|
||||
|
||||
@query("vaadin-combo-box-light") private _comboBox!: HTMLElement;
|
||||
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues) {
|
||||
return !(!changedProps.has("_opened") && this._opened);
|
||||
|
@@ -97,7 +97,7 @@ export class HaEntityPicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) private _opened = false;
|
||||
|
||||
@query("vaadin-combo-box-light") private _comboBox!: HTMLElement;
|
||||
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
|
||||
|
||||
private _initedStates = false;
|
||||
|
||||
@@ -215,6 +215,7 @@ export class HaEntityPicker extends LitElement {
|
||||
slot="suffix"
|
||||
class="clear-button"
|
||||
icon="hass:close"
|
||||
tabindex="-1"
|
||||
@click=${this._clearValue}
|
||||
no-ripple
|
||||
>
|
||||
@@ -230,6 +231,7 @@ export class HaEntityPicker extends LitElement {
|
||||
slot="suffix"
|
||||
class="toggle-button"
|
||||
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
|
||||
tabindex="-1"
|
||||
>
|
||||
Toggle
|
||||
</ha-icon-button>
|
||||
|
@@ -110,7 +110,9 @@ export class HaStateLabelBadge extends LitElement {
|
||||
return null;
|
||||
case "sensor":
|
||||
default:
|
||||
return state.state === UNKNOWN
|
||||
return state.attributes.device_class === "moon__phase"
|
||||
? null
|
||||
: state.state === UNKNOWN
|
||||
? "-"
|
||||
: state.attributes.unit_of_measurement
|
||||
? state.state
|
||||
@@ -162,7 +164,9 @@ export class HaStateLabelBadge extends LitElement {
|
||||
? "hass:timer-outline"
|
||||
: "hass:timer-off-outline";
|
||||
default:
|
||||
return null;
|
||||
return state?.attributes.device_class === "moon__phase"
|
||||
? stateIcon(state)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -33,7 +33,9 @@ class HaAttributes extends LitElement {
|
||||
).map(
|
||||
(attribute) => html`
|
||||
<div class="data-entry">
|
||||
<div class="key">${attribute.replace(/_/g, " ")}</div>
|
||||
<div class="key">
|
||||
${attribute.replace(/_/g, " ").replace("id", "ID")}
|
||||
</div>
|
||||
<div class="value">
|
||||
${this.formatAttribute(attribute)}
|
||||
</div>
|
||||
@@ -62,6 +64,9 @@ class HaAttributes extends LitElement {
|
||||
max-width: 200px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.key:first-letter {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.attribution {
|
||||
color: var(--secondary-text-color);
|
||||
text-align: right;
|
||||
|
@@ -34,8 +34,8 @@ export class HaBar extends LitElement {
|
||||
return svg`
|
||||
<svg>
|
||||
<g>
|
||||
<rect></rect>
|
||||
<rect width="${valuePrecentage}%"></rect>
|
||||
<rect/>
|
||||
<rect width="${valuePrecentage}%"/>
|
||||
</g>
|
||||
</svg>
|
||||
`;
|
||||
@@ -43,6 +43,9 @@ export class HaBar extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
rect {
|
||||
height: 100%;
|
||||
}
|
||||
rect:first-child {
|
||||
width: 100%;
|
||||
fill: var(--ha-bar-background-color, var(--secondary-background-color));
|
||||
|
@@ -23,7 +23,7 @@ export class HaButtonMenu extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@query("mwc-menu") private _menu?: Menu;
|
||||
@query("mwc-menu", true) private _menu?: Menu;
|
||||
|
||||
public get items() {
|
||||
return this._menu?.items;
|
||||
@@ -62,6 +62,9 @@ export class HaButtonMenu extends LitElement {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
::slotted([disabled]) {
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -1,20 +1,9 @@
|
||||
// @ts-ignore
|
||||
import progressStyles from "@material/circular-progress/dist/mdc.circular-progress.min.css";
|
||||
import {
|
||||
css,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
svg,
|
||||
SVGTemplateResult,
|
||||
TemplateResult,
|
||||
unsafeCSS,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { customElement, property } from "lit-element";
|
||||
import { CircularProgress } from "@material/mwc-circular-progress";
|
||||
|
||||
@customElement("ha-circular-progress")
|
||||
export class HaCircularProgress extends LitElement {
|
||||
// @ts-ignore
|
||||
export class HaCircularProgress extends CircularProgress {
|
||||
@property({ type: Boolean })
|
||||
public active = false;
|
||||
|
||||
@@ -24,65 +13,31 @@ export class HaCircularProgress extends LitElement {
|
||||
@property()
|
||||
public size: "small" | "medium" | "large" = "medium";
|
||||
|
||||
protected render(): TemplateResult {
|
||||
let indeterminatePart: SVGTemplateResult;
|
||||
|
||||
if (this.size === "small") {
|
||||
indeterminatePart = svg`
|
||||
<svg class="mdc-circular-progress__indeterminate-circle-graphic" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="8.75" stroke-dasharray="54.978" stroke-dashoffset="27.489"/>
|
||||
</svg>`;
|
||||
} else if (this.size === "large") {
|
||||
indeterminatePart = svg`
|
||||
<svg class="mdc-circular-progress__indeterminate-circle-graphic" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="24" cy="24" r="18" stroke-dasharray="113.097" stroke-dashoffset="56.549"/>
|
||||
</svg>`;
|
||||
} else {
|
||||
// medium
|
||||
indeterminatePart = svg`
|
||||
<svg class="mdc-circular-progress__indeterminate-circle-graphic" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="16" cy="16" r="12.5" stroke-dasharray="78.54" stroke-dashoffset="39.27"/>
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
// ignoring prettier as it will introduce unwanted whitespace
|
||||
// We have not implemented the determinate support of mdc circular progress.
|
||||
// prettier-ignore
|
||||
return html`
|
||||
<div
|
||||
class="mdc-circular-progress ${classMap({
|
||||
"mdc-circular-progress--indeterminate": this.active,
|
||||
[`mdc-circular-progress--${this.size}`]: true,
|
||||
})}"
|
||||
role="progressbar"
|
||||
aria-label=${this.alt}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="1"
|
||||
>
|
||||
<div class="mdc-circular-progress__indeterminate-container">
|
||||
<div class="mdc-circular-progress__spinner-layer">
|
||||
<div class="mdc-circular-progress__circle-clipper mdc-circular-progress__circle-left">
|
||||
${indeterminatePart}
|
||||
</div><div class="mdc-circular-progress__gap-patch">
|
||||
${indeterminatePart}
|
||||
</div><div class="mdc-circular-progress__circle-clipper mdc-circular-progress__circle-right">
|
||||
${indeterminatePart}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
// @ts-ignore
|
||||
public set density(_) {
|
||||
// just a dummy
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
unsafeCSS(progressStyles),
|
||||
css`
|
||||
:host {
|
||||
text-align: initial;
|
||||
}
|
||||
`,
|
||||
];
|
||||
public get density() {
|
||||
switch (this.size) {
|
||||
case "small":
|
||||
return -5;
|
||||
case "medium":
|
||||
return 0;
|
||||
case "large":
|
||||
return 5;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
public set indeterminate(_) {
|
||||
// just a dummy
|
||||
}
|
||||
|
||||
public get indeterminate() {
|
||||
return this.active;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -60,7 +60,7 @@ export class HaDateRangePicker extends LitElement {
|
||||
?ranges=${this.ranges !== undefined}
|
||||
>
|
||||
<div slot="input" class="date-range-inputs">
|
||||
<ha-svg-icon path=${mdiCalendar}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiCalendar}></ha-svg-icon>
|
||||
<paper-input
|
||||
.value=${formatDateTime(this.startDate, this.hass.language)}
|
||||
.label=${this.hass.localize(
|
||||
|
@@ -17,7 +17,7 @@ export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
|
||||
class="header_button"
|
||||
dir=${computeRTLDirection(hass)}
|
||||
>
|
||||
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`;
|
||||
|
||||
|
@@ -27,7 +27,7 @@ export class HaFormBoolean extends LitElement implements HaFormElement {
|
||||
|
||||
@property() public suffix!: string;
|
||||
|
||||
@query("paper-checkbox") private _input?: HTMLElement;
|
||||
@query("paper-checkbox", true) private _input?: HTMLElement;
|
||||
|
||||
public focus() {
|
||||
if (this._input) {
|
||||
|
@@ -21,7 +21,7 @@ export class HaFormFloat extends LitElement implements HaFormElement {
|
||||
|
||||
@property() public suffix!: string;
|
||||
|
||||
@query("paper-input") private _input?: HTMLElement;
|
||||
@query("paper-input", true) private _input?: HTMLElement;
|
||||
|
||||
public focus() {
|
||||
if (this._input) {
|
||||
|
@@ -35,7 +35,7 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
||||
|
||||
@internalProperty() private _init = false;
|
||||
|
||||
@query("paper-menu-button") private _input?: HTMLElement;
|
||||
@query("paper-menu-button", true) private _input?: HTMLElement;
|
||||
|
||||
public focus(): void {
|
||||
if (this._input) {
|
||||
|
@@ -20,7 +20,7 @@ export class HaFormTimePeriod extends LitElement implements HaFormElement {
|
||||
|
||||
@property() public suffix!: string;
|
||||
|
||||
@query("paper-time-input") private _input?: HTMLElement;
|
||||
@query("paper-time-input", true) private _input?: HTMLElement;
|
||||
|
||||
public focus() {
|
||||
if (this._input) {
|
||||
|
@@ -24,7 +24,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
|
||||
@property() public suffix!: string;
|
||||
|
||||
@query("ha-paper-dropdown-menu") private _input?: HTMLElement;
|
||||
@query("ha-paper-dropdown-menu", true) private _input?: HTMLElement;
|
||||
|
||||
public focus() {
|
||||
if (this._input) {
|
||||
|
@@ -55,6 +55,7 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
id="iconButton"
|
||||
title="Click to toggle between masked and clear password"
|
||||
@click=${this._toggleUnmaskedPassword}
|
||||
tabindex="-1"
|
||||
>
|
||||
</ha-icon-button>
|
||||
</paper-input>
|
||||
|
@@ -38,7 +38,7 @@ class HaHLSPlayer extends LitElement {
|
||||
@property({ type: Boolean, attribute: "allow-exoplayer" })
|
||||
public allowExoPlayer = false;
|
||||
|
||||
@query("video") private _videoEl!: HTMLVideoElement;
|
||||
@query("video", true) private _videoEl!: HTMLVideoElement;
|
||||
|
||||
@internalProperty() private _attached = false;
|
||||
|
||||
|
@@ -62,7 +62,7 @@ class HaMenuButton extends LitElement {
|
||||
aria-label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
|
||||
@click=${this._toggleMenu}
|
||||
>
|
||||
<ha-svg-icon path=${mdiMenu}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiMenu}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
${hasNotifications ? html` <div class="dot"></div> ` : ""}
|
||||
`;
|
||||
|
@@ -20,7 +20,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const isEmpty = (obj: object): boolean => {
|
||||
const isEmpty = (obj: Record<string, unknown>): boolean => {
|
||||
if (typeof obj !== "object") {
|
||||
return false;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export class HaYamlEditor extends LitElement {
|
||||
|
||||
@internalProperty() private _yaml = "";
|
||||
|
||||
@query("ha-code-editor") private _editor?: HaCodeEditor;
|
||||
@query("ha-code-editor", true) private _editor?: HaCodeEditor;
|
||||
|
||||
public setValue(value): void {
|
||||
try {
|
||||
@@ -105,6 +105,10 @@ export class HaYamlEditor extends LitElement {
|
||||
|
||||
fireEvent(this, "value-changed", { value: parsed, isValid } as any);
|
||||
}
|
||||
|
||||
get yaml() {
|
||||
return this._editor?.value;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -61,8 +61,8 @@ class LocationEditor extends LitElement {
|
||||
if (!this._leafletMap || !this.location) {
|
||||
return;
|
||||
}
|
||||
if ((this._locationMarker as Circle).getBounds) {
|
||||
this._leafletMap.fitBounds((this._locationMarker as Circle).getBounds());
|
||||
if (this._locationMarker && "getBounds" in this._locationMarker) {
|
||||
this._leafletMap.fitBounds(this._locationMarker.getBounds());
|
||||
} else {
|
||||
this._leafletMap.setView(this.location, this.fitZoom);
|
||||
}
|
||||
|
@@ -90,8 +90,8 @@ export class HaLocationsEditor extends LitElement {
|
||||
if (!marker) {
|
||||
return;
|
||||
}
|
||||
if ((marker as Circle).getBounds) {
|
||||
this._leafletMap.fitBounds((marker as Circle).getBounds());
|
||||
if ("getBounds" in marker) {
|
||||
this._leafletMap.fitBounds(marker.getBounds());
|
||||
(marker as Circle).bringToFront();
|
||||
} else {
|
||||
const circle = this._circles[id];
|
||||
@@ -296,8 +296,8 @@ export class HaLocationsEditor extends LitElement {
|
||||
// @ts-ignore
|
||||
(ev: MouseEvent) => this._markerClicked(ev)
|
||||
)
|
||||
.addTo(this._leafletMap);
|
||||
marker.id = location.id;
|
||||
.addTo(this._leafletMap!);
|
||||
(marker as any).id = location.id;
|
||||
|
||||
this._locationMarkers![location.id] = marker;
|
||||
}
|
||||
|
@@ -159,7 +159,7 @@ class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
|
||||
if (prevState !== null) {
|
||||
dataRow.push([prevLastChanged, endTime, locState, prevState]);
|
||||
}
|
||||
datasets.push({ data: dataRow });
|
||||
datasets.push({ data: dataRow, entity_id: stateInfo.entity_id });
|
||||
labels.push(entityDisplay);
|
||||
});
|
||||
|
||||
@@ -173,12 +173,22 @@ class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
|
||||
return [state, start, end];
|
||||
};
|
||||
|
||||
const formatTooltipBeforeBody = (item, data) => {
|
||||
if (!this.hass.userData || !this.hass.userData.showAdvanced || !item[0]) {
|
||||
return "";
|
||||
}
|
||||
// Extract the entity ID from the dataset.
|
||||
const values = data.datasets[item[0].datasetIndex];
|
||||
return values.entity_id || "";
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
type: "timeline",
|
||||
options: {
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
label: formatTooltipLabel,
|
||||
beforeBody: formatTooltipBeforeBody,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
|
@@ -217,12 +217,12 @@ export const subscribeTrigger = (
|
||||
hass: HomeAssistant,
|
||||
onChange: (result: {
|
||||
variables: {
|
||||
trigger: {};
|
||||
trigger: Record<string, unknown>;
|
||||
};
|
||||
context: Context;
|
||||
}) => void,
|
||||
trigger: Trigger | Trigger[],
|
||||
variables?: {}
|
||||
variables?: Record<string, unknown>
|
||||
) =>
|
||||
hass.connection.subscribeMessage(onChange, {
|
||||
type: "subscribe_trigger",
|
||||
@@ -233,7 +233,7 @@ export const subscribeTrigger = (
|
||||
export const testCondition = (
|
||||
hass: HomeAssistant,
|
||||
condition: Condition | Condition[],
|
||||
variables?: {}
|
||||
variables?: Record<string, unknown>
|
||||
) =>
|
||||
hass.callWS<{ result: boolean }>({
|
||||
type: "test_condition",
|
||||
|
@@ -10,7 +10,6 @@ import {
|
||||
} from "./history";
|
||||
|
||||
export interface CacheConfig {
|
||||
refresh: number;
|
||||
cacheKey: string;
|
||||
hoursToShow: number;
|
||||
}
|
||||
|
@@ -17,12 +17,12 @@ interface OptimisticCollection<T> extends Collection<T> {
|
||||
*/
|
||||
|
||||
export const getOptimisticCollection = <StateType>(
|
||||
saveCollection: (conn: Connection, data: StateType) => Promise<unknown>,
|
||||
saveCollection: (conn2: Connection, data: StateType) => Promise<unknown>,
|
||||
conn: Connection,
|
||||
key: string,
|
||||
fetchCollection: (conn: Connection) => Promise<StateType>,
|
||||
fetchCollection: (conn2: Connection) => Promise<StateType>,
|
||||
subscribeUpdates?: (
|
||||
conn: Connection,
|
||||
conn2: Connection,
|
||||
store: Store<StateType>
|
||||
) => Promise<UnsubscribeFunc>
|
||||
): OptimisticCollection<StateType> => {
|
||||
|
@@ -15,7 +15,7 @@ export interface EntityRegistryEntry {
|
||||
|
||||
export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
|
||||
unique_id: string;
|
||||
capabilities: object;
|
||||
capabilities: Record<string, unknown>;
|
||||
original_name?: string;
|
||||
original_icon?: string;
|
||||
}
|
||||
|
@@ -34,9 +34,9 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
||||
version_latest: string;
|
||||
boot: "auto" | "manual";
|
||||
build: boolean;
|
||||
options: object;
|
||||
network: null | object;
|
||||
network_description: null | object;
|
||||
options: Record<string, unknown>;
|
||||
network: null | Record<string, number>;
|
||||
network_description: null | Record<string, string>;
|
||||
host_network: boolean;
|
||||
host_pid: boolean;
|
||||
host_ipc: boolean;
|
||||
@@ -96,11 +96,11 @@ export interface HassioAddonRepository {
|
||||
export interface HassioAddonSetOptionParams {
|
||||
audio_input?: string | null;
|
||||
audio_output?: string | null;
|
||||
options?: object | null;
|
||||
options?: Record<string, unknown> | null;
|
||||
boot?: "auto" | "manual";
|
||||
auto_update?: boolean;
|
||||
ingress_panel?: boolean;
|
||||
network?: object | null;
|
||||
network?: Record<string, unknown> | null;
|
||||
watchdog?: boolean;
|
||||
}
|
||||
|
||||
|
@@ -18,7 +18,7 @@ export interface HassioHardwareInfo {
|
||||
input: string[];
|
||||
disk: string[];
|
||||
gpio: string[];
|
||||
audio: object;
|
||||
audio: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export const fetchHassioHardwareAudio = async (hass: HomeAssistant) => {
|
||||
|
@@ -6,12 +6,14 @@ import { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE_STATES } from "./entity";
|
||||
|
||||
const LOGBOOK_LOCALIZE_PATH = "ui.components.logbook.messages";
|
||||
export const CONTINUOUS_DOMAINS = ["proximity", "sensor"];
|
||||
|
||||
export interface LogbookEntry {
|
||||
when: string;
|
||||
name: string;
|
||||
message?: string;
|
||||
entity_id?: string;
|
||||
icon?: string;
|
||||
domain: string;
|
||||
context_user_id?: string;
|
||||
context_event_type?: string;
|
||||
@@ -43,11 +45,12 @@ export const getLogbookData = async (
|
||||
);
|
||||
|
||||
for (const entry of logbookData) {
|
||||
if (entry.state) {
|
||||
const stateObj = hass!.states[entry.entity_id!];
|
||||
if (entry.state && stateObj) {
|
||||
entry.message = getLogbookMessage(
|
||||
hass,
|
||||
entry.state,
|
||||
hass!.states[entry.entity_id!],
|
||||
stateObj,
|
||||
computeDomain(entry.entity_id!)
|
||||
);
|
||||
}
|
||||
|
@@ -9,6 +9,6 @@ export interface CustomPanelConfig {
|
||||
html_url?: string;
|
||||
}
|
||||
|
||||
export type CustomPanelInfo<T = {}> = PanelInfo<
|
||||
export type CustomPanelInfo<T = Record<string, unknown>> = PanelInfo<
|
||||
T & { _panel_custom: CustomPanelConfig }
|
||||
>;
|
||||
|
@@ -105,7 +105,7 @@ export type Action =
|
||||
export const triggerScript = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
variables?: {}
|
||||
variables?: Record<string, unknown>
|
||||
) => hass.callService("script", computeObjectId(entityId), variables);
|
||||
|
||||
export const canExcecute = (state: ScriptEntity) => {
|
||||
|
@@ -33,8 +33,8 @@ export const getHassTranslations = async (
|
||||
category: TranslationCategory,
|
||||
integration?: string,
|
||||
config_flow?: boolean
|
||||
): Promise<{}> => {
|
||||
const result = await hass.callWS<{ resources: {} }>({
|
||||
): Promise<Record<string, unknown>> => {
|
||||
const result = await hass.callWS<{ resources: Record<string, unknown> }>({
|
||||
type: "frontend/get_translations",
|
||||
language,
|
||||
category,
|
||||
@@ -47,8 +47,8 @@ export const getHassTranslations = async (
|
||||
export const getHassTranslationsPre109 = async (
|
||||
hass: HomeAssistant,
|
||||
language: string
|
||||
): Promise<{}> => {
|
||||
const result = await hass.callWS<{ resources: {} }>({
|
||||
): Promise<Record<string, unknown>> => {
|
||||
const result = await hass.callWS<{ resources: Record<string, unknown> }>({
|
||||
type: "frontend/get_translations",
|
||||
language,
|
||||
});
|
||||
|
@@ -48,7 +48,7 @@ const snowyStates = new Set<string>(["snowy", "snowy-rainy"]);
|
||||
|
||||
const lightningStates = new Set<string>(["lightning", "lightning-rainy"]);
|
||||
|
||||
export const cardinalDirections = [
|
||||
const cardinalDirections = [
|
||||
"N",
|
||||
"NNE",
|
||||
"NE",
|
||||
@@ -77,13 +77,29 @@ const getWindBearingText = (degree: string): string => {
|
||||
return degree;
|
||||
};
|
||||
|
||||
export const getWindBearing = (bearing: string): string => {
|
||||
const getWindBearing = (bearing: string): string => {
|
||||
if (bearing != null) {
|
||||
return getWindBearingText(bearing);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
export const getWind = (
|
||||
hass: HomeAssistant,
|
||||
speed: string,
|
||||
bearing: string
|
||||
): string => {
|
||||
if (bearing !== null) {
|
||||
const cardinalDirection = getWindBearing(bearing);
|
||||
return `${speed} ${getWeatherUnit(hass!, "wind_speed")} (${
|
||||
hass.localize(
|
||||
`ui.card.weather.cardinal_direction.${cardinalDirection.toLowerCase()}`
|
||||
) || cardinalDirection
|
||||
})`;
|
||||
}
|
||||
return `${speed} ${getWeatherUnit(hass!, "wind_speed")}`;
|
||||
};
|
||||
|
||||
export const getWeatherUnit = (
|
||||
hass: HomeAssistant,
|
||||
measure: string
|
||||
@@ -210,7 +226,7 @@ export const weatherSVGStyles = css`
|
||||
}
|
||||
`;
|
||||
|
||||
export const getWeatherStateSVG = (
|
||||
const getWeatherStateSVG = (
|
||||
state: string,
|
||||
nightTime?: boolean
|
||||
): SVGTemplateResult => {
|
||||
|
@@ -17,7 +17,7 @@ export const subscribeRenderTemplate = (
|
||||
params: {
|
||||
template: string;
|
||||
entity_ids?: string | string[];
|
||||
variables?: object;
|
||||
variables?: Record<string, unknown>;
|
||||
timeout?: number;
|
||||
}
|
||||
): Promise<UnsubscribeFunc> => {
|
||||
|
@@ -19,7 +19,8 @@ import { HassDialog } from "../make-dialog-manager";
|
||||
import { HaDomainTogglerDialogParams } from "./show-dialog-domain-toggler";
|
||||
|
||||
@customElement("dialog-domain-toggler")
|
||||
class DomainTogglerDialog extends LitElement implements HassDialog {
|
||||
class DomainTogglerDialog extends LitElement
|
||||
implements HassDialog<HaDomainTogglerDialogParams> {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _params?: HaDomainTogglerDialogParams;
|
||||
|
@@ -29,7 +29,7 @@ export class HaImagecropperDialog extends LitElement {
|
||||
|
||||
@internalProperty() private _open = false;
|
||||
|
||||
@query("img") private _image!: HTMLImageElement;
|
||||
@query("img", true) private _image!: HTMLImageElement;
|
||||
|
||||
private _cropper?: Cropper;
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
@@ -9,44 +10,47 @@ import {
|
||||
} from "lit-element";
|
||||
import { html, TemplateResult } from "lit-html";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../../../components/ha-icon";
|
||||
|
||||
const cardinalDirections = [
|
||||
"N",
|
||||
"NNE",
|
||||
"NE",
|
||||
"ENE",
|
||||
"E",
|
||||
"ESE",
|
||||
"SE",
|
||||
"SSE",
|
||||
"S",
|
||||
"SSW",
|
||||
"SW",
|
||||
"WSW",
|
||||
"W",
|
||||
"WNW",
|
||||
"NW",
|
||||
"NNW",
|
||||
"N",
|
||||
];
|
||||
import { getWind, getWeatherUnit } from "../../../data/weather";
|
||||
|
||||
import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiEye,
|
||||
mdiGauge,
|
||||
mdiThermometer,
|
||||
mdiWaterPercent,
|
||||
mdiWeatherCloudy,
|
||||
mdiWeatherFog,
|
||||
mdiWeatherHail,
|
||||
mdiWeatherLightning,
|
||||
mdiWeatherLightningRainy,
|
||||
mdiWeatherNight,
|
||||
mdiWeatherPartlyCloudy,
|
||||
mdiWeatherPouring,
|
||||
mdiWeatherRainy,
|
||||
mdiWeatherSnowy,
|
||||
mdiWeatherSnowyRainy,
|
||||
mdiWeatherSunny,
|
||||
mdiWeatherWindy,
|
||||
mdiWeatherWindyVariant,
|
||||
} from "@mdi/js";
|
||||
|
||||
const weatherIcons = {
|
||||
"clear-night": "hass:weather-night",
|
||||
cloudy: "hass:weather-cloudy",
|
||||
exceptional: "hass:alert-circle-outline",
|
||||
fog: "hass:weather-fog",
|
||||
hail: "hass:weather-hail",
|
||||
lightning: "hass:weather-lightning",
|
||||
"lightning-rainy": "hass:weather-lightning-rainy",
|
||||
partlycloudy: "hass:weather-partly-cloudy",
|
||||
pouring: "hass:weather-pouring",
|
||||
rainy: "hass:weather-rainy",
|
||||
snowy: "hass:weather-snowy",
|
||||
"snowy-rainy": "hass:weather-snowy-rainy",
|
||||
sunny: "hass:weather-sunny",
|
||||
windy: "hass:weather-windy",
|
||||
"windy-variant": "hass:weather-windy-variant",
|
||||
"clear-night": mdiWeatherNight,
|
||||
cloudy: mdiWeatherCloudy,
|
||||
exceptional: mdiAlertCircleOutline,
|
||||
fog: mdiWeatherFog,
|
||||
hail: mdiWeatherHail,
|
||||
lightning: mdiWeatherLightning,
|
||||
"lightning-rainy": mdiWeatherLightningRainy,
|
||||
partlycloudy: mdiWeatherPartlyCloudy,
|
||||
pouring: mdiWeatherPouring,
|
||||
rainy: mdiWeatherRainy,
|
||||
snowy: mdiWeatherSnowy,
|
||||
"snowy-rainy": mdiWeatherSnowyRainy,
|
||||
sunny: mdiWeatherSunny,
|
||||
windy: mdiWeatherWindy,
|
||||
"windy-variant": mdiWeatherWindyVariant,
|
||||
};
|
||||
|
||||
@customElement("more-info-weather")
|
||||
@@ -79,24 +83,25 @@ class MoreInfoWeather extends LitElement {
|
||||
|
||||
return html`
|
||||
<div class="flex">
|
||||
<ha-icon icon="hass:thermometer"></ha-icon>
|
||||
<ha-svg-icon .path=${mdiThermometer}></ha-svg-icon>
|
||||
<div class="main">
|
||||
${this.hass.localize("ui.card.weather.attributes.temperature")}
|
||||
</div>
|
||||
<div>
|
||||
${this.stateObj.attributes.temperature} ${this.getUnit("temperature")}
|
||||
${this.stateObj.attributes.temperature}
|
||||
${getWeatherUnit(this.hass, "temperature")}
|
||||
</div>
|
||||
</div>
|
||||
${this._showValue(this.stateObj.attributes.pressure)
|
||||
? html`
|
||||
<div class="flex">
|
||||
<ha-icon icon="hass:gauge"></ha-icon>
|
||||
<ha-svg-icon .path=${mdiGauge}></ha-svg-icon>
|
||||
<div class="main">
|
||||
${this.hass.localize("ui.card.weather.attributes.air_pressure")}
|
||||
</div>
|
||||
<div>
|
||||
${this.stateObj.attributes.pressure}
|
||||
${this.getUnit("air_pressure")}
|
||||
${getWeatherUnit(this.hass, "air_pressure")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@@ -104,7 +109,7 @@ class MoreInfoWeather extends LitElement {
|
||||
${this._showValue(this.stateObj.attributes.humidity)
|
||||
? html`
|
||||
<div class="flex">
|
||||
<ha-icon icon="hass:water-percent"></ha-icon>
|
||||
<ha-svg-icon .path=${mdiWaterPercent}></ha-svg-icon>
|
||||
<div class="main">
|
||||
${this.hass.localize("ui.card.weather.attributes.humidity")}
|
||||
</div>
|
||||
@@ -115,12 +120,13 @@ class MoreInfoWeather extends LitElement {
|
||||
${this._showValue(this.stateObj.attributes.wind_speed)
|
||||
? html`
|
||||
<div class="flex">
|
||||
<ha-icon icon="hass:weather-windy"></ha-icon>
|
||||
<ha-svg-icon .path=${mdiWeatherWindy}></ha-svg-icon>
|
||||
<div class="main">
|
||||
${this.hass.localize("ui.card.weather.attributes.wind_speed")}
|
||||
</div>
|
||||
<div>
|
||||
${this.getWind(
|
||||
${getWind(
|
||||
this.hass,
|
||||
this.stateObj.attributes.wind_speed,
|
||||
this.stateObj.attributes.wind_bearing
|
||||
)}
|
||||
@@ -131,12 +137,13 @@ class MoreInfoWeather extends LitElement {
|
||||
${this._showValue(this.stateObj.attributes.visibility)
|
||||
? html`
|
||||
<div class="flex">
|
||||
<ha-icon icon="hass:eye"></ha-icon>
|
||||
<ha-svg-icon .path=${mdiEye}></ha-svg-icon>
|
||||
<div class="main">
|
||||
${this.hass.localize("ui.card.weather.attributes.visibility")}
|
||||
</div>
|
||||
<div>
|
||||
${this.stateObj.attributes.visibility} ${this.getUnit("length")}
|
||||
${this.stateObj.attributes.visibility}
|
||||
${getWeatherUnit(this.hass, "length")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@@ -151,9 +158,9 @@ class MoreInfoWeather extends LitElement {
|
||||
<div class="flex">
|
||||
${item.condition
|
||||
? html`
|
||||
<ha-icon
|
||||
.icon="${weatherIcons[item.condition]}"
|
||||
></ha-icon>
|
||||
<ha-svg-icon
|
||||
.path="${weatherIcons[item.condition]}"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
${!this._showValue(item.templow)
|
||||
@@ -169,12 +176,14 @@ class MoreInfoWeather extends LitElement {
|
||||
${this.computeDate(item.datetime)}
|
||||
</div>
|
||||
<div class="templow">
|
||||
${item.templow} ${this.getUnit("temperature")}
|
||||
${item.templow}
|
||||
${getWeatherUnit(this.hass, "temperature")}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="temp">
|
||||
${item.temperature} ${this.getUnit("temperature")}
|
||||
${item.temperature}
|
||||
${getWeatherUnit(this.hass, "temperature")}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -193,7 +202,7 @@ class MoreInfoWeather extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-icon {
|
||||
ha-svg-icon {
|
||||
color: var(--paper-item-icon-color);
|
||||
}
|
||||
.section {
|
||||
@@ -247,41 +256,6 @@ class MoreInfoWeather extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private getUnit(measure: string): string {
|
||||
const lengthUnit = this.hass.config.unit_system.length || "";
|
||||
switch (measure) {
|
||||
case "air_pressure":
|
||||
return lengthUnit === "km" ? "hPa" : "inHg";
|
||||
case "length":
|
||||
return lengthUnit;
|
||||
case "precipitation":
|
||||
return lengthUnit === "km" ? "mm" : "in";
|
||||
default:
|
||||
return this.hass.config.unit_system[measure] || "";
|
||||
}
|
||||
}
|
||||
|
||||
private windBearingToText(degree: string): string {
|
||||
const degreenum = parseInt(degree, 10);
|
||||
if (isFinite(degreenum)) {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return cardinalDirections[(((degreenum + 11.25) / 22.5) | 0) % 16];
|
||||
}
|
||||
return degree;
|
||||
}
|
||||
|
||||
private getWind(speed: string, bearing: string) {
|
||||
if (bearing != null) {
|
||||
const cardinalDirection = this.windBearingToText(bearing);
|
||||
return `${speed} ${this.getUnit("length")}/h (${
|
||||
this.hass.localize(
|
||||
`ui.card.weather.cardinal_direction.${cardinalDirection.toLowerCase()}`
|
||||
) || cardinalDirection
|
||||
})`;
|
||||
}
|
||||
return `${speed} ${this.getUnit("length")}/h`;
|
||||
}
|
||||
|
||||
private _showValue(item: string): boolean {
|
||||
return typeof item !== "undefined" && item !== null;
|
||||
}
|
||||
|
@@ -40,7 +40,14 @@ import "./ha-more-info-logbook";
|
||||
import "./controls/more-info-default";
|
||||
|
||||
const DOMAINS_NO_INFO = ["camera", "configurator"];
|
||||
/**
|
||||
* Entity domains that should be editable *if* they have an id present;
|
||||
* {@see shouldShowEditIcon}.
|
||||
* */
|
||||
const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"];
|
||||
/**
|
||||
* Entity Domains that should always be editable; {@see shouldShowEditIcon}.
|
||||
* */
|
||||
const EDITABLE_DOMAINS = ["script"];
|
||||
|
||||
export interface MoreInfoDialogParams {
|
||||
@@ -73,6 +80,20 @@ export class MoreInfoDialog extends LitElement {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected shouldShowEditIcon(domain, stateObj): boolean {
|
||||
if (EDITABLE_DOMAINS_WITH_ID.includes(domain) && stateObj.attributes.id) {
|
||||
return true;
|
||||
}
|
||||
if (EDITABLE_DOMAINS.includes(domain)) {
|
||||
return true;
|
||||
}
|
||||
if (domain === "person" && stateObj.attributes.editable !== "false") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected updated(changedProperties) {
|
||||
if (!this.hass || !this._entityId || !changedProperties.has("_entityId")) {
|
||||
return;
|
||||
@@ -137,10 +158,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
</mwc-icon-button>
|
||||
`
|
||||
: ""}
|
||||
${this.hass.user!.is_admin &&
|
||||
((EDITABLE_DOMAINS_WITH_ID.includes(domain) &&
|
||||
stateObj.attributes.id) ||
|
||||
EDITABLE_DOMAINS.includes(domain))
|
||||
${this.shouldShowEditIcon(domain, stateObj)
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
slot="actionItems"
|
||||
@@ -283,14 +301,12 @@ export class MoreInfoDialog extends LitElement {
|
||||
private _gotoEdit() {
|
||||
const stateObj = this.hass.states[this._entityId!];
|
||||
const domain = computeDomain(this._entityId!);
|
||||
navigate(
|
||||
this,
|
||||
`/config/${domain}/edit/${
|
||||
EDITABLE_DOMAINS_WITH_ID.includes(domain)
|
||||
? stateObj.attributes.id
|
||||
: stateObj.entity_id
|
||||
}`
|
||||
);
|
||||
let idToPassThroughUrl = stateObj.entity_id;
|
||||
if (EDITABLE_DOMAINS_WITH_ID.includes(domain) || domain === "person") {
|
||||
idToPassThroughUrl = stateObj.attributes.id;
|
||||
}
|
||||
|
||||
navigate(this, `/config/${domain}/edit/${idToPassThroughUrl}`);
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
|
@@ -78,7 +78,6 @@ export class MoreInfoHistory extends LitElement {
|
||||
this.hass!,
|
||||
this.entityId,
|
||||
{
|
||||
refresh: 60,
|
||||
cacheKey: `more_info.${this.entityId}`,
|
||||
hoursToShow: 24,
|
||||
},
|
||||
|
@@ -13,7 +13,11 @@ import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { throttle } from "../../common/util/throttle";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/state-history-charts";
|
||||
import { getLogbookData, LogbookEntry } from "../../data/logbook";
|
||||
import {
|
||||
CONTINUOUS_DOMAINS,
|
||||
getLogbookData,
|
||||
LogbookEntry,
|
||||
} from "../../data/logbook";
|
||||
import "../../panels/logbook/ha-logbook";
|
||||
import { haStyle, haStyleScrollbar } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
@@ -40,7 +44,12 @@ export class MoreInfoLogbook extends LitElement {
|
||||
}
|
||||
const stateObj = this.hass.states[this.entityId];
|
||||
|
||||
if (!stateObj) {
|
||||
if (!stateObj || stateObj.attributes.unit_of_measurement) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const domain = computeStateDomain(stateObj);
|
||||
if (CONTINUOUS_DOMAINS.includes(domain)) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
@@ -155,9 +164,9 @@ export class MoreInfoLogbook extends LitElement {
|
||||
overflow: auto;
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-logbook {
|
||||
max-height: unset;
|
||||
}
|
||||
ha-logbook {
|
||||
max-height: unset;
|
||||
}
|
||||
}
|
||||
ha-circular-progress {
|
||||
display: flex;
|
||||
|
@@ -88,6 +88,7 @@ export class HuiNotificationDrawer extends EventsMixin(
|
||||
notifications: {
|
||||
type: Array,
|
||||
computed: "_computeNotifications(open, hass, _notificationsBackend)",
|
||||
observer: "_notificationsChanged",
|
||||
},
|
||||
_notificationsBackend: {
|
||||
type: Array,
|
||||
@@ -130,6 +131,17 @@ export class HuiNotificationDrawer extends EventsMixin(
|
||||
}
|
||||
}
|
||||
|
||||
_notificationsChanged(newNotifications, oldNotifications) {
|
||||
// automatically close drawer when last notification has been dismissed
|
||||
if (
|
||||
this.open &&
|
||||
oldNotifications.length > 0 &&
|
||||
!newNotifications.length === 0
|
||||
) {
|
||||
this.open = false;
|
||||
}
|
||||
}
|
||||
|
||||
_computeNotifications(open, hass, notificationsBackend) {
|
||||
if (!open) {
|
||||
return [];
|
||||
|
225
src/dialogs/quick-bar/ha-quick-bar.ts
Normal file
225
src/dialogs/quick-bar/ha-quick-bar.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import "../../components/ha-header-bar";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import {
|
||||
css,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-dialog";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant, ServiceCallRequest } from "../../types";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { fuzzySequentialMatch } from "../../common/string/sequence_matching";
|
||||
import { componentsWithService } from "../../common/config/components_with_service";
|
||||
import { domainIcon } from "../../common/entity/domain_icon";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import { QuickBarParams } from "./show-dialog-quick-bar";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { compare } from "../../common/string/compare";
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
interface CommandItem extends ServiceCallRequest {
|
||||
text: string;
|
||||
}
|
||||
|
||||
@customElement("ha-quick-bar")
|
||||
export class QuickBar extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _commandItems: CommandItem[] = [];
|
||||
|
||||
@internalProperty() private _itemFilter = "";
|
||||
|
||||
@internalProperty() private _opened = false;
|
||||
|
||||
@internalProperty() private _commandMode = false;
|
||||
|
||||
public async showDialog(params: QuickBarParams) {
|
||||
this._commandMode = params.commandMode || false;
|
||||
this._opened = true;
|
||||
this._commandItems = this._generateCommandItems();
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._opened = false;
|
||||
this._itemFilter = "";
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._opened) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog .heading=${true} open @closed=${this.closeDialog} hideActions>
|
||||
<paper-input
|
||||
dialogInitialFocus
|
||||
no-label-float
|
||||
slot="heading"
|
||||
class="heading"
|
||||
@value-changed=${this._entityFilterChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.quick-bar.filter_placeholder"
|
||||
)}
|
||||
type="search"
|
||||
value=${this._commandMode ? `>${this._itemFilter}` : this._itemFilter}
|
||||
></paper-input>
|
||||
<mwc-list>
|
||||
${this._commandMode
|
||||
? this.renderCommandsList(this._itemFilter)
|
||||
: this.renderEntityList(this._itemFilter)}
|
||||
</mwc-list>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
protected renderCommandsList = memoizeOne((filter) => {
|
||||
const items = this._filterCommandItems(this._commandItems, filter);
|
||||
|
||||
return html`
|
||||
${items.map(
|
||||
({ text, domain, service, serviceData }) => html`
|
||||
<mwc-list-item
|
||||
@click=${this._executeCommand}
|
||||
.domain=${domain}
|
||||
.service=${service}
|
||||
.serviceData=${serviceData}
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-icon .icon=${domainIcon(domain)} slot="graphic"></ha-icon>
|
||||
${text}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
});
|
||||
|
||||
protected renderEntityList = memoizeOne((filter) => {
|
||||
const entities = this._filterEntityItems(
|
||||
Object.keys(this.hass.states),
|
||||
filter
|
||||
);
|
||||
|
||||
return html`
|
||||
${entities.map((entity_id) => {
|
||||
const domain = computeDomain(entity_id);
|
||||
return html`
|
||||
<mwc-list-item @click=${this._entityMoreInfo} graphic="icon">
|
||||
<ha-icon .icon=${domainIcon(domain)} slot="graphic"></ha-icon>
|
||||
${entity_id}
|
||||
</mwc-list-item>
|
||||
`;
|
||||
})}
|
||||
`;
|
||||
});
|
||||
|
||||
private _entityFilterChanged(ev: PolymerChangedEvent<string>) {
|
||||
const newFilter = ev.detail.value;
|
||||
|
||||
if (newFilter.startsWith(">")) {
|
||||
this._commandMode = true;
|
||||
this._itemFilter = newFilter.substring(1);
|
||||
} else {
|
||||
this._commandMode = false;
|
||||
this._itemFilter = newFilter;
|
||||
}
|
||||
}
|
||||
|
||||
private _generateCommandItems(): CommandItem[] {
|
||||
const reloadableDomains = componentsWithService(this.hass, "reload").sort();
|
||||
|
||||
return reloadableDomains.map((domain) => ({
|
||||
text:
|
||||
this.hass.localize(`ui.dialogs.quick-bar.commands.reload.${domain}`) ||
|
||||
this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.reload.reload",
|
||||
"domain",
|
||||
domainToName(this.hass.localize, domain)
|
||||
),
|
||||
domain,
|
||||
service: "reload",
|
||||
}));
|
||||
}
|
||||
|
||||
private _filterCommandItems(
|
||||
items: CommandItem[],
|
||||
filter: string
|
||||
): CommandItem[] {
|
||||
return items
|
||||
.filter(({ text }) =>
|
||||
fuzzySequentialMatch(filter.toLowerCase(), text.toLowerCase())
|
||||
)
|
||||
.sort((itemA, itemB) => compare(itemA.text, itemB.text));
|
||||
}
|
||||
|
||||
private _filterEntityItems(
|
||||
entity_ids: HassEntity["entity_id"][],
|
||||
filter: string
|
||||
): HassEntity["entity_id"][] {
|
||||
return entity_ids
|
||||
.filter((entity_id) =>
|
||||
fuzzySequentialMatch(filter.toLowerCase(), entity_id)
|
||||
)
|
||||
.sort();
|
||||
}
|
||||
|
||||
private async _executeCommand(ev: Event) {
|
||||
const target = ev.currentTarget as any;
|
||||
|
||||
await this.hass.callService(
|
||||
target.domain,
|
||||
target.service,
|
||||
target.serviceData
|
||||
);
|
||||
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private _entityMoreInfo(ev: Event) {
|
||||
ev.preventDefault();
|
||||
fireEvent(this, "hass-more-info", {
|
||||
entityId: (ev.target as any).text,
|
||||
});
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.heading {
|
||||
padding: 20px 20px 0px;
|
||||
}
|
||||
|
||||
ha-dialog {
|
||||
--dialog-z-index: 8;
|
||||
--dialog-content-padding: 0px 24px 20px;
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 800px;
|
||||
--mdc-dialog-min-width: 500px;
|
||||
--dialog-surface-position: fixed;
|
||||
--dialog-surface-top: 40px;
|
||||
--mdc-dialog-max-height: calc(100% - 72px);
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-quick-bar": QuickBar;
|
||||
}
|
||||
}
|
20
src/dialogs/quick-bar/show-dialog-quick-bar.ts
Normal file
20
src/dialogs/quick-bar/show-dialog-quick-bar.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
export interface QuickBarParams {
|
||||
entityFilter?: string;
|
||||
commandMode?: boolean;
|
||||
}
|
||||
|
||||
export const loadQuickBar = () =>
|
||||
import(/* webpackChunkName: "quick-bar-dialog" */ "./ha-quick-bar");
|
||||
|
||||
export const showQuickBar = (
|
||||
element: HTMLElement,
|
||||
dialogParams: QuickBarParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "ha-quick-bar",
|
||||
dialogImport: loadQuickBar,
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@@ -57,7 +57,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
|
||||
@internalProperty() private _agentInfo?: AgentInfo;
|
||||
|
||||
@query("#messages") private messages!: PaperDialogScrollableElement;
|
||||
@query("#messages", true) private messages!: PaperDialogScrollableElement;
|
||||
|
||||
private recognition!: SpeechRecognition;
|
||||
|
||||
|
@@ -35,7 +35,10 @@ function setProperties(properties) {
|
||||
setCustomPanelProperties(panelEl, properties);
|
||||
}
|
||||
|
||||
function initialize(panel: CustomPanelInfo, properties: {}) {
|
||||
function initialize(
|
||||
panel: CustomPanelInfo,
|
||||
properties: Record<string, unknown>
|
||||
) {
|
||||
const style = document.createElement("style");
|
||||
style.innerHTML = "body{margin:0}";
|
||||
document.head.appendChild(style);
|
||||
|
@@ -64,7 +64,13 @@ export class ExternalAuth extends Auth {
|
||||
});
|
||||
}
|
||||
|
||||
private _tokenCallbackPromise?: Promise<RefreshTokenResponse>;
|
||||
|
||||
public async refreshAccessToken(force?: boolean) {
|
||||
if (this._tokenCallbackPromise && !force) {
|
||||
await this._tokenCallbackPromise;
|
||||
return;
|
||||
}
|
||||
const payload: GetExternalAuthPayload = {
|
||||
callback: CALLBACK_SET_TOKEN,
|
||||
};
|
||||
@@ -72,14 +78,15 @@ export class ExternalAuth extends Auth {
|
||||
payload.force = true;
|
||||
}
|
||||
|
||||
const callbackPromise = new Promise<RefreshTokenResponse>(
|
||||
this._tokenCallbackPromise = new Promise<RefreshTokenResponse>(
|
||||
(resolve, reject) => {
|
||||
window[CALLBACK_SET_TOKEN] = (success, data) =>
|
||||
success ? resolve(data) : reject(data);
|
||||
}
|
||||
);
|
||||
|
||||
await 0;
|
||||
// we sleep 1 microtask to get the promise to actually set it on the window object.
|
||||
await Promise.resolve();
|
||||
|
||||
if (window.externalApp) {
|
||||
window.externalApp.getExternalAuth(JSON.stringify(payload));
|
||||
@@ -87,10 +94,11 @@ export class ExternalAuth extends Auth {
|
||||
window.webkit!.messageHandlers.getExternalAuth.postMessage(payload);
|
||||
}
|
||||
|
||||
const tokens = await callbackPromise;
|
||||
const tokens = await this._tokenCallbackPromise;
|
||||
|
||||
this.data.access_token = tokens.access_token;
|
||||
this.data.expires = tokens.expires_in * 1000 + Date.now();
|
||||
this._tokenCallbackPromise = undefined;
|
||||
}
|
||||
|
||||
public async revoke() {
|
||||
@@ -101,7 +109,8 @@ export class ExternalAuth extends Auth {
|
||||
success ? resolve(data) : reject(data);
|
||||
});
|
||||
|
||||
await 0;
|
||||
// we sleep 1 microtask to get the promise to actually set it on the window object.
|
||||
await Promise.resolve();
|
||||
|
||||
if (window.externalApp) {
|
||||
window.externalApp.revokeExternalAuth(JSON.stringify(payload));
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<meta name='viewport' content='width=device-width, user-scalable=no, viewport-fit=cover'>
|
||||
<meta name='viewport' content='width=device-width, viewport-fit=cover'>
|
||||
<style>
|
||||
body {
|
||||
font-family: Roboto, sans-serif;
|
||||
|
@@ -44,17 +44,17 @@
|
||||
<%= renderTemplate('_js_base') %>
|
||||
<%= renderTemplate('_preload_roboto') %>
|
||||
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestPageJS %>";
|
||||
<script crossorigin="use-credentials">
|
||||
import("<%= latestPageJS %>");
|
||||
window.latestJS = true;
|
||||
window.providersPromise = fetch("/auth/providers", {
|
||||
credentials: "same-origin",
|
||||
});
|
||||
</script>
|
||||
|
||||
<script nomodule>
|
||||
<script>
|
||||
(function() {
|
||||
// Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
if (!window.latestJS) {
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5PageJS %>");
|
||||
|
@@ -52,17 +52,17 @@
|
||||
<%= renderTemplate('_js_base') %>
|
||||
<%= renderTemplate('_preload_roboto') %>
|
||||
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestPageJS %>";
|
||||
<script crossorigin="use-credentials">
|
||||
import("<%= latestPageJS %>");
|
||||
window.latestJS = true;
|
||||
window.stepsPromise = fetch("/api/onboarding", {
|
||||
credentials: "same-origin",
|
||||
});
|
||||
</script>
|
||||
|
||||
<script nomodule>
|
||||
<script>
|
||||
(function() {
|
||||
// Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
if (!window.latestJS) {
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5PageJS %>");
|
||||
|
@@ -99,7 +99,7 @@ export class HaTabsSubpageDataTable extends LitElement {
|
||||
*/
|
||||
@property() public tabs!: PageNavigation[];
|
||||
|
||||
@query("ha-data-table") private _dataTable!: HaDataTable;
|
||||
@query("ha-data-table", true) private _dataTable!: HaDataTable;
|
||||
|
||||
public clearSelection() {
|
||||
this._dataTable.clearSelection();
|
||||
|
@@ -17,9 +17,10 @@ import {
|
||||
import "./ha-init-page";
|
||||
import "./home-assistant-main";
|
||||
import { storeState } from "../util/ha-pref-storage";
|
||||
import QuickBarMixin from "../state/quick-bar-mixin";
|
||||
|
||||
@customElement("home-assistant")
|
||||
export class HomeAssistantAppEl extends HassElement {
|
||||
export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
@internalProperty() private _route?: Route;
|
||||
|
||||
@internalProperty() private _error = false;
|
||||
|
@@ -33,7 +33,7 @@ class NotificationManager extends LitElement {
|
||||
|
||||
@internalProperty() private _noCancelOnOutsideClick = false;
|
||||
|
||||
@query("ha-toast") private _toast!: HaToast;
|
||||
@query("ha-toast", true) private _toast!: HaToast;
|
||||
|
||||
public async showDialog({
|
||||
message,
|
||||
|
26
src/mixins/keyboard-shortcut-mixin.ts
Normal file
26
src/mixins/keyboard-shortcut-mixin.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { LitElement } from "lit-element";
|
||||
import { Constructor } from "../types";
|
||||
|
||||
export const KeyboardShortcutMixin = <T extends Constructor<LitElement>>(
|
||||
superClass: T
|
||||
) =>
|
||||
class extends superClass {
|
||||
private _keydownEvent = (event: KeyboardEvent) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === "s") {
|
||||
event.preventDefault();
|
||||
this.handleKeyboardSave();
|
||||
}
|
||||
};
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.addEventListener("keydown", this._keydownEvent);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
this.removeEventListener("keydown", this._keydownEvent);
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
protected handleKeyboardSave() {}
|
||||
};
|
@@ -130,7 +130,7 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
)}"
|
||||
@click=${this._createArea}
|
||||
>
|
||||
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
|
@@ -133,7 +133,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
)}
|
||||
@click=${this._moveUp}
|
||||
>
|
||||
<ha-svg-icon path=${mdiArrowUp}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiArrowUp}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`
|
||||
: ""}
|
||||
@@ -148,7 +148,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
)}
|
||||
@click=${this._moveDown}
|
||||
>
|
||||
<ha-svg-icon path=${mdiArrowDown}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiArrowDown}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`
|
||||
: ""}
|
||||
@@ -157,7 +157,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
slot="trigger"
|
||||
.title=${this.hass.localize("ui.common.menu")}
|
||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
|
||||
><ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item .disabled=${!this._uiModeAvailable}>
|
||||
${yamlMode
|
||||
|
@@ -40,7 +40,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
|
||||
)}
|
||||
>
|
||||
<ha-svg-icon path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<div class="card-content">
|
||||
<h2>
|
||||
|
@@ -21,7 +21,7 @@ export class HaEventAction extends LitElement implements ActionElement {
|
||||
|
||||
@property() public action!: EventAction;
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
@query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
private _actionData?: EventAction["event_data"];
|
||||
|
||||
|
@@ -34,7 +34,7 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ attribute: false }) public action!: ServiceAction;
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
@query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
private _actionData?: ServiceAction["data"];
|
||||
|
||||
|
@@ -40,7 +40,9 @@ export class HaWaitAction extends LitElement implements ActionElement {
|
||||
></paper-input>
|
||||
<br />
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize("ui.panel.config.automation.editor.actions.type.wait_template.continue_timeout")}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.wait_template.continue_timeout"
|
||||
)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${continue_on_timeout}
|
||||
|
@@ -71,7 +71,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
.title=${this.hass.localize("ui.common.menu")}
|
||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||
slot="trigger"
|
||||
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon
|
||||
><ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon
|
||||
></mwc-icon-button>
|
||||
<mwc-list-item>
|
||||
${this._yamlMode
|
||||
|
@@ -1,9 +1,17 @@
|
||||
import "@material/mwc-fab";
|
||||
import { mdiContentDuplicate, mdiContentSave, mdiDelete } from "@mdi/js";
|
||||
import {
|
||||
mdiCheck,
|
||||
mdiContentDuplicate,
|
||||
mdiContentSave,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import { PaperListboxElement } from "@polymer/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
@@ -14,12 +22,16 @@ import {
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
|
||||
import {
|
||||
AutomationConfig,
|
||||
AutomationEntity,
|
||||
@@ -37,6 +49,7 @@ import {
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/ha-app-layout";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
@@ -59,7 +72,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export class HaAutomationEditor extends LitElement {
|
||||
export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public automationId!: string;
|
||||
@@ -80,6 +93,10 @@ export class HaAutomationEditor extends LitElement {
|
||||
|
||||
@internalProperty() private _entityId?: string;
|
||||
|
||||
@internalProperty() private _mode: "gui" | "yaml" = "gui";
|
||||
|
||||
@query("ha-yaml-editor", true) private _editor?: HaYamlEditor;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const stateObj = this._entityId
|
||||
? this.hass.states[this._entityId]
|
||||
@@ -92,29 +109,82 @@ export class HaAutomationEditor extends LitElement {
|
||||
.backCallback=${() => this._backTapped()}
|
||||
.tabs=${configSections.automation}
|
||||
>
|
||||
${!this.automationId
|
||||
? ""
|
||||
: html`
|
||||
<mwc-icon-button
|
||||
slot="toolbar-icon"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.duplicate_automation"
|
||||
)}"
|
||||
@click=${this._duplicate}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiContentDuplicate}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-icon-button
|
||||
class="warning"
|
||||
slot="toolbar-icon"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.delete_automation"
|
||||
)}"
|
||||
@click=${this._deleteConfirm}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`}
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
slot="toolbar-icon"
|
||||
@action=${this._handleMenuAction}
|
||||
activatable
|
||||
>
|
||||
<mwc-icon-button
|
||||
slot="trigger"
|
||||
.title=${this.hass.localize("ui.common.menu")}
|
||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
|
||||
<mwc-list-item
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.edit_ui"
|
||||
)}
|
||||
graphic="icon"
|
||||
?activated=${this._mode === "gui"}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.edit_ui")}
|
||||
${this._mode === "gui"
|
||||
? html`<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiCheck}
|
||||
></ha-svg-icon>`
|
||||
: ``}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.edit_yaml"
|
||||
)}
|
||||
graphic="icon"
|
||||
?activated=${this._mode === "yaml"}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.edit_yaml")}
|
||||
${this._mode === "yaml"
|
||||
? html`<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiCheck}
|
||||
></ha-svg-icon>`
|
||||
: ``}
|
||||
</mwc-list-item>
|
||||
|
||||
<li divider role="separator"></li>
|
||||
|
||||
<mwc-list-item
|
||||
.disabled=${!this.automationId}
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.duplicate_automation"
|
||||
)}
|
||||
graphic="icon"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.duplicate_automation"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiContentDuplicate}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
|
||||
<mwc-list-item
|
||||
.disabled=${!this.automationId}
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.delete_automation"
|
||||
)}
|
||||
class=${classMap({ warning: this.automationId })}
|
||||
graphic="icon"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.delete_automation"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiDelete}></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
${this._config
|
||||
? html`
|
||||
${this.narrow
|
||||
@@ -124,215 +194,270 @@ export class HaAutomationEditor extends LitElement {
|
||||
${this._errors
|
||||
? html` <div class="errors">${this._errors}</div> `
|
||||
: ""}
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
${!this.narrow
|
||||
? html` <span slot="header">${this._config.alias}</span> `
|
||||
: ""}
|
||||
<span slot="introduction">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.introduction"
|
||||
)}
|
||||
</span>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.alias"
|
||||
)}
|
||||
name="alias"
|
||||
.value=${this._config.alias}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</paper-input>
|
||||
<paper-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.label"
|
||||
)}
|
||||
.placeholder=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.placeholder"
|
||||
)}
|
||||
name="description"
|
||||
.value=${this._config.description}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-textarea>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.modes.description",
|
||||
"documentation_link",
|
||||
html`<a
|
||||
${this._mode === "gui"
|
||||
? html`
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
${!this.narrow
|
||||
? html`
|
||||
<span slot="header">${this._config.alias}</span>
|
||||
`
|
||||
: ""}
|
||||
<span slot="introduction">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.introduction"
|
||||
)}
|
||||
</span>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.alias"
|
||||
)}
|
||||
name="alias"
|
||||
.value=${this._config.alias}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</paper-input>
|
||||
<paper-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.label"
|
||||
)}
|
||||
.placeholder=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.placeholder"
|
||||
)}
|
||||
name="description"
|
||||
.value=${this._config.description}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-textarea>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.modes.description",
|
||||
"documentation_link",
|
||||
html`<a
|
||||
href="${documentationUrl(
|
||||
this.hass,
|
||||
"/integrations/automation/#automation-modes"
|
||||
)}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.modes.documentation"
|
||||
)}</a
|
||||
>`
|
||||
)}
|
||||
</p>
|
||||
<paper-dropdown-menu-light
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.modes.label"
|
||||
)}
|
||||
no-animations
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${this._config.mode
|
||||
? MODES.indexOf(this._config.mode)
|
||||
: 0}
|
||||
@iron-select=${this._modeChanged}
|
||||
>
|
||||
${MODES.map(
|
||||
(mode) => html`
|
||||
<paper-item .mode=${mode}>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.modes.${mode}`
|
||||
) || mode}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu-light>
|
||||
${this._config.mode &&
|
||||
MODES_MAX.includes(this._config.mode)
|
||||
? html`<paper-input
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.max.${this._config.mode}`
|
||||
)}
|
||||
type="number"
|
||||
name="max"
|
||||
.value=${this._config.max || "10"}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</paper-input>`
|
||||
: html``}
|
||||
</div>
|
||||
${stateObj
|
||||
? html`
|
||||
<div
|
||||
class="card-actions layout horizontal justified center"
|
||||
>
|
||||
<div class="layout horizontal center">
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
></ha-entity-toggle>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.enable_disable"
|
||||
)}
|
||||
</div>
|
||||
<mwc-button
|
||||
@click=${this._excuteAutomation}
|
||||
.stateObj=${stateObj}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.card.automation.trigger"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.introduction"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="${documentationUrl(
|
||||
this.hass,
|
||||
"/integrations/automation/#automation-modes"
|
||||
"/docs/automation/trigger/"
|
||||
)}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.modes.documentation"
|
||||
)}</a
|
||||
>`
|
||||
)}
|
||||
</p>
|
||||
<paper-dropdown-menu-light
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.modes.label"
|
||||
)}
|
||||
no-animations
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${this._config.mode
|
||||
? MODES.indexOf(this._config.mode)
|
||||
: 0}
|
||||
@iron-select=${this._modeChanged}
|
||||
>
|
||||
${MODES.map(
|
||||
(mode) => html`
|
||||
<paper-item .mode=${mode}>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.modes.${mode}`
|
||||
) || mode}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu-light>
|
||||
${this._config.mode &&
|
||||
MODES_MAX.includes(this._config.mode)
|
||||
? html` <paper-input
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.max.${this._config.mode}`
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.learn_more"
|
||||
)}
|
||||
type="number"
|
||||
name="max"
|
||||
.value=${this._config.max || "10"}
|
||||
@value-changed=${this._valueChanged}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-trigger
|
||||
.triggers=${this._config.trigger}
|
||||
@value-changed=${this._triggerChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-trigger>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.introduction"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="${documentationUrl(
|
||||
this.hass,
|
||||
"/docs/scripts/conditions/"
|
||||
)}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
</paper-input>`
|
||||
: html``}
|
||||
</div>
|
||||
${stateObj
|
||||
? html`
|
||||
<div
|
||||
class="card-actions layout horizontal justified center"
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-condition
|
||||
.conditions=${this._config.condition || []}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-condition>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.introduction"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="${documentationUrl(
|
||||
this.hass,
|
||||
"/docs/automation/action/"
|
||||
)}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div class="layout horizontal center">
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
></ha-entity-toggle>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-action
|
||||
.actions=${this._config.action}
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
</ha-config-section>
|
||||
`
|
||||
: this._mode === "yaml"
|
||||
? html`
|
||||
<ha-config-section .isWide=${false}>
|
||||
${!this.narrow
|
||||
? html`
|
||||
<span slot="header">${this._config.alias}</span>
|
||||
`
|
||||
: ``}
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<ha-yaml-editor
|
||||
.defaultValue=${this._preprocessYaml()}
|
||||
@value-changed=${this._yamlChanged}
|
||||
></ha-yaml-editor>
|
||||
<mwc-button @click=${this._copyYaml}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.enable_disable"
|
||||
)}
|
||||
</div>
|
||||
<mwc-button
|
||||
@click=${this._excuteAutomation}
|
||||
.stateObj=${stateObj}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.card.automation.trigger"
|
||||
"ui.panel.config.automation.editor.copy_to_clipboard"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.introduction"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="${documentationUrl(
|
||||
this.hass,
|
||||
"/docs/automation/trigger/"
|
||||
)}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-trigger
|
||||
.triggers=${this._config.trigger}
|
||||
@value-changed=${this._triggerChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-trigger>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.introduction"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="${documentationUrl(
|
||||
this.hass,
|
||||
"/docs/scripts/conditions/"
|
||||
)}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-condition
|
||||
.conditions=${this._config.condition || []}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-condition>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.introduction"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="${documentationUrl(
|
||||
this.hass,
|
||||
"/docs/automation/action/"
|
||||
)}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-action
|
||||
.actions=${this._config.action}
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
</ha-config-section>
|
||||
${stateObj
|
||||
? html`
|
||||
<div
|
||||
class="card-actions layout horizontal justified center"
|
||||
>
|
||||
<div class="layout horizontal center">
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
></ha-entity-toggle>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.enable_disable"
|
||||
)}
|
||||
</div>
|
||||
<mwc-button
|
||||
@click=${this._excuteAutomation}
|
||||
.stateObj=${stateObj}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.card.automation.trigger"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
<ha-config-section> </ha-config-section
|
||||
></ha-config-section>
|
||||
`
|
||||
: ``}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
@@ -342,7 +467,7 @@ export class HaAutomationEditor extends LitElement {
|
||||
.title=${this.hass.localize("ui.panel.config.automation.editor.save")}
|
||||
@click=${this._saveAutomation}
|
||||
>
|
||||
<ha-svg-icon slot="icon" path=${mdiContentSave}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
@@ -484,6 +609,33 @@ export class HaAutomationEditor extends LitElement {
|
||||
triggerAutomation(this.hass, (ev.target as any).stateObj.entity_id);
|
||||
}
|
||||
|
||||
private _preprocessYaml() {
|
||||
const cleanConfig = this._config;
|
||||
if (!cleanConfig) {
|
||||
return {};
|
||||
}
|
||||
|
||||
delete cleanConfig.id;
|
||||
|
||||
return cleanConfig;
|
||||
}
|
||||
|
||||
private async _copyYaml() {
|
||||
if (this._editor?.yaml) {
|
||||
navigator.clipboard.writeText(this._editor.yaml);
|
||||
}
|
||||
}
|
||||
|
||||
private _yamlChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
}
|
||||
this._config = ev.detail.value;
|
||||
this._errors = undefined;
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
private _backTapped(): void {
|
||||
if (this._dirty) {
|
||||
showConfirmationDialog(this, {
|
||||
@@ -540,6 +692,23 @@ export class HaAutomationEditor extends LitElement {
|
||||
history.back();
|
||||
}
|
||||
|
||||
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._mode = "gui";
|
||||
break;
|
||||
case 1:
|
||||
this._mode = "yaml";
|
||||
break;
|
||||
case 2:
|
||||
this._duplicate();
|
||||
break;
|
||||
case 3:
|
||||
this._deleteConfirm();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _saveAutomation(): void {
|
||||
const id = this.automationId || String(Date.now());
|
||||
this.hass!.callApi(
|
||||
@@ -561,6 +730,10 @@ export class HaAutomationEditor extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
protected handleKeyboardSave() {
|
||||
this._saveAutomation();
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import "@material/mwc-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import "@material/mwc-icon-button";
|
||||
import { mdiPlus, mdiHelpCircle } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
CSSResult,
|
||||
@@ -16,8 +17,8 @@ import { formatDateTime } from "../../../common/datetime/format_date_time";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../components/entity/ha-entity-toggle";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
AutomationConfig,
|
||||
@@ -31,6 +32,7 @@ import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showThingtalkDialog } from "./show-dialog-thingtalk";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
|
||||
@customElement("ha-automation-picker")
|
||||
class HaAutomationPicker extends LitElement {
|
||||
@@ -169,6 +171,9 @@ class HaAutomationPicker extends LitElement {
|
||||
)}
|
||||
hasFab
|
||||
>
|
||||
<mwc-icon-button slot="toolbar-icon" @click=${this._showHelp}>
|
||||
<ha-svg-icon .path=${mdiHelpCircle}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-fab
|
||||
slot="fab"
|
||||
title=${this.hass.localize(
|
||||
@@ -176,7 +181,7 @@ class HaAutomationPicker extends LitElement {
|
||||
)}
|
||||
@click=${this._createNew}
|
||||
>
|
||||
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
@@ -188,6 +193,26 @@ class HaAutomationPicker extends LitElement {
|
||||
fireEvent(this, "hass-more-info", { entityId });
|
||||
}
|
||||
|
||||
private _showHelp() {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.automation.caption"),
|
||||
text: html`
|
||||
${this.hass.localize("ui.panel.config.automation.picker.introduction")}
|
||||
<p>
|
||||
<a
|
||||
href="${documentationUrl(this.hass, "/docs/automation/editor/")}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</p>
|
||||
`,
|
||||
});
|
||||
}
|
||||
|
||||
private _execute(ev) {
|
||||
const entityId = ev.currentTarget.automation.entity_id;
|
||||
triggerAutomation(this.hass, entityId);
|
||||
|
@@ -50,7 +50,7 @@ class DialogThingtalk extends LitElement {
|
||||
|
||||
@internalProperty() private _placeholders?: PlaceholderContainer;
|
||||
|
||||
@query("#input") private _input?: PaperInputElement;
|
||||
@query("#input", true) private _input?: PaperInputElement;
|
||||
|
||||
private _value!: string;
|
||||
|
||||
@@ -133,10 +133,13 @@ class DialogThingtalk extends LitElement {
|
||||
Skip
|
||||
</mwc-button>
|
||||
<mwc-button @click="${this._generate}" .disabled=${this._submitting}>
|
||||
<ha-circular-progress
|
||||
?active="${this._submitting}"
|
||||
alt="Creating your automation..."
|
||||
></ha-circular-progress>
|
||||
${this._submitting
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
size="small"
|
||||
title="Creating your automation..."
|
||||
></ha-circular-progress>`
|
||||
: ""}
|
||||
Create automation
|
||||
</mwc-button>
|
||||
</div>
|
||||
@@ -246,17 +249,6 @@ class DialogThingtalk extends LitElement {
|
||||
mwc-button.left {
|
||||
margin-right: auto;
|
||||
}
|
||||
mwc-button ha-circular-progress {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
ha-circular-progress {
|
||||
display: none;
|
||||
}
|
||||
ha-circular-progress[active] {
|
||||
display: block;
|
||||
}
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
@@ -102,7 +102,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
slot="trigger"
|
||||
.title=${this.hass.localize("ui.common.menu")}
|
||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon
|
||||
><ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon
|
||||
></mwc-icon-button>
|
||||
<mwc-list-item .disabled=${selected === -1}>
|
||||
${yamlMode
|
||||
|
@@ -89,7 +89,7 @@ export class CloudWebhooks extends LitElement {
|
||||
`
|
||||
: this._localHooks.map(
|
||||
(entry) => html`
|
||||
<ha-settings-row .narrow=${this.narrow}>
|
||||
<ha-settings-row .narrow=${this.narrow} .entry=${entry}>
|
||||
<span slot="heading">
|
||||
${entry.name}
|
||||
${entry.domain !== entry.name.toLowerCase()
|
||||
@@ -157,7 +157,7 @@ export class CloudWebhooks extends LitElement {
|
||||
}
|
||||
|
||||
private async _enableWebhook(ev: MouseEvent) {
|
||||
const entry = (ev.currentTarget as any).parentElement.entry;
|
||||
const entry = (ev.currentTarget as any).parentElement!.entry as Webhook;
|
||||
this._progress = [...this._progress, entry.webhook_id];
|
||||
let updatedWebhook;
|
||||
|
||||
|
@@ -131,7 +131,7 @@ class CloudAlexa extends LitElement {
|
||||
"not-exposed": !isExposed,
|
||||
})}
|
||||
.disabled=${!emptyFilter}
|
||||
.title=${this.hass!.localize("ui.panel.config.cloud.google.expose")}
|
||||
.title=${this.hass!.localize("ui.panel.config.cloud.alexa.expose")}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${config.should_expose !== null
|
||||
@@ -169,7 +169,7 @@ class CloudAlexa extends LitElement {
|
||||
${iconButton}
|
||||
<mwc-list-item hasMeta>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.expose_entity"
|
||||
"ui.panel.config.cloud.alexa.expose_entity"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="exposed"
|
||||
@@ -179,7 +179,7 @@ class CloudAlexa extends LitElement {
|
||||
</mwc-list-item>
|
||||
<mwc-list-item hasMeta>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.dont_expose_entity"
|
||||
"ui.panel.config.cloud.alexa.dont_expose_entity"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="not-exposed"
|
||||
@@ -189,7 +189,7 @@ class CloudAlexa extends LitElement {
|
||||
</mwc-list-item>
|
||||
<mwc-list-item hasMeta>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.follow_domain"
|
||||
"ui.panel.config.cloud.alexa.follow_domain"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class=${classMap({
|
||||
|
@@ -13,7 +13,7 @@ import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
@customElement("mqtt-discovery-payload")
|
||||
class MQTTDiscoveryPayload extends LitElement {
|
||||
@property() public payload!: object;
|
||||
@property() public payload!: Record<string, unknown>;
|
||||
|
||||
@property() public showAsYaml = false;
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user