mirror of
https://github.com/home-assistant/frontend.git
synced 2026-08-25 08:07:25 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21574edb44 | ||
|
|
42023ed4a5 |
@@ -369,13 +369,14 @@ gulp.task(
|
||||
const newData = {};
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
// Filter out translations without native name.
|
||||
if (value.nativeName) {
|
||||
newData[key] = value;
|
||||
if (data[key].nativeName) {
|
||||
newData[key] = data[key];
|
||||
} else {
|
||||
console.warn(
|
||||
`Skipping language ${key}. Native name was not translated.`
|
||||
);
|
||||
}
|
||||
if (data[key]) newData[key] = value;
|
||||
});
|
||||
return newData;
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const webpack = require("webpack");
|
||||
const path = require("path");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
||||
const ManifestPlugin = require("webpack-manifest-plugin");
|
||||
const paths = require("./paths.js");
|
||||
const bundle = require("./bundle");
|
||||
const log = require("fancy-log");
|
||||
@@ -68,7 +68,7 @@ const createWebpackConfig = ({
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new WebpackManifestPlugin({
|
||||
new ManifestPlugin({
|
||||
// Only include the JS of entrypoints
|
||||
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
|
||||
}),
|
||||
|
||||
@@ -69,8 +69,7 @@ class HassioAddonStore extends LitElement {
|
||||
if (this.supervisor.addon.repositories) {
|
||||
repos = this.addonRepositories(
|
||||
this.supervisor.addon.repositories,
|
||||
this.supervisor.addon.addons,
|
||||
this._filter
|
||||
this.supervisor.addon.addons
|
||||
);
|
||||
}
|
||||
|
||||
@@ -141,11 +140,7 @@ class HassioAddonStore extends LitElement {
|
||||
}
|
||||
|
||||
private addonRepositories = memoizeOne(
|
||||
(
|
||||
repositories: HassioAddonRepository[],
|
||||
addons: HassioAddonInfo[],
|
||||
filter?: string
|
||||
) => {
|
||||
(repositories: HassioAddonRepository[], addons: HassioAddonInfo[]) => {
|
||||
return repositories.sort(sortRepos).map((repo) => {
|
||||
const filteredAddons = addons.filter(
|
||||
(addon) => addon.repository === repo.slug
|
||||
@@ -157,7 +152,7 @@ class HassioAddonStore extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.repo=${repo}
|
||||
.addons=${filteredAddons}
|
||||
.filter=${filter!}
|
||||
.filter=${this._filter!}
|
||||
></hassio-addon-repository>
|
||||
`
|
||||
: html``;
|
||||
@@ -197,10 +192,8 @@ class HassioAddonStore extends LitElement {
|
||||
}
|
||||
|
||||
private async _loadData() {
|
||||
fireEvent(this, "supervisor-colllection-refresh", { colllection: "addon" });
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "supervisor",
|
||||
});
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "addon" });
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
|
||||
}
|
||||
|
||||
private async _filterChanged(e) {
|
||||
|
||||
@@ -26,15 +26,16 @@ class HassioAddonConfigDashboard extends LitElement {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
}
|
||||
const hasConfiguration =
|
||||
(this.addon.options && Object.keys(this.addon.options).length) ||
|
||||
(this.addon.schema && Object.keys(this.addon.schema).length);
|
||||
const hasOptions =
|
||||
this.addon.options && Object.keys(this.addon.options).length;
|
||||
const hasSchema =
|
||||
hasOptions && this.addon.schema && Object.keys(this.addon.schema).length;
|
||||
|
||||
return html`
|
||||
<div class="content">
|
||||
${hasConfiguration || this.addon.network || this.addon.audio
|
||||
${hasOptions || hasSchema || this.addon.network || this.addon.audio
|
||||
? html`
|
||||
${hasConfiguration
|
||||
${hasOptions || hasSchema
|
||||
? html`
|
||||
<hassio-addon-config
|
||||
.hass=${this.hass}
|
||||
|
||||
@@ -15,15 +15,11 @@ import {
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-button-menu";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/ha-form";
|
||||
import "../../../../src/components/ha-formfield";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import "../../../../src/components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
|
||||
import {
|
||||
@@ -52,8 +48,6 @@ class HassioAddonConfig extends LitElement {
|
||||
|
||||
@internalProperty() private _canShowSchema = false;
|
||||
|
||||
@internalProperty() private _showOptional = false;
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@internalProperty() private _options?: Record<string, unknown>;
|
||||
@@ -62,21 +56,7 @@ class HassioAddonConfig extends LitElement {
|
||||
|
||||
@query("ha-yaml-editor") private _editor?: HaYamlEditor;
|
||||
|
||||
private _filteredShchema = memoizeOne(
|
||||
(options: Record<string, unknown>, schema: HaFormSchema[]) => {
|
||||
return schema.filter((entry) => entry.name in options || entry.required);
|
||||
}
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const showForm =
|
||||
!this._yamlMode && this._canShowSchema && this.addon.schema;
|
||||
const hasHiddenOptions =
|
||||
showForm &&
|
||||
JSON.stringify(this.addon.schema) !==
|
||||
JSON.stringify(
|
||||
this._filteredShchema(this.addon.options, this.addon.schema!)
|
||||
);
|
||||
return html`
|
||||
<h1>${this.addon.name}</h1>
|
||||
<ha-card>
|
||||
@@ -98,16 +78,11 @@ class HassioAddonConfig extends LitElement {
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
${showForm
|
||||
${!this._yamlMode && this._canShowSchema && this.addon.schema
|
||||
? html`<ha-form
|
||||
.data=${this._options!}
|
||||
@value-changed=${this._configChanged}
|
||||
.schema=${this._showOptional
|
||||
? this.addon.schema!
|
||||
: this._filteredShchema(
|
||||
this.addon.options,
|
||||
this.addon.schema!
|
||||
)}
|
||||
.schema=${this.addon.schema}
|
||||
></ha-form>`
|
||||
: html` <ha-yaml-editor
|
||||
@value-changed=${this._configChanged}
|
||||
@@ -119,18 +94,6 @@ class HassioAddonConfig extends LitElement {
|
||||
? ""
|
||||
: html` <div class="errors">Invalid YAML</div> `}
|
||||
</div>
|
||||
${hasHiddenOptions
|
||||
? html`<ha-formfield
|
||||
class="show-additional"
|
||||
label="Show unused optional configuration options"
|
||||
>
|
||||
<ha-switch
|
||||
@change=${this._toggleOptional}
|
||||
.checked=${this._showOptional}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>`
|
||||
: ""}
|
||||
<div class="card-actions right">
|
||||
<ha-progress-button
|
||||
@click=${this._saveTapped}
|
||||
@@ -145,10 +108,12 @@ class HassioAddonConfig extends LitElement {
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._canShowSchema = !this.addon.schema!.find(
|
||||
// @ts-ignore
|
||||
(entry) => !SUPPORTED_UI_TYPES.includes(entry.type) || entry.multiple
|
||||
);
|
||||
this._canShowSchema =
|
||||
Object.keys(this.addon.options).length !== 0 &&
|
||||
!this.addon.schema!.find(
|
||||
// @ts-ignore
|
||||
(entry) => !SUPPORTED_UI_TYPES.includes(entry.type) || entry.multiple
|
||||
);
|
||||
this._yamlMode = !this._canShowSchema;
|
||||
}
|
||||
|
||||
@@ -181,10 +146,6 @@ class HassioAddonConfig extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleOptional() {
|
||||
this._showOptional = !this._showOptional;
|
||||
}
|
||||
|
||||
private _configChanged(ev): void {
|
||||
if (this.addon.schema && this._canShowSchema && !this._yamlMode) {
|
||||
this._valid = true;
|
||||
@@ -314,10 +275,6 @@ class HassioAddonConfig extends LitElement {
|
||||
.card-actions.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.show-additional {
|
||||
padding: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -190,9 +190,7 @@ class HassioAddonDashboard extends LitElement {
|
||||
const path: string = pathSplit[pathSplit.length - 1];
|
||||
|
||||
if (["uninstall", "install", "update", "start", "stop"].includes(path)) {
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "supervisor",
|
||||
});
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
|
||||
}
|
||||
|
||||
if (path === "uninstall") {
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { atLeastVersion } from "../../../../src/common/config/version";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../../../src/common/navigate";
|
||||
@@ -50,6 +49,7 @@ import {
|
||||
startHassioAddon,
|
||||
stopHassioAddon,
|
||||
uninstallHassioAddon,
|
||||
updateHassioAddon,
|
||||
validateHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import {
|
||||
@@ -57,7 +57,6 @@ import {
|
||||
fetchHassioStats,
|
||||
HassioStats,
|
||||
} from "../../../../src/data/hassio/common";
|
||||
import { StoreAddon } from "../../../../src/data/supervisor/store";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@@ -68,7 +67,6 @@ import { HomeAssistant } from "../../../../src/types";
|
||||
import { bytesToString } from "../../../../src/util/bytes-to-string";
|
||||
import "../../components/hassio-card-content";
|
||||
import "../../components/supervisor-metric";
|
||||
import { showDialogSupervisorAddonUpdate } from "../../dialogs/addon/show-dialog-addon-update";
|
||||
import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { addonArchIsSupported } from "../../util/addon";
|
||||
@@ -150,16 +148,7 @@ class HassioAddonInfo extends LitElement {
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
private _addonStoreInfo = memoizeOne(
|
||||
(slug: string, storeAddons: StoreAddon[]) =>
|
||||
storeAddons.find((addon) => addon.slug === slug)
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const addonStoreInfo =
|
||||
!this.addon.detached && !this.addon.available
|
||||
? this._addonStoreInfo(this.addon.slug, this.supervisor.store.addons)
|
||||
: undefined;
|
||||
const metrics = [
|
||||
{
|
||||
description: "Add-on CPU Usage",
|
||||
@@ -187,32 +176,32 @@ class HassioAddonInfo extends LitElement {
|
||||
icon=${mdiArrowUpBoldCircle}
|
||||
iconClass="update"
|
||||
></hassio-card-content>
|
||||
${!this.addon.available && addonStoreInfo
|
||||
${!this.addon.available
|
||||
? !addonArchIsSupported(
|
||||
this.supervisor.info.supported_arch,
|
||||
this.addon.arch
|
||||
)
|
||||
? html`
|
||||
<p class="warning">
|
||||
<p>
|
||||
This add-on is not compatible with the processor of
|
||||
your device or the operating system you have installed
|
||||
on your device.
|
||||
</p>
|
||||
`
|
||||
: html`
|
||||
<p class="warning">
|
||||
<p>
|
||||
You are running Home Assistant
|
||||
${this.supervisor.core.version}, to update to this
|
||||
version of the add-on you need at least version
|
||||
${addonStoreInfo.homeassistant} of Home Assistant
|
||||
${this.addon.homeassistant} of Home Assistant
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._updateClicked}>
|
||||
<ha-progress-button @click=${this._updateClicked}>
|
||||
Update
|
||||
</mwc-button>
|
||||
</ha-progress-button>
|
||||
${this.addon.changelog
|
||||
? html`
|
||||
<mwc-button @click=${this._openChangelog}>
|
||||
@@ -562,7 +551,7 @@ class HassioAddonInfo extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${!this.addon.version && addonStoreInfo && !this.addon.available
|
||||
${!this.addon.available
|
||||
? !addonArchIsSupported(
|
||||
this.supervisor.info.supported_arch,
|
||||
this.addon.arch
|
||||
@@ -578,8 +567,8 @@ class HassioAddonInfo extends LitElement {
|
||||
<p class="warning">
|
||||
You are running Home Assistant
|
||||
${this.supervisor.core.version}, to install this add-on you
|
||||
need at least version ${addonStoreInfo!.homeassistant} of
|
||||
Home Assistant
|
||||
need at least version ${this.addon.homeassistant} of Home
|
||||
Assistant
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
@@ -933,11 +922,38 @@ class HassioAddonInfo extends LitElement {
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _updateClicked(): Promise<void> {
|
||||
showDialogSupervisorAddonUpdate(this, {
|
||||
addon: this.addon,
|
||||
supervisor: this.supervisor,
|
||||
private async _updateClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.addon.name,
|
||||
text: "Are you sure you want to update this add-on?",
|
||||
confirmText: "update add-on",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._error = undefined;
|
||||
try {
|
||||
await updateHassioAddon(this.hass, this.addon.slug);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "update",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update addon",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _startClicked(ev: CustomEvent): Promise<void> {
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { showDialogSupervisorCoreUpdate } from "../dialogs/core/show-dialog-core-update";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-update")
|
||||
@@ -135,10 +134,6 @@ export class HassioUpdate extends LitElement {
|
||||
|
||||
private async _confirmUpdate(ev): Promise<void> {
|
||||
const item = ev.currentTarget;
|
||||
if (item.key === "core") {
|
||||
showDialogSupervisorCoreUpdate(this, { supervisor: this.supervisor });
|
||||
return;
|
||||
}
|
||||
item.progress = true;
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: `Update ${item.name}`,
|
||||
@@ -153,17 +148,11 @@ export class HassioUpdate extends LitElement {
|
||||
}
|
||||
try {
|
||||
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: item.key,
|
||||
});
|
||||
fireEvent(this, "supervisor-store-refresh", { store: item.key });
|
||||
} catch (err) {
|
||||
// Only show an error if the status code was not expected (user behind proxy)
|
||||
// or no status at all(connection terminated)
|
||||
if (
|
||||
this.hass.connection.connected &&
|
||||
err.status_code &&
|
||||
!ignoredStatusCodes.has(err.status_code)
|
||||
) {
|
||||
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Update failed",
|
||||
text: extractApiErrorMessage(err),
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
updateHassioAddon,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { SupervisorDialogSupervisorAddonUpdateParams } from "./show-dialog-addon-update";
|
||||
|
||||
@customElement("dialog-supervisor-addon-update")
|
||||
class DialogSupervisorAddonUpdate extends LitElement {
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
public addon!: HassioAddonDetails;
|
||||
|
||||
@internalProperty() private _opened = false;
|
||||
|
||||
@internalProperty() private _createSnapshot = true;
|
||||
|
||||
@internalProperty() private _action: "snapshot" | "update" | null = null;
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
public async showDialog(
|
||||
params: SupervisorDialogSupervisorAddonUpdateParams
|
||||
): Promise<void> {
|
||||
this._opened = true;
|
||||
this.addon = params.addon;
|
||||
this.supervisor = params.supervisor;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._action = null;
|
||||
this._createSnapshot = true;
|
||||
this._opened = false;
|
||||
this._error = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.updateComplete.then(() =>
|
||||
(this.shadowRoot?.querySelector(
|
||||
"[dialogInitialFocus]"
|
||||
) as HTMLElement)?.focus()
|
||||
);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-dialog .open=${this._opened} scrimClickAction escapeKeyAction>
|
||||
${this._action === null
|
||||
? html`<slot name="heading">
|
||||
<h2 id="title" class="header_title">
|
||||
Update ${this.addon.name}
|
||||
</h2>
|
||||
</slot>
|
||||
<div>
|
||||
Are you sure you want to update the ${this.addon.name} add-on to
|
||||
version ${this.addon.version_latest}?
|
||||
</div>
|
||||
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Snapshot
|
||||
</span>
|
||||
<span slot="description">
|
||||
Create a snapshot of the ${this.addon.name} add-on before
|
||||
updating
|
||||
</span>
|
||||
<ha-switch
|
||||
.checked=${this._createSnapshot}
|
||||
haptic
|
||||
title="Create snapshot"
|
||||
@click=${this._toggleSnapshot}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-settings-row>
|
||||
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
|
||||
Cancel
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
.disabled=${this._error !== undefined ||
|
||||
this.supervisor.info.state !== "running"}
|
||||
@click=${this._update}
|
||||
slot="primaryAction"
|
||||
>
|
||||
Update
|
||||
</mwc-button>`
|
||||
: html`<ha-circular-progress alt="Updating" size="large" active>
|
||||
</ha-circular-progress>
|
||||
<p class="progress-text">
|
||||
${this._action === "update"
|
||||
? `Updating ${this.addon.name} to version ${this.addon.version_latest}`
|
||||
: "Creating snapshot of Home Assistant Core"}
|
||||
</p>`}
|
||||
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggleSnapshot() {
|
||||
this._createSnapshot = !this._createSnapshot;
|
||||
}
|
||||
|
||||
private async _update() {
|
||||
if (this._createSnapshot) {
|
||||
this._action = "snapshot";
|
||||
try {
|
||||
await createHassioPartialSnapshot(this.hass, {
|
||||
name: `addon_${this.addon.slug}_${this.addon.version}`,
|
||||
addons: [this.addon.slug],
|
||||
homeassistant: false,
|
||||
});
|
||||
} catch (err) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._action = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._action = "update";
|
||||
try {
|
||||
await updateHassioAddon(this.hass, this.addon.slug);
|
||||
} catch (err) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._action = null;
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "supervisor-colllection-refresh", { colllection: "addon" });
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "supervisor",
|
||||
});
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
ha-settings-row {
|
||||
margin-top: 32px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
margin: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-supervisor-addon-update": DialogSupervisorAddonUpdate;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
|
||||
export interface SupervisorDialogSupervisorAddonUpdateParams {
|
||||
addon: HassioAddonDetails;
|
||||
supervisor: Supervisor;
|
||||
}
|
||||
|
||||
export const showDialogSupervisorAddonUpdate = (
|
||||
element: HTMLElement,
|
||||
dialogParams: SupervisorDialogSupervisorAddonUpdateParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-supervisor-addon-update",
|
||||
dialogImport: () => import("./dialog-supervisor-addon-update"),
|
||||
dialogParams,
|
||||
});
|
||||
};
|
||||
@@ -1,182 +0,0 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
|
||||
import { updateCore } from "../../../../src/data/supervisor/core";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { SupervisorDialogSupervisorCoreUpdateParams } from "./show-dialog-core-update";
|
||||
|
||||
@customElement("dialog-supervisor-core-update")
|
||||
class DialogSupervisorCoreUpdate extends LitElement {
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _opened = false;
|
||||
|
||||
@internalProperty() private _createSnapshot = true;
|
||||
|
||||
@internalProperty() private _action: "snapshot" | "update" | null = null;
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
public async showDialog(
|
||||
params: SupervisorDialogSupervisorCoreUpdateParams
|
||||
): Promise<void> {
|
||||
this._opened = true;
|
||||
this.supervisor = params.supervisor;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._action = null;
|
||||
this._createSnapshot = true;
|
||||
this._opened = false;
|
||||
this._error = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.updateComplete.then(() =>
|
||||
(this.shadowRoot?.querySelector(
|
||||
"[dialogInitialFocus]"
|
||||
) as HTMLElement)?.focus()
|
||||
);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-dialog .open=${this._opened} scrimClickAction escapeKeyAction>
|
||||
${this._action === null
|
||||
? html`<slot name="heading">
|
||||
<h2 id="title" class="header_title">
|
||||
Update Home Assistant Core
|
||||
</h2>
|
||||
</slot>
|
||||
<div>
|
||||
Are you sure you want to update Home Assistant Core to version
|
||||
${this.supervisor.core.version_latest}?
|
||||
</div>
|
||||
|
||||
<ha-settings-row three-rows>
|
||||
<span slot="heading">
|
||||
Snapshot
|
||||
</span>
|
||||
<span slot="description">
|
||||
Create a snapshot of Home Assistant Core before updating
|
||||
</span>
|
||||
<ha-switch
|
||||
.checked=${this._createSnapshot}
|
||||
haptic
|
||||
title="Create snapshot"
|
||||
@click=${this._toggleSnapshot}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-settings-row>
|
||||
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
|
||||
Cancel
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
.disabled=${this._error !== undefined ||
|
||||
this.supervisor.info.state !== "running"}
|
||||
@click=${this._update}
|
||||
slot="primaryAction"
|
||||
>
|
||||
Update
|
||||
</mwc-button>`
|
||||
: html`<ha-circular-progress alt="Updating" size="large" active>
|
||||
</ha-circular-progress>
|
||||
<p class="progress-text">
|
||||
${this._action === "update"
|
||||
? `Updating Home Assistant Core to version ${this.supervisor.core.version_latest}`
|
||||
: "Creating snapshot of Home Assistant Core"}
|
||||
</p>`}
|
||||
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggleSnapshot() {
|
||||
this._createSnapshot = !this._createSnapshot;
|
||||
}
|
||||
|
||||
private async _update() {
|
||||
if (this._createSnapshot) {
|
||||
this._action = "snapshot";
|
||||
try {
|
||||
await createHassioPartialSnapshot(this.hass, {
|
||||
name: `core_${this.supervisor.core.version}`,
|
||||
folders: ["homeassistant"],
|
||||
homeassistant: true,
|
||||
});
|
||||
} catch (err) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._action = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._action = "update";
|
||||
try {
|
||||
await updateCore(this.hass);
|
||||
} catch (err) {
|
||||
if (this.hass.connection.connected) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._action = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
fireEvent(this, "supervisor-colllection-refresh", { colllection: "core" });
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
ha-settings-row {
|
||||
margin-top: 32px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
margin: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-supervisor-core-update": DialogSupervisorCoreUpdate;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
|
||||
export interface SupervisorDialogSupervisorCoreUpdateParams {
|
||||
supervisor: Supervisor;
|
||||
}
|
||||
|
||||
export const showDialogSupervisorCoreUpdate = (
|
||||
element: HTMLElement,
|
||||
dialogParams: SupervisorDialogSupervisorCoreUpdateParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-supervisor-core-update",
|
||||
dialogImport: () => import("./dialog-supervisor-core-update"),
|
||||
dialogParams,
|
||||
});
|
||||
};
|
||||
@@ -95,7 +95,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
|
||||
@internalProperty() private _snapshotPassword!: string;
|
||||
|
||||
@internalProperty() private _restoreHass = true;
|
||||
@internalProperty() private _restoreHass: boolean | null | undefined = true;
|
||||
|
||||
public async showDialog(params: HassioSnapshotDialogParams) {
|
||||
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
|
||||
@@ -109,9 +109,6 @@ class HassioSnapshotDialog extends LitElement {
|
||||
this._dialogParams = params;
|
||||
this._onboarding = params.onboarding ?? false;
|
||||
this.supervisor = params.supervisor;
|
||||
if (!this._snapshot.homeassistant) {
|
||||
this._restoreHass = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -137,17 +134,15 @@ class HassioSnapshotDialog extends LitElement {
|
||||
(${this._computeSize})<br />
|
||||
${this._formatDatetime(this._snapshot.date)}
|
||||
</div>
|
||||
${this._snapshot.homeassistant
|
||||
? html`<div>Home Assistant:</div>
|
||||
<paper-checkbox
|
||||
.checked=${this._restoreHass}
|
||||
@change="${(ev: Event) => {
|
||||
this._restoreHass = (ev.target as PaperCheckboxElement).checked!;
|
||||
}}"
|
||||
>
|
||||
Home Assistant ${this._snapshot.homeassistant}
|
||||
</paper-checkbox>`
|
||||
: ""}
|
||||
<div>Home Assistant:</div>
|
||||
<paper-checkbox
|
||||
.checked=${this._restoreHass}
|
||||
@change="${(ev: Event) => {
|
||||
this._restoreHass = (ev.target as PaperCheckboxElement).checked;
|
||||
}}"
|
||||
>
|
||||
Home Assistant ${this._snapshot.homeassistant}
|
||||
</paper-checkbox>
|
||||
${this._folders.length
|
||||
? html`
|
||||
<div>Folders:</div>
|
||||
@@ -339,7 +334,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
.map((folder) => folder.slug);
|
||||
|
||||
const data: {
|
||||
homeassistant: boolean;
|
||||
homeassistant: boolean | null | undefined;
|
||||
addons: any;
|
||||
folders: any;
|
||||
password?: string;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { atLeastVersion } from "../../src/common/config/version";
|
||||
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../src/common/dom/fire_event";
|
||||
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
|
||||
import { supervisorCollection } from "../../src/data/supervisor/supervisor";
|
||||
import { supervisorStore } from "../../src/data/supervisor/supervisor";
|
||||
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
|
||||
import "../../src/layouts/hass-loading-screen";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
@@ -77,9 +77,7 @@ export class HassioMain extends SupervisorBaseElement {
|
||||
}
|
||||
|
||||
if (
|
||||
Object.keys(supervisorCollection).some(
|
||||
(colllection) => !this.supervisor![colllection]
|
||||
)
|
||||
Object.keys(supervisorStore).some((store) => !this.supervisor![store])
|
||||
) {
|
||||
return html`<hass-loading-screen></hass-loading-screen>`;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class HassioRouter extends HassRouterPage {
|
||||
protected routerOptions: RouterOptions = {
|
||||
// Hass.io has a page with tabs, so we route all non-matching routes to it.
|
||||
defaultPage: "dashboard",
|
||||
initialLoad: () => this._redirectIngress(),
|
||||
initialLoad: () => this._fetchData(),
|
||||
showLoading: true,
|
||||
routes: {
|
||||
dashboard: {
|
||||
@@ -50,13 +50,7 @@ class HassioRouter extends HassRouterPage {
|
||||
|
||||
protected updatePageEl(el) {
|
||||
// the tabs page does its own routing so needs full route.
|
||||
const hassioPanel = el.nodeName === "HASSIO-PANEL";
|
||||
const route = hassioPanel ? this.route : this.routeTail;
|
||||
|
||||
if (hassioPanel && this.panel.config?.ingress) {
|
||||
this._redirectIngress();
|
||||
return;
|
||||
}
|
||||
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
|
||||
|
||||
el.hass = this.hass;
|
||||
el.narrow = this.narrow;
|
||||
@@ -69,14 +63,15 @@ class HassioRouter extends HassRouterPage {
|
||||
}
|
||||
}
|
||||
|
||||
private async _redirectIngress() {
|
||||
private async _fetchData() {
|
||||
if (this.panel.config && this.panel.config.ingress) {
|
||||
this.route = {
|
||||
prefix: "/hassio",
|
||||
path: `/ingress/${this.panel.config.ingress}`,
|
||||
};
|
||||
this._redirectIngress(this.panel.config.ingress);
|
||||
}
|
||||
}
|
||||
|
||||
private _redirectIngress(addonSlug: string) {
|
||||
this.route = { prefix: "/hassio", path: `/ingress/${addonSlug}` };
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
} from "lit-element";
|
||||
import { atLeastVersion } from "../../src/common/config/version";
|
||||
import { fetchHassioAddonsInfo } from "../../src/data/hassio/addon";
|
||||
import { HassioResponse } from "../../src/data/hassio/common";
|
||||
import {
|
||||
hassioApiResultExtractor,
|
||||
HassioResponse,
|
||||
} from "../../src/data/hassio/common";
|
||||
import {
|
||||
fetchHassioHassOsInfo,
|
||||
fetchHassioHostInfo,
|
||||
@@ -19,13 +22,15 @@ import {
|
||||
fetchHassioInfo,
|
||||
fetchHassioSupervisorInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import { fetchSupervisorStore } from "../../src/data/supervisor/store";
|
||||
import {
|
||||
getSupervisorEventCollection,
|
||||
subscribeSupervisorEvents,
|
||||
Supervisor,
|
||||
supervisorApiRequest,
|
||||
SupervisorAPIRequestParams,
|
||||
supervisorApiWsRequest,
|
||||
SupervisorObject,
|
||||
supervisorCollection,
|
||||
supervisorStore,
|
||||
} from "../../src/data/supervisor/supervisor";
|
||||
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
|
||||
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
|
||||
@@ -33,7 +38,7 @@ import { urlSyncMixin } from "../../src/state/url-sync-mixin";
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"supervisor-update": Partial<Supervisor>;
|
||||
"supervisor-colllection-refresh": { colllection: SupervisorObject };
|
||||
"supervisor-store-refresh": { store: SupervisorObject };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +62,11 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
}
|
||||
|
||||
protected _updateSupervisor(obj: Partial<Supervisor>): void {
|
||||
this.supervisor = { ...this.supervisor!, ...obj };
|
||||
this.supervisor = {
|
||||
...this.supervisor!,
|
||||
...obj,
|
||||
callApi: (params) => this._callAPI(params),
|
||||
};
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
@@ -66,40 +75,40 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
}
|
||||
|
||||
private async _handleSupervisorStoreRefreshEvent(ev) {
|
||||
const colllection = ev.detail.colllection;
|
||||
const store = ev.detail.store;
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
this._collections[colllection].refresh();
|
||||
this._collections[store].refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await this.hass.callApi<HassioResponse<any>>(
|
||||
"GET",
|
||||
`hassio${supervisorCollection[colllection]}`
|
||||
`hassio${supervisorStore[store]}`
|
||||
);
|
||||
this._updateSupervisor({ [colllection]: response.data });
|
||||
this._updateSupervisor({ [store]: response.data });
|
||||
}
|
||||
|
||||
private async _initSupervisor(): Promise<void> {
|
||||
this.addEventListener(
|
||||
"supervisor-colllection-refresh",
|
||||
"supervisor-store-refresh",
|
||||
this._handleSupervisorStoreRefreshEvent
|
||||
);
|
||||
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
Object.keys(supervisorCollection).forEach((colllection) => {
|
||||
this._unsubs[colllection] = subscribeSupervisorEvents(
|
||||
Object.keys(supervisorStore).forEach((store) => {
|
||||
this._unsubs[store] = subscribeSupervisorEvents(
|
||||
this.hass,
|
||||
(data) => this._updateSupervisor({ [colllection]: data }),
|
||||
colllection,
|
||||
supervisorCollection[colllection]
|
||||
(data) => this._updateSupervisor({ [store]: data }),
|
||||
store,
|
||||
supervisorStore[store]
|
||||
);
|
||||
if (this._collections[colllection]) {
|
||||
this._collections[colllection].refresh();
|
||||
if (this._collections[store]) {
|
||||
this._collections[store].refresh();
|
||||
} else {
|
||||
this._collections[colllection] = getSupervisorEventCollection(
|
||||
this._collections[store] = getSupervisorEventCollection(
|
||||
this.hass.connection,
|
||||
colllection,
|
||||
supervisorCollection[colllection]
|
||||
store,
|
||||
supervisorStore[store]
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -123,7 +132,6 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
os,
|
||||
network,
|
||||
resolution,
|
||||
store,
|
||||
] = await Promise.all([
|
||||
fetchHassioAddonsInfo(this.hass),
|
||||
fetchHassioSupervisorInfo(this.hass),
|
||||
@@ -133,7 +141,6 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
fetchHassioHassOsInfo(this.hass),
|
||||
fetchNetworkInfo(this.hass),
|
||||
fetchHassioResolution(this.hass),
|
||||
fetchSupervisorStore(this.hass),
|
||||
]);
|
||||
|
||||
this.supervisor = {
|
||||
@@ -145,11 +152,48 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
os,
|
||||
network,
|
||||
resolution,
|
||||
store,
|
||||
callApi: (params) => this._callAPI(params),
|
||||
};
|
||||
|
||||
this.addEventListener("supervisor-update", (ev) =>
|
||||
this._updateSupervisor(ev.detail)
|
||||
);
|
||||
}
|
||||
|
||||
private async _callAPI<T>(params: SupervisorAPIRequestParams): Promise<T> {
|
||||
const hasHass = this.hass !== undefined;
|
||||
const canUseWS =
|
||||
!params.rest &&
|
||||
hasHass &&
|
||||
atLeastVersion(this.hass.config.version, 2021, 2, 4);
|
||||
|
||||
if (canUseWS) {
|
||||
const connection = hasHass ? this.hass.connection : params.connection;
|
||||
if (connection === undefined) {
|
||||
throw Error(`No connection found, aborting API call - ${params}`);
|
||||
}
|
||||
const requestParams: supervisorApiRequest = {
|
||||
...params,
|
||||
};
|
||||
delete requestParams.rest;
|
||||
delete requestParams.connection;
|
||||
return await supervisorApiWsRequest<T>(connection, requestParams);
|
||||
} else {
|
||||
const method =
|
||||
params.method === "post"
|
||||
? "POST"
|
||||
: params.method === "put"
|
||||
? "PUT"
|
||||
: params.method === "delete"
|
||||
? "DELETE"
|
||||
: "GET";
|
||||
return hassioApiResultExtractor<T>(
|
||||
await this.hass.callApi<HassioResponse<T>>(
|
||||
method,
|
||||
`hassio${params.endpoint}`,
|
||||
params.data
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-card";
|
||||
@@ -19,7 +20,7 @@ import {
|
||||
fetchHassioStats,
|
||||
HassioStats,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { restartCore } from "../../../src/data/supervisor/core";
|
||||
import { restartCore, updateCore } from "../../../src/data/supervisor/core";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@@ -29,7 +30,6 @@ import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { bytesToString } from "../../../src/util/bytes-to-string";
|
||||
import "../components/supervisor-metric";
|
||||
import { showDialogSupervisorCoreUpdate } from "../dialogs/core/show-dialog-core-update";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-core-info")
|
||||
@@ -140,19 +140,42 @@ class HassioCoreInfo extends LitElement {
|
||||
try {
|
||||
await restartCore(this.hass);
|
||||
} catch (err) {
|
||||
if (this.hass.connection.connected) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to restart Home Assistant Core",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to restart Home Assistant Core",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
} finally {
|
||||
button.progress = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _coreUpdate(): Promise<void> {
|
||||
showDialogSupervisorCoreUpdate(this, { supervisor: this.supervisor });
|
||||
private async _coreUpdate(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Update Home Assistant Core",
|
||||
text: `Are you sure you want to update Home Assistant Core to version ${this.supervisor.core.version_latest}?`,
|
||||
confirmText: "update",
|
||||
dismissText: "cancel",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateCore(this.hass);
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "core" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update Home Assistant Core",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
} finally {
|
||||
button.progress = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
|
||||
@@ -340,14 +340,12 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
try {
|
||||
await updateOS(this.hass);
|
||||
fireEvent(this, "supervisor-colllection-refresh", { colllection: "os" });
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "os" });
|
||||
} catch (err) {
|
||||
if (this.hass.connection.connected) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
@@ -371,9 +369,7 @@ class HassioHostInfo extends LitElement {
|
||||
if (hostname && hostname !== curHostname) {
|
||||
try {
|
||||
await changeHostOptions(this.hass, { hostname });
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "host",
|
||||
});
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "host" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Setting hostname failed",
|
||||
@@ -386,9 +382,7 @@ class HassioHostInfo extends LitElement {
|
||||
private async _importFromUSB(): Promise<void> {
|
||||
try {
|
||||
await configSyncOS(this.hass);
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "host",
|
||||
});
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "host" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to import from USB",
|
||||
@@ -399,9 +393,7 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "network",
|
||||
});
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "network" });
|
||||
} else {
|
||||
const network = await fetchNetworkInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { network });
|
||||
|
||||
@@ -317,9 +317,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
private async _reloadSupervisor(): Promise<void> {
|
||||
await reloadSupervisor(this.hass);
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "supervisor",
|
||||
});
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
|
||||
}
|
||||
|
||||
private async _supervisorRestart(ev: CustomEvent): Promise<void> {
|
||||
@@ -368,9 +366,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
try {
|
||||
await updateSupervisor(this.hass);
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "supervisor",
|
||||
});
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update the supervisor",
|
||||
|
||||
+9
-19
@@ -23,16 +23,6 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.0",
|
||||
"@codemirror/commands": "^0.17.0",
|
||||
"@codemirror/gutter": "^0.17.0",
|
||||
"@codemirror/highlight": "^0.17.0",
|
||||
"@codemirror/history": "^0.17.0",
|
||||
"@codemirror/legacy-modes": "^0.17.0",
|
||||
"@codemirror/search": "^0.17.0",
|
||||
"@codemirror/state": "^0.17.0",
|
||||
"@codemirror/stream-parser": "^0.17.0",
|
||||
"@codemirror/text": "^0.17.0",
|
||||
"@codemirror/view": "^0.17.0",
|
||||
"@formatjs/intl-getcanonicallocales": "^1.4.6",
|
||||
"@formatjs/intl-pluralrules": "^3.4.10",
|
||||
"@fullcalendar/common": "5.1.0",
|
||||
@@ -56,8 +46,8 @@
|
||||
"@material/mwc-tab": "^0.20.0",
|
||||
"@material/mwc-tab-bar": "^0.20.0",
|
||||
"@material/top-app-bar": "=9.0.0-canary.1c156d69d.0",
|
||||
"@mdi/js": "5.9.55",
|
||||
"@mdi/svg": "5.9.55",
|
||||
"@mdi/js": "5.6.55",
|
||||
"@mdi/svg": "5.6.55",
|
||||
"@polymer/app-layout": "^3.0.2",
|
||||
"@polymer/app-route": "^3.0.2",
|
||||
"@polymer/app-storage": "^3.0.2",
|
||||
@@ -111,7 +101,7 @@
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^0.13.2",
|
||||
"home-assistant-js-websocket": "^5.9.0",
|
||||
"home-assistant-js-websocket": "^5.8.1",
|
||||
"idb-keyval": "^3.2.0",
|
||||
"intl-messageformat": "^8.3.9",
|
||||
"js-yaml": "^3.13.1",
|
||||
@@ -187,7 +177,7 @@
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb-typescript": "^7.2.1",
|
||||
"eslint-config-prettier": "^6.10.1",
|
||||
"eslint-import-resolver-webpack": "^0.13.0",
|
||||
"eslint-import-resolver-webpack": "^0.12.2",
|
||||
"eslint-plugin-disable": "^2.0.1",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-lit": "^1.2.0",
|
||||
@@ -223,16 +213,16 @@
|
||||
"sinon": "^7.3.1",
|
||||
"source-map-url": "^0.4.0",
|
||||
"systemjs": "^6.3.2",
|
||||
"terser-webpack-plugin": "^5.1.1",
|
||||
"terser-webpack-plugin": "^5.0.0",
|
||||
"ts-lit-plugin": "^1.2.1",
|
||||
"ts-mocha": "^7.0.0",
|
||||
"typescript": "^4.0.3",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"webpack": "^5.24.1",
|
||||
"webpack-cli": "^4.5.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-manifest-plugin": "^3.0.0",
|
||||
"webpack": "5.1.3",
|
||||
"webpack-cli": "4.1.0",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"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",
|
||||
|
||||
+1
-1
@@ -12,5 +12,5 @@ yarn install
|
||||
script/build_frontend
|
||||
|
||||
rm -rf dist
|
||||
python3 setup.py -q sdist
|
||||
python3 setup.py sdist
|
||||
python3 -m twine upload dist/* --skip-existing
|
||||
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20210301.0",
|
||||
version="20210208.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
||||
@@ -5,15 +5,11 @@ export const atLeastVersion = (
|
||||
patch?: number
|
||||
): boolean => {
|
||||
const [haMajor, haMinor, haPatch] = version.split(".", 3);
|
||||
|
||||
return (
|
||||
Number(haMajor) > major ||
|
||||
(Number(haMajor) === major && (patch === undefined
|
||||
? Number(haMinor) >= minor
|
||||
: Number(haMinor) > minor)) ||
|
||||
(Number(haMajor) === major && Number(haMinor) >= minor) ||
|
||||
(patch !== undefined &&
|
||||
Number(haMajor) === major &&
|
||||
Number(haMinor) === minor &&
|
||||
Number(haMajor) === major && Number(haMinor) === minor &&
|
||||
Number(haPatch) >= patch)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,19 +8,12 @@ export const batteryIcon = (
|
||||
const battery = Number(batteryState.state);
|
||||
const battery_charging =
|
||||
batteryChargingState && batteryChargingState.state === "on";
|
||||
let icon = "hass:battery";
|
||||
|
||||
if (isNaN(battery)) {
|
||||
if (batteryState.state === "off") {
|
||||
icon += "-full";
|
||||
} else if (batteryState.state === "on") {
|
||||
icon += "-alert";
|
||||
} else {
|
||||
icon += "-unknown";
|
||||
}
|
||||
return icon;
|
||||
return "hass:battery-unknown";
|
||||
}
|
||||
|
||||
let icon = "hass:battery";
|
||||
const batteryRound = Math.round(battery / 10) * 10;
|
||||
if (battery_charging && battery > 10) {
|
||||
icon += `-charging-${batteryRound}`;
|
||||
|
||||
@@ -15,7 +15,7 @@ export const iconColorCSS = css`
|
||||
ha-icon[data-domain="media_player"][data-state="on"],
|
||||
ha-icon[data-domain="media_player"][data-state="paused"],
|
||||
ha-icon[data-domain="media_player"][data-state="playing"],
|
||||
ha-icon[data-domain="script"][data-state="on"],
|
||||
ha-icon[data-domain="script"][data-state="running"],
|
||||
ha-icon[data-domain="sun"][data-state="above_horizon"],
|
||||
ha-icon[data-domain="switch"][data-state="on"],
|
||||
ha-icon[data-domain="timer"][data-state="active"],
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
@@ -34,8 +33,7 @@ import {
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-combo-box";
|
||||
import { HaComboBox } from "../ha-combo-box";
|
||||
|
||||
interface Device {
|
||||
name: string;
|
||||
@@ -109,9 +107,8 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@internalProperty() private _opened?: boolean;
|
||||
@property({ type: Boolean })
|
||||
private _opened?: boolean;
|
||||
|
||||
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||
|
||||
@@ -293,7 +290,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
: this.label}
|
||||
.value=${this._value}
|
||||
.renderer=${rowRenderer}
|
||||
.disabled=${this.disabled}
|
||||
item-value-path="id"
|
||||
item-id-path="id"
|
||||
item-label-path="name"
|
||||
|
||||
@@ -115,7 +115,7 @@ export class StateBadge extends LitElement {
|
||||
// eslint-disable-next-line
|
||||
console.warn(errorMessage);
|
||||
}
|
||||
// lowest brightness will be around 50% (that's pretty dark)
|
||||
// lowest brighntess will be around 50% (that's pretty dark)
|
||||
iconStyle.filter = `brightness(${(brightness + 245) / 5}%)`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { compare } from "../common/string/compare";
|
||||
import { HassioAddonInfo } from "../data/hassio/addon";
|
||||
import { fetchHassioSupervisorInfo } from "../data/hassio/supervisor";
|
||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { HaComboBox } from "./ha-combo-box";
|
||||
|
||||
const rowRenderer = (
|
||||
root: HTMLElement,
|
||||
_owner,
|
||||
model: { item: HassioAddonInfo }
|
||||
) => {
|
||||
if (!root.firstElementChild) {
|
||||
root.innerHTML = `
|
||||
<style>
|
||||
paper-item {
|
||||
margin: -10px 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<paper-item>
|
||||
<paper-item-body two-line="">
|
||||
<div class='name'>[[item.name]]</div>
|
||||
<div secondary>[[item.slug]]</div>
|
||||
</paper-item-body>
|
||||
</paper-item>
|
||||
`;
|
||||
}
|
||||
|
||||
root.querySelector(".name")!.textContent = model.item.name;
|
||||
root.querySelector("[secondary]")!.textContent = model.item.slug;
|
||||
};
|
||||
|
||||
@customElement("ha-addon-picker")
|
||||
class HaAddonPicker extends LitElement {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value = "";
|
||||
|
||||
@internalProperty() private _addons?: HassioAddonInfo[];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@query("ha-combo-box") private _comboBox!: HaComboBox;
|
||||
|
||||
public open() {
|
||||
this._comboBox?.open();
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this._comboBox?.focus();
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
this._getAddons();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._addons) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.label=${this.label === undefined && this.hass
|
||||
? this.hass.localize("ui.components.addon-picker.addon")
|
||||
: this.label}
|
||||
.value=${this._value}
|
||||
.renderer=${rowRenderer}
|
||||
.items=${this._addons}
|
||||
item-value-path="slug"
|
||||
item-id-path="slug"
|
||||
item-label-path="name"
|
||||
@value-changed=${this._addonChanged}
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _getAddons() {
|
||||
try {
|
||||
if (isComponentLoaded(this.hass, "hassio")) {
|
||||
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
this._addons = supervisorInfo.addons.sort((a, b) =>
|
||||
compare(a.name, b.name)
|
||||
);
|
||||
} else {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.componencts.addon-picker.error.no_supervisor.title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.componencts.addon-picker.error.no_supervisor.description"
|
||||
),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.componencts.addon-picker.error.fetch_addons.title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.componencts.addon-picker.error.fetch_addons.description"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.value || "";
|
||||
}
|
||||
|
||||
private _addonChanged(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
|
||||
if (newValue !== this._value) {
|
||||
this._setValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private _setValue(value: string) {
|
||||
this.value = value;
|
||||
setTimeout(() => {
|
||||
fireEvent(this, "value-changed", { value });
|
||||
fireEvent(this, "change");
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-addon-picker": HaAddonPicker;
|
||||
}
|
||||
}
|
||||
@@ -117,8 +117,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public entityFilter?: (entity: EntityRegistryEntry) => boolean;
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@internalProperty() private _areas?: AreaRegistryEntry[];
|
||||
|
||||
@internalProperty() private _devices?: DeviceRegistryEntry[];
|
||||
@@ -140,7 +138,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
this._devices = devices;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities.filter((entity) => entity.area_id);
|
||||
this._entities = entities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
@@ -193,14 +191,11 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
inputDevices = devices;
|
||||
inputEntities = entities;
|
||||
} else {
|
||||
if (deviceFilter) {
|
||||
inputDevices = devices;
|
||||
}
|
||||
if (entityFilter) {
|
||||
inputEntities = entities;
|
||||
}
|
||||
inputEntities = entities.filter((entity) => entity.area_id);
|
||||
} else if (deviceFilter) {
|
||||
inputDevices = devices;
|
||||
} else if (entityFilter) {
|
||||
inputEntities = entities.filter((entity) => entity.area_id);
|
||||
}
|
||||
|
||||
if (includeDomains) {
|
||||
@@ -344,7 +339,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
item-label-path="name"
|
||||
.value=${this._value}
|
||||
.renderer=${rowRenderer}
|
||||
.disabled=${this.disabled}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._areaChanged}
|
||||
>
|
||||
@@ -355,7 +349,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
.placeholder=${this.placeholder
|
||||
? this._area(this.placeholder)?.name
|
||||
: undefined}
|
||||
.disabled=${this.disabled}
|
||||
class="input"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { StreamLanguage } from "@codemirror/stream-parser";
|
||||
import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
|
||||
import { Editor } from "codemirror";
|
||||
import {
|
||||
customElement,
|
||||
internalProperty,
|
||||
@@ -16,40 +15,32 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const modeTag = Symbol("mode");
|
||||
|
||||
const readOnlyTag = Symbol("readOnly");
|
||||
|
||||
const saveKeyBinding: KeyBinding = {
|
||||
key: "Mod-s",
|
||||
run: (view: EditorView) => {
|
||||
fireEvent(view.dom, "editor-save");
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
@customElement("ha-code-editor")
|
||||
export class HaCodeEditor extends UpdatingElement {
|
||||
public codemirror?: EditorView;
|
||||
public codemirror?: Editor;
|
||||
|
||||
@property() public mode = "yaml";
|
||||
@property() public mode?: string;
|
||||
|
||||
@property({ type: Boolean }) public autofocus = false;
|
||||
|
||||
@property({ type: Boolean }) public readOnly = false;
|
||||
|
||||
@property() public rtl = false;
|
||||
|
||||
@property() public error = false;
|
||||
|
||||
@internalProperty() private _value = "";
|
||||
|
||||
@internalProperty() private _langs?: Record<string, StreamLanguage<unknown>>;
|
||||
|
||||
public set value(value: string) {
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
return this.codemirror ? this.codemirror.state.doc.toString() : this._value;
|
||||
return this.codemirror ? this.codemirror.getValue() : this._value;
|
||||
}
|
||||
|
||||
public get hasComments(): boolean {
|
||||
return !!this.shadowRoot!.querySelector("span.cm-comment");
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
@@ -57,6 +48,7 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
if (!this.codemirror) {
|
||||
return;
|
||||
}
|
||||
this.codemirror.refresh();
|
||||
if (this.autofocus !== false) {
|
||||
this.codemirror.focus();
|
||||
}
|
||||
@@ -70,27 +62,17 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
}
|
||||
|
||||
if (changedProps.has("mode")) {
|
||||
this.codemirror.dispatch({
|
||||
reconfigure: {
|
||||
[modeTag]: this._mode,
|
||||
},
|
||||
});
|
||||
this.codemirror.setOption("mode", this.mode);
|
||||
}
|
||||
if (changedProps.has("readOnly")) {
|
||||
this.codemirror.dispatch({
|
||||
reconfigure: {
|
||||
[readOnlyTag]: !this.readOnly,
|
||||
},
|
||||
});
|
||||
if (changedProps.has("autofocus")) {
|
||||
this.codemirror.setOption("autofocus", this.autofocus !== false);
|
||||
}
|
||||
if (changedProps.has("_value") && this._value !== this.value) {
|
||||
this.codemirror.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: this.codemirror.state.doc.length,
|
||||
insert: this._value,
|
||||
},
|
||||
});
|
||||
this.codemirror.setValue(this._value);
|
||||
}
|
||||
if (changedProps.has("rtl")) {
|
||||
this.codemirror.setOption("gutters", this._calcGutters());
|
||||
this._setScrollBarDirection();
|
||||
}
|
||||
if (changedProps.has("error")) {
|
||||
this.classList.toggle("error-state", this.error);
|
||||
@@ -103,66 +85,159 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
this._load();
|
||||
}
|
||||
|
||||
private get _mode() {
|
||||
return this._langs![this.mode];
|
||||
}
|
||||
|
||||
private async _load(): Promise<void> {
|
||||
const loaded = await loadCodeMirror();
|
||||
|
||||
this._langs = loaded.langs;
|
||||
const codeMirror = loaded.codeMirror;
|
||||
|
||||
const shadowRoot = this.attachShadow({ mode: "open" });
|
||||
|
||||
shadowRoot!.innerHTML = `<style>
|
||||
:host(.error-state) div.cm-wrap .cm-gutters {
|
||||
shadowRoot!.innerHTML = `
|
||||
<style>
|
||||
${loaded.codeMirrorCss}
|
||||
.CodeMirror {
|
||||
height: var(--code-mirror-height, auto);
|
||||
direction: var(--code-mirror-direction, ltr);
|
||||
font-family: var(--code-font-family, monospace);
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
max-height: var(--code-mirror-max-height, --code-mirror-height);
|
||||
}
|
||||
:host(.error-state) .CodeMirror-gutters {
|
||||
border-color: var(--error-state-color, red);
|
||||
}
|
||||
.CodeMirror-focused .CodeMirror-gutters {
|
||||
border-right: 2px solid var(--paper-input-container-focus-color, var(--primary-color));
|
||||
}
|
||||
.CodeMirror-linenumber {
|
||||
color: var(--paper-dialog-color, var(--secondary-text-color));
|
||||
}
|
||||
.rtl .CodeMirror-vscrollbar {
|
||||
right: auto;
|
||||
left: 0px;
|
||||
}
|
||||
.rtl-gutter {
|
||||
width: 20px;
|
||||
}
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid var(--paper-input-container-color, var(--secondary-text-color));
|
||||
background-color: var(--paper-dialog-background-color, var(--primary-background-color));
|
||||
transition: 0.2s ease border-right;
|
||||
}
|
||||
.cm-s-default.CodeMirror {
|
||||
background-color: var(--code-editor-background-color, var(--card-background-color));
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.cm-s-default .CodeMirror-cursor {
|
||||
border-left: 1px solid var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.cm-s-default div.CodeMirror-selected, .cm-s-default.CodeMirror-focused div.CodeMirror-selected {
|
||||
background: rgba(var(--rgb-primary-color), 0.2);
|
||||
}
|
||||
|
||||
.cm-s-default .CodeMirror-line::selection,
|
||||
.cm-s-default .CodeMirror-line>span::selection,
|
||||
.cm-s-default .CodeMirror-line>span>span::selection {
|
||||
background: rgba(var(--rgb-primary-color), 0.2);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-keyword {
|
||||
color: var(--codemirror-keyword, #6262FF);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-operator {
|
||||
color: var(--codemirror-operator, #cda869);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-variable-2 {
|
||||
color: var(--codemirror-variable-2, #690);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-builtin {
|
||||
color: var(--codemirror-builtin, #9B7536);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-atom {
|
||||
color: var(--codemirror-atom, #F90);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-number {
|
||||
color: var(--codemirror-number, #ca7841);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-def {
|
||||
color: var(--codemirror-def, #8DA6CE);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-string {
|
||||
color: var(--codemirror-string, #07a);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-string-2 {
|
||||
color: var(--codemirror-string-2, #bd6b18);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-comment {
|
||||
color: var(--codemirror-comment, #777);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-variable {
|
||||
color: var(--codemirror-variable, #07a);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-tag {
|
||||
color: var(--codemirror-tag, #997643);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-meta {
|
||||
color: var(--codemirror-meta, var(--primary-text-color));
|
||||
}
|
||||
|
||||
.cm-s-default .cm-attribute {
|
||||
color: var(--codemirror-attribute, #d6bb6d);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-property {
|
||||
color: var(--codemirror-property, #905);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-qualifier {
|
||||
color: var(--codemirror-qualifier, #690);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-variable-3 {
|
||||
color: var(--codemirror-variable-3, #07a);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-type {
|
||||
color: var(--codemirror-type, #07a);
|
||||
}
|
||||
</style>`;
|
||||
|
||||
const container = document.createElement("span");
|
||||
|
||||
shadowRoot.appendChild(container);
|
||||
|
||||
this.codemirror = new loaded.EditorView({
|
||||
state: loaded.EditorState.create({
|
||||
doc: this._value,
|
||||
extensions: [
|
||||
loaded.lineNumbers(),
|
||||
loaded.history(),
|
||||
loaded.highlightSelectionMatches(),
|
||||
loaded.keymap.of([
|
||||
...loaded.defaultKeymap,
|
||||
...loaded.searchKeymap,
|
||||
...loaded.historyKeymap,
|
||||
...loaded.tabKeyBindings,
|
||||
saveKeyBinding,
|
||||
] as KeyBinding[]),
|
||||
loaded.tagExtension(modeTag, this._mode),
|
||||
loaded.theme,
|
||||
loaded.Prec.fallback(loaded.highlightStyle),
|
||||
loaded.tagExtension(
|
||||
readOnlyTag,
|
||||
loaded.EditorView.editable.of(!this.readOnly)
|
||||
),
|
||||
loaded.EditorView.updateListener.of((update) =>
|
||||
this._onUpdate(update)
|
||||
),
|
||||
],
|
||||
}),
|
||||
root: shadowRoot,
|
||||
parent: container,
|
||||
this.codemirror = codeMirror(shadowRoot, {
|
||||
value: this._value,
|
||||
lineNumbers: true,
|
||||
tabSize: 2,
|
||||
mode: this.mode,
|
||||
autofocus: this.autofocus !== false,
|
||||
viewportMargin: Infinity,
|
||||
readOnly: this.readOnly,
|
||||
extraKeys: {
|
||||
Tab: "indentMore",
|
||||
"Shift-Tab": "indentLess",
|
||||
},
|
||||
gutters: this._calcGutters(),
|
||||
});
|
||||
this._setScrollBarDirection();
|
||||
this.codemirror!.on("changes", () => this._onChange());
|
||||
}
|
||||
|
||||
private _blockKeyboardShortcuts() {
|
||||
this.addEventListener("keydown", (ev) => ev.stopPropagation());
|
||||
}
|
||||
|
||||
private _onUpdate(update: ViewUpdate): void {
|
||||
if (!update.docChanged) {
|
||||
return;
|
||||
}
|
||||
private _onChange(): void {
|
||||
const newValue = this.value;
|
||||
if (newValue === this._value) {
|
||||
return;
|
||||
@@ -170,6 +245,16 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
this._value = newValue;
|
||||
fireEvent(this, "value-changed", { value: this._value });
|
||||
}
|
||||
|
||||
private _calcGutters(): string[] {
|
||||
return this.rtl ? ["rtl-gutter", "CodeMirror-linenumbers"] : [];
|
||||
}
|
||||
|
||||
private _setScrollBarDirection(): void {
|
||||
if (this.codemirror) {
|
||||
this.codemirror.getWrapperElement().classList.toggle("rtl", this.rtl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
@@ -68,9 +67,8 @@ export class HaComboBox extends LitElement {
|
||||
model: { item: any }
|
||||
) => void;
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@internalProperty() private _opened?: boolean;
|
||||
@property({ type: Boolean })
|
||||
private _opened?: boolean;
|
||||
|
||||
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
|
||||
|
||||
@@ -97,14 +95,12 @@ export class HaComboBox extends LitElement {
|
||||
.filteredItems=${this.filteredItems}
|
||||
.renderer=${this.renderer || defaultRowRenderer}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
.disabled=${this.disabled}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<paper-input
|
||||
.label=${this.label}
|
||||
.disabled=${this.disabled}
|
||||
class="input"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
|
||||
@@ -21,11 +21,8 @@ export class HaActionSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-automation-action
|
||||
.disabled=${this.disabled}
|
||||
.actions=${this.value || []}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>`;
|
||||
@@ -37,10 +34,6 @@ export class HaActionSelector extends LitElement {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
:host([disabled]) ha-automation-action {
|
||||
opacity: var(--light-disabled-opacity);
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { customElement, html, LitElement, property } from "lit-element";
|
||||
import { AddonSelector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-addon-picker";
|
||||
|
||||
@customElement("ha-selector-addon")
|
||||
export class HaAddonSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: AddonSelector;
|
||||
|
||||
@property() public value?: any;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-addon-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
allow-custom-entity
|
||||
></ha-addon-picker>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-addon": HaAddonSelector;
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,6 @@ export class HaAreaSelector extends LitElement {
|
||||
|
||||
@internalProperty() public _configEntries?: ConfigEntry[];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected updated(changedProperties) {
|
||||
if (changedProperties.has("selector")) {
|
||||
const oldSelector = changedProperties.get("selector");
|
||||
@@ -52,7 +50,6 @@ export class HaAreaSelector extends LitElement {
|
||||
.includeDomains=${this.selector.area.entity?.domain
|
||||
? [this.selector.area.entity.domain]
|
||||
: undefined}
|
||||
.disabled=${this.disabled}
|
||||
></ha-area-picker>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,14 +19,11 @@ export class HaBooleanSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
return html` <ha-formfield alignEnd spaceBetween .label=${this.label}>
|
||||
<ha-switch
|
||||
.checked=${this.value}
|
||||
@change=${this._handleChange}
|
||||
.disabled=${this.disabled}
|
||||
></ha-switch>
|
||||
</ha-formfield>`;
|
||||
}
|
||||
|
||||
@@ -23,12 +23,10 @@ export class HaDeviceSelector extends LitElement {
|
||||
|
||||
@internalProperty() public _configEntries?: ConfigEntry[];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected updated(changedProperties) {
|
||||
if (changedProperties.has("selector")) {
|
||||
const oldSelector = changedProperties.get("selector");
|
||||
if (oldSelector !== this.selector && this.selector.device?.integration) {
|
||||
if (oldSelector !== this.selector && this.selector.device.integration) {
|
||||
this._loadConfigEntries();
|
||||
}
|
||||
}
|
||||
@@ -46,25 +44,24 @@ export class HaDeviceSelector extends LitElement {
|
||||
.includeDomains=${this.selector.device.entity?.domain
|
||||
? [this.selector.device.entity.domain]
|
||||
: undefined}
|
||||
.disabled=${this.disabled}
|
||||
allow-custom-entity
|
||||
></ha-device-picker>`;
|
||||
}
|
||||
|
||||
private _filterDevices(device: DeviceRegistryEntry): boolean {
|
||||
if (
|
||||
this.selector.device?.manufacturer &&
|
||||
this.selector.device.manufacturer &&
|
||||
device.manufacturer !== this.selector.device.manufacturer
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
this.selector.device?.model &&
|
||||
this.selector.device.model &&
|
||||
device.model !== this.selector.device.model
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (this.selector.device?.integration) {
|
||||
if (this.selector.device.integration) {
|
||||
if (
|
||||
this._configEntries &&
|
||||
!this._configEntries.some((entry) =>
|
||||
|
||||
@@ -25,15 +25,12 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.entityFilter=${(entity) => this._filterEntities(entity)}
|
||||
.disabled=${this.disabled}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>`;
|
||||
}
|
||||
@@ -54,12 +51,12 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _filterEntities(entity: HassEntity): boolean {
|
||||
if (this.selector.entity?.domain) {
|
||||
if (this.selector.entity.domain) {
|
||||
if (computeStateDomain(entity) !== this.selector.entity.domain) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this.selector.entity?.device_class) {
|
||||
if (this.selector.entity.device_class) {
|
||||
if (
|
||||
!entity.attributes.device_class ||
|
||||
entity.attributes.device_class !== this.selector.entity.device_class
|
||||
@@ -67,7 +64,7 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this.selector.entity?.integration) {
|
||||
if (this.selector.entity.integration) {
|
||||
if (
|
||||
!this._entityPlaformLookup ||
|
||||
this._entityPlaformLookup[entity.entity_id] !==
|
||||
|
||||
@@ -21,12 +21,8 @@ export class HaNumberSelector extends LitElement {
|
||||
|
||||
@property() public value?: number;
|
||||
|
||||
@property() public placeholder?: number;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
return html`${this.label}
|
||||
${this.selector.number.mode === "slider"
|
||||
@@ -35,7 +31,6 @@ export class HaNumberSelector extends LitElement {
|
||||
.max=${this.selector.number.max}
|
||||
.value=${this._value}
|
||||
.step=${this.selector.number.step}
|
||||
.disabled=${this.disabled}
|
||||
pin
|
||||
ignore-bar-touch
|
||||
@change=${this._handleSliderChange}
|
||||
@@ -47,14 +42,12 @@ export class HaNumberSelector extends LitElement {
|
||||
.label=${this.selector.number.mode === "slider"
|
||||
? undefined
|
||||
: this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
.noLabelFloat=${this.selector.number.mode === "slider"}
|
||||
class=${classMap({ single: this.selector.number.mode === "box" })}
|
||||
.min=${this.selector.number.min}
|
||||
.max=${this.selector.number.max}
|
||||
.value=${this.value}
|
||||
.step=${this.selector.number.step}
|
||||
.disabled=${this.disabled}
|
||||
type="number"
|
||||
auto-validate
|
||||
@value-changed=${this._handleInputChange}
|
||||
|
||||
@@ -11,14 +11,8 @@ export class HaObjectSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-yaml-editor
|
||||
.disabled=${this.disabled}
|
||||
.placeholder=${this.placeholder}
|
||||
.defaultValue=${this.value}
|
||||
@value-changed=${this._handleChange}
|
||||
></ha-yaml-editor>`;
|
||||
|
||||
@@ -21,13 +21,8 @@ export class HaSelectSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-paper-dropdown-menu
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.label}
|
||||
>
|
||||
return html`<ha-paper-dropdown-menu .label=${this.label}>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="item-value"
|
||||
@@ -46,7 +41,7 @@ export class HaSelectSelector extends LitElement {
|
||||
}
|
||||
|
||||
private _valueChanged(ev) {
|
||||
if (this.disabled || !ev.detail.value) {
|
||||
if (!ev.detail.value) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
|
||||
@@ -42,8 +42,6 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
@internalProperty() private _configEntries?: ConfigEntry[];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
@@ -86,7 +84,6 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
||||
.includeDomains=${this.selector.target.entity?.domain
|
||||
? [this.selector.target.entity.domain]
|
||||
: undefined}
|
||||
.disabled=${this.disabled}
|
||||
></ha-target-picker>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,20 +13,14 @@ export class HaTextSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property() public selector!: StringSelector;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
if (this.selector.text?.multiline) {
|
||||
return html`<paper-textarea
|
||||
.label=${this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
.value=${this.value}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._handleChange}
|
||||
.value="${this.value}"
|
||||
@value-changed="${this._handleChange}"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
@@ -35,8 +29,6 @@ export class HaTextSelector extends LitElement {
|
||||
return html`<paper-input
|
||||
required
|
||||
.value=${this.value}
|
||||
.placeholder=${this.placeholder}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._handleChange}
|
||||
.label=${this.label}
|
||||
></paper-input>`;
|
||||
|
||||
@@ -17,8 +17,6 @@ export class HaTimeSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
const parts = this.value?.split(":") || [];
|
||||
const hours = useAMPM ? parts[0] ?? "12" : parts[0] ?? "0";
|
||||
@@ -31,7 +29,6 @@ export class HaTimeSelector extends LitElement {
|
||||
.sec=${parts[2] ?? "00"}
|
||||
.format=${useAMPM ? 12 : 24}
|
||||
.amPm=${useAMPM && (Number(hours) > 12 ? "PM" : "AM")}
|
||||
.disabled=${this.disabled}
|
||||
@change=${this._timeChanged}
|
||||
@am-pm-changed=${this._timeChanged}
|
||||
hide-label
|
||||
|
||||
@@ -3,7 +3,6 @@ import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import { Selector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "./ha-selector-action";
|
||||
import "./ha-selector-addon";
|
||||
import "./ha-selector-area";
|
||||
import "./ha-selector-boolean";
|
||||
import "./ha-selector-device";
|
||||
@@ -25,10 +24,6 @@ export class HaSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public placeholder?: any;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
public focus() {
|
||||
const input = this.shadowRoot!.getElementById("selector");
|
||||
if (!input) {
|
||||
@@ -48,8 +43,6 @@ export class HaSelector extends LitElement {
|
||||
selector: this.selector,
|
||||
value: this.value,
|
||||
label: this.label,
|
||||
placeholder: this.placeholder,
|
||||
disabled: this.disabled,
|
||||
id: "selector",
|
||||
})}
|
||||
`;
|
||||
|
||||
@@ -22,7 +22,6 @@ import "./ha-selector/ha-selector";
|
||||
import "./ha-service-picker";
|
||||
import "./ha-settings-row";
|
||||
import "./ha-yaml-editor";
|
||||
import "./ha-checkbox";
|
||||
import type { HaYamlEditor } from "./ha-yaml-editor";
|
||||
|
||||
interface ExtHassService extends Omit<HassService, "fields"> {
|
||||
@@ -31,7 +30,6 @@ interface ExtHassService extends Omit<HassService, "fields"> {
|
||||
name?: string;
|
||||
description: string;
|
||||
required?: boolean;
|
||||
advanced?: boolean;
|
||||
default?: any;
|
||||
example?: any;
|
||||
selector?: Selector;
|
||||
@@ -50,26 +48,14 @@ export class HaServiceControl extends LitElement {
|
||||
|
||||
@property({ reflect: true, type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property({ type: Boolean }) public showAdvanced?: boolean;
|
||||
|
||||
@internalProperty() private _serviceData?: ExtHassService;
|
||||
|
||||
@internalProperty() private _checkedKeys = new Set();
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("value")) {
|
||||
return;
|
||||
}
|
||||
const oldValue = changedProperties.get("value") as
|
||||
| undefined
|
||||
| this["value"];
|
||||
|
||||
if (oldValue?.service !== this.value?.service) {
|
||||
this._checkedKeys = new Set();
|
||||
}
|
||||
|
||||
this._serviceData = this.value?.service
|
||||
? this._getServiceInfo(this.value.service)
|
||||
: undefined;
|
||||
@@ -77,33 +63,13 @@ export class HaServiceControl extends LitElement {
|
||||
if (
|
||||
this._serviceData &&
|
||||
"target" in this._serviceData &&
|
||||
(this.value?.data?.entity_id ||
|
||||
this.value?.data?.area_id ||
|
||||
this.value?.data?.device_id)
|
||||
this.value?.data?.entity_id
|
||||
) {
|
||||
const target = {
|
||||
...this.value.target,
|
||||
};
|
||||
|
||||
if (this.value.data.entity_id && !this.value.target?.entity_id) {
|
||||
target.entity_id = this.value.data.entity_id;
|
||||
}
|
||||
if (this.value.data.area_id && !this.value.target?.area_id) {
|
||||
target.area_id = this.value.data.area_id;
|
||||
}
|
||||
if (this.value.data.device_id && !this.value.target?.device_id) {
|
||||
target.device_id = this.value.data.device_id;
|
||||
}
|
||||
|
||||
this.value = {
|
||||
...this.value,
|
||||
target,
|
||||
data: { ...this.value.data },
|
||||
target: { ...this.value.target, entity_id: this.value.data.entity_id },
|
||||
};
|
||||
|
||||
delete this.value.data!.entity_id;
|
||||
delete this.value.data!.device_id;
|
||||
delete this.value.data!.area_id;
|
||||
}
|
||||
|
||||
if (this.value?.data) {
|
||||
@@ -159,46 +125,24 @@ export class HaServiceControl extends LitElement {
|
||||
legacy &&
|
||||
this._serviceData?.fields.find((field) => field.key === "entity_id");
|
||||
|
||||
const hasOptional = Boolean(
|
||||
!legacy &&
|
||||
this._serviceData?.fields.some(
|
||||
(field) => field.selector && !field.required
|
||||
)
|
||||
);
|
||||
|
||||
return html`<ha-service-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value?.service}
|
||||
@value-changed=${this._serviceChanged}
|
||||
></ha-service-picker>
|
||||
<p>${this._serviceData?.description}</p>
|
||||
${this._serviceData && "target" in this._serviceData
|
||||
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||
${hasOptional
|
||||
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
||||
: ""}
|
||||
<span slot="heading"
|
||||
>${this.hass.localize(
|
||||
"ui.components.service-control.target"
|
||||
)}</span
|
||||
>
|
||||
<span slot="description"
|
||||
>${this.hass.localize(
|
||||
"ui.components.service-control.target_description"
|
||||
)}</span
|
||||
><ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${this._serviceData.target
|
||||
? { target: this._serviceData.target }
|
||||
: {
|
||||
target: {
|
||||
entity: { domain: computeDomain(this.value!.service) },
|
||||
},
|
||||
}}
|
||||
@value-changed=${this._targetChanged}
|
||||
.value=${this.value?.target}
|
||||
></ha-selector
|
||||
></ha-settings-row>`
|
||||
? html`<ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${this._serviceData.target
|
||||
? { target: this._serviceData.target }
|
||||
: {
|
||||
target: {
|
||||
entity: { domain: computeDomain(this.value!.service) },
|
||||
},
|
||||
}}
|
||||
@value-changed=${this._targetChanged}
|
||||
.value=${this.value?.target}
|
||||
></ha-selector>`
|
||||
: entityId
|
||||
? html`<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
@@ -212,76 +156,38 @@ export class HaServiceControl extends LitElement {
|
||||
${legacy
|
||||
? html`<ha-yaml-editor
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.service-control.service_data"
|
||||
"ui.panel.config.automation.editor.actions.type.service.service_data"
|
||||
)}
|
||||
.name=${"data"}
|
||||
.defaultValue=${this.value?.data}
|
||||
@value-changed=${this._dataChanged}
|
||||
></ha-yaml-editor>`
|
||||
: this._serviceData?.fields.map((dataField) =>
|
||||
dataField.selector && (!dataField.advanced || this.showAdvanced)
|
||||
dataField.selector
|
||||
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||
${dataField.required
|
||||
? hasOptional
|
||||
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
||||
: ""
|
||||
: html`<ha-checkbox
|
||||
.key=${dataField.key}
|
||||
.checked=${this._checkedKeys.has(dataField.key) ||
|
||||
(this.value?.data &&
|
||||
this.value.data[dataField.key] !== undefined)}
|
||||
@change=${this._checkboxChanged}
|
||||
slot="prefix"
|
||||
></ha-checkbox>`}
|
||||
<span slot="heading">${dataField.name || dataField.key}</span>
|
||||
<span slot="description">${dataField?.description}</span
|
||||
><ha-selector
|
||||
.disabled=${!dataField.required &&
|
||||
!this._checkedKeys.has(dataField.key) &&
|
||||
(!this.value?.data ||
|
||||
this.value.data[dataField.key] === undefined)}
|
||||
.hass=${this.hass}
|
||||
.selector=${dataField.selector}
|
||||
.key=${dataField.key}
|
||||
@value-changed=${this._serviceDataChanged}
|
||||
.value=${this.value?.data &&
|
||||
this.value.data[dataField.key] !== undefined
|
||||
? this.value.data[dataField.key]
|
||||
: dataField.default}
|
||||
.value=${(this.value?.data &&
|
||||
this.value.data[dataField.key]) ||
|
||||
dataField.default}
|
||||
></ha-selector
|
||||
></ha-settings-row>`
|
||||
: ""
|
||||
)} `;
|
||||
}
|
||||
|
||||
private _checkboxChanged(ev) {
|
||||
const checked = ev.currentTarget.checked;
|
||||
const key = ev.currentTarget.key;
|
||||
if (checked) {
|
||||
this._checkedKeys.add(key);
|
||||
} else {
|
||||
this._checkedKeys.delete(key);
|
||||
const data = { ...this.value?.data };
|
||||
|
||||
delete data[key];
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.value,
|
||||
data,
|
||||
},
|
||||
});
|
||||
}
|
||||
this.requestUpdate("_checkedKeys");
|
||||
}
|
||||
|
||||
private _serviceChanged(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
if (ev.detail.value === this.value?.service) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { service: ev.detail.value || "" },
|
||||
value: { service: ev.detail.value || "", data: {} },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -362,27 +268,10 @@ export class HaServiceControl extends LitElement {
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-settings-row {
|
||||
padding: var(--service-control-padding, 0 16px);
|
||||
padding: 0;
|
||||
}
|
||||
ha-settings-row {
|
||||
--paper-time-input-justify-content: flex-end;
|
||||
border-top: var(
|
||||
--service-control-items-border-top,
|
||||
1px solid var(--divider-color)
|
||||
);
|
||||
}
|
||||
ha-service-picker,
|
||||
ha-entity-picker,
|
||||
ha-yaml-editor {
|
||||
display: block;
|
||||
margin: var(--service-control-padding, 0 16px);
|
||||
}
|
||||
ha-yaml-editor {
|
||||
padding: 16px 0;
|
||||
}
|
||||
p {
|
||||
margin: var(--service-control-padding, 0 16px);
|
||||
padding: 16px 0;
|
||||
}
|
||||
:host(:not([narrow])) ha-settings-row paper-input {
|
||||
width: 60%;
|
||||
@@ -390,12 +279,6 @@ export class HaServiceControl extends LitElement {
|
||||
:host(:not([narrow])) ha-settings-row ha-selector {
|
||||
width: 60%;
|
||||
}
|
||||
.checkbox-spacer {
|
||||
width: 32px;
|
||||
}
|
||||
ha-checkbox {
|
||||
margin-left: -16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { html, internalProperty, LitElement, property } from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { domainToName } from "../data/integration";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-combo-box";
|
||||
|
||||
const rowRenderer = (
|
||||
root: HTMLElement,
|
||||
_owner,
|
||||
model: { item: { service: string; name: string } }
|
||||
model: { item: { service: string; description: string } }
|
||||
) => {
|
||||
if (!root.firstElementChild) {
|
||||
root.innerHTML = `
|
||||
@@ -21,16 +19,15 @@ const rowRenderer = (
|
||||
</style>
|
||||
<paper-item>
|
||||
<paper-item-body two-line="">
|
||||
<div class='name'>[[item.name]]</div>
|
||||
<div class='name'>[[item.description]]</div>
|
||||
<div secondary>[[item.service]]</div>
|
||||
</paper-item-body>
|
||||
</paper-item>
|
||||
`;
|
||||
}
|
||||
|
||||
root.querySelector(".name")!.textContent = model.item.name;
|
||||
root.querySelector("[secondary]")!.textContent =
|
||||
model.item.name === model.item.service ? "" : model.item.service;
|
||||
root.querySelector(".name")!.textContent = model.item.description;
|
||||
root.querySelector("[secondary]")!.textContent = model.item.service;
|
||||
};
|
||||
|
||||
class HaServicePicker extends LitElement {
|
||||
@@ -46,14 +43,13 @@ class HaServicePicker extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize("ui.components.service-picker.service")}
|
||||
.filteredItems=${this._filteredServices(
|
||||
this.hass.localize,
|
||||
this.hass.services,
|
||||
this._filter
|
||||
)}
|
||||
.value=${this.value}
|
||||
.renderer=${rowRenderer}
|
||||
item-value-path="service"
|
||||
item-label-path="name"
|
||||
item-label-path="description"
|
||||
allow-custom-value
|
||||
@filter-changed=${this._filterChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
@@ -61,48 +57,38 @@ class HaServicePicker extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _services = memoizeOne(
|
||||
(
|
||||
localize: LocalizeFunc,
|
||||
services: HomeAssistant["services"]
|
||||
): {
|
||||
service: string;
|
||||
name: string;
|
||||
}[] => {
|
||||
if (!services) {
|
||||
return [];
|
||||
}
|
||||
const result: { service: string; name: string }[] = [];
|
||||
|
||||
Object.keys(services)
|
||||
.sort()
|
||||
.forEach((domain) => {
|
||||
const services_keys = Object.keys(services[domain]).sort();
|
||||
|
||||
for (const service of services_keys) {
|
||||
result.push({
|
||||
service: `${domain}.${service}`,
|
||||
name: `${domainToName(localize, domain)}: ${
|
||||
services[domain][service].name || service
|
||||
}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
private _services = memoizeOne((services: HomeAssistant["services"]): {
|
||||
service: string;
|
||||
description: string;
|
||||
}[] => {
|
||||
if (!services) {
|
||||
return [];
|
||||
}
|
||||
);
|
||||
const result: { service: string; description: string }[] = [];
|
||||
|
||||
Object.keys(services)
|
||||
.sort()
|
||||
.forEach((domain) => {
|
||||
const services_keys = Object.keys(services[domain]).sort();
|
||||
|
||||
for (const service of services_keys) {
|
||||
result.push({
|
||||
service: `${domain}.${service}`,
|
||||
description:
|
||||
services[domain][service].description || `${domain}.${service}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
private _filteredServices = memoizeOne(
|
||||
(
|
||||
localize: LocalizeFunc,
|
||||
services: HomeAssistant["services"],
|
||||
filter?: string
|
||||
) => {
|
||||
(services: HomeAssistant["services"], filter?: string) => {
|
||||
if (!services) {
|
||||
return [];
|
||||
}
|
||||
const processedServices = this._services(localize, services);
|
||||
const processedServices = this._services(services);
|
||||
|
||||
if (!filter) {
|
||||
return processedServices;
|
||||
@@ -110,7 +96,7 @@ class HaServicePicker extends LitElement {
|
||||
return processedServices.filter(
|
||||
(service) =>
|
||||
service.service.toLowerCase().includes(filter) ||
|
||||
service.name?.toLowerCase().includes(filter)
|
||||
service.description.toLowerCase().includes(filter)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
SVGTemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
@customElement("ha-settings-row")
|
||||
@@ -16,18 +16,15 @@ export class HaSettingsRow extends LitElement {
|
||||
@property({ type: Boolean, attribute: "three-line" })
|
||||
public threeLine = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
protected render(): SVGTemplateResult {
|
||||
return html`
|
||||
<div class="prefix-wrap">
|
||||
<slot name="prefix"></slot>
|
||||
<paper-item-body
|
||||
?two-line=${!this.threeLine}
|
||||
?three-line=${this.threeLine}
|
||||
>
|
||||
<slot name="heading"></slot>
|
||||
<div secondary><slot name="description"></slot></div>
|
||||
</paper-item-body>
|
||||
</div>
|
||||
<paper-item-body
|
||||
?two-line=${!this.threeLine}
|
||||
?three-line=${this.threeLine}
|
||||
>
|
||||
<slot name="heading"></slot>
|
||||
<div secondary><slot name="description"></slot></div>
|
||||
</paper-item-body>
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
@@ -62,13 +59,6 @@ export class HaSettingsRow extends LitElement {
|
||||
div[secondary] {
|
||||
white-space: normal;
|
||||
}
|
||||
.prefix-wrap {
|
||||
display: contents;
|
||||
}
|
||||
:host([narrow]) .prefix-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,14 +79,6 @@ class HaSlider extends PaperSliderClass {
|
||||
return subTemplate;
|
||||
}
|
||||
|
||||
_setImmediateValue(newImmediateValue) {
|
||||
super._setImmediateValue(
|
||||
this.step >= 1
|
||||
? Math.round(newImmediateValue)
|
||||
: Math.round(newImmediateValue * 100) / 100
|
||||
);
|
||||
}
|
||||
|
||||
_calcStep(value) {
|
||||
if (!this.step) {
|
||||
return parseFloat(value);
|
||||
|
||||
@@ -84,8 +84,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@internalProperty() private _areas?: { [areaId: string]: AreaRegistryEntry };
|
||||
|
||||
@internalProperty() private _devices?: {
|
||||
@@ -440,9 +438,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
type: string,
|
||||
id: string
|
||||
): this["value"] {
|
||||
const newVal = ensureArray(value![type])!.filter(
|
||||
(val) => String(val) !== id
|
||||
);
|
||||
const newVal = ensureArray(value![type])!.filter((val) => val !== id);
|
||||
if (newVal.length) {
|
||||
return {
|
||||
...value,
|
||||
@@ -603,10 +599,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
paper-tooltip.expand {
|
||||
min-width: 200px;
|
||||
}
|
||||
:host([disabled]) .mdc-chip {
|
||||
opacity: var(--light-disabled-opacity);
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,20 @@ import {
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { afterNextRender } from "../common/util/render-status";
|
||||
import "./ha-code-editor";
|
||||
import type { HaCodeEditor } from "./ha-code-editor";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"editor-refreshed": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const isEmpty = (obj: Record<string, unknown>): boolean => {
|
||||
if (typeof obj !== "object") {
|
||||
@@ -34,14 +44,22 @@ export class HaYamlEditor extends LitElement {
|
||||
|
||||
@internalProperty() private _yaml = "";
|
||||
|
||||
@query("ha-code-editor", true) private _editor?: HaCodeEditor;
|
||||
|
||||
public setValue(value): void {
|
||||
try {
|
||||
this._yaml = value && !isEmpty(value) ? safeDump(value) : "";
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err, value);
|
||||
console.error(err);
|
||||
alert(`There was an error converting to YAML: ${err}`);
|
||||
}
|
||||
afterNextRender(() => {
|
||||
if (this._editor?.codemirror) {
|
||||
this._editor.codemirror.refresh();
|
||||
}
|
||||
afterNextRender(() => fireEvent(this, "editor-refreshed"));
|
||||
});
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
@@ -55,7 +73,7 @@ export class HaYamlEditor extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${this.label ? html`<p>${this.label}</p>` : ""}
|
||||
${this.label ? html` <p>${this.label}</p> ` : ""}
|
||||
<ha-code-editor
|
||||
.value=${this._yaml}
|
||||
mode="yaml"
|
||||
@@ -67,13 +85,13 @@ export class HaYamlEditor extends LitElement {
|
||||
|
||||
private _onChange(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
this._yaml = ev.detail.value;
|
||||
const value = ev.detail.value;
|
||||
let parsed;
|
||||
let isValid = true;
|
||||
|
||||
if (this._yaml) {
|
||||
if (value) {
|
||||
try {
|
||||
parsed = safeLoad(this._yaml);
|
||||
parsed = safeLoad(value);
|
||||
} catch (err) {
|
||||
// Invalid YAML
|
||||
isValid = false;
|
||||
@@ -89,7 +107,7 @@ export class HaYamlEditor extends LitElement {
|
||||
}
|
||||
|
||||
get yaml() {
|
||||
return this._yaml;
|
||||
return this._editor?.value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "./state-history-chart-line";
|
||||
@@ -84,10 +83,6 @@ class StateHistoryCharts extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
return !(changedProps.size === 1 && changedProps.has("hass"));
|
||||
}
|
||||
|
||||
private _isHistoryEmpty(): boolean {
|
||||
const historyDataEmpty =
|
||||
!this.historyData ||
|
||||
|
||||
@@ -205,13 +205,9 @@ export type Condition =
|
||||
| DeviceCondition
|
||||
| LogicalCondition;
|
||||
|
||||
export const triggerAutomationActions = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string
|
||||
) => {
|
||||
export const triggerAutomation = (hass: HomeAssistant, entityId: string) => {
|
||||
hass.callService("automation", "trigger", {
|
||||
entity_id: entityId,
|
||||
skip_condition: true,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ export interface ConfigEntry {
|
||||
connection_class: string;
|
||||
supports_options: boolean;
|
||||
supports_unload: boolean;
|
||||
disabled_by: string | null;
|
||||
}
|
||||
|
||||
export interface ConfigEntryMutableParams {
|
||||
@@ -44,27 +43,6 @@ export const reloadConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
|
||||
require_restart: boolean;
|
||||
}>("POST", `config/config_entries/entry/${configEntryId}/reload`);
|
||||
|
||||
export const disableConfigEntry = (
|
||||
hass: HomeAssistant,
|
||||
configEntryId: string
|
||||
) =>
|
||||
hass.callWS<{
|
||||
require_restart: boolean;
|
||||
}>({
|
||||
type: "config_entries/disable",
|
||||
entry_id: configEntryId,
|
||||
disabled_by: "user",
|
||||
});
|
||||
|
||||
export const enableConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
|
||||
hass.callWS<{
|
||||
require_restart: boolean;
|
||||
}>({
|
||||
type: "config_entries/disable",
|
||||
entry_id: configEntryId,
|
||||
disabled_by: null,
|
||||
});
|
||||
|
||||
export const getConfigEntrySystemOptions = (
|
||||
hass: HomeAssistant,
|
||||
configEntryId: string
|
||||
|
||||
@@ -65,18 +65,16 @@ export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) =>
|
||||
export const getConfigFlowHandlers = (hass: HomeAssistant) =>
|
||||
hass.callApi<string[]>("GET", "config/config_entries/flow_handlers");
|
||||
|
||||
export const fetchConfigFlowInProgress = (
|
||||
conn: Connection
|
||||
): Promise<DataEntryFlowProgress[]> =>
|
||||
const fetchConfigFlowInProgress = (conn) =>
|
||||
conn.sendMessagePromise({
|
||||
type: "config_entries/flow/progress",
|
||||
});
|
||||
|
||||
const subscribeConfigFlowInProgressUpdates = (conn: Connection, store) =>
|
||||
const subscribeConfigFlowInProgressUpdates = (conn, store) =>
|
||||
conn.subscribeEvents(
|
||||
debounce(
|
||||
() =>
|
||||
fetchConfigFlowInProgress(conn).then((flows: DataEntryFlowProgress[]) =>
|
||||
fetchConfigFlowInProgress(conn).then((flows) =>
|
||||
store.setState(flows, true)
|
||||
),
|
||||
500,
|
||||
|
||||
@@ -4,33 +4,20 @@ import { HomeAssistant } from "../../types";
|
||||
import { SupervisorArch } from "../supervisor/supervisor";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
|
||||
export type AddonStage = "stable" | "experimental" | "deprecated";
|
||||
export type AddonAppArmour = "disable" | "default" | "profile";
|
||||
export type AddonRole = "default" | "homeassistant" | "manager" | "admin";
|
||||
export type AddonStartup =
|
||||
| "initialize"
|
||||
| "system"
|
||||
| "services"
|
||||
| "application"
|
||||
| "once";
|
||||
export type AddonState = "started" | "stopped" | null;
|
||||
export type AddonRepository = "core" | "local" | string;
|
||||
|
||||
export interface HassioAddonInfo {
|
||||
advanced: boolean;
|
||||
available: boolean;
|
||||
build: boolean;
|
||||
description: string;
|
||||
detached: boolean;
|
||||
homeassistant: string;
|
||||
icon: boolean;
|
||||
installed: boolean;
|
||||
logo: boolean;
|
||||
name: string;
|
||||
repository: AddonRepository;
|
||||
repository: "core" | "local" | string;
|
||||
slug: string;
|
||||
stage: AddonStage;
|
||||
state: AddonState;
|
||||
stage: "stable" | "experimental" | "deprecated";
|
||||
state: "started" | "stopped" | null;
|
||||
update_available: boolean;
|
||||
url: string | null;
|
||||
version_latest: string;
|
||||
@@ -38,7 +25,7 @@ export interface HassioAddonInfo {
|
||||
}
|
||||
|
||||
export interface HassioAddonDetails extends HassioAddonInfo {
|
||||
apparmor: AddonAppArmour;
|
||||
apparmor: "disable" | "default" | "profile";
|
||||
arch: SupervisorArch[];
|
||||
audio_input: null | string;
|
||||
audio_output: null | string;
|
||||
@@ -56,9 +43,10 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
||||
full_access: boolean;
|
||||
gpio: boolean;
|
||||
hassio_api: boolean;
|
||||
hassio_role: AddonRole;
|
||||
hassio_role: "default" | "homeassistant" | "manager" | "admin";
|
||||
hostname: string;
|
||||
homeassistant_api: boolean;
|
||||
homeassistant: string;
|
||||
host_dbus: boolean;
|
||||
host_ipc: boolean;
|
||||
host_network: boolean;
|
||||
@@ -80,7 +68,7 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
||||
schema: HaFormSchema[] | null;
|
||||
services_role: string[];
|
||||
slug: string;
|
||||
startup: AddonStartup;
|
||||
startup: "initialize" | "system" | "services" | "application" | "once";
|
||||
stdin: boolean;
|
||||
watchdog: null | boolean;
|
||||
webui: null | string;
|
||||
@@ -300,7 +288,7 @@ export const updateHassioAddon = async (
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/store/addons/${slug}/update`,
|
||||
endpoint: `/addons/${slug}/update`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
|
||||
@@ -37,9 +37,8 @@ export const validateHassioSession = async (
|
||||
type: "supervisor/api",
|
||||
endpoint: "/ingress/validate_session",
|
||||
method: "post",
|
||||
data: { session },
|
||||
data: session,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
|
||||
@@ -29,10 +29,9 @@ export interface HassioFullSnapshotCreateParams {
|
||||
}
|
||||
export interface HassioPartialSnapshotCreateParams {
|
||||
name: string;
|
||||
folders?: string[];
|
||||
addons?: string[];
|
||||
folders: string[];
|
||||
addons: string[];
|
||||
password?: string;
|
||||
homeassistant?: boolean;
|
||||
}
|
||||
|
||||
export const fetchHassioSnapshots = async (
|
||||
@@ -117,7 +116,7 @@ export const createHassioFullSnapshot = async (
|
||||
|
||||
export const createHassioPartialSnapshot = async (
|
||||
hass: HomeAssistant,
|
||||
data: HassioPartialSnapshotCreateParams
|
||||
data: HassioFullSnapshotCreateParams
|
||||
) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
|
||||
+4
-11
@@ -216,6 +216,7 @@ export const getLogbookMessage = (
|
||||
case "cold":
|
||||
case "gas":
|
||||
case "heat":
|
||||
case "colightld":
|
||||
case "moisture":
|
||||
case "motion":
|
||||
case "occupancy":
|
||||
@@ -245,17 +246,9 @@ export const getLogbookMessage = (
|
||||
}
|
||||
|
||||
case "cover":
|
||||
switch (state) {
|
||||
case "open":
|
||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_opened`);
|
||||
case "opening":
|
||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.is_opening`);
|
||||
case "closing":
|
||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.is_closing`);
|
||||
case "closed":
|
||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_closed`);
|
||||
}
|
||||
break;
|
||||
return state === "open"
|
||||
? hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_opened`)
|
||||
: hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_closed`);
|
||||
|
||||
case "lock":
|
||||
if (state === "unlocked") {
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@ export const triggerScript = (
|
||||
variables?: Record<string, unknown>
|
||||
) => hass.callService("script", computeObjectId(entityId), variables);
|
||||
|
||||
export const canRun = (state: ScriptEntity) => {
|
||||
export const canExcecute = (state: ScriptEntity) => {
|
||||
if (state.state === "off") {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export type Selector =
|
||||
| AddonSelector
|
||||
| EntitySelector
|
||||
| DeviceSelector
|
||||
| AreaSelector
|
||||
@@ -31,13 +30,6 @@ export interface DeviceSelector {
|
||||
};
|
||||
}
|
||||
|
||||
export interface AddonSelector {
|
||||
addon: {
|
||||
name?: string;
|
||||
slug?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AreaSelector {
|
||||
area: {
|
||||
entity?: {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { AddonRepository, AddonStage } from "../hassio/addon";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "../hassio/common";
|
||||
|
||||
export interface StoreAddon {
|
||||
advanced: boolean;
|
||||
available: boolean;
|
||||
build: boolean;
|
||||
description: string;
|
||||
homeassistant: string | null;
|
||||
icon: boolean;
|
||||
installed: boolean;
|
||||
logo: boolean;
|
||||
name: string;
|
||||
repository: AddonRepository;
|
||||
slug: string;
|
||||
stage: AddonStage;
|
||||
update_available: boolean;
|
||||
url: string;
|
||||
version: string | null;
|
||||
version_latest: string;
|
||||
}
|
||||
interface StoreRepository {
|
||||
maintainer: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
source: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface SupervisorStore {
|
||||
addons: StoreAddon[];
|
||||
repositories: StoreRepository[];
|
||||
}
|
||||
|
||||
export const fetchSupervisorStore = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<SupervisorStore> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/store",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<SupervisorStore>>("GET", `hassio/store`)
|
||||
);
|
||||
};
|
||||
@@ -10,14 +10,13 @@ import {
|
||||
HassioInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../hassio/supervisor";
|
||||
import { SupervisorStore } from "./store";
|
||||
|
||||
export const supervisorWSbaseCommand = {
|
||||
type: "supervisor/api",
|
||||
method: "GET",
|
||||
};
|
||||
|
||||
export const supervisorCollection = {
|
||||
export const supervisorStore = {
|
||||
host: "/host/info",
|
||||
supervisor: "/supervisor/info",
|
||||
info: "/info",
|
||||
@@ -26,7 +25,6 @@ export const supervisorCollection = {
|
||||
resolution: "/resolution/info",
|
||||
os: "/os/info",
|
||||
addon: "/addons",
|
||||
store: "/store",
|
||||
};
|
||||
|
||||
export type SupervisorArch = "armhf" | "armv7" | "aarch64" | "i386" | "amd64";
|
||||
@@ -38,13 +36,11 @@ export type SupervisorObject =
|
||||
| "network"
|
||||
| "resolution"
|
||||
| "os"
|
||||
| "addon"
|
||||
| "store";
|
||||
| "addon";
|
||||
|
||||
interface supervisorApiRequest {
|
||||
endpoint: string;
|
||||
method?: "get" | "post" | "delete" | "put";
|
||||
force_rest?: boolean;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
@@ -55,6 +51,14 @@ export interface SupervisorEvent {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface SupervisorAPIRequestParams {
|
||||
connection?: any;
|
||||
rest?: boolean;
|
||||
data?: any;
|
||||
endpoint: string;
|
||||
method?: "get" | "post" | "delete" | "put";
|
||||
}
|
||||
|
||||
export interface Supervisor {
|
||||
host: HassioHostInfo;
|
||||
supervisor: HassioSupervisorInfo;
|
||||
@@ -64,7 +68,7 @@ export interface Supervisor {
|
||||
resolution: HassioResolution;
|
||||
os: HassioHassOSInfo;
|
||||
addon: HassioAddonsInfo;
|
||||
store: SupervisorStore;
|
||||
callApi<T>(params: SupervisorAPIRequestParams): Promise<T>;
|
||||
}
|
||||
|
||||
export const supervisorApiWsRequest = <T>(
|
||||
@@ -79,13 +83,17 @@ async function processEvent(
|
||||
event: SupervisorEvent,
|
||||
key: string
|
||||
) {
|
||||
if (event.event !== "supervisor-update" || event.update_key !== key) {
|
||||
if (
|
||||
!event.data ||
|
||||
event.data.event !== "supervisor-update" ||
|
||||
event.data.update_key !== key
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.keys(event.data).length === 0) {
|
||||
if (Object.keys(event.data.data).length === 0) {
|
||||
const data = await supervisorApiWsRequest<any>(conn, {
|
||||
endpoint: supervisorCollection[key],
|
||||
endpoint: supervisorStore[key],
|
||||
});
|
||||
store.setState(data);
|
||||
return;
|
||||
@@ -98,7 +106,7 @@ async function processEvent(
|
||||
|
||||
store.setState({
|
||||
...state,
|
||||
...event.data,
|
||||
...event.data.data,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -107,11 +115,9 @@ const subscribeSupervisorEventUpdates = (
|
||||
store: Store<unknown>,
|
||||
key: string
|
||||
) =>
|
||||
conn.subscribeMessage(
|
||||
conn.subscribeEvents(
|
||||
(event) => processEvent(conn, store, event as SupervisorEvent, key),
|
||||
{
|
||||
type: "supervisor/subscribe",
|
||||
}
|
||||
"supervisor_event"
|
||||
);
|
||||
|
||||
export const getSupervisorEventCollection = (
|
||||
|
||||
@@ -89,11 +89,6 @@ export const reconfigureNode = (
|
||||
ieee: ieeeAddress,
|
||||
});
|
||||
|
||||
export const refreshTopology = (hass: HomeAssistant): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "zha/topology/update",
|
||||
});
|
||||
|
||||
export const fetchAttributesForCluster = (
|
||||
hass: HomeAssistant,
|
||||
ieeeAddress: string,
|
||||
|
||||
@@ -22,9 +22,7 @@ import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../data/area_registry";
|
||||
import { fetchConfigFlowInProgress } from "../../data/config_flow";
|
||||
import type {
|
||||
DataEntryFlowProgress,
|
||||
DataEntryFlowProgressedEvent,
|
||||
DataEntryFlowStep,
|
||||
} from "../../data/data_entry_flow";
|
||||
@@ -43,7 +41,6 @@ import "./step-flow-form";
|
||||
import "./step-flow-loading";
|
||||
import "./step-flow-pick-handler";
|
||||
import "./step-flow-progress";
|
||||
import "./step-flow-pick-flow";
|
||||
|
||||
let instance = 0;
|
||||
|
||||
@@ -79,10 +76,6 @@ class DataEntryFlowDialog extends LitElement {
|
||||
|
||||
@internalProperty() private _handlers?: string[];
|
||||
|
||||
@internalProperty() private _handler?: string;
|
||||
|
||||
@internalProperty() private _flowsInProgress?: DataEntryFlowProgress[];
|
||||
|
||||
private _unsubAreas?: UnsubscribeFunc;
|
||||
|
||||
private _unsubDevices?: UnsubscribeFunc;
|
||||
@@ -91,93 +84,59 @@ class DataEntryFlowDialog extends LitElement {
|
||||
this._params = params;
|
||||
this._instance = instance++;
|
||||
|
||||
if (params.startFlowHandler) {
|
||||
this._checkFlowsInProgress(params.startFlowHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.continueFlowId) {
|
||||
this._loading = true;
|
||||
const curInstance = this._instance;
|
||||
let step: DataEntryFlowStep;
|
||||
try {
|
||||
step = await params.flowConfig.fetchFlow(
|
||||
this.hass,
|
||||
params.continueFlowId
|
||||
);
|
||||
} catch (err) {
|
||||
this._step = undefined;
|
||||
this._params = undefined;
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.error"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.could_not_load"
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Happens if second showDialog called
|
||||
if (curInstance !== this._instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._processStep(step);
|
||||
this._loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new config flow. Show picker
|
||||
if (!params.flowConfig.getFlowHandlers) {
|
||||
throw new Error("No getFlowHandlers defined in flow config");
|
||||
}
|
||||
this._step = null;
|
||||
|
||||
// We only load the handlers once
|
||||
if (this._handlers === undefined) {
|
||||
this._loading = true;
|
||||
try {
|
||||
this._handlers = await params.flowConfig.getFlowHandlers(this.hass);
|
||||
} finally {
|
||||
this._loading = false;
|
||||
if (!params.continueFlowId && !params.startFlowHandler) {
|
||||
if (!params.flowConfig.getFlowHandlers) {
|
||||
throw new Error("No getFlowHandlers defined in flow config");
|
||||
}
|
||||
this._step = null;
|
||||
|
||||
// We only load the handlers once
|
||||
if (this._handlers === undefined) {
|
||||
this._loading = true;
|
||||
try {
|
||||
this._handlers = await params.flowConfig.getFlowHandlers(this.hass);
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
await this.updateComplete;
|
||||
return;
|
||||
}
|
||||
await this.updateComplete;
|
||||
|
||||
this._loading = true;
|
||||
const curInstance = this._instance;
|
||||
let step: DataEntryFlowStep;
|
||||
try {
|
||||
step = await (params.continueFlowId
|
||||
? params.flowConfig.fetchFlow(this.hass, params.continueFlowId)
|
||||
: params.flowConfig.createFlow(this.hass, params.startFlowHandler!));
|
||||
} catch (err) {
|
||||
this._step = undefined;
|
||||
this._params = undefined;
|
||||
showAlertDialog(this, {
|
||||
title: "Error",
|
||||
text: "Config flow could not be loaded",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Happens if second showDialog called
|
||||
if (curInstance !== this._instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._processStep(step);
|
||||
this._loading = false;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
const flowFinished = Boolean(
|
||||
this._step && ["create_entry", "abort"].includes(this._step.type)
|
||||
);
|
||||
|
||||
// If we created this flow, delete it now.
|
||||
if (this._step && !flowFinished && !this._params.continueFlowId) {
|
||||
this._params.flowConfig.deleteFlow(this.hass, this._step.flow_id);
|
||||
}
|
||||
|
||||
if (this._step !== null && this._params.dialogClosedCallback) {
|
||||
this._params.dialogClosedCallback({
|
||||
flowFinished,
|
||||
});
|
||||
}
|
||||
|
||||
this._step = undefined;
|
||||
this._params = undefined;
|
||||
this._devices = undefined;
|
||||
this._flowsInProgress = undefined;
|
||||
this._handler = undefined;
|
||||
if (this._unsubAreas) {
|
||||
this._unsubAreas();
|
||||
this._unsubAreas = undefined;
|
||||
}
|
||||
if (this._unsubDevices) {
|
||||
this._unsubDevices();
|
||||
this._unsubDevices = undefined;
|
||||
if (this._step) {
|
||||
this._flowDone();
|
||||
} else if (this._step === null) {
|
||||
// Flow aborted during picking flow
|
||||
this._step = undefined;
|
||||
this._params = undefined;
|
||||
}
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
@@ -197,9 +156,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
>
|
||||
<div>
|
||||
${this._loading ||
|
||||
(this._step === null &&
|
||||
this._handlers === undefined &&
|
||||
this._handler === undefined)
|
||||
(this._step === null && this._handlers === undefined)
|
||||
? html`
|
||||
<step-flow-loading
|
||||
.label=${this.hass.localize(
|
||||
@@ -221,22 +178,15 @@ class DataEntryFlowDialog extends LitElement {
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
></ha-icon-button>
|
||||
${this._step === null
|
||||
? this._handler
|
||||
? html`<step-flow-pick-flow
|
||||
? // Show handler picker
|
||||
html`
|
||||
<step-flow-pick-handler
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.hass=${this.hass}
|
||||
.handler=${this._handler}
|
||||
.flowsInProgress=${this._flowsInProgress}
|
||||
></step-flow-pick-flow>`
|
||||
: // Show handler picker
|
||||
html`
|
||||
<step-flow-pick-handler
|
||||
.hass=${this.hass}
|
||||
.handlers=${this._handlers}
|
||||
.showAdvanced=${this._params.showAdvanced}
|
||||
@handler-picked=${this._handlerPicked}
|
||||
></step-flow-pick-handler>
|
||||
`
|
||||
.handlers=${this._handlers}
|
||||
.showAdvanced=${this._params.showAdvanced}
|
||||
></step-flow-pick-handler>
|
||||
`
|
||||
: this._step.type === "form"
|
||||
? html`
|
||||
<step-flow-form
|
||||
@@ -341,43 +291,6 @@ class DataEntryFlowDialog extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _checkFlowsInProgress(handler: string) {
|
||||
this._loading = true;
|
||||
|
||||
const flowsInProgress = (
|
||||
await fetchConfigFlowInProgress(this.hass.connection)
|
||||
).filter((flow) => flow.handler === handler);
|
||||
|
||||
if (!flowsInProgress.length) {
|
||||
let step: DataEntryFlowStep;
|
||||
try {
|
||||
step = await this._params!.flowConfig.createFlow(this.hass, handler);
|
||||
} catch (err) {
|
||||
this._step = undefined;
|
||||
this._params = undefined;
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.error"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.could_not_load"
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._processStep(step);
|
||||
} else {
|
||||
this._step = null;
|
||||
this._handler = handler;
|
||||
this._flowsInProgress = flowsInProgress;
|
||||
}
|
||||
this._loading = false;
|
||||
}
|
||||
|
||||
private _handlerPicked(ev) {
|
||||
this._checkFlowsInProgress(ev.detail.handler);
|
||||
}
|
||||
|
||||
private async _processStep(
|
||||
step: DataEntryFlowStep | undefined | Promise<DataEntryFlowStep>
|
||||
): Promise<void> {
|
||||
@@ -392,7 +305,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
}
|
||||
|
||||
if (step === undefined) {
|
||||
this.closeDialog();
|
||||
this._flowDone();
|
||||
return;
|
||||
}
|
||||
this._step = undefined;
|
||||
@@ -400,6 +313,38 @@ class DataEntryFlowDialog extends LitElement {
|
||||
this._step = step;
|
||||
}
|
||||
|
||||
private _flowDone(): void {
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
const flowFinished = Boolean(
|
||||
this._step && ["create_entry", "abort"].includes(this._step.type)
|
||||
);
|
||||
|
||||
// If we created this flow, delete it now.
|
||||
if (this._step && !flowFinished && !this._params.continueFlowId) {
|
||||
this._params.flowConfig.deleteFlow(this.hass, this._step.flow_id);
|
||||
}
|
||||
|
||||
if (this._params.dialogClosedCallback) {
|
||||
this._params.dialogClosedCallback({
|
||||
flowFinished,
|
||||
});
|
||||
}
|
||||
|
||||
this._step = undefined;
|
||||
this._params = undefined;
|
||||
this._devices = undefined;
|
||||
if (this._unsubAreas) {
|
||||
this._unsubAreas();
|
||||
this._unsubAreas = undefined;
|
||||
}
|
||||
if (this._unsubDevices) {
|
||||
this._unsubDevices();
|
||||
this._unsubDevices = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyleDialog,
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-icon-next";
|
||||
import { localizeConfigFlowTitle } from "../../data/config_flow";
|
||||
import { DataEntryFlowProgress } from "../../data/data_entry_flow";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { brandsUrl } from "../../util/brands-url";
|
||||
import { FlowConfig } from "./show-dialog-data-entry-flow";
|
||||
import { configFlowContentStyles } from "./styles";
|
||||
|
||||
@customElement("step-flow-pick-flow")
|
||||
class StepFlowPickFlow extends LitElement {
|
||||
public flowConfig!: FlowConfig;
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false })
|
||||
public flowsInProgress!: DataEntryFlowProgress[];
|
||||
|
||||
@property() public handler!: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.pick_flow_step.title"
|
||||
)}
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
${this.flowsInProgress.map(
|
||||
(flow) => html` <paper-icon-item
|
||||
@click=${this._flowInProgressPicked}
|
||||
.flow=${flow}
|
||||
>
|
||||
<img
|
||||
slot="item-icon"
|
||||
loading="lazy"
|
||||
src=${brandsUrl(flow.handler, "icon", true)}
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
|
||||
<paper-item-body>
|
||||
${localizeConfigFlowTitle(this.hass.localize, flow)}
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-icon-item>`
|
||||
)}
|
||||
<paper-item @click=${this._startNewFlowPicked} .handler=${this.handler}>
|
||||
<paper-item-body>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.pick_flow_step.new_flow",
|
||||
"integration",
|
||||
domainToName(this.hass.localize, this.handler)
|
||||
)}
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _startNewFlowPicked(ev) {
|
||||
this._startFlow(ev.currentTarget.handler);
|
||||
}
|
||||
|
||||
private _startFlow(handler: string) {
|
||||
fireEvent(this, "flow-update", {
|
||||
stepPromise: this.flowConfig.createFlow(this.hass, handler),
|
||||
});
|
||||
}
|
||||
|
||||
private _flowInProgressPicked(ev) {
|
||||
const flow: DataEntryFlowProgress = ev.currentTarget.flow;
|
||||
fireEvent(this, "flow-update", {
|
||||
stepPromise: this.flowConfig.fetchFlow(this.hass, flow.flow_id),
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
configFlowContentStyles,
|
||||
css`
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
ha-icon-next {
|
||||
margin-right: 8px;
|
||||
}
|
||||
div {
|
||||
overflow: auto;
|
||||
max-height: 600px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
h2 {
|
||||
padding-right: 66px;
|
||||
}
|
||||
@media all and (max-height: 900px) {
|
||||
div {
|
||||
max-height: calc(100vh - 134px);
|
||||
}
|
||||
}
|
||||
paper-icon-item,
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"step-flow-pick-flow": StepFlowPickFlow;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import { domainToName } from "../../data/integration";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { brandsUrl } from "../../util/brands-url";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import { FlowConfig } from "./show-dialog-data-entry-flow";
|
||||
import { configFlowContentStyles } from "./styles";
|
||||
|
||||
interface HandlerObj {
|
||||
@@ -29,24 +30,17 @@ interface HandlerObj {
|
||||
slug: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"handler-picked": {
|
||||
handler: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("step-flow-pick-handler")
|
||||
class StepFlowPickHandler extends LitElement {
|
||||
public flowConfig!: FlowConfig;
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public handlers!: string[];
|
||||
|
||||
@property() public showAdvanced?: boolean;
|
||||
|
||||
@internalProperty() private _filter?: string;
|
||||
@internalProperty() private filter?: string;
|
||||
|
||||
private _width?: number;
|
||||
|
||||
@@ -80,7 +74,7 @@ class StepFlowPickHandler extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
const handlers = this._getHandlers(
|
||||
this.handlers,
|
||||
this._filter,
|
||||
this.filter,
|
||||
this.hass.localize
|
||||
);
|
||||
|
||||
@@ -88,7 +82,7 @@ class StepFlowPickHandler extends LitElement {
|
||||
<h2>${this.hass.localize("ui.panel.config.integrations.new")}</h2>
|
||||
<search-input
|
||||
autofocus
|
||||
.filter=${this._filter}
|
||||
.filter=${this.filter}
|
||||
@value-changed=${this._filterChanged}
|
||||
.label=${this.hass.localize("ui.panel.config.integrations.search")}
|
||||
></search-input>
|
||||
@@ -170,12 +164,15 @@ class StepFlowPickHandler extends LitElement {
|
||||
}
|
||||
|
||||
private async _filterChanged(e) {
|
||||
this._filter = e.detail.value;
|
||||
this.filter = e.detail.value;
|
||||
}
|
||||
|
||||
private async _handlerPicked(ev) {
|
||||
fireEvent(this, "handler-picked", {
|
||||
handler: ev.currentTarget.handler.slug,
|
||||
fireEvent(this, "flow-update", {
|
||||
stepPromise: this.flowConfig.createFlow(
|
||||
this.hass,
|
||||
ev.currentTarget.handler.slug
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -198,9 +195,6 @@ class StepFlowPickHandler extends LitElement {
|
||||
overflow: auto;
|
||||
max-height: 600px;
|
||||
}
|
||||
h2 {
|
||||
padding-right: 66px;
|
||||
}
|
||||
@media all and (max-height: 900px) {
|
||||
div {
|
||||
max-height: calc(100vh - 134px);
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../components/ha-relative-time";
|
||||
import { triggerAutomationActions } from "../../../data/automation";
|
||||
import { triggerAutomation } from "../../../data/automation";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
@@ -36,7 +36,7 @@ class MoreInfoAutomation extends LitElement {
|
||||
|
||||
<div class="actions">
|
||||
<mwc-button
|
||||
@click=${this._runActions}
|
||||
@click=${this.handleAction}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj!.state)}
|
||||
>
|
||||
${this.hass.localize("ui.card.automation.trigger")}
|
||||
@@ -45,8 +45,8 @@ class MoreInfoAutomation extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _runActions() {
|
||||
triggerAutomationActions(this.hass, this.stateObj!.entity_id);
|
||||
private handleAction() {
|
||||
triggerAutomation(this.hass, this.stateObj!.entity_id);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
|
||||
@@ -52,7 +52,6 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
caption="[[localize('ui.card.fan.speed')]]"
|
||||
min="0"
|
||||
max="100"
|
||||
step="[[computePercentageStepSize(stateObj)]]"
|
||||
value="{{percentageSliderValue}}"
|
||||
on-change="percentageChanged"
|
||||
pin=""
|
||||
@@ -114,7 +113,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
<ha-attributes
|
||||
state-obj="[[stateObj]]"
|
||||
extra-filters="percentage_step,speed,preset_mode,preset_modes,speed_list,percentage,oscillating,direction"
|
||||
extra-filters="speed,preset_mode,preset_modes,speed_list,percentage,oscillating,direction"
|
||||
></ha-attributes>
|
||||
`;
|
||||
}
|
||||
@@ -155,13 +154,6 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
}
|
||||
}
|
||||
|
||||
computePercentageStepSize(stateObj) {
|
||||
if (stateObj.attributes.percentage_step) {
|
||||
return stateObj.attributes.percentage_step;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
computeClassNames(stateObj) {
|
||||
return (
|
||||
"more-info-fan " +
|
||||
|
||||
+3
-12
@@ -48,19 +48,10 @@ const authProm = isExternal
|
||||
const connProm = async (auth) => {
|
||||
try {
|
||||
const conn = await createConnection({ auth });
|
||||
// Clear auth data from url if we have been able to establish a connection
|
||||
|
||||
// Clear url if we have been able to establish a connection
|
||||
if (location.search.includes("auth_callback=1")) {
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
// https://github.com/home-assistant/home-assistant-js-websocket/blob/master/lib/auth.ts
|
||||
// Remove all data from QueryCallbackData type
|
||||
searchParams.delete("auth_callback");
|
||||
searchParams.delete("code");
|
||||
searchParams.delete("state");
|
||||
history.replaceState(
|
||||
null,
|
||||
"",
|
||||
`${location.pathname}?${searchParams.toString()}`
|
||||
);
|
||||
history.replaceState(null, "", location.pathname);
|
||||
}
|
||||
|
||||
return { auth, conn };
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
@@ -64,7 +62,6 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.value=${this._action}
|
||||
.showAdvanced=${this.hass.userData?.showAdvanced}
|
||||
@value-changed=${this._actionChanged}
|
||||
></ha-service-control>
|
||||
`;
|
||||
@@ -75,15 +72,6 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-service-control {
|
||||
display: block;
|
||||
margin: 0 -16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -21,7 +21,7 @@ import "../../../components/ha-selector/ha-selector";
|
||||
import "../../../components/ha-settings-row";
|
||||
import {
|
||||
BlueprintAutomationConfig,
|
||||
triggerAutomationActions,
|
||||
triggerAutomation,
|
||||
} from "../../../data/automation";
|
||||
import {
|
||||
BlueprintOrError,
|
||||
@@ -105,7 +105,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<mwc-button
|
||||
@click=${this._runActions}
|
||||
@click=${this._excuteAutomation}
|
||||
.stateObj=${this.stateObj}
|
||||
>
|
||||
${this.hass.localize("ui.card.automation.trigger")}
|
||||
@@ -197,8 +197,8 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
this._blueprints = await fetchBlueprints(this.hass, "automation");
|
||||
}
|
||||
|
||||
private _runActions(ev: Event) {
|
||||
triggerAutomationActions(this.hass, (ev.target as any).stateObj.entity_id);
|
||||
private _excuteAutomation(ev: Event) {
|
||||
triggerAutomation(this.hass, (ev.target as any).stateObj.entity_id);
|
||||
}
|
||||
|
||||
private _blueprintChanged(ev) {
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
deleteAutomation,
|
||||
getAutomationEditorInitData,
|
||||
showAutomationEditor,
|
||||
triggerAutomationActions,
|
||||
triggerAutomation,
|
||||
} from "../../../data/automation";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@@ -256,7 +256,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
)}
|
||||
</div>
|
||||
<mwc-button
|
||||
@click=${this._runActions}
|
||||
@click=${this._excuteAutomation}
|
||||
.stateObj=${stateObj}
|
||||
>
|
||||
${this.hass.localize(
|
||||
@@ -381,8 +381,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
this._errors = undefined;
|
||||
}
|
||||
|
||||
private _runActions(ev: Event) {
|
||||
triggerAutomationActions(this.hass, (ev.target as any).stateObj.entity_id);
|
||||
private _excuteAutomation(ev: Event) {
|
||||
triggerAutomation(this.hass, (ev.target as any).stateObj.entity_id);
|
||||
}
|
||||
|
||||
private _preprocessYaml() {
|
||||
|
||||
@@ -20,10 +20,7 @@ import { DataTableColumnContainer } from "../../../components/data-table/ha-data
|
||||
import "../../../components/entity/ha-entity-toggle";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
AutomationEntity,
|
||||
triggerAutomationActions,
|
||||
} from "../../../data/automation";
|
||||
import { AutomationEntity, triggerAutomation } from "../../../data/automation";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
@@ -91,12 +88,12 @@ class HaAutomationPicker extends LitElement {
|
||||
},
|
||||
};
|
||||
if (!narrow) {
|
||||
columns.trigger = {
|
||||
columns.execute = {
|
||||
title: "",
|
||||
template: (_info, automation: any) => html`
|
||||
<mwc-button
|
||||
.automation=${automation}
|
||||
@click=${(ev) => this._runActions(ev)}
|
||||
@click=${(ev) => this._execute(ev)}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(automation.state)}
|
||||
>
|
||||
${this.hass.localize("ui.card.automation.trigger")}
|
||||
@@ -213,9 +210,9 @@ class HaAutomationPicker extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _runActions(ev) {
|
||||
private _execute(ev) {
|
||||
const entityId = ev.currentTarget.automation.entity_id;
|
||||
triggerAutomationActions(this.hass, entityId);
|
||||
triggerAutomation(this.hass, entityId);
|
||||
}
|
||||
|
||||
private _createNew() {
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
Condition,
|
||||
ManualAutomationConfig,
|
||||
Trigger,
|
||||
triggerAutomationActions,
|
||||
triggerAutomation,
|
||||
} from "../../../data/automation";
|
||||
import { Action, MODES, MODES_MAX } from "../../../data/script";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
@@ -140,7 +140,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<mwc-button
|
||||
@click=${this._runActions}
|
||||
@click=${this._excuteAutomation}
|
||||
.stateObj=${this.stateObj}
|
||||
>
|
||||
${this.hass.localize("ui.card.automation.trigger")}
|
||||
@@ -240,8 +240,8 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
</ha-config-section>`;
|
||||
}
|
||||
|
||||
private _runActions(ev: Event) {
|
||||
triggerAutomationActions(this.hass, (ev.target as any).stateObj.entity_id);
|
||||
private _excuteAutomation(ev: Event) {
|
||||
triggerAutomation(this.hass, (ev.target as any).stateObj.entity_id);
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
|
||||
@@ -211,7 +211,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
"ui.panel.config.blueprint.overview.add_blueprint"
|
||||
)}
|
||||
extended
|
||||
@click=${this._addBlueprintClicked}
|
||||
@click=${this._addBlueprint}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
@@ -249,10 +249,6 @@ class HaBlueprintOverview extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _addBlueprintClicked(): void {
|
||||
this._addBlueprint();
|
||||
}
|
||||
|
||||
private _reload() {
|
||||
fireEvent(this, "reload-blueprints");
|
||||
}
|
||||
|
||||
@@ -29,7 +29,11 @@ class HaConfigNavigation extends LitElement {
|
||||
${this.pages.map((page) =>
|
||||
canShowPage(this.hass, page)
|
||||
? html`
|
||||
<a href=${page.path} aria-role="option" tabindex="-1">
|
||||
<a
|
||||
href=${`/config/${page.component}`}
|
||||
aria-role="option"
|
||||
tabindex="-1"
|
||||
>
|
||||
<paper-icon-item>
|
||||
<ha-svg-icon
|
||||
.path=${page.iconPath}
|
||||
|
||||
-12
@@ -79,11 +79,6 @@ export class HaDeviceActionsZha extends LitElement {
|
||||
"ui.dialogs.zha_device_info.buttons.clusters"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._onViewInVisualizationClick}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.view_in_visualization"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button class="warning" @click=${this._removeDevice}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.remove"
|
||||
@@ -109,13 +104,6 @@ export class HaDeviceActionsZha extends LitElement {
|
||||
navigate(this, "/config/zha/add/" + this._zhaDevice!.ieee);
|
||||
}
|
||||
|
||||
private _onViewInVisualizationClick() {
|
||||
navigate(
|
||||
this,
|
||||
"/config/zha/visualization/" + this._zhaDevice!.device_reg_id
|
||||
);
|
||||
}
|
||||
|
||||
private async _handleZigbeeInfoClicked() {
|
||||
showZHADeviceZigbeeInfoDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
@@ -12,14 +12,13 @@ import {
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { compare } from "../../../common/string/compare";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import "../../../components/entity/ha-battery-icon";
|
||||
import "../../../components/ha-icon-next";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import { ConfigEntry, disableConfigEntry } from "../../../data/config_entries";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
@@ -161,8 +160,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
const batteryState = batteryEntity
|
||||
? this.hass.states[batteryEntity.entity_id]
|
||||
: undefined;
|
||||
const batteryIsBinary = batteryState
|
||||
&& computeStateDomain(batteryState) === "binary_sensor";
|
||||
const batteryChargingState = batteryChargingEntity
|
||||
? this.hass.states[batteryChargingEntity.entity_id]
|
||||
: undefined;
|
||||
@@ -218,7 +215,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
batteryState
|
||||
? html`
|
||||
<div class="battery">
|
||||
${batteryIsBinary ? "" : batteryState.state + "%"}
|
||||
${batteryState.state}%
|
||||
<ha-battery-icon
|
||||
.hass=${this.hass!}
|
||||
.batteryStateObj=${batteryState}
|
||||
@@ -264,13 +261,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
${device.disabled_by === "user"
|
||||
? html` <div class="card-actions" slot="actions">
|
||||
<mwc-button unelevated @click=${this._enableDevice}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-button>
|
||||
</div>`
|
||||
: ""}
|
||||
<div class="card-actions" slot="actions">
|
||||
<mwc-button unelevated @click=${this._enableDevice}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: html``
|
||||
}
|
||||
@@ -631,41 +626,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
updateEntry: async (updates) => {
|
||||
const oldDeviceName = device.name_by_user || device.name;
|
||||
const newDeviceName = updates.name_by_user;
|
||||
const disabled =
|
||||
updates.disabled_by === "user" && device.disabled_by !== "user";
|
||||
|
||||
if (disabled) {
|
||||
for (const cnfg_entry of device.config_entries) {
|
||||
if (
|
||||
!this.devices.some(
|
||||
(dvc) =>
|
||||
dvc.id !== device.id &&
|
||||
dvc.config_entries.includes(cnfg_entry)
|
||||
)
|
||||
) {
|
||||
const config_entry = this.entries.find(
|
||||
(entry) => entry.entry_id === cnfg_entry
|
||||
);
|
||||
if (
|
||||
config_entry &&
|
||||
!config_entry.disabled_by &&
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
(await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.devices.confirm_disable_config_entry",
|
||||
"entry_name",
|
||||
config_entry.title
|
||||
),
|
||||
confirmText: this.hass.localize("ui.common.yes"),
|
||||
dismissText: this.hass.localize("ui.common.no"),
|
||||
}))
|
||||
) {
|
||||
disableConfigEntry(this.hass, cnfg_entry);
|
||||
delete updates.disabled_by;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await updateDeviceRegistryEntry(this.hass, this.deviceId, updates);
|
||||
|
||||
if (
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
@@ -294,11 +293,9 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
batteryEntityPair && batteryEntityPair[1]
|
||||
? this.hass.states[batteryEntityPair[1]]
|
||||
: undefined;
|
||||
const batteryIsBinary =
|
||||
battery && computeStateDomain(battery) === "binary_sensor";
|
||||
return battery && (batteryIsBinary || !isNaN(battery.state as any))
|
||||
return battery && !isNaN(battery.state as any)
|
||||
? html`
|
||||
${batteryIsBinary ? "" : battery.state + "%"}
|
||||
${battery.state}%
|
||||
<ha-battery-icon
|
||||
.hass=${this.hass!}
|
||||
.batteryStateObj=${battery}
|
||||
|
||||
@@ -111,10 +111,11 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
||||
],
|
||||
experimental: [
|
||||
{
|
||||
component: "tag",
|
||||
component: "tags",
|
||||
path: "/config/tags",
|
||||
translationKey: "ui.panel.config.tag.caption",
|
||||
translationKey: "ui.panel.config.tags.caption",
|
||||
iconPath: mdiNfcVariant,
|
||||
core: true,
|
||||
},
|
||||
],
|
||||
lovelace: [
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-icon-button";
|
||||
import { ActionDetail } from "@material/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical, mdiPlus } from "@mdi/js";
|
||||
import "@polymer/app-route/app-route";
|
||||
@@ -123,8 +122,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
|
||||
@internalProperty() private _showIgnored = false;
|
||||
|
||||
@internalProperty() private _showDisabled = false;
|
||||
|
||||
@internalProperty() private _searchParms = new URLSearchParams(
|
||||
window.location.hash.substring(1)
|
||||
);
|
||||
@@ -184,29 +181,18 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
(
|
||||
configEntries: ConfigEntryExtended[],
|
||||
filter?: string
|
||||
): [
|
||||
Map<string, ConfigEntryExtended[]>,
|
||||
ConfigEntryExtended[],
|
||||
Map<string, ConfigEntryExtended[]>
|
||||
] => {
|
||||
): [Map<string, ConfigEntryExtended[]>, ConfigEntryExtended[]] => {
|
||||
const filteredConfigEnties = this._filterConfigEntries(
|
||||
configEntries,
|
||||
filter
|
||||
);
|
||||
const ignored: ConfigEntryExtended[] = [];
|
||||
const disabled: ConfigEntryExtended[] = [];
|
||||
for (let i = filteredConfigEnties.length - 1; i >= 0; i--) {
|
||||
if (filteredConfigEnties[i].source === "ignore") {
|
||||
ignored.push(filteredConfigEnties.splice(i, 1)[0]);
|
||||
} else if (filteredConfigEnties[i].disabled_by !== null) {
|
||||
disabled.push(filteredConfigEnties.splice(i, 1)[0]);
|
||||
}
|
||||
}
|
||||
return [
|
||||
groupByIntegration(filteredConfigEnties),
|
||||
ignored,
|
||||
groupByIntegration(disabled),
|
||||
];
|
||||
return [groupByIntegration(filteredConfigEnties), ignored];
|
||||
}
|
||||
);
|
||||
|
||||
@@ -268,7 +254,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
const [
|
||||
groupedConfigEntries,
|
||||
ignoredConfigEntries,
|
||||
disabledConfigEntries,
|
||||
] = this._filterGroupConfigEntries(this._configEntries, this._filter);
|
||||
const configEntriesInProgress = this._filterConfigEntriesInProgress(
|
||||
this._configEntriesInProgress,
|
||||
@@ -304,7 +289,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
slot="toolbar-icon"
|
||||
@action=${this._handleMenuAction}
|
||||
@action=${this._toggleShowIgnored}
|
||||
>
|
||||
<mwc-icon-button
|
||||
.title=${this.hass.localize("ui.common.menu")}
|
||||
@@ -320,13 +305,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
: "ui.panel.config.integrations.ignore.show_ignored"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item>
|
||||
${this.hass.localize(
|
||||
this._showDisabled
|
||||
? "ui.panel.config.integrations.disable.hide_disabled"
|
||||
: "ui.panel.config.integrations.disable.show_disabled"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
|
||||
${!this.narrow
|
||||
@@ -341,20 +319,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
"ui.panel.config.integrations.search"
|
||||
)}
|
||||
></search-input>
|
||||
${!this._showDisabled && disabledConfigEntries.size
|
||||
? html`<div class="active-filters">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.disabled_integrations",
|
||||
"number",
|
||||
disabledConfigEntries.size
|
||||
)}
|
||||
<mwc-button @click=${this._toggleShowDisabled}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.filtering.show"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
@@ -469,21 +433,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
)
|
||||
: ""}
|
||||
${this._showDisabled
|
||||
? Array.from(disabledConfigEntries.entries()).map(
|
||||
([domain, items]) =>
|
||||
html`<ha-integration-card
|
||||
data-domain=${domain}
|
||||
disabled
|
||||
.hass=${this.hass}
|
||||
.domain=${domain}
|
||||
.items=${items}
|
||||
.manifest=${this._manifests[domain]}
|
||||
.entityRegistryEntries=${this._entityRegistryEntries}
|
||||
.deviceRegistryEntries=${this._deviceRegistryEntries}
|
||||
></ha-integration-card> `
|
||||
)
|
||||
: ""}
|
||||
${groupedConfigEntries.size
|
||||
? Array.from(groupedConfigEntries.entries()).map(
|
||||
([domain, items]) =>
|
||||
@@ -647,25 +596,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||
}
|
||||
|
||||
private _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._toggleShowIgnored();
|
||||
break;
|
||||
case 1:
|
||||
this._toggleShowDisabled();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleShowIgnored() {
|
||||
this._showIgnored = !this._showIgnored;
|
||||
}
|
||||
|
||||
private _toggleShowDisabled() {
|
||||
this._showDisabled = !this._showDisabled;
|
||||
}
|
||||
|
||||
private async _removeIgnoredIntegration(ev: Event) {
|
||||
const entry = (ev.target! as any).entry;
|
||||
showConfirmationDialog(this, {
|
||||
@@ -833,14 +767,11 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
margin-left: 16px;
|
||||
}
|
||||
.search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
background: var(--sidebar-background-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
.search search-input {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
@@ -865,32 +796,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
}
|
||||
.active-filters {
|
||||
color: var(--primary-text-color);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 2px 2px 8px;
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.active-filters ha-icon {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.active-filters mwc-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.active-filters::before {
|
||||
background-color: var(--primary-color);
|
||||
opacity: 0.12;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
content: "";
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -9,15 +9,12 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import "../../../components/ha-icon-next";
|
||||
import {
|
||||
ConfigEntry,
|
||||
deleteConfigEntry,
|
||||
disableConfigEntry,
|
||||
enableConfigEntry,
|
||||
reloadConfigEntry,
|
||||
updateConfigEntry,
|
||||
} from "../../../data/config_entries";
|
||||
@@ -91,8 +88,6 @@ export class HaIntegrationCard extends LitElement {
|
||||
|
||||
@property() public selectedConfigEntryId?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
}
|
||||
@@ -114,14 +109,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
|
||||
private _renderGroupedIntegration(): TemplateResult {
|
||||
return html`
|
||||
<ha-card outlined class="group ${classMap({ disabled: this.disabled })}">
|
||||
${this.disabled
|
||||
? html`<div class="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable.disabled"
|
||||
)}
|
||||
</div>`
|
||||
: ""}
|
||||
<ha-card outlined class="group">
|
||||
<div class="group-header">
|
||||
<img
|
||||
src=${brandsUrl(this.domain, "icon")}
|
||||
@@ -160,9 +148,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
class="single integration ${classMap({
|
||||
disabled: Boolean(item.disabled_by),
|
||||
})}"
|
||||
class="single integration"
|
||||
.configEntry=${item}
|
||||
.id=${item.entry_id}
|
||||
>
|
||||
@@ -173,17 +159,6 @@ export class HaIntegrationCard extends LitElement {
|
||||
@click=${this._back}
|
||||
></ha-icon-button>`
|
||||
: ""}
|
||||
${item.disabled_by
|
||||
? html`<div class="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable.disabled_cause",
|
||||
"cause",
|
||||
this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.disable.disabled_by.${item.disabled_by}`
|
||||
) || item.disabled_by
|
||||
)}
|
||||
</div>`
|
||||
: ""}
|
||||
<div class="card-content">
|
||||
<div class="image">
|
||||
<img
|
||||
@@ -247,16 +222,11 @@ export class HaIntegrationCard extends LitElement {
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<div>
|
||||
${item.disabled_by === "user"
|
||||
? html`<mwc-button unelevated @click=${this._handleEnable}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
<mwc-button @click=${this._editEntryName}>
|
||||
${this.hass.localize(
|
||||
<mwc-button @click=${this._editEntryName}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.rename"
|
||||
)}
|
||||
</mwc-button>
|
||||
)}</mwc-button
|
||||
>
|
||||
${item.domain in integrationsWithPanel
|
||||
? html`<a
|
||||
href=${`${
|
||||
@@ -309,25 +279,13 @@ export class HaIntegrationCard extends LitElement {
|
||||
</mwc-list-item>
|
||||
</a>
|
||||
`}
|
||||
${!item.disabled_by &&
|
||||
item.state === "loaded" &&
|
||||
item.supports_unload
|
||||
${item.state === "loaded" && item.supports_unload
|
||||
? html`<mwc-list-item @request-selected="${this._handleReload}">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.reload"
|
||||
)}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
${item.disabled_by === "user"
|
||||
? html`<mwc-list-item @request-selected="${this._handleEnable}">
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-list-item>`
|
||||
: html`<mwc-list-item
|
||||
class="warning"
|
||||
@request-selected="${this._handleDisable}"
|
||||
>
|
||||
${this.hass.localize("ui.common.disable")}
|
||||
</mwc-list-item>`}
|
||||
<mwc-list-item
|
||||
class="warning"
|
||||
@request-selected="${this._handleDelete}"
|
||||
@@ -412,24 +370,6 @@ export class HaIntegrationCard extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _handleDisable(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._disableIntegration(
|
||||
((ev.target as HTMLElement).closest("ha-card") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _handleEnable(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (ev.detail.source && !shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._enableIntegration(
|
||||
((ev.target as HTMLElement).closest("ha-card") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _handleSystemOptions(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
@@ -445,48 +385,6 @@ export class HaIntegrationCard extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _disableIntegration(configEntry: ConfigEntry) {
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable.disable_confirm"
|
||||
),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
const result = await disableConfigEntry(this.hass, entryId);
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_restart_confirm"
|
||||
),
|
||||
});
|
||||
}
|
||||
fireEvent(this, "entry-updated", {
|
||||
entry: { ...configEntry, disabled_by: "user" },
|
||||
});
|
||||
}
|
||||
|
||||
private async _enableIntegration(configEntry: ConfigEntry) {
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
const result = await enableConfigEntry(this.hass, entryId);
|
||||
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.enable_restart_confirm"
|
||||
),
|
||||
});
|
||||
}
|
||||
fireEvent(this, "entry-updated", {
|
||||
entry: { ...configEntry, disabled_by: null },
|
||||
});
|
||||
}
|
||||
|
||||
private async _removeIntegration(configEntry: ConfigEntry) {
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
@@ -499,29 +397,31 @@ export class HaIntegrationCard extends LitElement {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
const result = await deleteConfigEntry(this.hass, entryId);
|
||||
fireEvent(this, "entry-removed", { entryId });
|
||||
deleteConfigEntry(this.hass, entryId).then((result) => {
|
||||
fireEvent(this, "entry-removed", { entryId });
|
||||
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.restart_confirm"
|
||||
),
|
||||
});
|
||||
}
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.restart_confirm"
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async _reloadIntegration(configEntry: ConfigEntry) {
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
const result = await reloadConfigEntry(this.hass, entryId);
|
||||
const locale_key = result.require_restart
|
||||
? "reload_restart_confirm"
|
||||
: "reload_confirm";
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.${locale_key}`
|
||||
),
|
||||
reloadConfigEntry(this.hass, entryId).then((result) => {
|
||||
const locale_key = result.require_restart
|
||||
? "reload_restart_confirm"
|
||||
: "reload_confirm";
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.${locale_key}`
|
||||
),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -561,15 +461,6 @@ export class HaIntegrationCard extends LitElement {
|
||||
:host(.highlight) ha-card {
|
||||
border: 1px solid var(--accent-color);
|
||||
}
|
||||
.disabled {
|
||||
--ha-card-border-color: var(--warning-color);
|
||||
}
|
||||
.disabled .header {
|
||||
background: var(--warning-color);
|
||||
color: var(--text-primary-color);
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.card-content {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
|
||||
@@ -59,8 +59,6 @@ class ZHAConfigDashboardRouter extends HassRouterPage {
|
||||
el.groupId = this.routeTail.path.substr(1);
|
||||
} else if (this._currentPage === "device") {
|
||||
el.ieee = this.routeTail.path.substr(1);
|
||||
} else if (this._currentPage === "visualization") {
|
||||
el.zoomedDeviceId = this.routeTail.path.substr(1);
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
+17
-86
@@ -9,33 +9,23 @@ import {
|
||||
PropertyValues,
|
||||
query,
|
||||
} from "lit-element";
|
||||
|
||||
import "@material/mwc-button";
|
||||
import { Edge, EdgeOptions, Network, Node } from "vis-network";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import {
|
||||
fetchDevices,
|
||||
refreshTopology,
|
||||
ZHADevice,
|
||||
} from "../../../../../data/zha";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { Network, Edge, Node, EdgeOptions } from "vis-network";
|
||||
import "../../../../../common/search/search-input";
|
||||
import "../../../../../components/device/ha-device-picker";
|
||||
import "../../../../../components/ha-button-menu";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import { fetchDevices, ZHADevice } from "../../../../../data/zha";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
import { DeviceRegistryEntry } from "../../../../../data/device_registry";
|
||||
|
||||
@customElement("zha-network-visualization-page")
|
||||
export class ZHANetworkVisualizationPage extends LitElement {
|
||||
@property({ type: Object }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@property()
|
||||
public zoomedDeviceId?: string;
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@query("#visualization", true)
|
||||
private _visualization?: HTMLElement;
|
||||
@@ -55,6 +45,9 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
@internalProperty()
|
||||
private _filter?: string;
|
||||
|
||||
@internalProperty()
|
||||
private _zoomedDeviceId?: string;
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (this.hass) {
|
||||
@@ -105,12 +98,6 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._network.on("stabilized", () => {
|
||||
if (this.zoomedDeviceId) {
|
||||
this._zoomToDevice();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -134,18 +121,13 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
</search-input>
|
||||
<ha-device-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.zoomedDeviceId}
|
||||
.value=${this._zoomedDeviceId}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.zha.visualization.zoom_label"
|
||||
)}
|
||||
.deviceFilter=${(device) => this._filterDevices(device)}
|
||||
@value-changed=${this._onZoomToDevice}
|
||||
.includeDomains="['zha']"
|
||||
@value-changed=${this._zoomToDevice}
|
||||
></ha-device-picker>
|
||||
<mwc-button @click=${this._refreshTopology}
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.zha.visualization.refresh_topology"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
<div id="visualization"></div>
|
||||
</hass-subpage>
|
||||
@@ -266,7 +248,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
filteredNodeIds.push(node.id!);
|
||||
}
|
||||
});
|
||||
this.zoomedDeviceId = "";
|
||||
this._zoomedDeviceId = "";
|
||||
this._zoomOut();
|
||||
this._network.selectNodes(filteredNodeIds, true);
|
||||
} else {
|
||||
@@ -274,25 +256,21 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _onZoomToDevice(event: PolymerChangedEvent<string>) {
|
||||
private _zoomToDevice(event: PolymerChangedEvent<string>) {
|
||||
event.stopPropagation();
|
||||
this.zoomedDeviceId = event.detail.value;
|
||||
this._zoomedDeviceId = event.detail.value;
|
||||
if (!this._network) {
|
||||
return;
|
||||
}
|
||||
this._zoomToDevice();
|
||||
}
|
||||
|
||||
private _zoomToDevice() {
|
||||
this._filter = "";
|
||||
if (!this.zoomedDeviceId) {
|
||||
if (!this._zoomedDeviceId) {
|
||||
this._zoomOut();
|
||||
} else {
|
||||
const device: ZHADevice | undefined = this._devicesByDeviceId.get(
|
||||
this.zoomedDeviceId
|
||||
this._zoomedDeviceId
|
||||
);
|
||||
if (device) {
|
||||
this._network!.fit({
|
||||
this._network.fit({
|
||||
nodes: [device.ieee],
|
||||
animation: { duration: 500, easingFunction: "easeInQuad" },
|
||||
});
|
||||
@@ -307,24 +285,6 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _refreshTopology(): Promise<void> {
|
||||
await refreshTopology(this.hass);
|
||||
}
|
||||
|
||||
private _filterDevices(device: DeviceRegistryEntry): boolean {
|
||||
if (!this.hass) {
|
||||
return false;
|
||||
}
|
||||
for (const parts of device.identifiers) {
|
||||
for (const part of parts) {
|
||||
if (part === "zha") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
@@ -339,59 +299,30 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
line-height: var(--paper-font-display1_-_line-height);
|
||||
opacity: var(--dark-primary-opacity);
|
||||
}
|
||||
|
||||
.table-header {
|
||||
border-bottom: 1px solid --divider-color;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
height: var(--header-height);
|
||||
}
|
||||
|
||||
:host([narrow]) .table-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
height: var(--header-height) * 3;
|
||||
}
|
||||
|
||||
.search-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--secondary-text-color);
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
search-input {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
:host(:not([narrow])) search-input {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
search-input.header {
|
||||
left: -8px;
|
||||
}
|
||||
|
||||
ha-device-picker {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
:host(:not([narrow])) ha-device-picker {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
mwc-button {
|
||||
font-weight: 500;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
:host(:not([narrow])) mwc-button {
|
||||
margin: 5px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -299,12 +299,12 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
<mwc-button
|
||||
@click=${this._runScript}
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.picker.run_script"
|
||||
"ui.panel.config.script.picker.activate_script"
|
||||
)}"
|
||||
?disabled=${this._dirty}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.picker.run_script"
|
||||
"ui.card.script.execute"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
@@ -375,13 +375,11 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
<mwc-button
|
||||
@click=${this._runScript}
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.picker.run_script"
|
||||
"ui.panel.config.script.picker.activate_script"
|
||||
)}"
|
||||
?disabled=${this._dirty}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.picker.run_script"
|
||||
)}
|
||||
${this.hass.localize("ui.card.script.execute")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -72,7 +72,7 @@ class DialogTagDetail extends LitElement
|
||||
this.hass,
|
||||
this._params.entry
|
||||
? this._params.entry.name || this._params.entry.id
|
||||
: this.hass!.localize("ui.panel.config.tag.detail.new_tag")
|
||||
: this.hass!.localize("ui.panel.config.tags.detail.new_tag")
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
@@ -80,7 +80,7 @@ class DialogTagDetail extends LitElement
|
||||
<div class="form">
|
||||
${this._params.entry
|
||||
? html`${this.hass!.localize(
|
||||
"ui.panel.config.tag.detail.tag_id"
|
||||
"ui.panel.config.tags.detail.tag_id"
|
||||
)}:
|
||||
${this._params.entry.id}`
|
||||
: ""}
|
||||
@@ -89,9 +89,11 @@ class DialogTagDetail extends LitElement
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label="${this.hass!.localize("ui.panel.config.tag.detail.name")}"
|
||||
.label="${this.hass!.localize(
|
||||
"ui.panel.config.tags.detail.name"
|
||||
)}"
|
||||
.errorMessage="${this.hass!.localize(
|
||||
"ui.panel.config.tag.detail.required_error_msg"
|
||||
"ui.panel.config.tags.detail.required_error_msg"
|
||||
)}"
|
||||
required
|
||||
auto-validate
|
||||
@@ -102,10 +104,10 @@ class DialogTagDetail extends LitElement
|
||||
.configValue=${"id"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.tag.detail.tag_id"
|
||||
"ui.panel.config.tags.detail.tag_id"
|
||||
)}
|
||||
.placeholder=${this.hass!.localize(
|
||||
"ui.panel.config.tag.detail.tag_id_placeholder"
|
||||
"ui.panel.config.tags.detail.tag_id_placeholder"
|
||||
)}
|
||||
></paper-input>`
|
||||
: ""}
|
||||
@@ -115,14 +117,14 @@ class DialogTagDetail extends LitElement
|
||||
<div>
|
||||
<p>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.tag.detail.usage",
|
||||
"ui.panel.config.tags.detail.usage",
|
||||
"companion_link",
|
||||
html`<a
|
||||
href="https://companion.home-assistant.io/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.tag.detail.companion_apps"
|
||||
"ui.panel.config.tags.detail.companion_apps"
|
||||
)}</a
|
||||
>`
|
||||
)}
|
||||
@@ -149,7 +151,7 @@ class DialogTagDetail extends LitElement
|
||||
@click="${this._deleteEntry}"
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass!.localize("ui.panel.config.tag.detail.delete")}
|
||||
${this.hass!.localize("ui.panel.config.tags.detail.delete")}
|
||||
</mwc-button>
|
||||
`
|
||||
: html``}
|
||||
@@ -159,8 +161,8 @@ class DialogTagDetail extends LitElement
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this._params.entry
|
||||
? this.hass!.localize("ui.panel.config.tag.detail.update")
|
||||
: this.hass!.localize("ui.panel.config.tag.detail.create")}
|
||||
? this.hass!.localize("ui.panel.config.tags.detail.update")
|
||||
: this.hass!.localize("ui.panel.config.tags.detail.create")}
|
||||
</mwc-button>
|
||||
${this._params.openWrite && !this._params.entry
|
||||
? html` <mwc-button
|
||||
@@ -169,7 +171,7 @@ class DialogTagDetail extends LitElement
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.tag.detail.create_and_write"
|
||||
"ui.panel.config.tags.detail.create_and_write"
|
||||
)}
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
|
||||
@@ -74,7 +74,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
template: (_icon, tag) => html`<tag-image .tag=${tag}></tag-image>`,
|
||||
},
|
||||
display_name: {
|
||||
title: this.hass.localize("ui.panel.config.tag.headers.name"),
|
||||
title: this.hass.localize("ui.panel.config.tags.headers.name"),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
grows: true,
|
||||
@@ -86,14 +86,16 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
.hass=${this.hass}
|
||||
.datetime=${tag.last_scanned_datetime}
|
||||
></ha-relative-time>`
|
||||
: this.hass.localize("ui.panel.config.tag.never_scanned")}
|
||||
: this.hass.localize("ui.panel.config.tags.never_scanned")}
|
||||
</div>`
|
||||
: ""}`,
|
||||
},
|
||||
};
|
||||
if (!narrow) {
|
||||
columns.last_scanned_datetime = {
|
||||
title: this.hass.localize("ui.panel.config.tag.headers.last_scanned"),
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.tags.headers.last_scanned"
|
||||
),
|
||||
sortable: true,
|
||||
direction: "desc",
|
||||
width: "20%",
|
||||
@@ -103,7 +105,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
.hass=${this.hass}
|
||||
.datetime=${last_scanned_datetime}
|
||||
></ha-relative-time>`
|
||||
: this.hass.localize("ui.panel.config.tag.never_scanned")}
|
||||
: this.hass.localize("ui.panel.config.tags.never_scanned")}
|
||||
`,
|
||||
};
|
||||
}
|
||||
@@ -115,7 +117,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
.tag=${tag}
|
||||
@click=${(ev: Event) =>
|
||||
this._openWrite((ev.currentTarget as any).tag)}
|
||||
title=${this.hass.localize("ui.panel.config.tag.write")}
|
||||
title=${this.hass.localize("ui.panel.config.tags.write")}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiContentDuplicate}></ha-svg-icon>
|
||||
</mwc-icon-button>`,
|
||||
@@ -128,7 +130,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
.tag=${tag}
|
||||
@click=${(ev: Event) =>
|
||||
this._createAutomation((ev.currentTarget as any).tag)}
|
||||
title=${this.hass.localize("ui.panel.config.tag.create_automation")}
|
||||
title=${this.hass.localize("ui.panel.config.tags.create_automation")}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiRobot}></ha-svg-icon>
|
||||
</mwc-icon-button>`,
|
||||
@@ -140,7 +142,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
.tag=${tag}
|
||||
@click=${(ev: Event) =>
|
||||
this._openDialog((ev.currentTarget as any).tag)}
|
||||
title=${this.hass.localize("ui.panel.config.tag.edit")}
|
||||
title=${this.hass.localize("ui.panel.config.tags.edit")}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiCog}></ha-svg-icon>
|
||||
</mwc-icon-button>`,
|
||||
@@ -199,7 +201,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
this.hass.language
|
||||
)}
|
||||
.data=${this._data(this._tags)}
|
||||
.noDataText=${this.hass.localize("ui.panel.config.tag.no_tags")}
|
||||
.noDataText=${this.hass.localize("ui.panel.config.tags.no_tags")}
|
||||
hasFab
|
||||
>
|
||||
<mwc-icon-button slot="toolbar-icon" @click=${this._showHelp}>
|
||||
@@ -207,7 +209,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
</mwc-icon-button>
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hass.localize("ui.panel.config.tag.add_tag")}
|
||||
.label=${this.hass.localize("ui.panel.config.tags.add_tag")}
|
||||
extended
|
||||
@click=${this._addTag}
|
||||
>
|
||||
@@ -219,18 +221,18 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _showHelp() {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.tag.caption"),
|
||||
title: this.hass.localize("ui.panel.config.tags.caption"),
|
||||
text: html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.tag.detail.usage",
|
||||
"ui.panel.config.tags.detail.usage",
|
||||
"companion_link",
|
||||
html`<a
|
||||
href="https://companion.home-assistant.io/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.tag.detail.companion_apps"
|
||||
"ui.panel.config.tags.detail.companion_apps"
|
||||
)}</a
|
||||
>`
|
||||
)}
|
||||
@@ -241,7 +243,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.tag.learn_more")}
|
||||
${this.hass.localize("ui.panel.config.tags.learn_more")}
|
||||
</a>
|
||||
</p>
|
||||
`,
|
||||
@@ -262,7 +264,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
private _createAutomation(tag: Tag) {
|
||||
const data = {
|
||||
alias: this.hass.localize(
|
||||
"ui.panel.config.tag.automation_title",
|
||||
"ui.panel.config.tags.automation_title",
|
||||
"name",
|
||||
tag.name || tag.id
|
||||
),
|
||||
@@ -310,9 +312,9 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
private async _removeTag(selectedTag: Tag) {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: this.hass!.localize("ui.panel.config.tag.confirm_remove_title"),
|
||||
title: this.hass!.localize("ui.panel.config.tags.confirm_remove_title"),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.tag.confirm_remove",
|
||||
"ui.panel.config.tags.confirm_remove",
|
||||
"tag",
|
||||
selectedTag.name || selectedTag.id
|
||||
),
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { safeDump, safeLoad } from "js-yaml";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-code-editor";
|
||||
import "../../../components/ha-service-picker";
|
||||
import { ENTITY_COMPONENT_DOMAINS } from "../../../data/entity";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import "../../../util/app-localstorage-document";
|
||||
|
||||
const ERROR_SENTINEL = {};
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class HaPanelDevService extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style">
|
||||
:host {
|
||||
-ms-user-select: initial;
|
||||
-webkit-user-select: initial;
|
||||
-moz-user-select: initial;
|
||||
display: block;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.ha-form {
|
||||
margin-right: 16px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
ha-progress-button {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-top: 12px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.attributes th {
|
||||
text-align: left;
|
||||
background-color: var(--card-background-color);
|
||||
border-bottom: 1px solid var(--primary-text-color);
|
||||
}
|
||||
|
||||
:host([rtl]) .attributes th {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.attributes tr {
|
||||
vertical-align: top;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.attributes tr:nth-child(odd) {
|
||||
background-color: var(--table-row-background-color, #eee);
|
||||
}
|
||||
|
||||
.attributes tr:nth-child(even) {
|
||||
background-color: var(--table-row-alternative-background-color, #eee);
|
||||
}
|
||||
|
||||
.attributes td:nth-child(3) {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
font-family: var(--code-font-family, monospace);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
:host([rtl]) .desc-container {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:host([rtl]) .desc-container h3 {
|
||||
direction: ltr;
|
||||
}
|
||||
</style>
|
||||
|
||||
<app-localstorage-document
|
||||
key="panel-dev-service-state-domain-service"
|
||||
data="{{domainService}}"
|
||||
>
|
||||
</app-localstorage-document>
|
||||
<app-localstorage-document
|
||||
key="[[_computeServiceDataKey(domainService)]]"
|
||||
data="{{serviceData}}"
|
||||
>
|
||||
</app-localstorage-document>
|
||||
|
||||
<div class="content">
|
||||
<p>
|
||||
[[localize('ui.panel.developer-tools.tabs.services.description')]]
|
||||
</p>
|
||||
|
||||
<div class="ha-form">
|
||||
<ha-service-picker
|
||||
hass="[[hass]]"
|
||||
value="{{domainService}}"
|
||||
></ha-service-picker>
|
||||
<template is="dom-if" if="[[_computeHasEntity(_attributes)]]">
|
||||
<ha-entity-picker
|
||||
hass="[[hass]]"
|
||||
value="[[_computeEntityValue(parsedJSON)]]"
|
||||
on-change="_entityPicked"
|
||||
disabled="[[!validJSON]]"
|
||||
include-domains="[[_computeEntityDomainFilter(_domain)]]"
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
</template>
|
||||
<p>[[localize('ui.panel.developer-tools.tabs.services.data')]]</p>
|
||||
<ha-code-editor
|
||||
mode="yaml"
|
||||
value="[[serviceData]]"
|
||||
error="[[!validJSON]]"
|
||||
on-value-changed="_yamlChanged"
|
||||
></ha-code-editor>
|
||||
<ha-progress-button
|
||||
on-click="_callService"
|
||||
raised
|
||||
disabled="[[!validJSON]]"
|
||||
>
|
||||
[[localize('ui.panel.developer-tools.tabs.services.call_service')]]
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
<template is="dom-if" if="[[!domainService]]">
|
||||
[[localize('ui.panel.developer-tools.tabs.services.select_service')]]
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[domainService]]">
|
||||
<template is="dom-if" if="[[!_description]]">
|
||||
[[localize('ui.panel.developer-tools.tabs.services.no_description')]]
|
||||
</template>
|
||||
<template is="dom-if" if="[[_description]]">
|
||||
[[_description]]
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<template is="dom-if" if="[[_description]]">
|
||||
<template is="dom-if" if="[[!_attributes.length]]">
|
||||
[[localize('ui.panel.developer-tools.tabs.services.no_parameters')]]
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[_attributes.length]]">
|
||||
<table class="attributes">
|
||||
<tr>
|
||||
<th>
|
||||
[[localize('ui.panel.developer-tools.tabs.services.column_parameter')]]
|
||||
</th>
|
||||
<th>
|
||||
[[localize('ui.panel.developer-tools.tabs.services.column_description')]]
|
||||
</th>
|
||||
<th>
|
||||
[[localize('ui.panel.developer-tools.tabs.services.column_example')]]
|
||||
</th>
|
||||
</tr>
|
||||
<template is="dom-repeat" items="[[_attributes]]" as="attribute">
|
||||
<tr>
|
||||
<td><pre>[[attribute.key]]</pre></td>
|
||||
<td>[[attribute.description]]</td>
|
||||
<td>[[attribute.example]]</td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[_attributes.length]]">
|
||||
<mwc-button on-click="_fillExampleData">
|
||||
[[localize('ui.panel.developer-tools.tabs.services.fill_example_data')]]
|
||||
</mwc-button>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
domainService: {
|
||||
type: String,
|
||||
observer: "_domainServiceChanged",
|
||||
},
|
||||
|
||||
_domain: {
|
||||
type: String,
|
||||
computed: "_computeDomain(domainService)",
|
||||
},
|
||||
|
||||
_service: {
|
||||
type: String,
|
||||
computed: "_computeService(domainService)",
|
||||
},
|
||||
|
||||
serviceData: {
|
||||
type: String,
|
||||
value: "",
|
||||
},
|
||||
|
||||
parsedJSON: {
|
||||
type: Object,
|
||||
computed: "_computeParsedServiceData(serviceData)",
|
||||
},
|
||||
|
||||
validJSON: {
|
||||
type: Boolean,
|
||||
computed: "_computeValidJSON(parsedJSON)",
|
||||
},
|
||||
|
||||
_attributes: {
|
||||
type: Array,
|
||||
computed: "_computeAttributesArray(hass, _domain, _service)",
|
||||
},
|
||||
|
||||
_description: {
|
||||
type: String,
|
||||
computed: "_computeDescription(hass, _domain, _service)",
|
||||
},
|
||||
|
||||
rtl: {
|
||||
reflectToAttribute: true,
|
||||
computed: "_computeRTL(hass)",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_domainServiceChanged() {
|
||||
this.serviceData = "";
|
||||
}
|
||||
|
||||
_computeAttributesArray(hass, domain, service) {
|
||||
const serviceDomains = hass.services;
|
||||
if (!(domain in serviceDomains)) return [];
|
||||
if (!(service in serviceDomains[domain])) return [];
|
||||
|
||||
const fields = serviceDomains[domain][service].fields;
|
||||
return Object.keys(fields).map(function (field) {
|
||||
return { key: field, ...fields[field] };
|
||||
});
|
||||
}
|
||||
|
||||
_computeDescription(hass, domain, service) {
|
||||
const serviceDomains = hass.services;
|
||||
if (!(domain in serviceDomains)) return undefined;
|
||||
if (!(service in serviceDomains[domain])) return undefined;
|
||||
return serviceDomains[domain][service].description;
|
||||
}
|
||||
|
||||
_computeServiceDataKey(domainService) {
|
||||
return `panel-dev-service-state-servicedata.${domainService}`;
|
||||
}
|
||||
|
||||
_computeDomain(domainService) {
|
||||
return domainService.split(".", 1)[0];
|
||||
}
|
||||
|
||||
_computeService(domainService) {
|
||||
return domainService.split(".", 2)[1] || null;
|
||||
}
|
||||
|
||||
_computeParsedServiceData(serviceData) {
|
||||
try {
|
||||
return serviceData.trim() ? safeLoad(serviceData) : {};
|
||||
} catch (err) {
|
||||
return ERROR_SENTINEL;
|
||||
}
|
||||
}
|
||||
|
||||
_computeValidJSON(parsedJSON) {
|
||||
return parsedJSON !== ERROR_SENTINEL;
|
||||
}
|
||||
|
||||
_computeHasEntity(attributes) {
|
||||
return attributes.some((attr) => attr.key === "entity_id");
|
||||
}
|
||||
|
||||
_computeEntityValue(parsedJSON) {
|
||||
return parsedJSON === ERROR_SENTINEL ? "" : parsedJSON.entity_id;
|
||||
}
|
||||
|
||||
_computeEntityDomainFilter(domain) {
|
||||
return ENTITY_COMPONENT_DOMAINS.includes(domain) ? [domain] : null;
|
||||
}
|
||||
|
||||
_callService(ev) {
|
||||
const button = ev.target;
|
||||
if (this.parsedJSON === ERROR_SENTINEL) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.services.alert_parsing_yaml",
|
||||
"data",
|
||||
this.serviceData
|
||||
),
|
||||
});
|
||||
button.actionError();
|
||||
return;
|
||||
}
|
||||
this.hass
|
||||
.callService(this._domain, this._service, this.parsedJSON)
|
||||
.then(() => {
|
||||
button.actionSuccess();
|
||||
})
|
||||
.catch(() => {
|
||||
button.actionError();
|
||||
});
|
||||
}
|
||||
|
||||
_fillExampleData() {
|
||||
const example = {};
|
||||
this._attributes.forEach((attribute) => {
|
||||
if (attribute.example) {
|
||||
let value = "";
|
||||
try {
|
||||
value = safeLoad(attribute.example);
|
||||
} catch (err) {
|
||||
value = attribute.example;
|
||||
}
|
||||
example[attribute.key] = value;
|
||||
}
|
||||
});
|
||||
this.serviceData = safeDump(example);
|
||||
}
|
||||
|
||||
_entityPicked(ev) {
|
||||
this.serviceData = safeDump({
|
||||
...this.parsedJSON,
|
||||
entity_id: ev.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
_yamlChanged(ev) {
|
||||
this.serviceData = ev.detail.value;
|
||||
}
|
||||
|
||||
_computeRTL(hass) {
|
||||
return computeRTL(hass);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("developer-tools-service", HaPanelDevService);
|
||||
@@ -1,382 +0,0 @@
|
||||
import { safeLoad } from "js-yaml";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { LocalStorage } from "../../../common/decorators/local-storage";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeObjectId } from "../../../common/entity/compute_object_id";
|
||||
import { extractSearchParam } from "../../../common/url/search-params";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-expansion-panel";
|
||||
import "../../../components/ha-service-control";
|
||||
import "../../../components/ha-service-picker";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
|
||||
import { ServiceAction } from "../../../data/script";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../../../util/app-localstorage-document";
|
||||
|
||||
class HaPanelDevService extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@LocalStorage("panel-dev-service-state-service-data", true)
|
||||
private _serviceData?: ServiceAction = { service: "", target: {}, data: {} };
|
||||
|
||||
@LocalStorage("panel-dev-service-state-yaml-mode", true)
|
||||
private _yamlMode = false;
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
protected firstUpdated(params) {
|
||||
super.firstUpdated(params);
|
||||
const serviceParam = extractSearchParam("service");
|
||||
if (serviceParam) {
|
||||
this._serviceData = {
|
||||
service: serviceParam,
|
||||
target: {},
|
||||
data: {},
|
||||
};
|
||||
if (this._yamlMode) {
|
||||
this.updateComplete.then(() =>
|
||||
this._yamlEditor?.setValue(this._serviceData)
|
||||
);
|
||||
}
|
||||
} else if (!this._serviceData?.service) {
|
||||
const domain = Object.keys(this.hass.services).sort()[0];
|
||||
const service = Object.keys(this.hass.services[domain]).sort()[0];
|
||||
this._serviceData = {
|
||||
service: `${domain}.${service}`,
|
||||
target: {},
|
||||
data: {},
|
||||
};
|
||||
if (this._yamlMode) {
|
||||
this.updateComplete.then(() =>
|
||||
this._yamlEditor?.setValue(this._serviceData)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const { target, fields } = this._fields(
|
||||
this.hass.services,
|
||||
this._serviceData?.service
|
||||
);
|
||||
|
||||
const isValid = this._isValid(this._serviceData, fields, target);
|
||||
|
||||
return html`
|
||||
<div class="content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.services.description"
|
||||
)}
|
||||
</p>
|
||||
|
||||
${this._yamlMode
|
||||
? html`<ha-service-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._serviceData?.service}
|
||||
@value-changed=${this._serviceChanged}
|
||||
></ha-service-picker>
|
||||
<ha-yaml-editor
|
||||
.defaultValue=${this._serviceData}
|
||||
@value-changed=${this._yamlChanged}
|
||||
></ha-yaml-editor>`
|
||||
: html`<ha-card
|
||||
><div>
|
||||
<ha-service-control
|
||||
.hass=${this.hass}
|
||||
.value=${this._serviceData}
|
||||
.narrow=${this.narrow}
|
||||
showAdvanced
|
||||
@value-changed=${this._serviceDataChanged}
|
||||
></ha-service-control></div
|
||||
></ha-card>`}
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<div class="buttons">
|
||||
<mwc-button @click=${this._toggleYaml}>
|
||||
${this._yamlMode
|
||||
? this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.services.ui_mode"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.services.yaml_mode"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button .disabled=${!isValid} raised @click=${this._callService}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.services.call_service"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${(this._yamlMode ? fields : this._filterSelectorFields(fields)).length
|
||||
? html`<div class="content">
|
||||
<ha-expansion-panel
|
||||
.header=${this._yamlMode
|
||||
? this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.services.all_parameters"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.services.yaml_parameters"
|
||||
)}
|
||||
outlined
|
||||
.expanded=${this._yamlMode}
|
||||
>
|
||||
${this._yamlMode && target
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.services.accepts_target"
|
||||
)}
|
||||
</h3>`
|
||||
: ""}
|
||||
<table class="attributes">
|
||||
<tr>
|
||||
<th>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.services.column_parameter"
|
||||
)}
|
||||
</th>
|
||||
<th>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.services.column_description"
|
||||
)}
|
||||
</th>
|
||||
<th>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.services.column_example"
|
||||
)}
|
||||
</th>
|
||||
</tr>
|
||||
${(this._yamlMode
|
||||
? fields
|
||||
: this._filterSelectorFields(fields)
|
||||
).map(
|
||||
(field) => html` <tr>
|
||||
<td><pre>${field.key}</pre></td>
|
||||
<td>${field.description}</td>
|
||||
<td>${field.example}</td>
|
||||
</tr>`
|
||||
)}
|
||||
</table>
|
||||
${this._yamlMode
|
||||
? html`<mwc-button @click=${this._fillExampleData}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.services.fill_example_data"
|
||||
)}</mwc-button
|
||||
>`
|
||||
: ""}
|
||||
</ha-expansion-panel>
|
||||
</div>`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private _filterSelectorFields = memoizeOne((fields) =>
|
||||
fields.filter((field) => !field.selector)
|
||||
);
|
||||
|
||||
private _isValid = memoizeOne((serviceData, fields, target): boolean => {
|
||||
if (!serviceData?.service) {
|
||||
return false;
|
||||
}
|
||||
const domain = computeDomain(serviceData.service);
|
||||
const service = computeObjectId(serviceData.service);
|
||||
if (!domain || !service) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
target &&
|
||||
!serviceData.target &&
|
||||
!serviceData.data?.entity_id &&
|
||||
!serviceData.data?.device_id &&
|
||||
!serviceData.data?.area_id
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
for (const field of fields) {
|
||||
if (
|
||||
field.required &&
|
||||
(!serviceData.data || serviceData.data[field.key] === undefined)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
private _fields = memoizeOne(
|
||||
(
|
||||
serviceDomains: HomeAssistant["services"],
|
||||
domainService: string | undefined
|
||||
): { target: boolean; fields: any[] } => {
|
||||
if (!domainService) {
|
||||
return { target: false, fields: [] };
|
||||
}
|
||||
const domain = computeDomain(domainService);
|
||||
const service = computeObjectId(domainService);
|
||||
if (!(domain in serviceDomains)) {
|
||||
return { target: false, fields: [] };
|
||||
}
|
||||
if (!(service in serviceDomains[domain])) {
|
||||
return { target: false, fields: [] };
|
||||
}
|
||||
const target = "target" in serviceDomains[domain][service];
|
||||
const fields = serviceDomains[domain][service].fields;
|
||||
const result = Object.keys(fields).map((field) => {
|
||||
return { key: field, ...fields[field] };
|
||||
});
|
||||
|
||||
return {
|
||||
target,
|
||||
fields: result,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
private _callService() {
|
||||
const domain = computeDomain(this._serviceData!.service);
|
||||
const service = computeObjectId(this._serviceData!.service);
|
||||
if (!domain || !service) {
|
||||
return;
|
||||
}
|
||||
this.hass.callService(
|
||||
domain,
|
||||
service,
|
||||
this._serviceData!.data,
|
||||
this._serviceData!.target
|
||||
);
|
||||
}
|
||||
|
||||
private _toggleYaml() {
|
||||
this._yamlMode = !this._yamlMode;
|
||||
}
|
||||
|
||||
private _yamlChanged(ev) {
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
}
|
||||
this._serviceDataChanged(ev);
|
||||
}
|
||||
|
||||
private _serviceDataChanged(ev) {
|
||||
this._serviceData = ev.detail.value;
|
||||
}
|
||||
|
||||
private _serviceChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
this._serviceData = { service: ev.detail.value || "", data: {} };
|
||||
this._yamlEditor?.setValue(this._serviceData);
|
||||
}
|
||||
|
||||
private _fillExampleData() {
|
||||
const { fields } = this._fields(
|
||||
this.hass.services,
|
||||
this._serviceData?.service
|
||||
);
|
||||
const example = {};
|
||||
fields.forEach((field) => {
|
||||
if (field.example) {
|
||||
let value = "";
|
||||
try {
|
||||
value = safeLoad(field.example);
|
||||
} catch (err) {
|
||||
value = field.example;
|
||||
}
|
||||
example[field.key] = value;
|
||||
}
|
||||
});
|
||||
this._serviceData = { ...this._serviceData!, data: example };
|
||||
this._yamlEditor?.setValue(this._serviceData);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.content {
|
||||
padding: 16px;
|
||||
max-width: 1200px;
|
||||
margin: auto;
|
||||
}
|
||||
.button-row {
|
||||
padding: 8px 16px;
|
||||
border-top: 1px solid var(--divider-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
background: var(--card-background-color);
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-row .buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
max-width: 1200px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.attributes {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.attributes th {
|
||||
text-align: left;
|
||||
background-color: var(--card-background-color);
|
||||
border-bottom: 1px solid var(--primary-text-color);
|
||||
}
|
||||
|
||||
:host([rtl]) .attributes th {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.attributes tr {
|
||||
vertical-align: top;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.attributes tr:nth-child(odd) {
|
||||
background-color: var(--table-row-background-color, #eee);
|
||||
}
|
||||
|
||||
.attributes tr:nth-child(even) {
|
||||
background-color: var(--table-row-alternative-background-color, #eee);
|
||||
}
|
||||
|
||||
.attributes td:nth-child(3) {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.attributes td {
|
||||
padding: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("developer-tools-service", HaPanelDevService);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"developer-tools-service": HaPanelDevService;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import {
|
||||
mdiInformationOutline,
|
||||
mdiClipboardTextMultipleOutline
|
||||
} from "@mdi/js";
|
||||
import { mdiInformationOutline } from "@mdi/js";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
@@ -18,7 +15,6 @@ import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
|
||||
const ERROR_SENTINEL = {};
|
||||
/*
|
||||
@@ -169,7 +165,7 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
<th>[[localize('ui.panel.developer-tools.tabs.states.state')]]</th>
|
||||
<th hidden$="[[narrow]]">
|
||||
[[localize('ui.panel.developer-tools.tabs.states.attributes')]]
|
||||
<paper-checkbox checked="{{_showAttributes}}" on-change="{{saveAttributeCheckboxState}}"></paper-checkbox>
|
||||
<paper-checkbox checked="{{_showAttributes}}"></paper-checkbox>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -209,12 +205,6 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
title="[[localize('ui.panel.developer-tools.tabs.states.more_info')]]"
|
||||
path="[[informationOutlineIcon()]]"
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon
|
||||
on-click="copyEntity"
|
||||
alt="[[localize('ui.panel.developer-tools.tabs.states.copy_id')]]"
|
||||
title="[[localize('ui.panel.developer-tools.tabs.states.copy_id')]]"
|
||||
path="[[clipboardOutlineIcon()]]"
|
||||
></ha-svg-icon>
|
||||
<a href="#" on-click="entitySelected">[[entity.entity_id]]</a>
|
||||
</td>
|
||||
<td>
|
||||
@@ -285,7 +275,7 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
|
||||
_showAttributes: {
|
||||
type: Boolean,
|
||||
value: JSON.parse(localStorage.getItem("devToolsShowAttributes") || true),
|
||||
value: true,
|
||||
},
|
||||
|
||||
_entities: {
|
||||
@@ -306,11 +296,6 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
};
|
||||
}
|
||||
|
||||
copyEntity(ev) {
|
||||
ev.preventDefault();
|
||||
copyToClipboard(ev.model.entity.entity_id);
|
||||
}
|
||||
|
||||
entitySelected(ev) {
|
||||
const state = ev.model.entity;
|
||||
this._entityId = state.entity_id;
|
||||
@@ -360,10 +345,6 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
return mdiInformationOutline;
|
||||
}
|
||||
|
||||
clipboardOutlineIcon() {
|
||||
return mdiClipboardTextMultipleOutline;
|
||||
}
|
||||
|
||||
computeEntities(hass, _entityFilter, _stateFilter, _attributeFilter) {
|
||||
return Object.keys(hass.states)
|
||||
.map(function (key) {
|
||||
@@ -478,14 +459,6 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
return Array.isArray(value) ? value.join(", ") : value;
|
||||
}
|
||||
|
||||
saveAttributeCheckboxState(ev) {
|
||||
try {
|
||||
localStorage.setItem("devToolsShowAttributes", ev.target.checked);
|
||||
} catch (e) {
|
||||
// Catch for Safari private mode
|
||||
}
|
||||
}
|
||||
|
||||
_computeParsedStateAttributes(stateAttributes) {
|
||||
try {
|
||||
return stateAttributes.trim() ? safeLoad(stateAttributes) : {};
|
||||
|
||||
@@ -199,11 +199,11 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--ha-picture-card-background-color, rgba(0, 0, 0, 0.3));
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
padding: 16px;
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
color: var(--ha-picture-card-text-color, white);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.both {
|
||||
|
||||
@@ -314,11 +314,11 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--ha-picture-card-background-color, rgba(0, 0, 0, 0.3));
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
padding: 4px 8px;
|
||||
font-size: 16px;
|
||||
line-height: 40px;
|
||||
color: var(--ha-picture-card-text-color, white);
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
@@ -332,11 +332,11 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
||||
ha-icon-button {
|
||||
--mdc-icon-button-size: 40px;
|
||||
--disabled-text-color: currentColor;
|
||||
color: var(--ha-picture-icon-button-color, #a9a9a9);
|
||||
color: #a9a9a9;
|
||||
}
|
||||
|
||||
ha-icon-button.state-on {
|
||||
color: var(--ha-picture-icon-button-on-color, white);
|
||||
color: white;
|
||||
}
|
||||
.state {
|
||||
display: block;
|
||||
|
||||
@@ -49,15 +49,10 @@ export class HuiActionEditor extends LitElement {
|
||||
return config.url_path || "";
|
||||
}
|
||||
|
||||
get _service(): string {
|
||||
const config = this.config as CallServiceActionConfig;
|
||||
return config.service || "";
|
||||
}
|
||||
|
||||
private _serviceAction = memoizeOne(
|
||||
(config: CallServiceActionConfig): ServiceAction => {
|
||||
return {
|
||||
service: this._service,
|
||||
service: config.service || "",
|
||||
data: config.service_data,
|
||||
target: config.target,
|
||||
};
|
||||
@@ -160,25 +155,8 @@ export class HuiActionEditor extends LitElement {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let data;
|
||||
switch (value) {
|
||||
case "url": {
|
||||
data = { url_path: this._url_path };
|
||||
break;
|
||||
}
|
||||
case "call-service": {
|
||||
data = { service: this._service };
|
||||
break;
|
||||
}
|
||||
case "navigate": {
|
||||
data = { navigation_path: this._navigation_path };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { action: value, ...data },
|
||||
value: { action: value },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -216,9 +194,6 @@ export class HuiActionEditor extends LitElement {
|
||||
.dropdown {
|
||||
display: flex;
|
||||
}
|
||||
ha-service-control {
|
||||
--service-control-padding: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ export class HuiDialogEditCard extends LitElement
|
||||
}
|
||||
|
||||
private _opened() {
|
||||
this._cardEditorEl?.focusYamlEditor();
|
||||
this._cardEditorEl?.refreshYamlEditor();
|
||||
}
|
||||
|
||||
private get _canSave(): boolean {
|
||||
|
||||
@@ -9,11 +9,7 @@ export const configElementStyle = css`
|
||||
}
|
||||
.side-by-side > * {
|
||||
flex: 1;
|
||||
padding-right: 8px;
|
||||
}
|
||||
.side-by-side > *:last-child {
|
||||
flex: 1;
|
||||
padding-right: 0;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.suffix {
|
||||
margin: 0 8px;
|
||||
|
||||
@@ -61,8 +61,8 @@ export class HuiConditionalCardEditor extends LitElement
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public focusYamlEditor() {
|
||||
this._cardEditorEl?.focusYamlEditor();
|
||||
public refreshYamlEditor(focus) {
|
||||
this._cardEditorEl?.refreshYamlEditor(focus);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
|
||||
@@ -54,8 +54,8 @@ export class HuiStackCardEditor extends LitElement
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public focusYamlEditor() {
|
||||
this._cardEditorEl?.focusYamlEditor();
|
||||
public refreshYamlEditor(focus) {
|
||||
this._cardEditorEl?.refreshYamlEditor(focus);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
|
||||
@@ -157,14 +157,17 @@ export abstract class HuiElementEditor<T> extends LitElement {
|
||||
this.GUImode = !this.GUImode;
|
||||
}
|
||||
|
||||
public focusYamlEditor() {
|
||||
if (this._configElement?.focusYamlEditor) {
|
||||
this._configElement.focusYamlEditor();
|
||||
public refreshYamlEditor(focus = false) {
|
||||
if (this._configElement?.refreshYamlEditor) {
|
||||
this._configElement.refreshYamlEditor(focus);
|
||||
}
|
||||
if (!this._yamlEditor?.codemirror) {
|
||||
return;
|
||||
}
|
||||
this._yamlEditor.codemirror.focus();
|
||||
this._yamlEditor.codemirror.refresh();
|
||||
if (focus) {
|
||||
this._yamlEditor.codemirror.focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected async getConfigElement(): Promise<
|
||||
@@ -287,7 +290,7 @@ export abstract class HuiElementEditor<T> extends LitElement {
|
||||
|
||||
if (this._configElementType !== this.configElementType) {
|
||||
// If the type has changed, we need to load a new GUI editor
|
||||
this._guiSupported = undefined;
|
||||
this._guiSupported = false;
|
||||
this._configElement = undefined;
|
||||
|
||||
if (!this.configElementType) {
|
||||
|
||||
@@ -120,13 +120,13 @@ const actionConfigStructConfirmation = union([
|
||||
|
||||
const actionConfigStructUrl = object({
|
||||
action: literal("url"),
|
||||
url_path: string(),
|
||||
url_path: optional(string()),
|
||||
confirmation: optional(actionConfigStructConfirmation),
|
||||
});
|
||||
|
||||
const actionConfigStructService = object({
|
||||
action: literal("call-service"),
|
||||
service: string(),
|
||||
service: optional(string()),
|
||||
service_data: optional(object()),
|
||||
target: optional(
|
||||
object({
|
||||
@@ -140,7 +140,7 @@ const actionConfigStructService = object({
|
||||
|
||||
const actionConfigStructNavigate = object({
|
||||
action: literal("navigate"),
|
||||
navigation_path: string(),
|
||||
navigation_path: optional(string()),
|
||||
confirmation: optional(actionConfigStructConfirmation),
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { canRun, ScriptEntity } from "../../../data/script";
|
||||
import { canExcecute, ScriptEntity } from "../../../data/script";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import "../components/hui-generic-entity-row";
|
||||
@@ -54,8 +54,7 @@ class HuiScriptEntityRow extends LitElement implements LovelaceRow {
|
||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||
${stateObj.state === "on"
|
||||
? html`<mwc-button @click=${this._cancelScript}>
|
||||
${stateObj.attributes.mode !== "single" &&
|
||||
(stateObj.attributes.current || 0) > 0
|
||||
${(stateObj.attributes.current || 0) > 0
|
||||
? this.hass.localize(
|
||||
"ui.card.script.cancel_multiple",
|
||||
"number",
|
||||
@@ -66,12 +65,12 @@ class HuiScriptEntityRow extends LitElement implements LovelaceRow {
|
||||
: ""}
|
||||
${stateObj.state === "off" || stateObj.attributes.max
|
||||
? html`<mwc-button
|
||||
@click=${this._runScript}
|
||||
@click=${this._executeScript}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state) ||
|
||||
!canRun(stateObj)}
|
||||
!canExcecute(stateObj)}
|
||||
>
|
||||
${this._config.action_name ||
|
||||
this.hass!.localize("ui.card.script.run")}
|
||||
this.hass!.localize("ui.card.script.execute")}
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
</hui-generic-entity-row>
|
||||
@@ -91,7 +90,7 @@ class HuiScriptEntityRow extends LitElement implements LovelaceRow {
|
||||
this._callService("turn_off");
|
||||
}
|
||||
|
||||
private _runScript(ev): void {
|
||||
private _executeScript(ev): void {
|
||||
ev.stopPropagation();
|
||||
this._callService("turn_on");
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user