mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-12 18:59:26 +00:00
Compare commits
1 Commits
dash
...
dialog-clo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
45d78c0c77 |
@@ -28,7 +28,9 @@
|
||||
"__BUILD__": false,
|
||||
"__VERSION__": false,
|
||||
"__STATIC_PATH__": false,
|
||||
"Polymer": true
|
||||
"Polymer": true,
|
||||
"webkitSpeechRecognition": false,
|
||||
"ResizeObserver": false
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
@@ -104,6 +106,5 @@
|
||||
"lit/attribute-value-entities": 0
|
||||
},
|
||||
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
|
||||
"processor": "disable/disable",
|
||||
"ignorePatterns": ["src/resources/lit-virtualizer/*"]
|
||||
"processor": "disable/disable"
|
||||
}
|
||||
|
21
.gitignore
vendored
21
.gitignore
vendored
@@ -1,17 +1,10 @@
|
||||
.DS_Store
|
||||
.reify-cache
|
||||
|
||||
# build
|
||||
build
|
||||
build-translations/*
|
||||
hass_frontend/*
|
||||
dist
|
||||
|
||||
# yarn
|
||||
.yarn
|
||||
yarn-error.log
|
||||
node_modules/*
|
||||
npm-debug.log
|
||||
.DS_Store
|
||||
hass_frontend/*
|
||||
.reify-cache
|
||||
|
||||
# Python stuff
|
||||
*.py[cod]
|
||||
@@ -21,8 +14,11 @@ npm-debug.log
|
||||
# venv stuff
|
||||
pyvenv.cfg
|
||||
pip-selfcheck.json
|
||||
venv/*
|
||||
venv
|
||||
.venv
|
||||
lib
|
||||
bin
|
||||
dist
|
||||
|
||||
# vscode
|
||||
.vscode/*
|
||||
@@ -35,8 +31,9 @@ src/cast/dev_const.ts
|
||||
|
||||
# Secrets
|
||||
.lokalise_token
|
||||
yarn-error.log
|
||||
|
||||
# asdf
|
||||
#asdf
|
||||
.tool-versions
|
||||
|
||||
# Home Assistant config
|
||||
|
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
require: "test-mocha/testconf.js",
|
||||
timeout: 10000,
|
||||
};
|
@@ -52,7 +52,11 @@ const createRollupConfig = ({
|
||||
browser: true,
|
||||
rootDir: paths.polymer_dir,
|
||||
}),
|
||||
commonjs(),
|
||||
commonjs({
|
||||
namedExports: {
|
||||
"js-yaml": ["safeDump", "safeLoad"],
|
||||
},
|
||||
}),
|
||||
json(),
|
||||
babel({
|
||||
...bundle.babelOptions({ latestBuild }),
|
||||
|
@@ -116,9 +116,8 @@ const createWebpackConfig = ({
|
||||
// We need to change the import of the polyfill for EventTarget, so we replace the polyfill file with our customized one
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
new RegExp(
|
||||
path.resolve(
|
||||
paths.polymer_dir,
|
||||
"src/resources/lit-virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js"
|
||||
require.resolve(
|
||||
"@lit-labs/virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js"
|
||||
)
|
||||
),
|
||||
path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js")
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { load } from "js-yaml";
|
||||
import { safeLoad } from "js-yaml";
|
||||
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element";
|
||||
|
||||
class DemoCard extends PolymerElement {
|
||||
@@ -80,7 +80,7 @@ class DemoCard extends PolymerElement {
|
||||
card.removeChild(card.lastChild);
|
||||
}
|
||||
|
||||
const el = this._createCardElement(load(config.config)[0]);
|
||||
const el = this._createCardElement(safeLoad(config.config)[0]);
|
||||
card.appendChild(el);
|
||||
this._getSize(el);
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { dump } from "js-yaml";
|
||||
import { safeDump } from "js-yaml";
|
||||
import { html, css, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../src/components/ha-card";
|
||||
@@ -56,7 +56,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
||||
(conf) => html`
|
||||
<div class="action">
|
||||
<span>${describeAction(this.hass, conf as any)}</span>
|
||||
<pre>${dump(conf)}</pre>
|
||||
<pre>${safeDump(conf)}</pre>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { dump } from "js-yaml";
|
||||
import { safeDump } from "js-yaml";
|
||||
import { html, css, LitElement, TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../src/components/ha-card";
|
||||
@@ -26,7 +26,7 @@ export class DemoAutomationDescribeCondition extends LitElement {
|
||||
(conf) => html`
|
||||
<div class="condition">
|
||||
<span>${describeCondition(conf as any)}</span>
|
||||
<pre>${dump(conf)}</pre>
|
||||
<pre>${safeDump(conf)}</pre>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { dump } from "js-yaml";
|
||||
import { safeDump } from "js-yaml";
|
||||
import { html, css, LitElement, TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../src/components/ha-card";
|
||||
@@ -29,7 +29,7 @@ export class DemoAutomationDescribeTrigger extends LitElement {
|
||||
(conf) => html`
|
||||
<div class="trigger">
|
||||
<span>${describeTrigger(conf as any)}</span>
|
||||
<pre>${dump(conf)}</pre>
|
||||
<pre>${safeDump(conf)}</pre>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
|
@@ -28,11 +28,10 @@ const createConfigEntry = (
|
||||
title,
|
||||
source: "zeroconf",
|
||||
state: "loaded",
|
||||
connection_class: "local_push",
|
||||
supports_options: false,
|
||||
supports_unload: true,
|
||||
disabled_by: null,
|
||||
pref_disable_new_entities: false,
|
||||
pref_disable_polling: false,
|
||||
reason: null,
|
||||
...override,
|
||||
});
|
||||
@@ -65,9 +64,6 @@ const configPanelEntry = createConfigEntry("Config Panel", {
|
||||
const optionsFlowEntry = createConfigEntry("Options Flow", {
|
||||
supports_options: true,
|
||||
});
|
||||
const disabledPollingEntry = createConfigEntry("Disabled Polling", {
|
||||
pref_disable_polling: true,
|
||||
});
|
||||
const setupErrorEntry = createConfigEntry("Setup Error", {
|
||||
state: "setup_error",
|
||||
});
|
||||
@@ -140,7 +136,6 @@ const configEntries: Array<{
|
||||
{ items: [loadedEntry] },
|
||||
{ items: [configPanelEntry] },
|
||||
{ items: [optionsFlowEntry] },
|
||||
{ items: [disabledPollingEntry] },
|
||||
{ items: [nameAsDomainEntry] },
|
||||
{ items: [longNameEntry] },
|
||||
{ items: [longNonBreakingNameEntry] },
|
||||
|
@@ -3,7 +3,6 @@ import { ActionDetail } from "@material/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
|
||||
import { DEFAULT_SCHEMA, Type } from "js-yaml";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -12,7 +11,7 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
@@ -28,7 +27,6 @@ import {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
setHassioAddonOption,
|
||||
validateHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
@@ -40,13 +38,6 @@ import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"];
|
||||
|
||||
const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
|
||||
new Type("!secret", {
|
||||
kind: "scalar",
|
||||
construct: (data) => `!secret ${data}`,
|
||||
}),
|
||||
]);
|
||||
|
||||
@customElement("hassio-addon-config")
|
||||
class HassioAddonConfig extends LitElement {
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
@@ -134,7 +125,6 @@ class HassioAddonConfig extends LitElement {
|
||||
></ha-form>`
|
||||
: html` <ha-yaml-editor
|
||||
@value-changed=${this._configChanged}
|
||||
.schema=${ADDON_YAML_SCHEMA}
|
||||
></ha-yaml-editor>`}
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${!this._yamlMode ||
|
||||
@@ -279,14 +269,6 @@ class HassioAddonConfig extends LitElement {
|
||||
this._error = undefined;
|
||||
|
||||
try {
|
||||
const validation = await validateHassioAddonOption(
|
||||
this.hass,
|
||||
this.addon.slug,
|
||||
this._editor?.value
|
||||
);
|
||||
if (!validation.valid) {
|
||||
throw Error(validation.message);
|
||||
}
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, {
|
||||
options: this._yamlMode ? this._editor?.value : this._options,
|
||||
});
|
||||
|
@@ -64,7 +64,6 @@ class SupervisorMetric extends LitElement {
|
||||
.value {
|
||||
width: 48px;
|
||||
padding-right: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiArrowUpBoldCircle, mdiPlay, mdiPuzzle, mdiStop } from "@mdi/js";
|
||||
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
@@ -17,36 +17,7 @@ class HassioAddons extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<ha-card
|
||||
.header=${this.supervisor.localize("dashboard.addons")}
|
||||
>
|
||||
<div class="addons" ?narrow=${this.narrow}>
|
||||
${this.supervisor.supervisor.addons.map(
|
||||
(addon) => html`<div
|
||||
class="addon"
|
||||
@click=${this._addonTapped}
|
||||
.addon=${addon}
|
||||
>
|
||||
<div class="icon">
|
||||
<div class="overlay">
|
||||
<ha-svg-icon
|
||||
.title=${addon.state}
|
||||
.path=${addon.state === "started" ? mdiPlay : mdiStop}
|
||||
>
|
||||
</ha-svg-icon>
|
||||
</div>
|
||||
${addon.icon && atLeastVersion(this.hass.config.version, 0, 105)
|
||||
? html`<img src="/api/hassio/addons/${addon.slug}/icon" />`
|
||||
: html`<ha-svg-icon .path=${mdiPuzzle}></ha-svg-icon>`}
|
||||
</div>
|
||||
<div class="name">${addon.name}</div>
|
||||
</div>`
|
||||
)}
|
||||
</div>
|
||||
</ha-card>`;
|
||||
return html`
|
||||
<div class="content">
|
||||
<h1>${this.supervisor.localize("dashboard.addons")}</h1>
|
||||
@@ -117,42 +88,9 @@ class HassioAddons extends LitElement {
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
.addons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, auto);
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
.addons[narrow] {
|
||||
grid-template-columns: repeat(2, auto);
|
||||
}
|
||||
.addon {
|
||||
text-align: center;
|
||||
max-width: 100px;
|
||||
padding: 0 8px;
|
||||
ha-card {
|
||||
cursor: pointer;
|
||||
}
|
||||
.icon > *:not(.overlay) {
|
||||
position: relative;
|
||||
max-height: 60px;
|
||||
max-width: 60px;
|
||||
margin: auto;
|
||||
--mdc-icon-size: 60px;
|
||||
display: flex;
|
||||
}
|
||||
.icon {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.overlay {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
--mdc-icon-size: 24px;
|
||||
color: var(--secondary-text-color);
|
||||
background-color: var(--secondary-background-color);
|
||||
opacity: 0.6;
|
||||
border-radius: 100%;
|
||||
margin-left: 12px;
|
||||
border: 1px var(--secondary-text-color) solid;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@ import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import "./hassio-addons";
|
||||
import "./hassio-update";
|
||||
|
||||
@@ -33,17 +32,14 @@ class HassioDashboard extends LitElement {
|
||||
<span slot="header">
|
||||
${this.supervisor.localize("panel.dashboard")}
|
||||
</span>
|
||||
|
||||
<div class="content">
|
||||
<hassio-update
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
.narrow=${this.narrow}
|
||||
></hassio-update>
|
||||
<hassio-addons
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
.narrow=${this.narrow}
|
||||
></hassio-addons>
|
||||
</div>
|
||||
</hass-tabs-subpage>
|
||||
@@ -53,18 +49,9 @@ class HassioDashboard extends LitElement {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
.content {
|
||||
display: grid;
|
||||
max-width: 1400px;
|
||||
justify-content: center;
|
||||
grid-template-columns: repeat(2, auto);
|
||||
gap: 16px;
|
||||
}
|
||||
.content > * {
|
||||
display: block;
|
||||
min-width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
||||
import { mdiHomeAssistant } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -42,15 +42,11 @@ export class HassioUpdate extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
private _pendingUpdates = memoizeOne(
|
||||
(supervisor: Supervisor): number =>
|
||||
Object.keys(supervisor).filter(
|
||||
(value) => supervisor[value].update_available
|
||||
).length +
|
||||
supervisor.supervisor.addons.filter((addon) => addon.update_available)
|
||||
.length
|
||||
).length
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -64,38 +60,15 @@ export class HassioUpdate extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
.header="${this.supervisor.localize(
|
||||
"common.update_available",
|
||||
"count",
|
||||
updatesAvailable + 1
|
||||
)}
|
||||
🎉"
|
||||
>
|
||||
${this._renderUpdateRow({
|
||||
type: "os",
|
||||
heading: "Home Assistant Operating system",
|
||||
icon: mdiHomeAssistant,
|
||||
version: "5",
|
||||
version_latest: "6",
|
||||
})}
|
||||
${this.supervisor.addon.addons
|
||||
.filter((addon) => addon.update_available)
|
||||
.map((addon) =>
|
||||
this._renderUpdateRow({
|
||||
type: "addon",
|
||||
heading: addon.name,
|
||||
version: addon.version_latest,
|
||||
version_latest: addon.version,
|
||||
image: addon.icon
|
||||
? `/api/hassio/addons/${addon.slug}/icon`
|
||||
: undefined,
|
||||
icon: mdiPuzzle,
|
||||
})
|
||||
)}
|
||||
</ha-card>
|
||||
<div class="content">
|
||||
<h1></h1>
|
||||
<h1>
|
||||
${this.supervisor.localize(
|
||||
"common.update_available",
|
||||
"count",
|
||||
updatesAvailable
|
||||
)}
|
||||
🎉
|
||||
</h1>
|
||||
<div class="card-group">
|
||||
${this._renderUpdateCard(
|
||||
"Home Assistant Core",
|
||||
@@ -127,37 +100,6 @@ export class HassioUpdate extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderUpdateRow(options: {
|
||||
type: "supervisor" | "os" | "core" | "addon";
|
||||
heading: string;
|
||||
version: string;
|
||||
version_latest: string;
|
||||
icon?: string;
|
||||
image?: string;
|
||||
release_notes?: string;
|
||||
slug?: string;
|
||||
}): TemplateResult {
|
||||
return html`<div class="update-row">
|
||||
<paper-icon-item>
|
||||
<div class="icon" slot="item-icon">
|
||||
${options.image && atLeastVersion(this.hass.config.version, 0, 104)
|
||||
? html`<img src="${options.image}" />`
|
||||
: options.icon
|
||||
? html`<ha-svg-icon .path=${options.icon}></ha-svg-icon>`
|
||||
: ""}
|
||||
</div>
|
||||
<paper-item-body two-line>
|
||||
${options.heading}
|
||||
<div secondary>Version ${options.version_latest} is available</div>
|
||||
</paper-item-body>
|
||||
</paper-icon-item>
|
||||
<div class="update-row-actions" ?narrow=${false}>
|
||||
<mwc-button>Releaese notes</mwc-button>
|
||||
<mwc-button>Update</mwc-button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _renderUpdateCard(
|
||||
name: string,
|
||||
key: string,
|
||||
@@ -289,21 +231,31 @@ export class HassioUpdate extends LitElement {
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
.update-row,
|
||||
paper-icon-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.icon {
|
||||
--mdc-icon-size: 48px;
|
||||
float: right;
|
||||
margin: 0 0 2px 10px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.update-row {
|
||||
padding: 8px;
|
||||
justify-content: space-between;
|
||||
.update-heading {
|
||||
font-size: var(--paper-font-subhead_-_font-size);
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5em;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.icon > * {
|
||||
max-height: 32px;
|
||||
max-width: 32px;
|
||||
margin-right: 16px;
|
||||
--mdc-icon-size: 32px;
|
||||
.card-content {
|
||||
height: calc(100% - 47px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.card-actions {
|
||||
text-align: right;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
--paper-item-body-two-line-min-height: 32px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -45,7 +45,7 @@ class HassioRegistriesDialog extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._opened}
|
||||
@closed=${this.closeDialog}
|
||||
@closing=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
@@ -244,6 +244,9 @@ class HassioRegistriesDialog extends LitElement {
|
||||
mwc-list-item span[slot="secondary"] {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -67,7 +67,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._opened}
|
||||
@closed=${this.closeDialog}
|
||||
@closing=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
@@ -150,6 +150,9 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
mwc-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
ha-paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
margin: 32px;
|
||||
|
@@ -48,7 +48,6 @@ class HassioCreateSnapshotDialog extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
|
@@ -4,7 +4,6 @@ import { mdiDotsVertical } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { slugify } from "../../../../src/common/string/slugify";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-button-menu";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
@@ -64,8 +63,7 @@ class HassioSnapshotDialog
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
@closed=${this.closeDialog}
|
||||
@closing=${this.closeDialog}
|
||||
.heading=${createCloseHeading(this.hass, this._computeName)}
|
||||
>
|
||||
${this._restoringSnapshot
|
||||
@@ -90,7 +88,7 @@ class HassioSnapshotDialog
|
||||
fixed
|
||||
slot="primaryAction"
|
||||
@action=${this._handleMenuAction}
|
||||
@closed=${(ev: Event) => ev.stopPropagation()}
|
||||
@closing=${(ev: Event) => ev.stopPropagation()}
|
||||
>
|
||||
<mwc-icon-button slot="trigger" alt="menu">
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
@@ -288,9 +286,10 @@ class HassioSnapshotDialog
|
||||
}
|
||||
}
|
||||
|
||||
const name = this._computeName.replace(/[^a-z0-9]+/gi, "_");
|
||||
const a = document.createElement("a");
|
||||
a.href = signedPath.path;
|
||||
a.download = `home_assistant_snapshot_${slugify(this._computeName)}.tar`;
|
||||
a.download = `Hass_io_${name}.tar`;
|
||||
this.shadowRoot!.appendChild(a);
|
||||
a.click();
|
||||
this.shadowRoot!.removeChild(a);
|
||||
|
@@ -103,25 +103,27 @@ export class HassioMain extends SupervisorBaseElement {
|
||||
|
||||
private _applyTheme() {
|
||||
let themeName: string;
|
||||
let themeSettings: Partial<HomeAssistant["selectedTheme"]> | undefined;
|
||||
let themeSettings:
|
||||
| Partial<HomeAssistant["selectedThemeSettings"]>
|
||||
| undefined;
|
||||
|
||||
if (atLeastVersion(this.hass.config.version, 0, 114)) {
|
||||
themeName =
|
||||
this.hass.selectedTheme?.theme ||
|
||||
this.hass.selectedThemeSettings?.theme ||
|
||||
(this.hass.themes.darkMode && this.hass.themes.default_dark_theme
|
||||
? this.hass.themes.default_dark_theme!
|
||||
: this.hass.themes.default_theme);
|
||||
|
||||
themeSettings = this.hass.selectedTheme;
|
||||
themeSettings = this.hass.selectedThemeSettings;
|
||||
if (themeSettings?.dark === undefined) {
|
||||
themeSettings = {
|
||||
...this.hass.selectedTheme,
|
||||
...this.hass.selectedThemeSettings,
|
||||
dark: this.hass.themes.darkMode,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
themeName =
|
||||
((this.hass.selectedTheme as unknown) as string) ||
|
||||
((this.hass.selectedThemeSettings as unknown) as string) ||
|
||||
this.hass.themes.default_theme;
|
||||
}
|
||||
|
||||
|
@@ -1,17 +1,15 @@
|
||||
import "@material/mwc-button";
|
||||
import { ActionDetail } from "@material/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
|
||||
import { mdiDotsVertical, mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import relativeTime from "../../../src/common/datetime/relative_time";
|
||||
@@ -19,25 +17,18 @@ import { HASSDomEvent } from "../../../src/common/dom/fire_event";
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
SelectionChangedEvent,
|
||||
} from "../../../src/components/data-table/ha-data-table";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-fab";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
fetchHassioSnapshots,
|
||||
friendlyFolderName,
|
||||
HassioSnapshot,
|
||||
reloadHassioSnapshots,
|
||||
removeSnapshot,
|
||||
} from "../../../src/data/hassio/snapshot";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import "../../../src/layouts/hass-tabs-subpage-data-table";
|
||||
import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subpage-data-table";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { showHassioCreateSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-create-snapshot";
|
||||
@@ -58,15 +49,10 @@ export class HassioSnapshots extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
||||
@state() private _selectedSnapshots: string[] = [];
|
||||
private _firstUpdatedCalled = false;
|
||||
|
||||
@state() private _snapshots?: HassioSnapshot[] = [];
|
||||
|
||||
@query("hass-tabs-subpage-data-table", true)
|
||||
private _dataTable!: HaTabsSubpageDataTable;
|
||||
|
||||
private _firstUpdatedCalled = false;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.hass && this._firstUpdatedCalled) {
|
||||
@@ -167,9 +153,7 @@ export class HassioSnapshots extends LitElement {
|
||||
.data=${this._snapshotData(this._snapshots || [])}
|
||||
id="slug"
|
||||
@row-click=${this._handleRowClicked}
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
clickable
|
||||
selectable
|
||||
hasFab
|
||||
main-page
|
||||
supervisor
|
||||
@@ -192,45 +176,6 @@ export class HassioSnapshots extends LitElement {
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
|
||||
${this._selectedSnapshots.length
|
||||
? html`<div
|
||||
class=${classMap({
|
||||
"header-toolbar": this.narrow,
|
||||
"table-header": !this.narrow,
|
||||
})}
|
||||
slot="header"
|
||||
>
|
||||
<p class="selected-txt">
|
||||
${this.supervisor.localize("snapshot.selected", {
|
||||
number: this._selectedSnapshots.length,
|
||||
})}
|
||||
</p>
|
||||
<div class="header-btns">
|
||||
${!this.narrow
|
||||
? html`
|
||||
<mwc-button
|
||||
@click=${this._deleteSelected}
|
||||
class="warning"
|
||||
>
|
||||
${this.supervisor.localize("snapshot.delete_selected")}
|
||||
</mwc-button>
|
||||
`
|
||||
: html`
|
||||
<mwc-icon-button
|
||||
id="delete-btn"
|
||||
class="warning"
|
||||
@click=${this._deleteSelected}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<paper-tooltip animation-delay="0" for="delete-btn">
|
||||
${this.supervisor.localize("snapshot.delete_selected")}
|
||||
</paper-tooltip>
|
||||
`}
|
||||
</div>
|
||||
</div> `
|
||||
: ""}
|
||||
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
@click=${this._createSnapshot}
|
||||
@@ -254,12 +199,6 @@ export class HassioSnapshots extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _handleSelectionChanged(
|
||||
ev: HASSDomEvent<SelectionChangedEvent>
|
||||
): void {
|
||||
this._selectedSnapshots = ev.detail.value;
|
||||
}
|
||||
|
||||
private _showUploadSnapshotDialog() {
|
||||
showSnapshotUploadDialog(this, {
|
||||
showSnapshot: (slug: string) =>
|
||||
@@ -277,35 +216,6 @@ export class HassioSnapshots extends LitElement {
|
||||
this._snapshots = await fetchHassioSnapshots(this.hass);
|
||||
}
|
||||
|
||||
private async _deleteSelected() {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
title: this.supervisor.localize("snapshot.delete_snapshot_title"),
|
||||
text: this.supervisor.localize("snapshot.delete_snapshot_text", {
|
||||
number: this._selectedSnapshots.length,
|
||||
}),
|
||||
confirmText: this.supervisor.localize("snapshot.delete_snapshot_confirm"),
|
||||
});
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(
|
||||
this._selectedSnapshots.map((slug) => removeSnapshot(this.hass, slug))
|
||||
);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this.supervisor.localize("snapshot.failed_to_delete"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
return;
|
||||
}
|
||||
await reloadHassioSnapshots(this.hass);
|
||||
this._snapshots = await fetchHassioSnapshots(this.hass);
|
||||
this._dataTable.clearSelection();
|
||||
}
|
||||
|
||||
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
|
||||
const slug = ev.detail.id;
|
||||
showHassioSnapshotDialog(this, {
|
||||
@@ -334,45 +244,7 @@ export class HassioSnapshots extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
.table-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 58px;
|
||||
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
||||
}
|
||||
.header-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: var(--secondary-text-color);
|
||||
position: relative;
|
||||
top: -4px;
|
||||
}
|
||||
.selected-txt {
|
||||
font-weight: bold;
|
||||
padding-left: 16px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.table-header .selected-txt {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.header-toolbar .selected-txt {
|
||||
font-size: 16px;
|
||||
}
|
||||
.header-toolbar .header-btns {
|
||||
margin-right: -12px;
|
||||
}
|
||||
.header-btns > mwc-button,
|
||||
.header-btns > mwc-icon-button {
|
||||
margin: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
return [haStyle, hassioStyle];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,7 @@ import "@material/mwc-button";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import { dump } from "js-yaml";
|
||||
import { safeDump } from "js-yaml";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -233,7 +233,7 @@ class HassioHostInfo extends LitElement {
|
||||
const content = await fetchHassioHardwareInfo(this.hass);
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: this.supervisor.localize("system.host.hardware"),
|
||||
content: `<pre>${dump(content, { indent: 2 })}</pre>`,
|
||||
content: `<pre>${safeDump(content, { indent: 2 })}</pre>`,
|
||||
});
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
|
60
package.json
60
package.json
@@ -16,13 +16,13 @@
|
||||
"lint:lit": "lit-analyzer \"**/src/**/*.ts\" --format markdown --outFile result.md",
|
||||
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types",
|
||||
"format": "yarn run format:eslint && yarn run format:prettier",
|
||||
"mocha": "ts-mocha -p test-mocha/tsconfig.test.json \"test-mocha/**/*.ts\"",
|
||||
"mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts",
|
||||
"test": "yarn run lint && yarn run mocha"
|
||||
},
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.1",
|
||||
"@braintree/sanitize-url": "^5.0.0",
|
||||
"@codemirror/commands": "^0.18.0",
|
||||
"@codemirror/gutter": "^0.18.0",
|
||||
"@codemirror/highlight": "^0.18.0",
|
||||
@@ -35,8 +35,8 @@
|
||||
"@codemirror/text": "^0.18.0",
|
||||
"@codemirror/view": "^0.18.0",
|
||||
"@formatjs/intl-getcanonicallocales": "^1.5.10",
|
||||
"@formatjs/intl-locale": "^2.4.28",
|
||||
"@formatjs/intl-pluralrules": "^4.0.22",
|
||||
"@formatjs/intl-locale": "^2.4.24",
|
||||
"@formatjs/intl-pluralrules": "^4.0.18",
|
||||
"@fullcalendar/common": "5.1.0",
|
||||
"@fullcalendar/core": "5.1.0",
|
||||
"@fullcalendar/daygrid": "5.1.0",
|
||||
@@ -71,6 +71,7 @@
|
||||
"@polymer/iron-label": "^3.0.1",
|
||||
"@polymer/iron-overlay-behavior": "^3.0.2",
|
||||
"@polymer/iron-resizable-behavior": "^3.0.1",
|
||||
"@polymer/paper-card": "^3.0.1",
|
||||
"@polymer/paper-checkbox": "^3.1.0",
|
||||
"@polymer/paper-dialog": "^3.0.1",
|
||||
"@polymer/paper-dialog-behavior": "^3.0.1",
|
||||
@@ -100,26 +101,26 @@
|
||||
"@webcomponents/webcomponentsjs": "^2.2.7",
|
||||
"chart.js": "^2.9.4",
|
||||
"chartjs-chart-timeline": "^0.4.0",
|
||||
"comlink": "^4.3.1",
|
||||
"comlink": "^4.3.0",
|
||||
"core-js": "^3.6.5",
|
||||
"cropperjs": "^1.5.11",
|
||||
"cropperjs": "^1.5.7",
|
||||
"deep-clone-simple": "^1.1.1",
|
||||
"deep-freeze": "^0.0.1",
|
||||
"fecha": "^4.2.0",
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^1.0.4",
|
||||
"hls.js": "^1.0.3",
|
||||
"home-assistant-js-websocket": "^5.10.0",
|
||||
"idb-keyval": "^5.0.5",
|
||||
"intl-messageformat": "^9.6.16",
|
||||
"js-yaml": "^4.1.0",
|
||||
"idb-keyval": "^3.2.0",
|
||||
"intl-messageformat": "^9.6.13",
|
||||
"js-yaml": "^3.13.1",
|
||||
"leaflet": "^1.7.1",
|
||||
"leaflet-draw": "^1.0.4",
|
||||
"lit": "^2.0.0-rc.2",
|
||||
"lit-vaadin-helpers": "^0.1.3",
|
||||
"marked": "^2.0.5",
|
||||
"marked": "2.0.0",
|
||||
"mdn-polyfills": "^5.16.0",
|
||||
"memoize-one": "^5.2.1",
|
||||
"memoize-one": "^5.0.2",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "^0.3.1",
|
||||
"punycode": "^2.1.1",
|
||||
@@ -129,12 +130,12 @@
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"sortablejs": "^1.10.2",
|
||||
"superstruct": "^0.15.2",
|
||||
"tinykeys": "^1.1.3",
|
||||
"tinykeys": "^1.1.1",
|
||||
"tsparticles": "^1.19.2",
|
||||
"unfetch": "^4.1.0",
|
||||
"vis-data": "^7.1.2",
|
||||
"vis-data": "^7.1.1",
|
||||
"vis-network": "^8.5.4",
|
||||
"vue": "^2.6.12",
|
||||
"vue": "^2.6.11",
|
||||
"vue2-daterange-picker": "^0.5.1",
|
||||
"web-animations-js": "^2.3.2",
|
||||
"workbox-cacheable-response": "^6.1.5",
|
||||
@@ -146,7 +147,7 @@
|
||||
"xss": "^1.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.3",
|
||||
"@babel/core": "^7.14.0",
|
||||
"@babel/plugin-external-helpers": "^7.12.13",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/plugin-proposal-decorators": "^7.13.15",
|
||||
@@ -155,7 +156,7 @@
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
||||
"@babel/preset-env": "^7.14.2",
|
||||
"@babel/preset-env": "^7.14.0",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@koa/cors": "^3.1.0",
|
||||
"@open-wc/dev-server-hmr": "^0.0.2",
|
||||
@@ -164,13 +165,16 @@
|
||||
"@rollup/plugin-json": "^4.0.3",
|
||||
"@rollup/plugin-node-resolve": "^7.1.3",
|
||||
"@rollup/plugin-replace": "^2.3.2",
|
||||
"@types/chromecast-caf-receiver": "5.0.12",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/chromecast-caf-receiver": "^5.0.11",
|
||||
"@types/chromecast-caf-sender": "^1.0.3",
|
||||
"@types/js-yaml": "^4.0.1",
|
||||
"@types/js-yaml": "^3.12.1",
|
||||
"@types/leaflet": "^1.7.0",
|
||||
"@types/leaflet-draw": "^1.0.3",
|
||||
"@types/marked": "^2.0.3",
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/marked": "^1.2.2",
|
||||
"@types/memoize-one": "4.1.0",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/resize-observer-browser": "^0.1.3",
|
||||
"@types/sortablejs": "^1.10.6",
|
||||
"@types/webspeechapi": "^0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
@@ -178,7 +182,7 @@
|
||||
"@web/dev-server": "^0.0.24",
|
||||
"@web/dev-server-rollup": "^0.2.11",
|
||||
"babel-loader": "^8.1.0",
|
||||
"chai": "^4.3.4",
|
||||
"chai": "^4.2.0",
|
||||
"cpx": "^1.5.0",
|
||||
"del": "^4.0.0",
|
||||
"eslint": "^7.25.0",
|
||||
@@ -192,7 +196,7 @@
|
||||
"eslint-plugin-wc": "^1.3.0",
|
||||
"fancy-log": "^1.3.3",
|
||||
"fs-extra": "^7.0.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-foreach": "^0.1.0",
|
||||
"gulp-json-transform": "^0.4.6",
|
||||
"gulp-merge-json": "^1.3.1",
|
||||
@@ -206,7 +210,7 @@
|
||||
"magic-string": "^0.25.7",
|
||||
"map-stream": "^0.0.7",
|
||||
"merge-stream": "^1.0.1",
|
||||
"mocha": "^8.4.0",
|
||||
"mocha": "^7.2.0",
|
||||
"object-hash": "^2.0.3",
|
||||
"open": "^7.0.4",
|
||||
"prettier": "^2.0.4",
|
||||
@@ -216,13 +220,13 @@
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
"rollup-plugin-terser": "^5.3.0",
|
||||
"rollup-plugin-visualizer": "^4.0.4",
|
||||
"serve": "^11.3.2",
|
||||
"sinon": "^11.0.0",
|
||||
"serve": "^11.3.0",
|
||||
"sinon": "^7.3.1",
|
||||
"source-map-url": "^0.4.0",
|
||||
"systemjs": "^6.3.2",
|
||||
"terser-webpack-plugin": "^5.1.2",
|
||||
"terser-webpack-plugin": "^5.1.1",
|
||||
"ts-lit-plugin": "^1.2.1",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"ts-mocha": "^7.0.0",
|
||||
"typescript": "^4.2.4",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20210601.1",
|
||||
version="20210518.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@@ -31,7 +31,7 @@ export const applyThemesOnElement = (
|
||||
element,
|
||||
themes: HomeAssistant["themes"],
|
||||
selectedTheme?: string,
|
||||
themeSettings?: Partial<HomeAssistant["selectedTheme"]>
|
||||
themeSettings?: Partial<HomeAssistant["selectedThemeSettings"]>
|
||||
) => {
|
||||
let cacheKey = selectedTheme;
|
||||
let themeRules: Partial<ThemeVars> = {};
|
||||
@@ -39,7 +39,7 @@ export const applyThemesOnElement = (
|
||||
if (themeSettings) {
|
||||
if (themeSettings.dark) {
|
||||
cacheKey = `${cacheKey}__dark`;
|
||||
themeRules = { ...darkStyles };
|
||||
themeRules = darkStyles;
|
||||
}
|
||||
|
||||
if (selectedTheme === "default") {
|
||||
|
@@ -1,7 +1,14 @@
|
||||
/* eslint-disable */
|
||||
// @ts-ignore
|
||||
export const SpeechRecognition =
|
||||
// @ts-ignore
|
||||
window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
// @ts-ignore
|
||||
export const SpeechGrammarList =
|
||||
// @ts-ignore
|
||||
window.SpeechGrammarList || window.webkitSpeechGrammarList;
|
||||
// @ts-ignore
|
||||
export const SpeechRecognitionEvent =
|
||||
// @ts-expect-error
|
||||
// @ts-ignore
|
||||
window.SpeechRecognitionEvent || window.webkitSpeechRecognitionEvent;
|
||||
/* eslint-enable */
|
||||
|
@@ -89,6 +89,8 @@ export const domainIcon = (
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
console.warn(`Unable to find icon for domain ${domain}`);
|
||||
console.warn(
|
||||
"Unable to find icon for domain " + domain + " (" + stateObj + ")"
|
||||
);
|
||||
return DEFAULT_DOMAIN_ICON;
|
||||
};
|
||||
|
@@ -1,2 +0,0 @@
|
||||
export const escapeRegExp = (text: string): string =>
|
||||
text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
@@ -92,7 +92,7 @@ function isUpperCaseAtPos(pos: number, word: string, wordLow: string): boolean {
|
||||
return word[pos] !== wordLow[pos];
|
||||
}
|
||||
|
||||
export function isPatternInWord(
|
||||
function isPatternInWord(
|
||||
patternLow: string,
|
||||
patternPos: number,
|
||||
patternLen: number,
|
||||
@@ -121,7 +121,7 @@ enum Arrow {
|
||||
}
|
||||
|
||||
/**
|
||||
* An array representing a fuzzy match.
|
||||
* An array representating a fuzzy match.
|
||||
*
|
||||
* 0. the score
|
||||
* 1. the offset at which matching started
|
||||
|
@@ -5,7 +5,7 @@ import { fuzzyScore } from "./filter";
|
||||
* in that order, allowing for skipping. Ex: "chdr" exists in "chandelier")
|
||||
*
|
||||
* @param {string} filter - Sequence of letters to check for
|
||||
* @param {ScorableTextItem} item - Item against whose strings will be checked
|
||||
* @param {string} word - Word to check for sequence
|
||||
*
|
||||
* @return {number} Score representing how well the word matches the filter. Return of 0 means no match.
|
||||
*/
|
||||
|
@@ -1,11 +0,0 @@
|
||||
// https://regex101.com/r/kc5C14/2
|
||||
const regExpString = "^\\d{4}-(0[1-9]|1[0-2])-([12]\\d|0[1-9]|3[01])";
|
||||
|
||||
const regExp = new RegExp(regExpString + "$");
|
||||
// 2nd expression without the "end of string" enforced, so it can be used
|
||||
// to just verify the start of a string and then based on that result e.g.
|
||||
// check for a full timestamp string efficiently.
|
||||
const regExpNoStringEnd = new RegExp(regExpString);
|
||||
|
||||
export const isDate = (input: string, allowCharsAfterDate = false): boolean =>
|
||||
allowCharsAfterDate ? regExpNoStringEnd.test(input) : regExp.test(input);
|
@@ -1,11 +0,0 @@
|
||||
// https://stackoverflow.com/a/14322189/1947205
|
||||
// Changes:
|
||||
// 1. Do not allow a plus or minus at the start.
|
||||
// 2. Enforce that we have a "T" or a blank after the date portion
|
||||
// to ensure we have a timestamp and not only a date.
|
||||
// 3. Disallow dates based on week number.
|
||||
// 4. Disallow dates only consisting of a year.
|
||||
// https://regex101.com/r/kc5C14/3
|
||||
const regexp = /^\d{4}-(0[1-9]|1[0-2])-([12]\d|0[1-9]|3[01])[T| ](((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)(\8[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)$/;
|
||||
|
||||
export const isTimestamp = (input: string): boolean => regexp.test(input);
|
@@ -1,6 +0,0 @@
|
||||
import { refine, string } from "superstruct";
|
||||
|
||||
const isCustomType = (value: string) => value.startsWith("custom:");
|
||||
|
||||
export const customType = () =>
|
||||
refine(string(), "custom element type", isCustomType);
|
@@ -1,6 +1,11 @@
|
||||
import { refine, string } from "superstruct";
|
||||
|
||||
const isEntityId = (value: string): boolean => value.includes(".");
|
||||
const isEntityId = (value: string): boolean => {
|
||||
if (!value.includes(".")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const entityId = () =>
|
||||
refine(string(), "entity ID (domain.entity)", isEntityId);
|
||||
|
@@ -1,5 +1,10 @@
|
||||
import { refine, string } from "superstruct";
|
||||
|
||||
const isIcon = (value: string) => value.includes(":");
|
||||
const isIcon = (value: string) => {
|
||||
if (!value.includes(":")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const icon = () => refine(string(), "icon (mdi:icon-name)", isIcon);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { shouldPolyfill } from "@formatjs/intl-pluralrules/lib/should-polyfill";
|
||||
import { shouldPolyfill } from "@formatjs/intl-pluralrules/should-polyfill";
|
||||
import IntlMessageFormat from "intl-messageformat";
|
||||
import { Resources } from "../../types";
|
||||
|
||||
@@ -86,15 +86,11 @@ export const computeLocalize = async (
|
||||
| undefined;
|
||||
|
||||
if (!translatedMessage) {
|
||||
try {
|
||||
translatedMessage = new IntlMessageFormat(
|
||||
translatedValue,
|
||||
language,
|
||||
formats
|
||||
);
|
||||
} catch (err) {
|
||||
return "Translation error: " + err.message;
|
||||
}
|
||||
translatedMessage = new IntlMessageFormat(
|
||||
translatedValue,
|
||||
language,
|
||||
formats
|
||||
);
|
||||
cache._localizationCache[messageKey] = translatedMessage;
|
||||
}
|
||||
|
||||
|
@@ -4,25 +4,29 @@
|
||||
// be triggered. The function will be called after it stops being called for
|
||||
// N milliseconds. If `immediate` is passed, trigger the function on the
|
||||
// leading edge, instead of the trailing.
|
||||
|
||||
export const debounce = <T extends any[]>(
|
||||
func: (...args: T) => void,
|
||||
wait: number,
|
||||
// eslint-disable-next-line: ban-types
|
||||
export const debounce = <T extends (...args) => unknown>(
|
||||
func: T,
|
||||
wait,
|
||||
immediate = false
|
||||
) => {
|
||||
let timeout: number | undefined;
|
||||
return (...args: T): void => {
|
||||
): T => {
|
||||
let timeout;
|
||||
// @ts-ignore
|
||||
return function (...args) {
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const context = this;
|
||||
const later = () => {
|
||||
timeout = undefined;
|
||||
timeout = null;
|
||||
if (!immediate) {
|
||||
func(...args);
|
||||
func.apply(context, args);
|
||||
}
|
||||
};
|
||||
const callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = window.setTimeout(later, wait);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) {
|
||||
func(...args);
|
||||
func.apply(context, args);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@@ -1,10 +0,0 @@
|
||||
export const promiseTimeout = (ms: number, promise: Promise<any>) => {
|
||||
const timeout = new Promise((_resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(`Timed out in ${ms} ms.`);
|
||||
}, ms);
|
||||
});
|
||||
|
||||
// Returns a race between our timeout and the passed in promise
|
||||
return Promise.race([promise, timeout]);
|
||||
};
|
@@ -1,4 +1,4 @@
|
||||
import { Layout1d, scroll } from "../../resources/lit-virtualizer";
|
||||
import { Layout1d, scroll } from "@lit-labs/virtualizer";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import {
|
||||
css,
|
||||
@@ -246,7 +246,7 @@ export class HaDataTable extends LitElement {
|
||||
aria-rowcount=${this._filteredData.length + 1}
|
||||
style=${styleMap({
|
||||
height: this.autoHeight
|
||||
? `${(this._filteredData.length || 1) * 53 + 53}px`
|
||||
? `${(this._filteredData.length || 1) * 53 + 57}px`
|
||||
: `calc(100% - ${this._headerHeight}px)`,
|
||||
})}
|
||||
>
|
||||
@@ -340,10 +340,11 @@ export class HaDataTable extends LitElement {
|
||||
${scroll({
|
||||
items: this._items,
|
||||
layout: Layout1d,
|
||||
// @ts-expect-error
|
||||
renderItem: (row: DataTableRowData, index) => {
|
||||
// not sure how this happens...
|
||||
if (!row) {
|
||||
return html``;
|
||||
return "";
|
||||
}
|
||||
if (row.append) {
|
||||
return html`
|
||||
@@ -919,11 +920,13 @@ export class HaDataTable extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.scroller {
|
||||
display: flex;
|
||||
position: relative;
|
||||
contain: strict;
|
||||
height: calc(100% - 57px);
|
||||
}
|
||||
|
||||
.mdc-data-table__table.auto-height .scroller {
|
||||
overflow-y: hidden !important;
|
||||
.mdc-data-table__table:not(.auto-height) .scroller {
|
||||
overflow: auto;
|
||||
}
|
||||
.grows {
|
||||
flex-grow: 1;
|
||||
|
@@ -4,6 +4,7 @@ import {
|
||||
fetchDeviceActions,
|
||||
localizeDeviceAutomationAction,
|
||||
} from "../../data/device_automation";
|
||||
import "../ha-paper-dropdown-menu";
|
||||
import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
||||
|
||||
@customElement("ha-device-action-picker")
|
||||
|
@@ -4,6 +4,7 @@ import {
|
||||
fetchDeviceConditions,
|
||||
localizeDeviceAutomationCondition,
|
||||
} from "../../data/device_automation";
|
||||
import "../ha-paper-dropdown-menu";
|
||||
import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
||||
|
||||
@customElement("ha-device-condition-picker")
|
||||
|
@@ -4,6 +4,7 @@ import {
|
||||
fetchDeviceTriggers,
|
||||
localizeDeviceAutomationTrigger,
|
||||
} from "../../data/device_automation";
|
||||
import "../ha-paper-dropdown-menu";
|
||||
import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
||||
|
||||
@customElement("ha-device-trigger-picker")
|
||||
|
@@ -1,14 +1,16 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { until } from "lit/directives/until";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import { HomeAssistant } from "../types";
|
||||
import hassAttributeUtil, {
|
||||
formatAttributeName,
|
||||
formatAttributeValue,
|
||||
} from "../util/hass-attributes-util";
|
||||
import "./ha-expansion-panel";
|
||||
|
||||
let jsYamlPromise: Promise<typeof import("js-yaml")>;
|
||||
|
||||
@customElement("ha-attributes")
|
||||
class HaAttributes extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -54,17 +56,17 @@ class HaAttributes extends LitElement {
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
${this.stateObj.attributes.attribution
|
||||
? html`
|
||||
<div class="attribution">
|
||||
${this.stateObj.attributes.attribution}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
${this.stateObj.attributes.attribution
|
||||
? html`
|
||||
<div class="attribution">
|
||||
${this.stateObj.attributes.attribution}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -91,7 +93,6 @@ class HaAttributes extends LitElement {
|
||||
.attribution {
|
||||
color: var(--secondary-text-color);
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
}
|
||||
pre {
|
||||
font-family: inherit;
|
||||
@@ -123,7 +124,38 @@ class HaAttributes extends LitElement {
|
||||
return "-";
|
||||
}
|
||||
const value = this.stateObj.attributes[attribute];
|
||||
return formatAttributeValue(this.hass, value);
|
||||
return this.formatAttributeValue(value);
|
||||
}
|
||||
|
||||
private formatAttributeValue(value: any): string | TemplateResult {
|
||||
if (value === null) {
|
||||
return "-";
|
||||
}
|
||||
// YAML handling
|
||||
if (
|
||||
(Array.isArray(value) && value.some((val) => val instanceof Object)) ||
|
||||
(!Array.isArray(value) && value instanceof Object)
|
||||
) {
|
||||
if (!jsYamlPromise) {
|
||||
jsYamlPromise = import("js-yaml");
|
||||
}
|
||||
const yaml = jsYamlPromise.then((jsYaml) => jsYaml.safeDump(value));
|
||||
return html` <pre>${until(yaml, "")}</pre> `;
|
||||
}
|
||||
// URL handling
|
||||
if (typeof value === "string" && value.startsWith("http")) {
|
||||
try {
|
||||
// If invalid URL, exception will be raised
|
||||
const url = new URL(value);
|
||||
if (url.protocol === "http:" || url.protocol === "https:")
|
||||
return html`<a target="_blank" rel="noreferrer" href="${value}"
|
||||
>${value}</a
|
||||
>`;
|
||||
} catch (_) {
|
||||
// Nothing to do here
|
||||
}
|
||||
}
|
||||
return Array.isArray(value) ? value.join(", ") : value;
|
||||
}
|
||||
|
||||
private expandedChanged(ev) {
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { CircularProgress } from "@material/mwc-circular-progress";
|
||||
import { CSSResultGroup, css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
||||
@customElement("ha-circular-progress")
|
||||
@@ -42,17 +41,6 @@ export class HaCircularProgress extends CircularProgress {
|
||||
public get indeterminate() {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
super.styles,
|
||||
css`
|
||||
:host {
|
||||
overflow: hidden;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -23,8 +23,8 @@ class HaCoverControls extends LitElement {
|
||||
|
||||
@state() private _entityObj?: CoverEntity;
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has("stateObj")) {
|
||||
this._entityObj = new CoverEntity(this.hass, this.stateObj);
|
||||
|
@@ -22,8 +22,8 @@ class HaCoverTiltControls extends LitElement {
|
||||
|
||||
@state() private _entityObj?: CoverEntity;
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has("stateObj")) {
|
||||
this._entityObj = new CoverEntity(this.hass, this.stateObj);
|
||||
|
@@ -125,7 +125,6 @@ export class HaIcon extends LitElement {
|
||||
databaseIcon = await getIcon(iconName);
|
||||
} catch (_err) {
|
||||
// Firefox in private mode doesn't support IDB
|
||||
// iOS Safari sometimes doesn't open the DB
|
||||
databaseIcon = undefined;
|
||||
}
|
||||
|
||||
|
@@ -1,179 +0,0 @@
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
nothing,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, state, property } from "lit/decorators";
|
||||
import {
|
||||
Adapter,
|
||||
NetworkConfig,
|
||||
IPv6ConfiguredAddress,
|
||||
IPv4ConfiguredAddress,
|
||||
} from "../data/network";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-checkbox";
|
||||
import type { HaCheckbox } from "./ha-checkbox";
|
||||
import "./ha-settings-row";
|
||||
import "./ha-icon";
|
||||
|
||||
const format_addresses = (
|
||||
addresses: IPv6ConfiguredAddress[] | IPv4ConfiguredAddress[]
|
||||
): TemplateResult =>
|
||||
html`${addresses.map((address, i) => [
|
||||
html`<span>${address.address}/${address.network_prefix}</span>`,
|
||||
i < addresses.length - 1 ? ", " : nothing,
|
||||
])}`;
|
||||
|
||||
const format_auto_detected_interfaces = (
|
||||
adapters: Adapter[]
|
||||
): Array<TemplateResult | string> =>
|
||||
adapters.map((adapter) =>
|
||||
adapter.auto
|
||||
? html`${adapter.name}
|
||||
(${format_addresses([...adapter.ipv4, ...adapter.ipv6])})`
|
||||
: ""
|
||||
);
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"network-config-changed": { configured_adapters: string[] };
|
||||
}
|
||||
}
|
||||
@customElement("ha-network")
|
||||
export class HaNetwork extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public networkConfig?: NetworkConfig;
|
||||
|
||||
@state() private _expanded?: boolean;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this.networkConfig === undefined) {
|
||||
return html``;
|
||||
}
|
||||
const configured_adapters = this.networkConfig.configured_adapters || [];
|
||||
return html`
|
||||
<ha-settings-row>
|
||||
<span slot="prefix">
|
||||
<ha-checkbox
|
||||
id="auto_configure"
|
||||
@change=${this._handleAutoConfigureCheckboxClick}
|
||||
.checked=${!configured_adapters.length}
|
||||
name="auto_configure"
|
||||
>
|
||||
</ha-checkbox>
|
||||
</span>
|
||||
<span slot="heading" data-for="auto_configure"> Auto Configure </span>
|
||||
<span slot="description" data-for="auto_configure">
|
||||
Detected:
|
||||
${format_auto_detected_interfaces(this.networkConfig.adapters)}
|
||||
</span>
|
||||
</ha-settings-row>
|
||||
${configured_adapters.length || this._expanded
|
||||
? this.networkConfig.adapters.map(
|
||||
(adapter) =>
|
||||
html`<ha-settings-row>
|
||||
<span slot="prefix">
|
||||
<ha-checkbox
|
||||
id=${adapter.name}
|
||||
@change=${this._handleAdapterCheckboxClick}
|
||||
.checked=${configured_adapters.includes(adapter.name)}
|
||||
.adapter=${adapter.name}
|
||||
name=${adapter.name}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</span>
|
||||
<span slot="heading">
|
||||
Adapter: ${adapter.name}
|
||||
${adapter.default
|
||||
? html`<ha-icon .icon="hass:star"></ha-icon> (Default)`
|
||||
: ""}
|
||||
</span>
|
||||
<span slot="description">
|
||||
${format_addresses([...adapter.ipv4, ...adapter.ipv6])}
|
||||
</span>
|
||||
</ha-settings-row>`
|
||||
)
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleAutoConfigureCheckboxClick(ev: Event) {
|
||||
const checkbox = ev.currentTarget as HaCheckbox;
|
||||
if (this.networkConfig === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let configured_adapters = [...this.networkConfig.configured_adapters];
|
||||
|
||||
if (checkbox.checked) {
|
||||
this._expanded = false;
|
||||
configured_adapters = [];
|
||||
} else {
|
||||
this._expanded = true;
|
||||
for (const adapter of this.networkConfig.adapters) {
|
||||
if (adapter.default) {
|
||||
configured_adapters = [adapter.name];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fireEvent(this, "network-config-changed", {
|
||||
configured_adapters: configured_adapters,
|
||||
});
|
||||
}
|
||||
|
||||
private _handleAdapterCheckboxClick(ev: Event) {
|
||||
const checkbox = ev.currentTarget as HaCheckbox;
|
||||
const adapter_name = (checkbox as any).name;
|
||||
if (this.networkConfig === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const configured_adapters = [...this.networkConfig.configured_adapters];
|
||||
|
||||
if (checkbox.checked) {
|
||||
configured_adapters.push(adapter_name);
|
||||
} else {
|
||||
const index = configured_adapters.indexOf(adapter_name, 0);
|
||||
configured_adapters.splice(index, 1);
|
||||
}
|
||||
|
||||
fireEvent(this, "network-config-changed", {
|
||||
configured_adapters: configured_adapters,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
span[slot="heading"],
|
||||
span[slot="description"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-network": HaNetwork;
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import { DEFAULT_SCHEMA, dump, load, Schema } from "js-yaml";
|
||||
import { safeDump, safeLoad } from "js-yaml";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
@@ -20,8 +20,6 @@ const isEmpty = (obj: Record<string, unknown>): boolean => {
|
||||
export class HaYamlEditor extends LitElement {
|
||||
@property() public value?: any;
|
||||
|
||||
@property({ attribute: false }) public yamlSchema: Schema = DEFAULT_SCHEMA;
|
||||
|
||||
@property() public defaultValue?: any;
|
||||
|
||||
@property() public isValid = true;
|
||||
@@ -32,10 +30,7 @@ export class HaYamlEditor extends LitElement {
|
||||
|
||||
public setValue(value): void {
|
||||
try {
|
||||
this._yaml =
|
||||
value && !isEmpty(value)
|
||||
? dump(value, { schema: this.yamlSchema })
|
||||
: "";
|
||||
this._yaml = value && !isEmpty(value) ? safeDump(value) : "";
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err, value);
|
||||
@@ -72,7 +67,7 @@ export class HaYamlEditor extends LitElement {
|
||||
|
||||
if (this._yaml) {
|
||||
try {
|
||||
parsed = load(this._yaml, { schema: this.yamlSchema });
|
||||
parsed = safeLoad(this._yaml);
|
||||
} catch (err) {
|
||||
// Invalid YAML
|
||||
isValid = false;
|
||||
|
@@ -45,6 +45,7 @@ import "../ha-button-menu";
|
||||
import "../ha-card";
|
||||
import "../ha-circular-progress";
|
||||
import "../ha-fab";
|
||||
import "../ha-paper-dropdown-menu";
|
||||
import "../ha-svg-icon";
|
||||
|
||||
declare global {
|
||||
|
@@ -377,10 +377,6 @@ class StateHistoryChartLine extends LocalizeMixin(PolymerElement) {
|
||||
major: {
|
||||
fontStyle: "bold",
|
||||
},
|
||||
source: "auto",
|
||||
sampleSize: 5,
|
||||
autoSkipPadding: 20,
|
||||
maxRotation: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@@ -194,10 +194,7 @@ class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
|
||||
invertOnOff,
|
||||
]);
|
||||
}
|
||||
datasets.push({
|
||||
data: dataRow,
|
||||
entity_id: stateInfo.entity_id,
|
||||
});
|
||||
datasets.push({ data: dataRow, entity_id: stateInfo.entity_id });
|
||||
labels.push(entityDisplay);
|
||||
});
|
||||
|
||||
@@ -236,14 +233,6 @@ class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
|
||||
major: {
|
||||
fontStyle: "bold",
|
||||
},
|
||||
sampleSize: 5,
|
||||
autoSkipPadding: 50,
|
||||
maxRotation: 0,
|
||||
},
|
||||
categoryPercentage: undefined,
|
||||
barPercentage: undefined,
|
||||
time: {
|
||||
format: undefined,
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -253,17 +242,10 @@ class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
|
||||
yaxe.maxWidth = yaxe.chart.width * 0.18;
|
||||
},
|
||||
position: this._computeRTL ? "right" : "left",
|
||||
categoryPercentage: undefined,
|
||||
barPercentage: undefined,
|
||||
time: { format: undefined },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
datasets: {
|
||||
categoryPercentage: 0.8,
|
||||
barPercentage: 0.9,
|
||||
},
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: datasets,
|
||||
|
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
@@ -39,13 +32,13 @@ class UserBadge extends LitElement {
|
||||
|
||||
private _personEntityId?: string;
|
||||
|
||||
public willUpdate(changedProps: PropertyValues<this>) {
|
||||
super.willUpdate(changedProps);
|
||||
protected updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("user")) {
|
||||
this._getPersonPicture();
|
||||
return;
|
||||
}
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
const oldHass = changedProps.get("hass");
|
||||
if (
|
||||
this._personEntityId &&
|
||||
oldHass &&
|
||||
|
@@ -12,20 +12,20 @@ export interface ConfigEntry {
|
||||
| "setup_retry"
|
||||
| "not_loaded"
|
||||
| "failed_unload";
|
||||
connection_class: string;
|
||||
supports_options: boolean;
|
||||
supports_unload: boolean;
|
||||
pref_disable_new_entities: boolean;
|
||||
pref_disable_polling: boolean;
|
||||
disabled_by: "user" | null;
|
||||
reason: string | null;
|
||||
}
|
||||
|
||||
export type ConfigEntryMutableParams = Partial<
|
||||
Pick<
|
||||
ConfigEntry,
|
||||
"title" | "pref_disable_new_entities" | "pref_disable_polling"
|
||||
>
|
||||
>;
|
||||
export interface ConfigEntryMutableParams {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface ConfigEntrySystemOptions {
|
||||
disable_new_entities: boolean;
|
||||
}
|
||||
|
||||
export const getConfigEntries = (hass: HomeAssistant) =>
|
||||
hass.callApi<ConfigEntry[]>("GET", "config/config_entries/entry");
|
||||
@@ -33,9 +33,9 @@ export const getConfigEntries = (hass: HomeAssistant) =>
|
||||
export const updateConfigEntry = (
|
||||
hass: HomeAssistant,
|
||||
configEntryId: string,
|
||||
updatedValues: ConfigEntryMutableParams
|
||||
updatedValues: Partial<ConfigEntryMutableParams>
|
||||
) =>
|
||||
hass.callWS<{ require_restart: boolean; config_entry: ConfigEntry }>({
|
||||
hass.callWS<ConfigEntry>({
|
||||
type: "config_entries/update",
|
||||
entry_id: configEntryId,
|
||||
...updatedValues,
|
||||
@@ -51,15 +51,13 @@ export const reloadConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
|
||||
require_restart: boolean;
|
||||
}>("POST", `config/config_entries/entry/${configEntryId}/reload`);
|
||||
|
||||
export interface DisableConfigEntryResult {
|
||||
require_restart: boolean;
|
||||
}
|
||||
|
||||
export const disableConfigEntry = (
|
||||
hass: HomeAssistant,
|
||||
configEntryId: string
|
||||
) =>
|
||||
hass.callWS<DisableConfigEntryResult>({
|
||||
hass.callWS<{
|
||||
require_restart: boolean;
|
||||
}>({
|
||||
type: "config_entries/disable",
|
||||
entry_id: configEntryId,
|
||||
disabled_by: "user",
|
||||
@@ -73,3 +71,23 @@ export const enableConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
|
||||
entry_id: configEntryId,
|
||||
disabled_by: null,
|
||||
});
|
||||
|
||||
export const getConfigEntrySystemOptions = (
|
||||
hass: HomeAssistant,
|
||||
configEntryId: string
|
||||
) =>
|
||||
hass.callWS<ConfigEntrySystemOptions>({
|
||||
type: "config_entries/system_options/list",
|
||||
entry_id: configEntryId,
|
||||
});
|
||||
|
||||
export const updateConfigEntrySystemOptions = (
|
||||
hass: HomeAssistant,
|
||||
configEntryId: string,
|
||||
params: Partial<ConfigEntrySystemOptions>
|
||||
) =>
|
||||
hass.callWS({
|
||||
type: "config_entries/system_options/update",
|
||||
entry_id: configEntryId,
|
||||
...params,
|
||||
});
|
||||
|
@@ -212,15 +212,13 @@ export const setHassioAddonOption = async (
|
||||
|
||||
export const validateHassioAddonOption = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string,
|
||||
data?: any
|
||||
slug: string
|
||||
): Promise<{ message: string; valid: boolean }> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/options/validate`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -130,21 +130,6 @@ export const createHassioFullSnapshot = async (
|
||||
);
|
||||
};
|
||||
|
||||
export const removeSnapshot = async (hass: HomeAssistant, slug: string) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/snapshots/${slug}/remove`,
|
||||
method: "post",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/snapshots/${slug}/remove`
|
||||
);
|
||||
};
|
||||
|
||||
export const createHassioPartialSnapshot = async (
|
||||
hass: HomeAssistant,
|
||||
data: HassioPartialSnapshotCreateParams
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { clear, get, set, createStore, promisifyRequest } from "idb-keyval";
|
||||
import { promiseTimeout } from "../common/util/promise-timeout";
|
||||
import { clear, get, set, Store } from "idb-keyval";
|
||||
import { iconMetadata } from "../resources/icon-metadata";
|
||||
import { IconMeta } from "../types";
|
||||
|
||||
@@ -11,44 +10,45 @@ export interface Chunks {
|
||||
[key: string]: Promise<Icons>;
|
||||
}
|
||||
|
||||
export const iconStore = createStore("hass-icon-db", "mdi-icon-store");
|
||||
export const iconStore = new Store("hass-icon-db", "mdi-icon-store");
|
||||
|
||||
export const MDI_PREFIXES = ["mdi", "hass", "hassio", "hademo"];
|
||||
|
||||
let toRead: Array<
|
||||
[string, (iconPath: string | undefined) => void, (e: any) => void]
|
||||
> = [];
|
||||
let toRead: Array<[string, (string) => void, () => void]> = [];
|
||||
|
||||
// Queue up as many icon fetches in 1 transaction
|
||||
export const getIcon = (iconName: string) =>
|
||||
new Promise<string | undefined>((resolve, reject) => {
|
||||
new Promise<string>((resolve, reject) => {
|
||||
toRead.push([iconName, resolve, reject]);
|
||||
|
||||
if (toRead.length > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
promiseTimeout(
|
||||
1000,
|
||||
iconStore("readonly", (store) => {
|
||||
for (const [iconName_, resolve_, reject_] of toRead) {
|
||||
promisifyRequest<string | undefined>(store.get(iconName_))
|
||||
.then((icon) => resolve_(icon))
|
||||
.catch((e) => reject_(e));
|
||||
const results: Array<[(string) => void, IDBRequest]> = [];
|
||||
|
||||
iconStore
|
||||
._withIDBStore("readonly", (store) => {
|
||||
for (const [iconName_, resolve_] of toRead) {
|
||||
results.push([resolve_, store.get(iconName_)]);
|
||||
}
|
||||
toRead = [];
|
||||
})
|
||||
).catch((e) => {
|
||||
// Firefox in private mode doesn't support IDB
|
||||
// Safari sometime doesn't open the DB so we time out
|
||||
for (const [, , reject_] of toRead) {
|
||||
reject_(e);
|
||||
}
|
||||
toRead = [];
|
||||
});
|
||||
.then(() => {
|
||||
for (const [resolve_, request] of results) {
|
||||
resolve_(request.result);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Firefox in private mode doesn't support IDB
|
||||
for (const [, , reject_] of toRead) {
|
||||
reject_();
|
||||
}
|
||||
toRead = [];
|
||||
});
|
||||
});
|
||||
|
||||
export const findIconChunk = (icon: string): string => {
|
||||
export const findIconChunk = (icon): string => {
|
||||
let lastChunk: IconMeta;
|
||||
for (const chunk of iconMetadata.parts) {
|
||||
if (chunk.start !== undefined && icon < chunk.start) {
|
||||
@@ -63,7 +63,7 @@ export const writeCache = async (chunks: Chunks) => {
|
||||
const keys = Object.keys(chunks);
|
||||
const iconsSets: Icons[] = await Promise.all(Object.values(chunks));
|
||||
// We do a batch opening the store just once, for (considerable) performance
|
||||
iconStore("readwrite", (store) => {
|
||||
iconStore._withIDBStore("readwrite", (store) => {
|
||||
iconsSets.forEach((icons, idx) => {
|
||||
Object.entries(icons).forEach(([name, path]) => {
|
||||
store.put(path, name);
|
||||
@@ -73,13 +73,14 @@ export const writeCache = async (chunks: Chunks) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const checkCacheVersion = async () => {
|
||||
const version = await get("_version", iconStore);
|
||||
|
||||
if (!version) {
|
||||
set("_version", iconMetadata.version, iconStore);
|
||||
} else if (version !== iconMetadata.version) {
|
||||
await clear(iconStore);
|
||||
set("_version", iconMetadata.version, iconStore);
|
||||
}
|
||||
export const checkCacheVersion = () => {
|
||||
get("_version", iconStore).then((version) => {
|
||||
if (!version) {
|
||||
set("_version", iconMetadata.version, iconStore);
|
||||
} else if (version !== iconMetadata.version) {
|
||||
clear(iconStore).then(() =>
|
||||
set("_version", iconMetadata.version, iconStore)
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@@ -1,43 +0,0 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface IPv6ConfiguredAddress {
|
||||
address: string;
|
||||
flowinfo: number;
|
||||
scope_id: number;
|
||||
network_prefix: number;
|
||||
}
|
||||
|
||||
export interface IPv4ConfiguredAddress {
|
||||
address: string;
|
||||
network_prefix: number;
|
||||
}
|
||||
|
||||
export interface Adapter {
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
auto: boolean;
|
||||
default: boolean;
|
||||
ipv6: IPv6ConfiguredAddress[];
|
||||
ipv4: IPv4ConfiguredAddress[];
|
||||
}
|
||||
|
||||
export interface NetworkConfig {
|
||||
adapters: Adapter[];
|
||||
configured_adapters: string[];
|
||||
}
|
||||
|
||||
export const getNetworkConfig = (hass: HomeAssistant) =>
|
||||
hass.callWS<NetworkConfig>({
|
||||
type: "network",
|
||||
});
|
||||
|
||||
export const setNetworkConfig = (
|
||||
hass: HomeAssistant,
|
||||
configured_adapters: string[]
|
||||
) =>
|
||||
hass.callWS<string[]>({
|
||||
type: "network/configure",
|
||||
config: {
|
||||
configured_adapters: configured_adapters,
|
||||
},
|
||||
});
|
@@ -3,17 +3,17 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-formfield";
|
||||
import "../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../components/ha-switch";
|
||||
import {
|
||||
ConfigEntryMutableParams,
|
||||
updateConfigEntry,
|
||||
getConfigEntrySystemOptions,
|
||||
updateConfigEntrySystemOptions,
|
||||
} from "../../data/config_entries";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { showAlertDialog } from "../generic/show-dialog-box";
|
||||
import { ConfigEntrySystemOptionsDialogParams } from "./show-dialog-config-entry-system-options";
|
||||
|
||||
@customElement("dialog-config-entry-system-options")
|
||||
@@ -22,12 +22,12 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
|
||||
@state() private _disableNewEntities!: boolean;
|
||||
|
||||
@state() private _disablePolling!: boolean;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _params?: ConfigEntrySystemOptionsDialogParams;
|
||||
|
||||
@state() private _loading = false;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
public async showDialog(
|
||||
@@ -35,8 +35,13 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
): Promise<void> {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
this._disableNewEntities = params.entry.pref_disable_new_entities;
|
||||
this._disablePolling = params.entry.pref_disable_polling;
|
||||
this._loading = true;
|
||||
const systemOptions = await getConfigEntrySystemOptions(
|
||||
this.hass,
|
||||
params.entry.entry_id
|
||||
);
|
||||
this._loading = false;
|
||||
this._disableNewEntities = systemOptions.disable_new_entities;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
@@ -61,57 +66,45 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
this._params.entry.domain
|
||||
)}
|
||||
>
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
<ha-formfield
|
||||
.label=${html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.enable_new_entities_label"
|
||||
)}
|
||||
</p>
|
||||
<p class="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.enable_new_entities_description",
|
||||
"integration",
|
||||
this.hass.localize(
|
||||
`component.${this._params.entry.domain}.title`
|
||||
) || this._params.entry.domain
|
||||
)}
|
||||
</p>`}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${!this._disableNewEntities}
|
||||
@change=${this._disableNewEntitiesChanged}
|
||||
.disabled=${this._submitting}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
${this._allowUpdatePolling()
|
||||
? html`
|
||||
<ha-formfield
|
||||
.label=${html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.enable_polling_label"
|
||||
)}
|
||||
</p>
|
||||
<p class="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.enable_polling_description",
|
||||
"integration",
|
||||
this.hass.localize(
|
||||
`component.${this._params.entry.domain}.title`
|
||||
) || this._params.entry.domain
|
||||
)}
|
||||
</p>`}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${!this._disablePolling}
|
||||
@change=${this._disablePollingChanged}
|
||||
.disabled=${this._submitting}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
`
|
||||
: ""}
|
||||
<div>
|
||||
${this._loading
|
||||
? html`
|
||||
<div class="init-spinner">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
${this._error
|
||||
? html` <div class="error">${this._error}</div> `
|
||||
: ""}
|
||||
<div class="form">
|
||||
<ha-formfield
|
||||
.label=${html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.enable_new_entities_label"
|
||||
)}
|
||||
</p>
|
||||
<p class="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.enable_new_entities_description",
|
||||
"integration",
|
||||
this.hass.localize(
|
||||
`component.${this._params.entry.domain}.title`
|
||||
) || this._params.entry.domain
|
||||
)}
|
||||
</p>`}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${!this._disableNewEntities}
|
||||
@change=${this._disableNewEntitiesChanged}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
@@ -122,7 +115,7 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click="${this._updateEntry}"
|
||||
.disabled=${this._submitting}
|
||||
.disabled=${this._submitting || this._loading}
|
||||
>
|
||||
${this.hass.localize("ui.dialogs.config_entry_system_options.update")}
|
||||
</mwc-button>
|
||||
@@ -130,47 +123,22 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _allowUpdatePolling() {
|
||||
return (
|
||||
this._params!.manifest &&
|
||||
(this._params!.manifest.iot_class === "local_polling" ||
|
||||
this._params!.manifest.iot_class === "cloud_polling")
|
||||
);
|
||||
}
|
||||
|
||||
private _disableNewEntitiesChanged(ev: Event): void {
|
||||
this._error = undefined;
|
||||
this._disableNewEntities = !(ev.target as HaSwitch).checked;
|
||||
}
|
||||
|
||||
private _disablePollingChanged(ev: Event): void {
|
||||
this._error = undefined;
|
||||
this._disablePolling = !(ev.target as HaSwitch).checked;
|
||||
}
|
||||
|
||||
private async _updateEntry(): Promise<void> {
|
||||
this._submitting = true;
|
||||
const data: ConfigEntryMutableParams = {
|
||||
pref_disable_new_entities: this._disableNewEntities,
|
||||
};
|
||||
if (this._allowUpdatePolling()) {
|
||||
data.pref_disable_polling = this._disablePolling;
|
||||
}
|
||||
try {
|
||||
const result = await updateConfigEntry(
|
||||
await updateConfigEntrySystemOptions(
|
||||
this.hass,
|
||||
this._params!.entry.entry_id,
|
||||
data
|
||||
{
|
||||
disable_new_entities: this._disableNewEntities,
|
||||
}
|
||||
);
|
||||
if (result.require_restart) {
|
||||
await showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.restart_home_assistant"
|
||||
),
|
||||
});
|
||||
}
|
||||
this._params!.entryUpdated(result.config_entry);
|
||||
this.closeDialog();
|
||||
this._params = undefined;
|
||||
} catch (err) {
|
||||
this._error = err.message || "Unknown error";
|
||||
} finally {
|
||||
@@ -182,6 +150,20 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.init-spinner {
|
||||
padding: 50px 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form {
|
||||
padding-top: 6px;
|
||||
padding-bottom: 24px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { ConfigEntry } from "../../data/config_entries";
|
||||
import { IntegrationManifest } from "../../data/integration";
|
||||
|
||||
export interface ConfigEntrySystemOptionsDialogParams {
|
||||
entry: ConfigEntry;
|
||||
manifest?: IntegrationManifest;
|
||||
entryUpdated(entry: ConfigEntry): void;
|
||||
// updateEntry: (
|
||||
// updates: Partial<EntityRegistryEntryUpdateParams>
|
||||
// ) => Promise<unknown>;
|
||||
// removeEntry: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export const loadConfigEntrySystemOptionsDialog = () =>
|
||||
|
@@ -49,7 +49,7 @@ class DialogBox extends LitElement {
|
||||
open
|
||||
?scrimClickAction=${confirmPrompt}
|
||||
?escapeKeyAction=${confirmPrompt}
|
||||
@closed=${this._dialogClosed}
|
||||
@closing=${this._dialogClosed}
|
||||
defaultAction="ignore"
|
||||
.heading=${this._params.title
|
||||
? this._params.title
|
||||
|
74
src/dialogs/ha-store-auth-card.js
Normal file
74
src/dialogs/ha-store-auth-card.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { enableWrite } from "../common/auth/token_storage";
|
||||
import LocalizeMixin from "../mixins/localize-mixin";
|
||||
import "../styles/polymer-ha-style";
|
||||
|
||||
class HaStoreAuth extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style">
|
||||
paper-card {
|
||||
position: fixed;
|
||||
padding: 8px 0;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
text-align: right;
|
||||
border-top: 0;
|
||||
margin-right: -4px;
|
||||
}
|
||||
|
||||
:host(.small) paper-card {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
||||
<paper-card elevation="4">
|
||||
<div class="card-content">[[localize('ui.auth_store.ask')]]</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button on-click="_done"
|
||||
>[[localize('ui.auth_store.decline')]]</mwc-button
|
||||
>
|
||||
<mwc-button raised on-click="_save"
|
||||
>[[localize('ui.auth_store.confirm')]]</mwc-button
|
||||
>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.classList.toggle("small", window.innerWidth < 600);
|
||||
}
|
||||
|
||||
_save() {
|
||||
enableWrite();
|
||||
this._done();
|
||||
}
|
||||
|
||||
_done() {
|
||||
const card = this.shadowRoot.querySelector("paper-card");
|
||||
card.style.transition = "bottom .25s";
|
||||
card.style.bottom = `-${card.offsetHeight + 8}px`;
|
||||
setTimeout(() => this.parentNode.removeChild(this), 300);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-store-auth-card", HaStoreAuth);
|
@@ -1,78 +0,0 @@
|
||||
import { LitElement, TemplateResult, html, css } from "lit";
|
||||
import { property } from "lit/decorators";
|
||||
import { enableWrite } from "../common/auth/token_storage";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "../components/ha-card";
|
||||
import type { HaCard } from "../components/ha-card";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
|
||||
class HaStoreAuth extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
${this.hass.localize("ui.auth_store.ask")}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._dismiss}>
|
||||
${this.hass.localize("ui.auth_store.decline")}
|
||||
</mwc-button>
|
||||
<mwc-button raised @click=${this._save}>
|
||||
${this.hass.localize("ui.auth_store.confirm")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.classList.toggle("small", window.innerWidth < 600);
|
||||
}
|
||||
|
||||
private _save(): void {
|
||||
enableWrite();
|
||||
this._dismiss();
|
||||
}
|
||||
|
||||
private _dismiss(): void {
|
||||
const card = this.shadowRoot!.querySelector("ha-card") as HaCard;
|
||||
card.style.bottom = `-${card.offsetHeight + 8}px`;
|
||||
setTimeout(() => this.parentNode!.removeChild(this), 300);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
position: fixed;
|
||||
padding: 8px 0;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
transition: bottom 0.25s;
|
||||
--ha-card-box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2),
|
||||
0px 6px 10px 0px rgba(0, 0, 0, 0.14),
|
||||
0px 1px 18px 0px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
text-align: right;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
:host(.small) ha-card {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-store-auth-card", HaStoreAuth);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-store-auth-card": HaStoreAuth;
|
||||
}
|
||||
}
|
@@ -108,7 +108,7 @@ class MoreInfoWeather extends LitElement {
|
||||
this.stateObj.attributes.pressure,
|
||||
this.hass.locale
|
||||
)}
|
||||
${getWeatherUnit(this.hass, "pressure")}
|
||||
${getWeatherUnit(this.hass, "air_pressure")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
@@ -8,7 +8,7 @@ import "../../components/state-history-charts";
|
||||
import { getLogbookData, LogbookEntry } from "../../data/logbook";
|
||||
import { loadTraceContexts, TraceContexts } from "../../data/trace";
|
||||
import "../../panels/logbook/ha-logbook";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { haStyle, haStyleScrollbar } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { closeDialog } from "../make-dialog-manager";
|
||||
|
||||
@@ -52,6 +52,7 @@ export class MoreInfoLogbook extends LitElement {
|
||||
: this._logbookEntries.length
|
||||
? html`
|
||||
<ha-logbook
|
||||
class="ha-scrollbar"
|
||||
narrow
|
||||
no-icon
|
||||
no-name
|
||||
@@ -148,6 +149,7 @@ export class MoreInfoLogbook extends LitElement {
|
||||
static get styles() {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
.no-entries {
|
||||
text-align: center;
|
||||
@@ -155,11 +157,12 @@ export class MoreInfoLogbook extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-logbook {
|
||||
--logbook-max-height: 250px;
|
||||
max-height: 250px;
|
||||
overflow: auto;
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-logbook {
|
||||
--logbook-max-height: unset;
|
||||
max-height: unset;
|
||||
}
|
||||
}
|
||||
ha-circular-progress {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Layout1d, scroll } from "../../resources/lit-virtualizer";
|
||||
import { Layout1d, scroll } from "@lit-labs/virtualizer";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import type { List } from "@material/mwc-list/mwc-list";
|
||||
import { SingleSelectedEvent } from "@material/mwc-list/mwc-list-foundation";
|
||||
@@ -188,6 +188,7 @@ export class QuickBar extends LitElement {
|
||||
${scroll({
|
||||
items,
|
||||
layout: Layout1d,
|
||||
// @ts-expect-error
|
||||
renderItem: (item: QuickBarItem, index) =>
|
||||
this._renderItem(item, index),
|
||||
})}
|
||||
@@ -222,9 +223,6 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
|
||||
private _renderItem(item: QuickBarItem, index?: number) {
|
||||
if (!item) {
|
||||
return html``;
|
||||
}
|
||||
return isCommandItem(item)
|
||||
? this._renderCommandItem(item, index)
|
||||
: this._renderEntityItem(item as EntityItem, index);
|
||||
@@ -638,6 +636,18 @@ export class QuickBar extends LitElement {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.uni-virtualizer-host {
|
||||
display: block;
|
||||
position: relative;
|
||||
contain: strict;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.uni-virtualizer-host > * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
mwc-list-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
@@ -277,7 +277,7 @@ export const provideHass = (
|
||||
mockTheme(theme) {
|
||||
invalidateThemeCache();
|
||||
hass().updateHass({
|
||||
selectedTheme: { theme: theme ? "mock" : "default" },
|
||||
selectedThemeSettings: { theme: theme ? "mock" : "default" },
|
||||
themes: {
|
||||
...hass().themes,
|
||||
themes: {
|
||||
@@ -285,11 +285,11 @@ export const provideHass = (
|
||||
},
|
||||
},
|
||||
});
|
||||
const { themes, selectedTheme } = hass();
|
||||
const { themes, selectedThemeSettings } = hass();
|
||||
applyThemesOnElement(
|
||||
document.documentElement,
|
||||
themes,
|
||||
selectedTheme!.theme
|
||||
selectedThemeSettings!.theme
|
||||
);
|
||||
},
|
||||
|
||||
|
@@ -229,7 +229,7 @@ class HassTabsSubpage extends LitElement {
|
||||
color: var(--sidebar-text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
.bottom-bar a {
|
||||
:host([narrow]) .toolbar a {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
|
@@ -81,25 +81,27 @@ class SupervisorErrorScreen extends LitElement {
|
||||
|
||||
private _applyTheme() {
|
||||
let themeName: string;
|
||||
let themeSettings: Partial<HomeAssistant["selectedTheme"]> | undefined;
|
||||
let themeSettings:
|
||||
| Partial<HomeAssistant["selectedThemeSettings"]>
|
||||
| undefined;
|
||||
|
||||
if (atLeastVersion(this.hass.config.version, 0, 114)) {
|
||||
themeName =
|
||||
this.hass.selectedTheme?.theme ||
|
||||
this.hass.selectedThemeSettings?.theme ||
|
||||
(this.hass.themes.darkMode && this.hass.themes.default_dark_theme
|
||||
? this.hass.themes.default_dark_theme!
|
||||
: this.hass.themes.default_theme);
|
||||
|
||||
themeSettings = this.hass.selectedTheme;
|
||||
themeSettings = this.hass.selectedThemeSettings;
|
||||
if (themeName === "default" && themeSettings?.dark === undefined) {
|
||||
themeSettings = {
|
||||
...this.hass.selectedTheme,
|
||||
...this.hass.selectedThemeSettings,
|
||||
dark: this.hass.themes.darkMode,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
themeName =
|
||||
((this.hass.selectedTheme as unknown) as string) ||
|
||||
((this.hass.selectedThemeSettings as unknown) as string) ||
|
||||
this.hass.themes.default_theme;
|
||||
}
|
||||
|
||||
|
@@ -90,9 +90,9 @@ export class HAFullCalendar extends LitElement {
|
||||
|
||||
@property() public initialView: FullCalendarView = "dayGridMonth";
|
||||
|
||||
private calendar?: Calendar;
|
||||
@state() private calendar?: Calendar;
|
||||
|
||||
@state() private _activeView = this.initialView;
|
||||
@state() private _activeView?: FullCalendarView;
|
||||
|
||||
public updateSize(): void {
|
||||
this.calendar?.updateSize();
|
||||
@@ -181,8 +181,8 @@ export class HAFullCalendar extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (!this.calendar) {
|
||||
return;
|
||||
@@ -216,6 +216,8 @@ export class HAFullCalendar extends LitElement {
|
||||
initialView: this.initialView,
|
||||
};
|
||||
|
||||
this._activeView = this.initialView;
|
||||
|
||||
config.dateClick = (info) => this._handleDateClick(info);
|
||||
config.eventClick = (info) => this._handleEventClick(info);
|
||||
|
||||
|
@@ -48,11 +48,9 @@ class PanelCalendar extends LitElement {
|
||||
|
||||
private _end?: Date;
|
||||
|
||||
public willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
if (!this.hasUpdated) {
|
||||
this._calendars = getCalendars(this.hass);
|
||||
}
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
this._calendars = getCalendars(this.hass);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { dump } from "js-yaml";
|
||||
import { safeDump } from "js-yaml";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../components/ha-code-editor";
|
||||
@@ -15,7 +15,7 @@ export class HaAutomationTraceBlueprintConfig extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-code-editor
|
||||
.value=${dump(this.trace.blueprint_inputs || "").trimRight()}
|
||||
.value=${safeDump(this.trace.blueprint_inputs || "").trimRight()}
|
||||
readOnly
|
||||
></ha-code-editor>
|
||||
`;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { dump } from "js-yaml";
|
||||
import { safeDump } from "js-yaml";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../components/ha-code-editor";
|
||||
@@ -15,7 +15,7 @@ export class HaAutomationTraceConfig extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-code-editor
|
||||
.value=${dump(this.trace.config).trimRight()}
|
||||
.value=${safeDump(this.trace.config).trimRight()}
|
||||
readOnly
|
||||
></ha-code-editor>
|
||||
`;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { dump } from "js-yaml";
|
||||
import { safeDump } from "js-yaml";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -132,13 +132,13 @@ export class HaAutomationTracePathDetails extends LitElement {
|
||||
)}<br />
|
||||
${result
|
||||
? html`Result:
|
||||
<pre>${dump(result)}</pre>`
|
||||
<pre>${safeDump(result)}</pre>`
|
||||
: error
|
||||
? html`<div class="error">Error: ${error}</div>`
|
||||
: ""}
|
||||
${Object.keys(rest).length === 0
|
||||
? ""
|
||||
: html`<pre>${dump(rest)}</pre>`}
|
||||
: html`<pre>${safeDump(rest)}</pre>`}
|
||||
`;
|
||||
})
|
||||
);
|
||||
@@ -154,7 +154,7 @@ export class HaAutomationTracePathDetails extends LitElement {
|
||||
const config = getDataFromPath(this.trace!.config, this.selected.path);
|
||||
return config
|
||||
? html`<ha-code-editor
|
||||
.value=${dump(config).trimRight()}
|
||||
.value=${safeDump(config).trimRight()}
|
||||
readOnly
|
||||
></ha-code-editor>`
|
||||
: "Unable to find config";
|
||||
@@ -171,7 +171,9 @@ export class HaAutomationTracePathDetails extends LitElement {
|
||||
${idx > 0 ? html`<p>Iteration ${idx + 1}</p>` : ""}
|
||||
${Object.keys(trace.changed_variables || {}).length === 0
|
||||
? "No variables changed"
|
||||
: html`<pre>${dump(trace.changed_variables).trimRight()}</pre>`}
|
||||
: html`<pre>
|
||||
${safeDump(trace.changed_variables).trimRight()}</pre
|
||||
>`}
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
|
@@ -6,7 +6,7 @@ import { TagTrigger } from "../../../../../data/automation";
|
||||
import { fetchTags, Tag } from "../../../../../data/tag";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { TriggerElement } from "../ha-automation-trigger-row";
|
||||
import "../../../../../components/ha-paper-dropdown-menu";
|
||||
|
||||
@customElement("ha-automation-trigger-tag")
|
||||
export class HaTagTrigger extends LitElement implements TriggerElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
@@ -1,141 +0,0 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-checkbox";
|
||||
import "../../../components/ha-network";
|
||||
import "../../../components/ha-settings-row";
|
||||
import { fetchNetworkInfo } from "../../../data/hassio/network";
|
||||
import {
|
||||
getNetworkConfig,
|
||||
NetworkConfig,
|
||||
setNetworkConfig,
|
||||
} from "../../../data/network";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("ha-config-network")
|
||||
class ConfigNetwork extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _networkConfig?: NetworkConfig;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (
|
||||
!this.hass.userData?.showAdvanced ||
|
||||
!isComponentLoaded(this.hass, "network")
|
||||
) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card header="Network">
|
||||
<div class="card-content">
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : ""}
|
||||
<p>
|
||||
Configure which network adapters integrations will use. Currently
|
||||
this setting only affects multicast traffic. A restart is required
|
||||
for these settings to apply.
|
||||
</p>
|
||||
<ha-network
|
||||
@network-config-changed=${this._configChanged}
|
||||
.hass=${this.hass}
|
||||
.networkConfig=${this._networkConfig}
|
||||
></ha-network>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._save}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.save_button"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
if (isComponentLoaded(this.hass, "network")) {
|
||||
this._load();
|
||||
}
|
||||
}
|
||||
|
||||
private async _load() {
|
||||
this._error = undefined;
|
||||
try {
|
||||
const coreNetwork = await getNetworkConfig(this.hass);
|
||||
if (isComponentLoaded(this.hass, "hassio")) {
|
||||
const supervisorNetwork = await fetchNetworkInfo(this.hass);
|
||||
const interfaces = new Set(
|
||||
supervisorNetwork.interfaces.map((int) => int.interface)
|
||||
);
|
||||
if (interfaces.size) {
|
||||
coreNetwork.adapters = coreNetwork.adapters.filter((adapter) =>
|
||||
interfaces.has(adapter.name)
|
||||
);
|
||||
}
|
||||
}
|
||||
this._networkConfig = coreNetwork;
|
||||
} catch (err) {
|
||||
this._error = err.message || err;
|
||||
}
|
||||
}
|
||||
|
||||
private async _save() {
|
||||
this._error = undefined;
|
||||
try {
|
||||
await setNetworkConfig(
|
||||
this.hass,
|
||||
this._networkConfig?.configured_adapters || []
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = err.message || err;
|
||||
}
|
||||
}
|
||||
|
||||
private _configChanged(event: CustomEvent): void {
|
||||
this._networkConfig = {
|
||||
...this._networkConfig!,
|
||||
configured_adapters: event.detail.configured_adapters,
|
||||
};
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
`, // row-reverse so we tab first to "save"
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-network": ConfigNetwork;
|
||||
}
|
||||
}
|
@@ -11,7 +11,6 @@ import "../ha-config-section";
|
||||
import "./ha-config-analytics";
|
||||
import "./ha-config-core-form";
|
||||
import "./ha-config-name-form";
|
||||
import "./ha-config-network";
|
||||
import "./ha-config-url-form";
|
||||
|
||||
/*
|
||||
@@ -31,7 +30,6 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
|
||||
<ha-config-name-form hass="[[hass]]"></ha-config-name-form>
|
||||
<ha-config-core-form hass="[[hass]]"></ha-config-core-form>
|
||||
<ha-config-url-form hass="[[hass]]"></ha-config-url-form>
|
||||
<ha-config-network hass="[[hass]]"></ha-config-network>
|
||||
<ha-config-analytics hass="[[hass]]"></ha-config-analytics>
|
||||
</ha-config-section>
|
||||
`;
|
||||
|
@@ -49,7 +49,7 @@ class DialogMQTTDeviceDebugInfo extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this._close}
|
||||
@closing=${this._close}
|
||||
.heading="${this.hass!.localize(
|
||||
"ui.dialogs.mqtt_device_debug_info.title",
|
||||
"device",
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { dump } from "js-yaml";
|
||||
import { safeDump } from "js-yaml";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -31,7 +31,7 @@ class MQTTDiscoveryPayload extends LitElement {
|
||||
const payload = this.payload;
|
||||
return html`
|
||||
${this.showAsYaml
|
||||
? html` <pre>${dump(payload)}</pre> `
|
||||
? html` <pre>${safeDump(payload)}</pre> `
|
||||
: html` <pre>${JSON.stringify(payload, null, 2)}</pre> `}
|
||||
`;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { dump } from "js-yaml";
|
||||
import { safeDump } from "js-yaml";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -92,7 +92,7 @@ class MQTTMessages extends LitElement {
|
||||
return json
|
||||
? html`
|
||||
${this.showAsYaml
|
||||
? html` <pre>${dump(json)}</pre> `
|
||||
? html` <pre>${safeDump(json)}</pre> `
|
||||
: html` <pre>${JSON.stringify(json, null, 2)}</pre> `}
|
||||
`
|
||||
: html` <code>${message.payload}</code> `;
|
||||
|
@@ -11,11 +11,7 @@ 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,
|
||||
DisableConfigEntryResult,
|
||||
} from "../../../data/config_entries";
|
||||
import { ConfigEntry, disableConfigEntry } from "../../../data/config_entries";
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
@@ -29,10 +25,7 @@ import {
|
||||
} from "../../../data/entity_registry";
|
||||
import { SceneEntities, showSceneEditor } from "../../../data/scene";
|
||||
import { findRelated, RelatedResult } from "../../../data/search";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
@@ -678,41 +671,13 @@ export class HaConfigDevicePage extends LitElement {
|
||||
dismissText: this.hass.localize("ui.common.no"),
|
||||
}))
|
||||
) {
|
||||
let result: DisableConfigEntryResult;
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
result = await disableConfigEntry(this.hass, cnfg_entry);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_error"
|
||||
),
|
||||
text: err.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_restart_confirm"
|
||||
),
|
||||
});
|
||||
}
|
||||
disableConfigEntry(this.hass, cnfg_entry);
|
||||
delete updates.disabled_by;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
await updateDeviceRegistryEntry(this.hass, this.deviceId, updates);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.devices.update_device_error"
|
||||
),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
await updateDeviceRegistryEntry(this.hass, this.deviceId, updates);
|
||||
|
||||
if (
|
||||
!oldDeviceName ||
|
||||
|
@@ -68,7 +68,7 @@ export class DialogHelperDetail extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._opened}
|
||||
@closed=${this.closeDialog}
|
||||
@closing=${this.closeDialog}
|
||||
class=${classMap({ "button-left": !this._platform })}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
|
@@ -193,6 +193,9 @@ class HaInputNumberForm extends LitElement {
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -196,6 +196,9 @@ class HaInputSelectForm extends LitElement {
|
||||
mwc-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
ha-paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -179,6 +179,9 @@ class HaInputTextForm extends LitElement {
|
||||
.row {
|
||||
padding: 16px 0;
|
||||
}
|
||||
ha-paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -18,7 +18,6 @@ import {
|
||||
ConfigEntry,
|
||||
deleteConfigEntry,
|
||||
disableConfigEntry,
|
||||
DisableConfigEntryResult,
|
||||
enableConfigEntry,
|
||||
reloadConfigEntry,
|
||||
updateConfigEntry,
|
||||
@@ -111,7 +110,6 @@ export class HaIntegrationCard extends LitElement {
|
||||
: undefined}
|
||||
.localizedDomainName=${item ? item.localized_domain_name : undefined}
|
||||
.manifest=${this.manifest}
|
||||
.configEntry=${item}
|
||||
>
|
||||
${this.items.length > 1
|
||||
? html`
|
||||
@@ -468,11 +466,6 @@ export class HaIntegrationCard extends LitElement {
|
||||
private _showSystemOptions(configEntry: ConfigEntry) {
|
||||
showConfigEntrySystemOptionsDialog(this, {
|
||||
entry: configEntry,
|
||||
manifest: this.manifest,
|
||||
entryUpdated: (entry) =>
|
||||
fireEvent(this, "entry-updated", {
|
||||
entry,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -488,18 +481,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
let result: DisableConfigEntryResult;
|
||||
try {
|
||||
result = await disableConfigEntry(this.hass, entryId);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_error"
|
||||
),
|
||||
text: err.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const result = await disableConfigEntry(this.hass, entryId);
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
@@ -515,18 +497,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
private async _enableIntegration(configEntry: ConfigEntry) {
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
let result: DisableConfigEntryResult;
|
||||
try {
|
||||
result = await enableConfigEntry(this.hass, entryId);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_error"
|
||||
),
|
||||
text: err.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const result = await enableConfigEntry(this.hass, entryId);
|
||||
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
@@ -590,10 +561,10 @@ export class HaIntegrationCard extends LitElement {
|
||||
if (newName === null) {
|
||||
return;
|
||||
}
|
||||
const result = await updateConfigEntry(this.hass, configEntry.entry_id, {
|
||||
const newEntry = await updateConfigEntry(this.hass, configEntry.entry_id, {
|
||||
title: newName,
|
||||
});
|
||||
fireEvent(this, "entry-updated", { entry: result.config_entry });
|
||||
fireEvent(this, "entry-updated", { entry: newEntry });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import { mdiCloud, mdiPackageVariant, mdiSyncOff } from "@mdi/js";
|
||||
import { mdiCloud, mdiPackageVariant } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
import { domainToName, IntegrationManifest } from "../../../data/integration";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
@@ -22,8 +21,6 @@ export class HaIntegrationHeader extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public manifest?: IntegrationManifest;
|
||||
|
||||
@property({ attribute: false }) public configEntry?: ConfigEntry;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
let primary: string;
|
||||
let secondary: string | undefined;
|
||||
@@ -62,15 +59,6 @@ export class HaIntegrationHeader extends LitElement {
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
if (this.configEntry?.pref_disable_polling) {
|
||||
icons.push([
|
||||
mdiSyncOff,
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disabled_polling"
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return html`
|
||||
|
@@ -79,7 +79,7 @@ class DialogOZWRefreshNode extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed="${this._close}"
|
||||
@closing="${this._close}"
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.panel.config.ozw.refresh_node.title")
|
||||
|
@@ -61,7 +61,7 @@ class DialogZHACluster extends LitElement {
|
||||
<ha-dialog
|
||||
open
|
||||
hideActions
|
||||
@closed="${this._close}"
|
||||
@closing="${this._close}"
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.panel.config.zha.clusters.header")
|
||||
|
@@ -36,7 +36,7 @@ class DialogZHADeviceZigbeeInfo extends LitElement {
|
||||
<ha-dialog
|
||||
open
|
||||
hideActions
|
||||
@closed="${this._close}"
|
||||
@closing="${this._close}"
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize(`ui.dialogs.zha_device_info.device_signature`)
|
||||
|
@@ -9,7 +9,7 @@ import {
|
||||
} from "../../../../../data/zha";
|
||||
import "../../../../../layouts/hass-tabs-subpage";
|
||||
import type { HomeAssistant, Route } from "../../../../../types";
|
||||
import { Network, Edge, Node, EdgeOptions } from "vis-network/peer";
|
||||
import { Network, Edge, Node, EdgeOptions } from "vis-network";
|
||||
import "../../../../../common/search/search-input";
|
||||
import "../../../../../components/device/ha-device-picker";
|
||||
import "../../../../../components/ha-button-menu";
|
||||
|
@@ -74,7 +74,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed="${this._close}"
|
||||
@closing="${this._close}"
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
|
@@ -66,7 +66,7 @@ export class DialogLovelaceResourceDetail extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this._close}
|
||||
@closing=${this._close}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
|
@@ -84,7 +84,7 @@ export class DialogAddUser extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this._close}
|
||||
@closing=${this._close}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${this.hass.localize("ui.panel.config.users.add_user.caption")}
|
||||
|
@@ -55,7 +55,7 @@ class DialogUserDetail extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this._close}
|
||||
@closing=${this._close}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(this.hass, user.name)}
|
||||
|
@@ -4,7 +4,7 @@ import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { load } from "js-yaml";
|
||||
import { safeLoad } from "js-yaml";
|
||||
import "../../../components/ha-code-editor";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
@@ -151,7 +151,7 @@ class HaPanelDevEvent extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
|
||||
_computeParsedEventData(eventData) {
|
||||
try {
|
||||
return eventData.trim() ? load(eventData) : {};
|
||||
return eventData.trim() ? safeLoad(eventData) : {};
|
||||
} catch (err) {
|
||||
return ERROR_SENTINEL;
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { mdiHelpCircle } from "@mdi/js";
|
||||
import { ERR_CONNECTION_LOST } from "home-assistant-js-websocket";
|
||||
import { load } from "js-yaml";
|
||||
import { safeLoad } from "js-yaml";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -361,9 +361,9 @@ class HaPanelDevService extends LitElement {
|
||||
const example = {};
|
||||
fields.forEach((field) => {
|
||||
if (field.example) {
|
||||
let value: any = "";
|
||||
let value = "";
|
||||
try {
|
||||
value = load(field.example);
|
||||
value = safeLoad(field.example);
|
||||
} catch (err) {
|
||||
value = field.example;
|
||||
}
|
||||
|
@@ -9,10 +9,9 @@ import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { dump, load } from "js-yaml";
|
||||
import { safeDump, safeLoad } from "js-yaml";
|
||||
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { escapeRegExp } from "../../../common/string/escape_regexp";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-code-editor";
|
||||
@@ -92,12 +91,6 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
--mdc-icon-size: 20px;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.entities td:nth-child(1) {
|
||||
min-width: 300px;
|
||||
width: 30%;
|
||||
}
|
||||
.entities td:nth-child(3) {
|
||||
white-space: pre-wrap;
|
||||
@@ -108,15 +101,6 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.entities .id-name-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.entities .id-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:host([narrow]) .state-wrapper {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -235,30 +219,19 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
<template is="dom-repeat" items="[[_entities]]" as="entity">
|
||||
<tr>
|
||||
<td>
|
||||
<div class="id-name-container">
|
||||
<div class="id-name-row">
|
||||
<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
|
||||
>
|
||||
</div>
|
||||
<div class="id-name-row">
|
||||
<ha-svg-icon
|
||||
on-click="entityMoreInfo"
|
||||
alt="[[localize('ui.panel.developer-tools.tabs.states.more_info')]]"
|
||||
title="[[localize('ui.panel.developer-tools.tabs.states.more_info')]]"
|
||||
path="[[informationOutlineIcon()]]"
|
||||
></ha-svg-icon>
|
||||
<span class="secondary">
|
||||
[[entity.attributes.friendly_name]]
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ha-svg-icon
|
||||
on-click="entityMoreInfo"
|
||||
alt="[[localize('ui.panel.developer-tools.tabs.states.more_info')]]"
|
||||
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>[[entity.state]]</td>
|
||||
<template
|
||||
@@ -359,7 +332,7 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
this._entityId = state.entity_id;
|
||||
this._entity = state;
|
||||
this._state = state.state;
|
||||
this._stateAttributes = dump(state.attributes);
|
||||
this._stateAttributes = safeDump(state.attributes);
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
@@ -376,7 +349,7 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
}
|
||||
this._entity = state;
|
||||
this._state = state.state;
|
||||
this._stateAttributes = dump(state.attributes);
|
||||
this._stateAttributes = safeDump(state.attributes);
|
||||
}
|
||||
|
||||
entityMoreInfo(ev) {
|
||||
@@ -412,68 +385,50 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
}
|
||||
|
||||
computeEntities(hass, _entityFilter, _stateFilter, _attributeFilter) {
|
||||
const entityFilterRegExp =
|
||||
_entityFilter &&
|
||||
RegExp(escapeRegExp(_entityFilter).replace(/\\\*/g, ".*"), "i");
|
||||
|
||||
const stateFilterRegExp =
|
||||
_stateFilter &&
|
||||
RegExp(escapeRegExp(_stateFilter).replace(/\\\*/g, ".*"), "i");
|
||||
|
||||
let keyFilterRegExp;
|
||||
let valueFilterRegExp;
|
||||
let multiMode = false;
|
||||
|
||||
if (_attributeFilter) {
|
||||
const colonIndex = _attributeFilter.indexOf(":");
|
||||
multiMode = colonIndex !== -1;
|
||||
|
||||
const keyFilter = multiMode
|
||||
? _attributeFilter.substring(0, colonIndex).trim()
|
||||
: _attributeFilter;
|
||||
const valueFilter = multiMode
|
||||
? _attributeFilter.substring(colonIndex + 1).trim()
|
||||
: _attributeFilter;
|
||||
|
||||
keyFilterRegExp = RegExp(
|
||||
escapeRegExp(keyFilter).replace(/\\\*/g, ".*"),
|
||||
"i"
|
||||
);
|
||||
valueFilterRegExp = multiMode
|
||||
? RegExp(escapeRegExp(valueFilter).replace(/\\\*/g, ".*"), "i")
|
||||
: keyFilterRegExp;
|
||||
}
|
||||
|
||||
return Object.values(hass.states)
|
||||
.filter((value) => {
|
||||
if (
|
||||
entityFilterRegExp &&
|
||||
!entityFilterRegExp.test(value.entity_id) &&
|
||||
(value.attributes.friendly_name === undefined ||
|
||||
!entityFilterRegExp.test(value.attributes.friendly_name))
|
||||
) {
|
||||
return Object.keys(hass.states)
|
||||
.map(function (key) {
|
||||
return hass.states[key];
|
||||
})
|
||||
.filter(function (value) {
|
||||
if (!value.entity_id.includes(_entityFilter.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stateFilterRegExp && !stateFilterRegExp.test(value.state)) {
|
||||
if (!value.state.toLowerCase().includes(_stateFilter.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keyFilterRegExp && valueFilterRegExp) {
|
||||
for (const [key, attributeValue] of Object.entries(
|
||||
value.attributes
|
||||
)) {
|
||||
const match = keyFilterRegExp.test(key);
|
||||
if (match && !multiMode) {
|
||||
if (_attributeFilter !== "") {
|
||||
const attributeFilter = _attributeFilter.toLowerCase();
|
||||
const colonIndex = attributeFilter.indexOf(":");
|
||||
const multiMode = colonIndex !== -1;
|
||||
|
||||
let keyFilter = attributeFilter;
|
||||
let valueFilter = attributeFilter;
|
||||
|
||||
if (multiMode) {
|
||||
// we need to filter keys and values separately
|
||||
keyFilter = attributeFilter.substring(0, colonIndex).trim();
|
||||
valueFilter = attributeFilter.substring(colonIndex + 1).trim();
|
||||
}
|
||||
|
||||
const attributeKeys = Object.keys(value.attributes);
|
||||
|
||||
for (let i = 0; i < attributeKeys.length; i++) {
|
||||
const key = attributeKeys[i];
|
||||
|
||||
if (key.includes(keyFilter) && !multiMode) {
|
||||
return true; // in single mode we're already satisfied with this match
|
||||
}
|
||||
if (!match && multiMode) {
|
||||
if (!key.includes(keyFilter) && multiMode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const attributeValue = value.attributes[key];
|
||||
|
||||
if (
|
||||
attributeValue !== undefined &&
|
||||
valueFilterRegExp.test(JSON.stringify(attributeValue))
|
||||
JSON.stringify(attributeValue).toLowerCase().includes(valueFilter)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -538,7 +493,7 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
(Array.isArray(value) && value.some((val) => val instanceof Object)) ||
|
||||
(!Array.isArray(value) && value instanceof Object)
|
||||
) {
|
||||
return `\n${dump(value)}`;
|
||||
return `\n${safeDump(value)}`;
|
||||
}
|
||||
return Array.isArray(value) ? value.join(", ") : value;
|
||||
}
|
||||
@@ -553,7 +508,7 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
|
||||
_computeParsedStateAttributes(stateAttributes) {
|
||||
try {
|
||||
return stateAttributes.trim() ? load(stateAttributes) : {};
|
||||
return stateAttributes.trim() ? safeLoad(stateAttributes) : {};
|
||||
} catch (err) {
|
||||
return ERROR_SENTINEL;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Layout1d, scroll } from "../../resources/lit-virtualizer";
|
||||
import { Layout1d, scroll } from "@lit-labs/virtualizer";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -100,6 +100,7 @@ class HaLogbook extends LitElement {
|
||||
? scroll({
|
||||
items: this.entries,
|
||||
layout: Layout1d,
|
||||
// @ts-expect-error
|
||||
renderItem: (item: LogbookEntry, index) =>
|
||||
this._renderLogbookItem(item, index),
|
||||
})
|
||||
@@ -348,12 +349,16 @@ class HaLogbook extends LitElement {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-height: var(--logbook-max-height);
|
||||
:host([virtualize]) .container {
|
||||
display: block;
|
||||
position: relative;
|
||||
contain: strict;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:host([virtualize]) .container {
|
||||
height: 100%;
|
||||
.container > * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.narrow .entry {
|
||||
|
@@ -18,7 +18,6 @@ import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { formatAttributeValue } from "../../../util/hass-attributes-util";
|
||||
import { computeCardSize } from "../common/compute-card-size";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
@@ -125,12 +124,8 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
||||
<div class="info">
|
||||
<span class="value"
|
||||
>${"attribute" in this._config
|
||||
? stateObj.attributes[this._config.attribute!] !== undefined
|
||||
? formatAttributeValue(
|
||||
this.hass,
|
||||
stateObj.attributes[this._config.attribute!]
|
||||
)
|
||||
: this.hass.localize("state.default.unknown")
|
||||
? stateObj.attributes[this._config.attribute!] ??
|
||||
this.hass.localize("state.default.unknown")
|
||||
: stateObj.attributes.unit_of_measurement
|
||||
? formatNumber(stateObj.state, this.hass.locale)
|
||||
: computeStateDisplay(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user