mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-13 15:09:41 +00:00
Compare commits
1 Commits
dashboard_
...
20240529.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1a2daf8a7a |
@@ -126,5 +126,6 @@
|
||||
"lit-a11y/anchor-is-valid": "warn",
|
||||
"lit-a11y/role-has-required-aria-attrs": "warn"
|
||||
},
|
||||
"plugins": ["unused-imports"]
|
||||
"plugins": ["disable", "unused-imports"],
|
||||
"processor": "disable/disable"
|
||||
}
|
||||
|
4
.github/workflows/cast_deployment.yaml
vendored
4
.github/workflows/cast_deployment.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
ref: master
|
||||
|
||||
|
8
.github/workflows/ci.yaml
vendored
8
.github/workflows/ci.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
4
.github/workflows/demo_deployment.yaml
vendored
4
.github/workflows/demo_deployment.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
ref: master
|
||||
|
||||
|
2
.github/workflows/design_deployment.yaml
vendored
2
.github/workflows/design_deployment.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
|
2
.github/workflows/design_preview.yaml
vendored
2
.github/workflows/design_preview.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
|
2
.github/workflows/nightly.yaml
vendored
2
.github/workflows/nightly.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v5
|
||||
|
2
.github/workflows/relative-ci.yaml
vendored
2
.github/workflows/relative-ci.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send bundle stats and build information to RelativeCI
|
||||
uses: relative-ci/agent-action@v2.1.11
|
||||
uses: relative-ci/agent-action@v2.1.10
|
||||
with:
|
||||
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||
token: ${{ github.token }}
|
||||
|
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Verify version
|
||||
uses: home-assistant/actions/helpers/verify-version@master
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
script/release
|
||||
|
||||
- name: Upload release assets
|
||||
uses: softprops/action-gh-release@v2.0.6
|
||||
uses: softprops/action-gh-release@v2.0.5
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
|
2
.github/workflows/translations.yaml
vendored
2
.github/workflows/translations.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Upload Translations
|
||||
run: |
|
||||
|
File diff suppressed because one or more lines are too long
@@ -6,4 +6,4 @@ enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.3.1.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.2.2.cjs
|
||||
|
@@ -92,8 +92,8 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
useBuiltIns: "usage",
|
||||
corejs: dependencies["core-js"],
|
||||
useBuiltIns: latestBuild ? false : "usage",
|
||||
corejs: latestBuild ? false : dependencies["core-js"],
|
||||
bugfixes: true,
|
||||
shippedProposals: true,
|
||||
},
|
||||
@@ -157,7 +157,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
||||
exclude: [
|
||||
path.join(paths.polymer_dir, "src/resources/polyfills"),
|
||||
...[
|
||||
"@formatjs/(?:ecma402-abstract|intl-\\w+)",
|
||||
"@formatjs/intl-\\w+",
|
||||
"@lit-labs/virtualizer/polyfills",
|
||||
"@webcomponents/scoped-custom-element-registry",
|
||||
"element-internals-polyfill",
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list";
|
||||
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { Auth, Connection } from "home-assistant-js-websocket";
|
||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -27,7 +28,6 @@ import { LovelaceViewConfig } from "../../../../src/data/lovelace/config/view";
|
||||
import "../../../../src/layouts/hass-loading-screen";
|
||||
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
|
||||
import "./hc-layout";
|
||||
import "../../../../src/components/ha-list-item";
|
||||
|
||||
@customElement("hc-cast")
|
||||
class HcCast extends LitElement {
|
||||
@@ -83,37 +83,37 @@ class HcCast extends LitElement {
|
||||
`
|
||||
: html`
|
||||
<div class="section-header">PICK A VIEW</div>
|
||||
<mwc-list @action=${this._handlePickView} activatable>
|
||||
<paper-listbox
|
||||
attr-for-selected="data-path"
|
||||
.selected=${this.castManager.status.lovelacePath || ""}
|
||||
>
|
||||
${(
|
||||
this.lovelaceViews ?? [
|
||||
generateDefaultViewConfig({}, {}, {}, {}, () => ""),
|
||||
]
|
||||
).map(
|
||||
(view, idx) =>
|
||||
html`<ha-list-item
|
||||
graphic="avatar"
|
||||
.activated=${this.castManager.status?.lovelacePath ===
|
||||
(view.path ?? idx)}
|
||||
.selected=${this.castManager.status?.lovelacePath ===
|
||||
(view.path ?? idx)}
|
||||
(view, idx) => html`
|
||||
<paper-icon-item
|
||||
@click=${this._handlePickView}
|
||||
data-path=${view.path || idx}
|
||||
>
|
||||
${view.title || view.path || "Unnamed view"}
|
||||
${view.icon
|
||||
? html`
|
||||
<ha-icon
|
||||
.icon=${view.icon}
|
||||
slot="graphic"
|
||||
slot="item-icon"
|
||||
></ha-icon>
|
||||
`
|
||||
: html`<ha-svg-icon
|
||||
slot="item-icon"
|
||||
.path=${mdiViewDashboard}
|
||||
></ha-svg-icon>`}</ha-list-item
|
||||
> `
|
||||
)}</mwc-list
|
||||
>
|
||||
></ha-svg-icon>`}
|
||||
${view.title || view.path || "Unnamed view"}
|
||||
</paper-icon-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
`}
|
||||
|
||||
<div class="card-actions">
|
||||
${this.castManager.status
|
||||
? html`
|
||||
@@ -185,8 +185,8 @@ class HcCast extends LitElement {
|
||||
this.castManager.requestSession();
|
||||
}
|
||||
|
||||
private async _handlePickView(ev: CustomEvent<ActionDetail>) {
|
||||
const path = this.lovelaceViews![ev.detail.index].path ?? ev.detail.index;
|
||||
private async _handlePickView(ev: Event) {
|
||||
const path = (ev.currentTarget as any).getAttribute("data-path");
|
||||
await ensureConnectedCastSession(this.castManager!, this.auth!);
|
||||
castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path);
|
||||
}
|
||||
@@ -249,14 +249,26 @@ class HcCast extends LitElement {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
ha-list-item ha-icon,
|
||||
ha-list-item ha-svg-icon {
|
||||
paper-listbox {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
paper-listbox ha-icon,
|
||||
paper-listbox ha-svg-icon {
|
||||
padding: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
:host([hide-icons]) ha-icon {
|
||||
display: none;
|
||||
paper-icon-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
paper-icon-item[disabled] {
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
:host([hide-icons]) paper-icon-item {
|
||||
--paper-item-icon-width: 0px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import "@material/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { until } from "lit/directives/until";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-button";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card";
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
demoConfigs,
|
||||
selectedDemoConfig,
|
||||
selectedDemoConfigIndex,
|
||||
setDemoConfig,
|
||||
} from "../configs/demo-configs";
|
||||
|
||||
@customElement("ha-demo-card")
|
||||
@@ -64,9 +64,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ha-button @click=${this._nextConfig} .disabled=${this._switching}>
|
||||
<mwc-button @click=${this._nextConfig} .disabled=${this._switching}>
|
||||
${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")}
|
||||
</ha-button>
|
||||
</mwc-button>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p class="small-hidden">
|
||||
@@ -87,9 +87,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
</div>
|
||||
<div class="actions small-hidden">
|
||||
<a href="https://www.home-assistant.io" target="_blank">
|
||||
<ha-button>
|
||||
<mwc-button>
|
||||
${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
|
||||
</ha-button>
|
||||
</mwc-button>
|
||||
</a>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -113,7 +113,13 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
|
||||
private async _updateConfig(index: number) {
|
||||
this._switching = true;
|
||||
fireEvent(this, "set-demo-config" as any, { index });
|
||||
try {
|
||||
await setDemoConfig(this.hass, this.lovelace!, index);
|
||||
} catch (err: any) {
|
||||
alert("Failed to switch config :-(");
|
||||
} finally {
|
||||
this._switching = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
@@ -143,7 +149,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.picker ha-button {
|
||||
.picker mwc-button {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,9 @@
|
||||
import type { LocalizeFunc } from "../../../src/common/translations/localize";
|
||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
import {
|
||||
selectedDemoConfig,
|
||||
selectedDemoConfigIndex,
|
||||
setDemoConfig,
|
||||
} from "../configs/demo-configs";
|
||||
import { selectedDemoConfig } from "../configs/demo-configs";
|
||||
import "../custom-cards/cast-demo-row";
|
||||
import "../custom-cards/ha-demo-card";
|
||||
import type { HADemoCard } from "../custom-cards/ha-demo-card";
|
||||
|
||||
export const mockLovelace = (
|
||||
hass: MockHomeAssistant,
|
||||
@@ -22,22 +19,17 @@ export const mockLovelace = (
|
||||
hass.mockWS("lovelace/resources", () => Promise.resolve([]));
|
||||
};
|
||||
|
||||
customElements.whenDefined("hui-root").then(() => {
|
||||
customElements.whenDefined("hui-view").then(() => {
|
||||
// eslint-disable-next-line
|
||||
const HUIRoot = customElements.get("hui-root")!;
|
||||
const HUIView = customElements.get("hui-view");
|
||||
// Patch HUI-VIEW to make the lovelace object available to the demo card
|
||||
const oldCreateCard = HUIView!.prototype.createCardElement;
|
||||
|
||||
const oldFirstUpdated = HUIRoot.prototype.firstUpdated;
|
||||
|
||||
HUIRoot.prototype.firstUpdated = function (changedProperties) {
|
||||
oldFirstUpdated.call(this, changedProperties);
|
||||
this.addEventListener("set-demo-config", async (ev) => {
|
||||
const index = (ev as CustomEvent).detail.index;
|
||||
try {
|
||||
await setDemoConfig(this.hass, this.lovelace!, index);
|
||||
} catch (err: any) {
|
||||
setDemoConfig(this.hass, this.lovelace!, selectedDemoConfigIndex);
|
||||
alert("Failed to switch config :-(");
|
||||
}
|
||||
});
|
||||
HUIView!.prototype.createCardElement = function (config) {
|
||||
const el = oldCreateCard.call(this, config);
|
||||
if (el.tagName === "HA-DEMO-CARD") {
|
||||
(el as HADemoCard).lovelace = this.lovelace;
|
||||
}
|
||||
return el;
|
||||
};
|
||||
});
|
||||
|
@@ -1,9 +1,7 @@
|
||||
import { load } from "js-yaml";
|
||||
import { LitElement, PropertyValueMap, css, html, nothing } from "lit";
|
||||
import { html, css, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import "../../../src/panels/lovelace/cards/hui-card";
|
||||
import type { HuiCard } from "../../../src/panels/lovelace/cards/hui-card";
|
||||
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
export interface DemoCardConfig {
|
||||
@@ -21,12 +19,7 @@ class DemoCard extends LitElement {
|
||||
|
||||
@state() private _size?: number;
|
||||
|
||||
@query("hui-card", false) private _card?: HuiCard;
|
||||
|
||||
private _config = memoizeOne((config: string) => {
|
||||
const c = (load(config) as any)[0];
|
||||
return c;
|
||||
});
|
||||
@query("#card") private _card!: HTMLElement;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
@@ -37,32 +30,63 @@ class DemoCard extends LitElement {
|
||||
: ""}
|
||||
</h2>
|
||||
<div class="root">
|
||||
<hui-card
|
||||
.config=${this._config(this.config.config)}
|
||||
.hass=${this.hass}
|
||||
@card-updated=${this._cardUpdated}
|
||||
></hui-card>
|
||||
${this.showConfig
|
||||
? html`<pre>${this.config.config.trim()}</pre>`
|
||||
: nothing}
|
||||
<div id="card"></div>
|
||||
${this.showConfig ? html`<pre>${this.config.config.trim()}</pre>` : ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _cardUpdated(ev) {
|
||||
ev.stopPropagation();
|
||||
this._updateSize();
|
||||
updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("config")) {
|
||||
const card = this._card;
|
||||
while (card.lastChild) {
|
||||
card.removeChild(card.lastChild);
|
||||
}
|
||||
|
||||
const el = this._createCardElement((load(this.config.config) as any)[0]);
|
||||
card.appendChild(el);
|
||||
this._getSize(el);
|
||||
}
|
||||
|
||||
if (changedProps.has("hass")) {
|
||||
const card = this._card.lastChild;
|
||||
if (card) {
|
||||
(card as any).hass = this.hass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateSize() {
|
||||
this._size = await this._card?.getCardSize();
|
||||
async _getSize(el) {
|
||||
await customElements.whenDefined(el.localName);
|
||||
|
||||
if (!("getCardSize" in el)) {
|
||||
this._size = undefined;
|
||||
return;
|
||||
}
|
||||
this._size = await el.getCardSize();
|
||||
}
|
||||
|
||||
protected update(
|
||||
_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
||||
): void {
|
||||
super.update(_changedProperties);
|
||||
this._updateSize();
|
||||
_createCardElement(cardConfig) {
|
||||
const element = createCardElement(cardConfig);
|
||||
if (this.hass) {
|
||||
element.hass = this.hass;
|
||||
}
|
||||
element.addEventListener(
|
||||
"ll-rebuild",
|
||||
(ev) => {
|
||||
ev.stopPropagation();
|
||||
this._rebuildCard(element, cardConfig);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
return element;
|
||||
}
|
||||
|
||||
_rebuildCard(cardElToReplace, config) {
|
||||
const newCardEl = this._createCardElement(config);
|
||||
cardElToReplace.parentElement.replaceChild(newCardEl, cardElToReplace);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
@@ -77,7 +101,7 @@ class DemoCard extends LitElement {
|
||||
font-size: 0.5em;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
hui-card {
|
||||
#card {
|
||||
max-width: 400px;
|
||||
width: 100vw;
|
||||
}
|
||||
|
74
package.json
74
package.json
@@ -25,15 +25,15 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.24.7",
|
||||
"@braintree/sanitize-url": "7.0.3",
|
||||
"@codemirror/autocomplete": "6.16.3",
|
||||
"@codemirror/commands": "6.6.0",
|
||||
"@codemirror/language": "6.10.2",
|
||||
"@babel/runtime": "7.24.6",
|
||||
"@braintree/sanitize-url": "7.0.2",
|
||||
"@codemirror/autocomplete": "6.16.0",
|
||||
"@codemirror/commands": "6.5.0",
|
||||
"@codemirror/language": "6.10.1",
|
||||
"@codemirror/legacy-modes": "6.4.0",
|
||||
"@codemirror/search": "6.5.6",
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@codemirror/view": "6.28.2",
|
||||
"@codemirror/view": "6.26.3",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.12.5",
|
||||
"@formatjs/intl-displaynames": "6.6.8",
|
||||
@@ -43,17 +43,17 @@
|
||||
"@formatjs/intl-numberformat": "8.10.3",
|
||||
"@formatjs/intl-pluralrules": "5.2.14",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.14",
|
||||
"@fullcalendar/core": "6.1.11",
|
||||
"@fullcalendar/daygrid": "6.1.11",
|
||||
"@fullcalendar/interaction": "6.1.11",
|
||||
"@fullcalendar/list": "6.1.11",
|
||||
"@fullcalendar/luxon3": "6.1.11",
|
||||
"@fullcalendar/timegrid": "6.1.11",
|
||||
"@fullcalendar/core": "6.1.13",
|
||||
"@fullcalendar/daygrid": "6.1.13",
|
||||
"@fullcalendar/interaction": "6.1.13",
|
||||
"@fullcalendar/list": "6.1.13",
|
||||
"@fullcalendar/luxon3": "6.1.13",
|
||||
"@fullcalendar/timegrid": "6.1.13",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.7",
|
||||
"@lit-labs/observers": "2.0.2",
|
||||
"@lit-labs/virtualizer": "2.0.13",
|
||||
"@lit-labs/virtualizer": "2.0.12",
|
||||
"@lrnwebcomponents/simple-tooltip": "8.0.2",
|
||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||
@@ -88,8 +88,8 @@
|
||||
"@polymer/paper-tabs": "3.1.0",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.4.0",
|
||||
"@vaadin/vaadin-themable-mixin": "24.4.0",
|
||||
"@vaadin/combo-box": "24.3.13",
|
||||
"@vaadin/vaadin-themable-mixin": "24.3.13",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||
@@ -110,7 +110,7 @@
|
||||
"fuse.js": "7.0.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
||||
"home-assistant-js-websocket": "9.4.0",
|
||||
"home-assistant-js-websocket": "9.3.0",
|
||||
"idb-keyval": "6.2.1",
|
||||
"intl-messageformat": "10.5.14",
|
||||
"js-yaml": "4.1.0",
|
||||
@@ -133,7 +133,7 @@
|
||||
"tinykeys": "2.1.0",
|
||||
"tsparticles-engine": "2.12.0",
|
||||
"tsparticles-preset-links": "2.12.0",
|
||||
"ua-parser-js": "1.0.38",
|
||||
"ua-parser-js": "1.0.37",
|
||||
"unfetch": "5.0.0",
|
||||
"vis-data": "7.1.9",
|
||||
"vis-network": "9.1.9",
|
||||
@@ -149,26 +149,26 @@
|
||||
"xss": "1.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.24.7",
|
||||
"@babel/core": "7.24.6",
|
||||
"@babel/helper-define-polyfill-provider": "0.6.2",
|
||||
"@babel/plugin-proposal-decorators": "7.24.7",
|
||||
"@babel/plugin-transform-runtime": "7.24.7",
|
||||
"@babel/preset-env": "7.24.7",
|
||||
"@babel/preset-typescript": "7.24.7",
|
||||
"@babel/plugin-proposal-decorators": "7.24.6",
|
||||
"@babel/plugin-transform-runtime": "7.24.6",
|
||||
"@babel/preset-env": "7.24.6",
|
||||
"@babel/preset-typescript": "7.24.6",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.13.2",
|
||||
"@koa/cors": "5.0.0",
|
||||
"@lokalise/node-api": "12.5.0",
|
||||
"@octokit/auth-oauth-device": "7.1.1",
|
||||
"@octokit/plugin-retry": "7.1.1",
|
||||
"@octokit/rest": "21.0.0",
|
||||
"@octokit/rest": "20.1.1",
|
||||
"@open-wc/dev-server-hmr": "0.1.4",
|
||||
"@rollup/plugin-babel": "6.0.4",
|
||||
"@rollup/plugin-commonjs": "26.0.1",
|
||||
"@rollup/plugin-commonjs": "25.0.8",
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-node-resolve": "15.2.3",
|
||||
"@rollup/plugin-replace": "5.0.7",
|
||||
"@rollup/plugin-replace": "5.0.5",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.15",
|
||||
"@types/chromecast-caf-receiver": "6.0.14",
|
||||
"@types/chromecast-caf-sender": "1.0.10",
|
||||
"@types/color-name": "1.1.4",
|
||||
"@types/glob": "8.1.0",
|
||||
@@ -185,8 +185,8 @@
|
||||
"@types/tar": "6.1.13",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "7.13.1",
|
||||
"@typescript-eslint/parser": "7.13.1",
|
||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||
"@typescript-eslint/parser": "7.10.0",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"@web/dev-server-rollup": "0.4.1",
|
||||
"babel-loader": "9.1.3",
|
||||
@@ -198,14 +198,15 @@
|
||||
"eslint-config-airbnb-typescript": "18.0.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-import-resolver-webpack": "0.13.8",
|
||||
"eslint-plugin-disable": "2.0.3",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-lit": "1.14.0",
|
||||
"eslint-plugin-lit": "1.13.0",
|
||||
"eslint-plugin-lit-a11y": "4.1.2",
|
||||
"eslint-plugin-unused-imports": "4.0.0",
|
||||
"eslint-plugin-wc": "2.1.0",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.2.0",
|
||||
"glob": "10.4.2",
|
||||
"glob": "10.4.1",
|
||||
"gulp": "5.0.0",
|
||||
"gulp-json-transform": "0.5.0",
|
||||
"gulp-rename": "2.0.0",
|
||||
@@ -214,7 +215,7 @@
|
||||
"husky": "9.0.11",
|
||||
"instant-mocha": "1.5.2",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "15.2.7",
|
||||
"lint-staged": "15.2.5",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
@@ -224,7 +225,7 @@
|
||||
"object-hash": "3.0.0",
|
||||
"open": "10.1.0",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.3.2",
|
||||
"prettier": "3.2.5",
|
||||
"rollup": "2.79.1",
|
||||
"rollup-plugin-string": "3.0.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
@@ -233,18 +234,18 @@
|
||||
"sinon": "18.0.0",
|
||||
"source-map-url": "0.4.1",
|
||||
"systemjs": "6.15.1",
|
||||
"tar": "7.4.0",
|
||||
"tar": "7.1.0",
|
||||
"terser-webpack-plugin": "5.3.10",
|
||||
"transform-async-modules-webpack-plugin": "1.1.1",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.4.5",
|
||||
"webpack": "5.92.1",
|
||||
"webpack": "5.91.0",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "5.0.4",
|
||||
"webpack-manifest-plugin": "5.0.0",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "6.0.1",
|
||||
"workbox-build": "7.1.1"
|
||||
"workbox-build": "7.1.0"
|
||||
},
|
||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||
"resolutions": {
|
||||
@@ -253,9 +254,8 @@
|
||||
"lit": "2.8.0",
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "1.6.3",
|
||||
"@fullcalendar/daygrid": "6.1.11",
|
||||
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
|
||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
},
|
||||
"packageManager": "yarn@4.3.1"
|
||||
"packageManager": "yarn@4.2.2"
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20240610.0"
|
||||
version = "20240529.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@@ -31,7 +31,6 @@ import {
|
||||
mdiFormatListBulleted,
|
||||
mdiFormatListCheckbox,
|
||||
mdiFormTextbox,
|
||||
mdiForumOutline,
|
||||
mdiGauge,
|
||||
mdiGoogleAssistant,
|
||||
mdiGoogleCirclesCommunities,
|
||||
@@ -99,7 +98,7 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
calendar: mdiCalendar,
|
||||
climate: mdiThermostat,
|
||||
configurator: mdiCog,
|
||||
conversation: mdiForumOutline,
|
||||
conversation: mdiMicrophoneMessage,
|
||||
counter: mdiCounter,
|
||||
date: mdiCalendar,
|
||||
datetime: mdiCalendarClock,
|
||||
@@ -236,8 +235,6 @@ export const SENSOR_ENTITIES = [
|
||||
"weather",
|
||||
];
|
||||
|
||||
export const ASSIST_ENTITIES = ["conversation", "stt", "tts"];
|
||||
|
||||
/** Domains that render an input element instead of a text value when displayed in a row.
|
||||
* Those rows should then not show a cursor pointer when hovered (which would normally
|
||||
* be the default) unless the element itself enforces it (e.g. a button). Also those elements
|
||||
|
@@ -1 +0,0 @@
|
||||
export const preventDefault = (ev) => ev.preventDefault();
|
@@ -47,8 +47,6 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
|
||||
@property({ type: Boolean }) public readOnly = false;
|
||||
|
||||
@property({ type: Boolean }) public linewrap = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "autocomplete-entities" })
|
||||
public autocompleteEntities = false;
|
||||
|
||||
@@ -136,13 +134,6 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
),
|
||||
});
|
||||
}
|
||||
if (changedProps.has("linewrap")) {
|
||||
transactions.push({
|
||||
effects: this._loadedCodeMirror!.linewrapCompartment!.reconfigure(
|
||||
this.linewrap ? this._loadedCodeMirror!.EditorView.lineWrapping : []
|
||||
),
|
||||
});
|
||||
}
|
||||
if (changedProps.has("_value") && this._value !== this.value) {
|
||||
transactions.push({
|
||||
changes: {
|
||||
@@ -190,9 +181,6 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
this._loadedCodeMirror.readonlyCompartment.of(
|
||||
this._loadedCodeMirror.EditorView.editable.of(!this.readOnly)
|
||||
),
|
||||
this._loadedCodeMirror.linewrapCompartment.of(
|
||||
this.linewrap ? this._loadedCodeMirror.EditorView.lineWrapping : []
|
||||
),
|
||||
this._loadedCodeMirror.EditorView.updateListener.of(this._onUpdate),
|
||||
];
|
||||
|
||||
|
@@ -89,18 +89,13 @@ export class HaFilterDomains extends LitElement {
|
||||
});
|
||||
|
||||
return Array.from(domains.values())
|
||||
.map((domain) => ({
|
||||
domain,
|
||||
name: domainToName(this.hass.localize, domain),
|
||||
}))
|
||||
.filter(
|
||||
(entry) =>
|
||||
!filter ||
|
||||
entry.domain.toLowerCase().includes(filter) ||
|
||||
entry.name.toLowerCase().includes(filter)
|
||||
entry.toLowerCase().includes(filter) ||
|
||||
domainToName(this.hass.localize, entry).toLowerCase().includes(filter)
|
||||
)
|
||||
.sort((a, b) => stringCompare(a.name, b.name, this.hass.locale.language))
|
||||
.map((entry) => entry.domain);
|
||||
.sort((a, b) => stringCompare(a, b, this.hass.locale.language));
|
||||
});
|
||||
|
||||
protected updated(changed) {
|
||||
|
@@ -2,7 +2,7 @@ import type { Selector } from "../../data/selector";
|
||||
import type { HaFormSchema } from "./types";
|
||||
|
||||
export const computeInitialHaFormData = (
|
||||
schema: HaFormSchema[] | readonly HaFormSchema[]
|
||||
schema: HaFormSchema[]
|
||||
): Record<string, any> => {
|
||||
const data = {};
|
||||
schema.forEach((field) => {
|
||||
@@ -36,8 +36,6 @@ export const computeInitialHaFormData = (
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
};
|
||||
} else if (field.type === "expandable") {
|
||||
data[field.name] = computeInitialHaFormData(field.schema);
|
||||
} else if ("selector" in field) {
|
||||
const selector: Selector = field.selector;
|
||||
|
||||
|
@@ -1,233 +0,0 @@
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "./ha-icon-button";
|
||||
import "../panels/lovelace/editor/card-editor/ha-grid-layout-slider";
|
||||
|
||||
import { mdiRestore } from "@mdi/js";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
type GridSizeValue = {
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
};
|
||||
|
||||
@customElement("ha-grid-size-picker")
|
||||
export class HaGridSizeEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public value?: GridSizeValue;
|
||||
|
||||
@property({ attribute: false }) public rows = 6;
|
||||
|
||||
@property({ attribute: false }) public columns = 4;
|
||||
|
||||
@property({ attribute: false }) public rowMin?: number;
|
||||
|
||||
@property({ attribute: false }) public rowMax?: number;
|
||||
|
||||
@property({ attribute: false }) public columnMin?: number;
|
||||
|
||||
@property({ attribute: false }) public columnMax?: number;
|
||||
|
||||
@property({ attribute: false }) public isDefault?: boolean;
|
||||
|
||||
@state() public _localValue?: GridSizeValue = undefined;
|
||||
|
||||
protected willUpdate(changedProperties) {
|
||||
if (changedProperties.has("value")) {
|
||||
this._localValue = this.value;
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="grid">
|
||||
<ha-grid-layout-slider
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.grid-size-picker.columns"
|
||||
)}
|
||||
id="columns"
|
||||
.min=${this.columnMin ?? 1}
|
||||
.max=${this.columnMax ?? this.columns}
|
||||
.range=${this.columns}
|
||||
.value=${this.value?.columns}
|
||||
@value-changed=${this._valueChanged}
|
||||
@slider-moved=${this._sliderMoved}
|
||||
></ha-grid-layout-slider>
|
||||
<ha-grid-layout-slider
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.grid-size-picker.rows"
|
||||
)}
|
||||
id="rows"
|
||||
.min=${this.rowMin ?? 1}
|
||||
.max=${this.rowMax ?? this.rows}
|
||||
.range=${this.rows}
|
||||
vertical
|
||||
.value=${this.value?.rows}
|
||||
@value-changed=${this._valueChanged}
|
||||
@slider-moved=${this._sliderMoved}
|
||||
></ha-grid-layout-slider>
|
||||
${!this.isDefault
|
||||
? html`
|
||||
<ha-icon-button
|
||||
@click=${this._reset}
|
||||
class="reset"
|
||||
.path=${mdiRestore}
|
||||
label=${this.hass.localize(
|
||||
"ui.components.grid-size-picker.reset_default"
|
||||
)}
|
||||
title=${this.hass.localize(
|
||||
"ui.components.grid-size-picker.reset_default"
|
||||
)}
|
||||
>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
<div
|
||||
class="preview"
|
||||
style=${styleMap({
|
||||
"--total-rows": this.rows,
|
||||
"--total-columns": this.columns,
|
||||
"--rows": this._localValue?.rows,
|
||||
"--columns": this._localValue?.columns,
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
${Array(this.rows * this.columns)
|
||||
.fill(0)
|
||||
.map((_, index) => {
|
||||
const row = Math.floor(index / this.columns) + 1;
|
||||
const column = (index % this.columns) + 1;
|
||||
const disabled =
|
||||
(this.rowMin !== undefined && row < this.rowMin) ||
|
||||
(this.rowMax !== undefined && row > this.rowMax) ||
|
||||
(this.columnMin !== undefined && column < this.columnMin) ||
|
||||
(this.columnMax !== undefined && column > this.columnMax);
|
||||
return html`
|
||||
<div
|
||||
class="cell"
|
||||
data-row=${row}
|
||||
data-column=${column}
|
||||
?disabled=${disabled}
|
||||
@click=${this._cellClick}
|
||||
></div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
<div class="selected">
|
||||
<div class="cell"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
_cellClick(ev) {
|
||||
const cell = ev.currentTarget as HTMLElement;
|
||||
if (cell.getAttribute("disabled") !== null) return;
|
||||
const rows = Number(cell.getAttribute("data-row"));
|
||||
const columns = Number(cell.getAttribute("data-column"));
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { rows, columns },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const key = ev.currentTarget.id;
|
||||
const newValue = {
|
||||
...this.value,
|
||||
[key]: ev.detail.value,
|
||||
};
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
|
||||
private _reset(ev) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
rows: undefined,
|
||||
columns: undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _sliderMoved(ev) {
|
||||
ev.stopPropagation();
|
||||
const key = ev.currentTarget.id;
|
||||
const value = ev.detail.value;
|
||||
if (value === undefined) return;
|
||||
this._localValue = {
|
||||
...this.value,
|
||||
[key]: ev.detail.value,
|
||||
};
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"reset column-slider"
|
||||
"row-slider preview";
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
#columns {
|
||||
grid-area: column-slider;
|
||||
}
|
||||
#rows {
|
||||
grid-area: row-slider;
|
||||
}
|
||||
.reset {
|
||||
grid-area: reset;
|
||||
}
|
||||
.preview {
|
||||
position: relative;
|
||||
grid-area: preview;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
.preview > div {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--total-columns), 1fr);
|
||||
grid-template-rows: repeat(var(--total-rows), 1fr);
|
||||
gap: 4px;
|
||||
}
|
||||
.preview .cell {
|
||||
background-color: var(--disabled-color);
|
||||
grid-column: span 1;
|
||||
grid-row: span 1;
|
||||
border-radius: 4px;
|
||||
opacity: 0.2;
|
||||
cursor: pointer;
|
||||
}
|
||||
.preview .cell[disabled] {
|
||||
opacity: 0.05;
|
||||
cursor: initial;
|
||||
}
|
||||
.selected {
|
||||
pointer-events: none;
|
||||
}
|
||||
.selected .cell {
|
||||
background-color: var(--primary-color);
|
||||
grid-column: 1 / span var(--columns, 0);
|
||||
grid-row: 1 / span var(--rows, 0);
|
||||
opacity: 0.5;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-grid-size-picker": HaGridSizeEditor;
|
||||
}
|
||||
}
|
@@ -198,7 +198,8 @@ export class HaIconPicker extends LitElement {
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
*[slot="icon"] {
|
||||
ha-icon,
|
||||
ha-svg-icon {
|
||||
color: var(--primary-text-color);
|
||||
position: relative;
|
||||
bottom: 2px;
|
||||
|
@@ -64,12 +64,6 @@ const SELECTOR_SCHEMAS = {
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const,
|
||||
floor: [
|
||||
{
|
||||
name: "multiple",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const,
|
||||
icon: [] as const,
|
||||
location: [] as const,
|
||||
media: [] as const,
|
||||
|
@@ -32,7 +32,6 @@ export class HaTemplateSelector extends LitElement {
|
||||
autocomplete-icons
|
||||
@value-changed=${this._handleChange}
|
||||
dir="ltr"
|
||||
linewrap
|
||||
></ha-code-editor>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
|
@@ -327,7 +327,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
for (const entityId of Object.keys(this.hass.states)) {
|
||||
if (
|
||||
entityId.startsWith("update.") &&
|
||||
!this.hass.entities[entityId]?.hidden &&
|
||||
updateCanInstall(this.hass.states[entityId] as UpdateEntity)
|
||||
) {
|
||||
updateCount++;
|
||||
|
@@ -48,7 +48,7 @@ class HaEntityMarker extends LitElement {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: var(--ha-marker-font-size, 1.5em);
|
||||
border-radius: var(--ha-marker-border-radius, 50%);
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--ha-marker-color, var(--primary-color));
|
||||
color: var(--primary-text-color);
|
||||
background-color: var(--card-background-color);
|
||||
|
@@ -138,17 +138,6 @@ export const adminChangePassword = (
|
||||
password,
|
||||
});
|
||||
|
||||
export const adminChangeUsername = (
|
||||
hass: HomeAssistant,
|
||||
userId: string,
|
||||
username: string
|
||||
) =>
|
||||
hass.callWS<void>({
|
||||
type: "config/auth_provider/homeassistant/admin_change_username",
|
||||
user_id: userId,
|
||||
username,
|
||||
});
|
||||
|
||||
export const deleteAllRefreshTokens = (
|
||||
hass: HomeAssistant,
|
||||
token_type?: RefreshTokenType,
|
||||
|
@@ -901,7 +901,7 @@ const tryDescribeCondition = (
|
||||
)
|
||||
: undefined;
|
||||
|
||||
if (condition.above !== undefined && condition.below !== undefined) {
|
||||
if (condition.above && condition.below) {
|
||||
return hass.localize(
|
||||
`${conditionsTranslationBaseKey}.numeric_state.description.above-below`,
|
||||
{
|
||||
@@ -912,7 +912,7 @@ const tryDescribeCondition = (
|
||||
}
|
||||
);
|
||||
}
|
||||
if (condition.above !== undefined) {
|
||||
if (condition.above) {
|
||||
return hass.localize(
|
||||
`${conditionsTranslationBaseKey}.numeric_state.description.above`,
|
||||
{
|
||||
@@ -922,7 +922,7 @@ const tryDescribeCondition = (
|
||||
}
|
||||
);
|
||||
}
|
||||
if (condition.below !== undefined) {
|
||||
if (condition.below) {
|
||||
return hass.localize(
|
||||
`${conditionsTranslationBaseKey}.numeric_state.description.below`,
|
||||
{
|
||||
|
@@ -6,7 +6,6 @@ export interface ConfigUpdateValues {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
elevation: number;
|
||||
radius: number;
|
||||
unit_system: "metric" | "us_customary";
|
||||
time_zone: string;
|
||||
external_url?: string | null;
|
||||
|
@@ -249,22 +249,6 @@ export const localizeDeviceAutomationTrigger = (
|
||||
) ||
|
||||
(trigger.subtype ? `"${trigger.subtype}" ${trigger.type}` : trigger.type!);
|
||||
|
||||
export const localizeExtraFieldsComputeLabelCallback =
|
||||
(hass: HomeAssistant, deviceAutomation: DeviceAutomation) =>
|
||||
// Returns a callback for ha-form to calculate labels per schema object
|
||||
(schema): string =>
|
||||
hass.localize(
|
||||
`component.${deviceAutomation.domain}.device_automation.extra_fields.${schema.name}`
|
||||
) || schema.name;
|
||||
|
||||
export const localizeExtraFieldsComputeHelperCallback =
|
||||
(hass: HomeAssistant, deviceAutomation: DeviceAutomation) =>
|
||||
// Returns a callback for ha-form to calculate helper texts per schema object
|
||||
(schema): string | undefined =>
|
||||
hass.localize(
|
||||
`component.${deviceAutomation.domain}.device_automation.extra_fields_descriptions.${schema.name}`
|
||||
);
|
||||
|
||||
export const sortDeviceAutomations = (
|
||||
automationA: DeviceAutomation,
|
||||
automationB: DeviceAutomation
|
||||
|
@@ -30,7 +30,6 @@ export interface LovelaceViewElement extends HTMLElement {
|
||||
export interface LovelaceSectionElement extends HTMLElement {
|
||||
hass?: HomeAssistant;
|
||||
lovelace?: Lovelace;
|
||||
preview?: boolean;
|
||||
viewIndex?: number;
|
||||
index?: number;
|
||||
cards?: HuiCard[];
|
||||
|
@@ -2,7 +2,6 @@ import { HomeAssistant } from "../types";
|
||||
|
||||
export interface OTBRInfo {
|
||||
active_dataset_tlvs: string;
|
||||
border_agent_id: string;
|
||||
channel: number;
|
||||
extended_address: string;
|
||||
url: string;
|
||||
|
@@ -12,6 +12,14 @@ export interface RecorderInfo {
|
||||
thread_running: boolean;
|
||||
}
|
||||
|
||||
export interface RecordedEntities {
|
||||
entity_ids: string[];
|
||||
}
|
||||
export interface RecordedExcludedEntities {
|
||||
recorded_ids: string[];
|
||||
excluded_ids: string[];
|
||||
}
|
||||
|
||||
export type StatisticType = "change" | "state" | "sum" | "min" | "max" | "mean";
|
||||
|
||||
export interface Statistics {
|
||||
@@ -324,3 +332,25 @@ export const getDisplayUnit = (
|
||||
|
||||
export const isExternalStatistic = (statisticsId: string): boolean =>
|
||||
statisticsId.includes(":");
|
||||
|
||||
let recordedExcludedEntitiesCache: RecordedExcludedEntities | undefined;
|
||||
|
||||
export const getRecordedExcludedEntities = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<RecordedExcludedEntities> => {
|
||||
if (recordedExcludedEntitiesCache) {
|
||||
return recordedExcludedEntitiesCache;
|
||||
}
|
||||
const recordedEntities = await hass.callWS<RecordedEntities>({
|
||||
type: "recorder/recorded_entities",
|
||||
});
|
||||
|
||||
recordedExcludedEntitiesCache = {
|
||||
recorded_ids: recordedEntities.entity_ids,
|
||||
excluded_ids: Object.keys(hass.states).filter(
|
||||
(id) => !recordedEntities.entity_ids.includes(id)
|
||||
),
|
||||
};
|
||||
|
||||
return recordedExcludedEntitiesCache;
|
||||
};
|
||||
|
@@ -1,21 +0,0 @@
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface ThresholdPreview {
|
||||
state: string;
|
||||
attributes: Record<string, any>;
|
||||
}
|
||||
|
||||
export const subscribePreviewThreshold = (
|
||||
hass: HomeAssistant,
|
||||
flow_id: string,
|
||||
flow_type: "config_flow" | "options_flow",
|
||||
user_input: Record<string, any>,
|
||||
callback: (preview: ThresholdPreview) => void
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.connection.subscribeMessage(callback, {
|
||||
type: "threshold/start_preview",
|
||||
flow_id,
|
||||
flow_type,
|
||||
user_input,
|
||||
});
|
@@ -144,7 +144,7 @@ export const checkForEntityUpdates = async (
|
||||
|
||||
// there is no reliable way to know if all the updates are done updating, so we just wait a bit for now...
|
||||
await new Promise((r) => {
|
||||
setTimeout(r, 15000);
|
||||
setTimeout(r, 10000);
|
||||
});
|
||||
|
||||
unsubscribeEvents();
|
||||
|
@@ -1,9 +1,6 @@
|
||||
import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiGauge,
|
||||
mdiThermometer,
|
||||
mdiThermometerWater,
|
||||
mdiSunWireless,
|
||||
mdiWaterPercent,
|
||||
mdiWeatherCloudy,
|
||||
mdiWeatherFog,
|
||||
@@ -117,15 +114,10 @@ export const weatherIcons = {
|
||||
};
|
||||
|
||||
export const weatherAttrIcons = {
|
||||
apparent_temperature: mdiThermometer,
|
||||
cloud_coverage: mdiWeatherCloudy,
|
||||
dew_point: mdiThermometerWater,
|
||||
humidity: mdiWaterPercent,
|
||||
wind_bearing: mdiWeatherWindy,
|
||||
wind_speed: mdiWeatherWindy,
|
||||
pressure: mdiGauge,
|
||||
temperature: mdiThermometer,
|
||||
uv_index: mdiSunWireless,
|
||||
visibility: mdiWeatherFog,
|
||||
precipitation: mdiWeatherRainy,
|
||||
};
|
||||
@@ -229,8 +221,6 @@ export const getWeatherUnit = (
|
||||
stateObj.attributes.pressure_unit ||
|
||||
(lengthUnit === "km" ? "hPa" : "inHg")
|
||||
);
|
||||
case "apparent_temperature":
|
||||
case "dew_point":
|
||||
case "temperature":
|
||||
case "templow":
|
||||
return (
|
||||
@@ -238,7 +228,6 @@ export const getWeatherUnit = (
|
||||
);
|
||||
case "wind_speed":
|
||||
return stateObj.attributes.wind_speed_unit || `${lengthUnit}/h`;
|
||||
case "cloud_coverage":
|
||||
case "humidity":
|
||||
case "precipitation_probability":
|
||||
return "%";
|
||||
|
@@ -14,7 +14,6 @@ export interface Zone {
|
||||
export interface HomeZoneMutableParams {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
radius: number;
|
||||
}
|
||||
|
||||
export interface ZoneMutableParams {
|
||||
|
@@ -487,14 +487,14 @@ export const stopZwaveExclusion = (hass: HomeAssistant, entry_id: string) =>
|
||||
export const zwaveGrantSecurityClasses = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
securityClasses: SecurityClass[],
|
||||
clientSideAuth?: boolean
|
||||
security_classes: SecurityClass[],
|
||||
client_side_auth?: boolean
|
||||
) =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/grant_security_classes",
|
||||
entry_id,
|
||||
securityClasses,
|
||||
clientSideAuth,
|
||||
security_classes,
|
||||
client_side_auth,
|
||||
});
|
||||
|
||||
export const zwaveTryParseDskFromQrCode = (
|
||||
|
@@ -1,108 +0,0 @@
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { FlowType } from "../../../data/data_entry_flow";
|
||||
import {
|
||||
ThresholdPreview,
|
||||
subscribePreviewThreshold,
|
||||
} from "../../../data/threshold";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "./entity-preview-row";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
@customElement("flow-preview-threshold")
|
||||
class FlowPreviewThreshold extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public flowType!: FlowType;
|
||||
|
||||
public handler!: string;
|
||||
|
||||
@property() public stepId!: string;
|
||||
|
||||
@property() public flowId!: string;
|
||||
|
||||
@property() public stepData!: Record<string, any>;
|
||||
|
||||
@state() private _preview?: HassEntity;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
private _unsub?: Promise<UnsubscribeFunc>;
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
if (this._unsub) {
|
||||
this._unsub.then((unsub) => unsub());
|
||||
this._unsub = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
willUpdate(changedProps) {
|
||||
if (changedProps.has("stepData")) {
|
||||
this._debouncedSubscribePreview();
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (this._error) {
|
||||
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
|
||||
}
|
||||
return html`<entity-preview-row
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this._preview}
|
||||
></entity-preview-row>`;
|
||||
}
|
||||
|
||||
private _setPreview = (preview: ThresholdPreview) => {
|
||||
const now = new Date().toISOString();
|
||||
this._preview = {
|
||||
entity_id: `${this.stepId}.___flow_preview___`,
|
||||
last_changed: now,
|
||||
last_updated: now,
|
||||
context: { id: "", parent_id: null, user_id: null },
|
||||
...preview,
|
||||
};
|
||||
};
|
||||
|
||||
private _debouncedSubscribePreview = debounce(() => {
|
||||
this._subscribePreview();
|
||||
}, 250);
|
||||
|
||||
private async _subscribePreview() {
|
||||
if (this._unsub) {
|
||||
(await this._unsub)();
|
||||
this._unsub = undefined;
|
||||
}
|
||||
if (this.flowType === "repair_flow") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this._unsub = subscribePreviewThreshold(
|
||||
this.hass,
|
||||
this.flowId,
|
||||
this.flowType,
|
||||
this.stepData,
|
||||
this._setPreview
|
||||
);
|
||||
await this._unsub;
|
||||
fireEvent(this, "set-flow-errors", { errors: {} });
|
||||
} catch (err: any) {
|
||||
if (typeof err.message === "string") {
|
||||
this._error = err.message;
|
||||
} else {
|
||||
this._error = undefined;
|
||||
fireEvent(this, "set-flow-errors", err.message);
|
||||
}
|
||||
this._unsub = undefined;
|
||||
this._preview = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"flow-preview-threshold": FlowPreviewThreshold;
|
||||
}
|
||||
}
|
@@ -126,6 +126,7 @@ class MoreInfoUpdate extends LitElement {
|
||||
></ha-checkbox>
|
||||
</ha-formfield> `
|
||||
: ""}
|
||||
<hr />
|
||||
<div class="actions">
|
||||
${this.stateObj.attributes.auto_update
|
||||
? ""
|
||||
@@ -239,20 +240,10 @@ class MoreInfoUpdate extends LitElement {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.actions {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
background: var(
|
||||
--ha-dialog-surface-background,
|
||||
var(--mdc-theme-surface, #fff)
|
||||
);
|
||||
margin: 8px 0 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding: 12px 0;
|
||||
margin-bottom: -24px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.actions mwc-button {
|
||||
|
@@ -215,10 +215,10 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
<div class="messages">
|
||||
<div class="messages-container" id="scroll-container">
|
||||
${this._conversation!.map(
|
||||
// New lines matter for messages
|
||||
// prettier-ignore
|
||||
(message) => html`
|
||||
<div class=${this._computeMessageClasses(message)}>${message.text}</div>
|
||||
<div class=${this._computeMessageClasses(message)}>
|
||||
${message.text}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
@@ -355,7 +355,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
|
||||
private _handleSendMessage() {
|
||||
if (this._messageInput.value) {
|
||||
this._processText(this._messageInput.value.trim());
|
||||
this._processText(this._messageInput.value);
|
||||
this._messageInput.value = "";
|
||||
this._showSendButton = false;
|
||||
}
|
||||
@@ -427,28 +427,34 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
private async _showNotSupportedMessage() {
|
||||
this._addMessage({
|
||||
who: "hass",
|
||||
text:
|
||||
// New lines matter for messages
|
||||
// prettier-ignore
|
||||
html`${this.hass.localize(
|
||||
"ui.dialogs.voice_command.not_supported_microphone_browser"
|
||||
)}
|
||||
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.voice_command.not_supported_microphone_documentation",
|
||||
{
|
||||
documentation_link: html`<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/docs/configuration/securing/#remote-access"
|
||||
)}
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.voice_command.not_supported_microphone_documentation_link"
|
||||
)}</a>`,
|
||||
}
|
||||
)}`,
|
||||
text: html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.voice_command.not_supported_microphone_browser"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.voice_command.not_supported_microphone_documentation",
|
||||
{
|
||||
documentation_link: html`
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/docs/configuration/securing/#remote-access"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.voice_command.not_supported_microphone_documentation_link"
|
||||
)}
|
||||
</a>
|
||||
`,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -750,7 +756,6 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
max-height: 100%;
|
||||
}
|
||||
.message {
|
||||
white-space: pre-line;
|
||||
font-size: 18px;
|
||||
clear: both;
|
||||
margin: 8px 0;
|
||||
@@ -787,14 +792,10 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.message.user a {
|
||||
.message a {
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
|
||||
.message.hass a {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.message img {
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
|
@@ -2,6 +2,7 @@
|
||||
import "../resources/compatibility";
|
||||
import "../auth/ha-authorize";
|
||||
import "../resources/safari-14-attachshadow-patch";
|
||||
import "../resources/array.flat.polyfill";
|
||||
|
||||
import("../resources/ha-style");
|
||||
import("@polymer/polymer/lib/utils/settings").then(
|
||||
|
@@ -25,6 +25,7 @@ import { subscribePanels } from "../data/ws-panels";
|
||||
import { subscribeThemes } from "../data/ws-themes";
|
||||
import { subscribeUser } from "../data/ws-user";
|
||||
import type { ExternalAuth } from "../external_app/external_auth";
|
||||
import "../resources/array.flat.polyfill";
|
||||
import "../resources/safari-14-attachshadow-patch";
|
||||
|
||||
window.name = MAIN_WINDOW_NAME;
|
||||
|
@@ -2,6 +2,7 @@
|
||||
import "../resources/compatibility";
|
||||
import "../onboarding/ha-onboarding";
|
||||
import "../resources/safari-14-attachshadow-patch";
|
||||
import "../resources/array.flat.polyfill";
|
||||
|
||||
import("../resources/ha-style");
|
||||
import("@polymer/polymer/lib/utils/settings").then(
|
||||
|
@@ -5,7 +5,6 @@ export const demoConfig: HassConfig = {
|
||||
elevation: 300,
|
||||
latitude: 52.3731339,
|
||||
longitude: 4.8903147,
|
||||
radius: 100,
|
||||
unit_system: {
|
||||
length: "km",
|
||||
mass: "kg",
|
||||
|
@@ -96,7 +96,7 @@ export class HaConfigApplicationCredentials extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
back-path="/config"
|
||||
backPath="/config"
|
||||
.tabs=${configSections.devices}
|
||||
.columns=${this._columns(this.narrow, this.hass.localize)}
|
||||
.data=${this._getApplicationCredentials(
|
||||
|
@@ -12,8 +12,6 @@ import {
|
||||
deviceAutomationsEqual,
|
||||
DeviceCapabilities,
|
||||
fetchDeviceActionCapabilities,
|
||||
localizeExtraFieldsComputeLabelCallback,
|
||||
localizeExtraFieldsComputeHelperCallback,
|
||||
} from "../../../../../data/device_automation";
|
||||
import { EntityRegistryEntry } from "../../../../../data/entity_registry";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
@@ -86,13 +84,8 @@ export class HaDeviceAction extends LitElement {
|
||||
.data=${this._extraFieldsData(this.action, this._capabilities)}
|
||||
.schema=${this._capabilities.extra_fields}
|
||||
.disabled=${this.disabled}
|
||||
.computeLabel=${localizeExtraFieldsComputeLabelCallback(
|
||||
this.hass,
|
||||
this.action
|
||||
)}
|
||||
.computeHelper=${localizeExtraFieldsComputeHelperCallback(
|
||||
this.hass,
|
||||
this.action
|
||||
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
||||
this.hass.localize
|
||||
)}
|
||||
@value-changed=${this._extraFieldsChanged}
|
||||
></ha-form>
|
||||
@@ -159,6 +152,14 @@ export class HaDeviceAction extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _extraFieldsComputeLabelCallback(localize) {
|
||||
// Returns a callback for ha-form to calculate labels per schema object
|
||||
return (schema) =>
|
||||
localize(
|
||||
`ui.panel.config.automation.editor.actions.type.device_id.extra_fields.${schema.name}`
|
||||
) || schema.name;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-device-picker {
|
||||
display: block;
|
||||
|
@@ -147,7 +147,7 @@ class DialogAutomationMode extends LitElement implements HassDialog {
|
||||
type="number"
|
||||
name="max"
|
||||
.value=${this._newMax?.toString() ?? ""}
|
||||
@input=${this._valueChanged}
|
||||
@change=${this._valueChanged}
|
||||
class="max"
|
||||
>
|
||||
</ha-textfield>
|
||||
|
@@ -12,8 +12,6 @@ import {
|
||||
DeviceCapabilities,
|
||||
DeviceCondition,
|
||||
fetchDeviceConditionCapabilities,
|
||||
localizeExtraFieldsComputeLabelCallback,
|
||||
localizeExtraFieldsComputeHelperCallback,
|
||||
} from "../../../../../data/device_automation";
|
||||
import { EntityRegistryEntry } from "../../../../../data/entity_registry";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
@@ -86,13 +84,8 @@ export class HaDeviceCondition extends LitElement {
|
||||
.data=${this._extraFieldsData(this.condition, this._capabilities)}
|
||||
.schema=${this._capabilities.extra_fields}
|
||||
.disabled=${this.disabled}
|
||||
.computeLabel=${localizeExtraFieldsComputeLabelCallback(
|
||||
this.hass,
|
||||
this.condition
|
||||
)}
|
||||
.computeHelper=${localizeExtraFieldsComputeHelperCallback(
|
||||
this.hass,
|
||||
this.condition
|
||||
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
||||
this.hass.localize
|
||||
)}
|
||||
@value-changed=${this._extraFieldsChanged}
|
||||
></ha-form>
|
||||
@@ -160,6 +153,14 @@ export class HaDeviceCondition extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _extraFieldsComputeLabelCallback(localize) {
|
||||
// Returns a callback for ha-form to calculate labels per schema object
|
||||
return (schema) =>
|
||||
localize(
|
||||
`ui.panel.config.automation.editor.conditions.type.device.extra_fields.${schema.name}`
|
||||
) || schema.name;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-device-picker {
|
||||
display: block;
|
||||
|
@@ -145,14 +145,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _filteredAutomations?: string[] | null;
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "automation-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter = "";
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "automation-table-filters-full",
|
||||
@@ -555,8 +547,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
"ui.panel.config.automation.picker.no_automations"
|
||||
)}
|
||||
@clear-filter=${this._clearFilter}
|
||||
.filter=${this._filter}
|
||||
@search-changed=${this._handleSearchChange}
|
||||
hasFab
|
||||
clickable
|
||||
class=${this.narrow ? "narrow" : ""}
|
||||
@@ -934,10 +924,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
this._applyFilters();
|
||||
};
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
private _filterChanged(ev) {
|
||||
const type = ev.target.localName;
|
||||
this._filters = { ...this._filters, [type]: ev.detail };
|
||||
|
@@ -596,9 +596,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
|
||||
private _showTriggeredInfo() {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.triggering_event_detail"
|
||||
),
|
||||
text: html`
|
||||
<ha-yaml-editor
|
||||
readOnly
|
||||
|
@@ -14,8 +14,6 @@ import {
|
||||
DeviceCapabilities,
|
||||
DeviceTrigger,
|
||||
fetchDeviceTriggerCapabilities,
|
||||
localizeExtraFieldsComputeLabelCallback,
|
||||
localizeExtraFieldsComputeHelperCallback,
|
||||
} from "../../../../../data/device_automation";
|
||||
import { EntityRegistryEntry } from "../../../../../data/entity_registry";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
@@ -90,13 +88,8 @@ export class HaDeviceTrigger extends LitElement {
|
||||
.data=${this._extraFieldsData(this.trigger, this._capabilities)}
|
||||
.schema=${this._capabilities.extra_fields}
|
||||
.disabled=${this.disabled}
|
||||
.computeLabel=${localizeExtraFieldsComputeLabelCallback(
|
||||
this.hass,
|
||||
this.trigger
|
||||
)}
|
||||
.computeHelper=${localizeExtraFieldsComputeHelperCallback(
|
||||
this.hass,
|
||||
this.trigger
|
||||
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
||||
this.hass.localize
|
||||
)}
|
||||
@value-changed=${this._extraFieldsChanged}
|
||||
></ha-form>
|
||||
@@ -184,6 +177,14 @@ export class HaDeviceTrigger extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _extraFieldsComputeLabelCallback(localize) {
|
||||
// Returns a callback for ha-form to calculate labels per schema object
|
||||
return (schema) =>
|
||||
localize(
|
||||
`ui.panel.config.automation.editor.triggers.type.device.extra_fields.${schema.name}`
|
||||
) || schema.name;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-device-picker {
|
||||
display: block;
|
||||
|
@@ -107,14 +107,6 @@ class HaBlueprintOverview extends LitElement {
|
||||
})
|
||||
private _activeCollapsed?: string;
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "blueprint-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter: string = "";
|
||||
|
||||
private _processedBlueprints = memoizeOne(
|
||||
(
|
||||
blueprints: Record<string, Blueprints>,
|
||||
@@ -316,8 +308,6 @@ class HaBlueprintOverview extends LitElement {
|
||||
@sorting-changed=${this._handleSortingChanged}
|
||||
@grouping-changed=${this._handleGroupingChanged}
|
||||
@collapsed-changed=${this._handleCollapseChanged}
|
||||
.filter=${this._filter}
|
||||
@search-changed=${this._handleSearchChange}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
@@ -552,10 +542,6 @@ class HaBlueprintOverview extends LitElement {
|
||||
this._activeCollapsed = ev.detail.value;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return haStyle;
|
||||
}
|
||||
|
@@ -82,13 +82,10 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.updates.show_skipped")}
|
||||
</ha-check-list-item>
|
||||
${this._supervisorInfo
|
||||
${this._supervisorInfo && this._supervisorInfo.channel !== "dev"
|
||||
? html`
|
||||
<li divider role="separator"></li>
|
||||
<mwc-list-item
|
||||
@request-selected=${this._toggleBeta}
|
||||
.disabled=${this._supervisorInfo.channel === "dev"}
|
||||
>
|
||||
<mwc-list-item @request-selected=${this._toggleBeta}>
|
||||
${this._supervisorInfo.channel === "stable"
|
||||
? this.hass.localize("ui.panel.config.updates.join_beta")
|
||||
: this.hass.localize(
|
||||
|
@@ -6,7 +6,7 @@ import {
|
||||
mdiPower,
|
||||
mdiRefresh,
|
||||
} from "@mdi/js";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { HassEntities, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
@@ -177,10 +177,7 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const { updates: canInstallUpdates, total: totalUpdates } =
|
||||
this._filterUpdateEntitiesWithInstall(
|
||||
this.hass.states,
|
||||
this.hass.entities
|
||||
);
|
||||
this._filterUpdateEntitiesWithInstall(this.hass.states);
|
||||
|
||||
const { issues: repairsIssues, total: totalRepairIssues } =
|
||||
this._repairsIssues;
|
||||
@@ -309,13 +306,8 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _filterUpdateEntitiesWithInstall = memoizeOne(
|
||||
(
|
||||
entities: HomeAssistant["states"],
|
||||
entityRegistry: HomeAssistant["entities"]
|
||||
): { updates: UpdateEntity[]; total: number } => {
|
||||
const updates = filterUpdateEntitiesWithInstall(entities).filter(
|
||||
(entity) => !entityRegistry[entity.entity_id]?.hidden
|
||||
);
|
||||
(entities: HassEntities): { updates: UpdateEntity[]; total: number } => {
|
||||
const updates = filterUpdateEntitiesWithInstall(entities);
|
||||
|
||||
return {
|
||||
updates: updates.slice(0, updates.length === 3 ? updates.length : 2),
|
||||
|
@@ -24,7 +24,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { SENSOR_ENTITIES, ASSIST_ENTITIES } from "../../../common/const";
|
||||
import { SENSOR_ENTITIES } from "../../../common/const";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
@@ -190,42 +190,26 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
private _entitiesByCategory = memoizeOne(
|
||||
(entities: EntityRegistryEntry[]) => {
|
||||
const result = groupBy(entities, (entry) => {
|
||||
const domain = computeDomain(entry.entity_id);
|
||||
|
||||
if (entry.entity_category) {
|
||||
return entry.entity_category;
|
||||
}
|
||||
|
||||
if (domain === "event" || domain === "notify") {
|
||||
return domain;
|
||||
}
|
||||
|
||||
if (SENSOR_ENTITIES.includes(domain)) {
|
||||
return "sensor";
|
||||
}
|
||||
|
||||
if (ASSIST_ENTITIES.includes(domain)) {
|
||||
return "assist";
|
||||
}
|
||||
|
||||
return "control";
|
||||
}) as Record<
|
||||
const result = groupBy(entities, (entry) =>
|
||||
entry.entity_category
|
||||
? entry.entity_category
|
||||
: computeDomain(entry.entity_id) === "event"
|
||||
? "event"
|
||||
: SENSOR_ENTITIES.includes(computeDomain(entry.entity_id))
|
||||
? "sensor"
|
||||
: "control"
|
||||
) as Record<
|
||||
| "control"
|
||||
| "event"
|
||||
| "sensor"
|
||||
| "assist"
|
||||
| "notify"
|
||||
| NonNullable<EntityRegistryEntry["entity_category"]>,
|
||||
EntityRegistryStateEntry[]
|
||||
>;
|
||||
for (const key of [
|
||||
"assist",
|
||||
"config",
|
||||
"control",
|
||||
"diagnostic",
|
||||
"event",
|
||||
"notify",
|
||||
"sensor",
|
||||
]) {
|
||||
if (!(key in result)) {
|
||||
@@ -870,15 +854,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
</div>
|
||||
<div class="column">
|
||||
${(
|
||||
[
|
||||
"control",
|
||||
"sensor",
|
||||
"notify",
|
||||
"event",
|
||||
"assist",
|
||||
"config",
|
||||
"diagnostic",
|
||||
] as const
|
||||
["control", "sensor", "event", "config", "diagnostic"] as const
|
||||
).map((category) =>
|
||||
// Make sure we render controls if no other cards will be rendered
|
||||
entitiesByCategory[category].length > 0 ||
|
||||
@@ -1028,9 +1004,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
: this.hass.localize(
|
||||
`ui.panel.config.devices.confirm_delete`
|
||||
),
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
destructive: true,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
|
@@ -122,13 +122,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _selected: string[] = [];
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "devices-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter: string = history.state?.filter || "";
|
||||
@state() private _filter: string = history.state?.filter || "";
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
@@ -205,33 +199,38 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _setFiltersFromUrl() {
|
||||
const domain = this._searchParms.get("domain");
|
||||
const configEntry = this._searchParms.get("config_entry");
|
||||
|
||||
if (!domain && !configEntry) {
|
||||
return;
|
||||
if (this._searchParms.has("domain")) {
|
||||
this._filters = {
|
||||
...this._filters,
|
||||
"ha-filter-states": {
|
||||
value: [
|
||||
...((this._filters["ha-filter-states"]?.value as string[]) || []),
|
||||
"disabled",
|
||||
],
|
||||
items: undefined,
|
||||
},
|
||||
"ha-filter-integrations": {
|
||||
value: [this._searchParms.get("domain")!],
|
||||
items: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (this._searchParms.has("config_entry")) {
|
||||
this._filters = {
|
||||
...this._filters,
|
||||
"ha-filter-states": {
|
||||
value: [
|
||||
...((this._filters["ha-filter-states"]?.value as string[]) || []),
|
||||
"disabled",
|
||||
],
|
||||
items: undefined,
|
||||
},
|
||||
config_entry: {
|
||||
value: [this._searchParms.get("config_entry")!],
|
||||
items: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
this._filter = history.state?.filter || "";
|
||||
|
||||
this._filters = {
|
||||
"ha-filter-states": {
|
||||
value: [
|
||||
...((this._filters["ha-filter-states"]?.value as string[]) || []),
|
||||
"disabled",
|
||||
],
|
||||
items: undefined,
|
||||
},
|
||||
"ha-filter-integrations": {
|
||||
value: domain ? [domain] : [],
|
||||
items: undefined,
|
||||
},
|
||||
config_entry: {
|
||||
value: configEntry ? [configEntry] : [],
|
||||
items: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
if (this._searchParms.has("label")) {
|
||||
this._filterLabel();
|
||||
}
|
||||
|
@@ -164,9 +164,6 @@ export class EntitySettingsHelperTab extends LitElement {
|
||||
text: this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.confirm_delete"
|
||||
),
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
destructive: true,
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
|
@@ -215,9 +215,6 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
text: this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.confirm_delete"
|
||||
),
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
destructive: true,
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
|
@@ -144,13 +144,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entities!: EntityRegistryEntry[];
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "entities-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter: string = history.state?.filter || "";
|
||||
@state() private _filter: string = history.state?.filter || "";
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
|
||||
@@ -709,6 +703,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
)
|
||||
).length
|
||||
}
|
||||
.filter=${this._filter}
|
||||
selectable
|
||||
.selected=${this._selected.length}
|
||||
.initialGroupColumn=${this._activeGrouping}
|
||||
@@ -720,7 +715,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
clickable
|
||||
@clear-filter=${this._clearFilter}
|
||||
.filter=${this._filter}
|
||||
@search-changed=${this._handleSearchChange}
|
||||
@row-click=${this._openEditEntry}
|
||||
id="entity_id"
|
||||
@@ -839,7 +833,7 @@ ${
|
||||
></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entities.picker.delete_selected.button"
|
||||
"ui.panel.config.entities.picker.remove_selected.button"
|
||||
)}
|
||||
</div>
|
||||
</ha-menu-item>
|
||||
@@ -949,7 +943,6 @@ ${
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
this._setFiltersFromUrl();
|
||||
if (Object.keys(this._filters).length) {
|
||||
return;
|
||||
}
|
||||
@@ -959,36 +952,39 @@ ${
|
||||
items: undefined,
|
||||
},
|
||||
};
|
||||
this._setFiltersFromUrl();
|
||||
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
||||
this._entitySources = sources;
|
||||
});
|
||||
}
|
||||
|
||||
private _setFiltersFromUrl() {
|
||||
const domain = this._searchParms.get("domain");
|
||||
const configEntry = this._searchParms.get("config_entry");
|
||||
|
||||
if (!domain && !configEntry) {
|
||||
return;
|
||||
if (this._searchParms.has("domain")) {
|
||||
this._filters = {
|
||||
...this._filters,
|
||||
"ha-filter-states": {
|
||||
value: [],
|
||||
items: undefined,
|
||||
},
|
||||
"ha-filter-integrations": {
|
||||
value: [this._searchParms.get("domain")!],
|
||||
items: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (this._searchParms.has("config_entry")) {
|
||||
this._filters = {
|
||||
...this._filters,
|
||||
"ha-filter-states": {
|
||||
value: [],
|
||||
items: undefined,
|
||||
},
|
||||
config_entry: {
|
||||
value: [this._searchParms.get("config_entry")!],
|
||||
items: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
this._filter = history.state?.filter || "";
|
||||
|
||||
this._filters = {
|
||||
"ha-filter-states": {
|
||||
value: [],
|
||||
items: undefined,
|
||||
},
|
||||
"ha-filter-integrations": {
|
||||
value: domain ? [domain] : [],
|
||||
items: undefined,
|
||||
},
|
||||
config_entry: {
|
||||
value: configEntry ? [configEntry] : [],
|
||||
items: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
if (this._searchParms.has("label")) {
|
||||
this._filterLabel();
|
||||
}
|
||||
@@ -1256,23 +1252,25 @@ ${rejected
|
||||
});
|
||||
showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
`ui.panel.config.entities.picker.delete_selected.confirm_title`
|
||||
`ui.panel.config.entities.picker.remove_selected.confirm_${
|
||||
removeableEntities.length !== this._selected.length ? "partly_" : ""
|
||||
}title`,
|
||||
{ number: removeableEntities.length }
|
||||
),
|
||||
text:
|
||||
removeableEntities.length === this._selected.length
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.entities.picker.delete_selected.confirm_text"
|
||||
"ui.panel.config.entities.picker.remove_selected.confirm_text"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.entities.picker.delete_selected.confirm_partly_text",
|
||||
"ui.panel.config.entities.picker.remove_selected.confirm_partly_text",
|
||||
{
|
||||
deletable: removeableEntities.length,
|
||||
removable: removeableEntities.length,
|
||||
selected: this._selected.length,
|
||||
}
|
||||
),
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
confirmText: this.hass.localize("ui.common.remove"),
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
destructive: true,
|
||||
confirm: () => {
|
||||
removeableEntities.forEach((entity) =>
|
||||
removeEntityRegistryEntry(this.hass, entity)
|
||||
|
@@ -159,14 +159,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
})
|
||||
private _activeCollapsed?: string;
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "helpers-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter = "";
|
||||
|
||||
@state() private _stateItems: HassEntity[] = [];
|
||||
|
||||
@state() private _entityEntries?: Record<string, EntityRegistryEntry>;
|
||||
@@ -567,8 +559,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
.activeFilters=${this._activeFilters}
|
||||
@clear-filter=${this._clearFilter}
|
||||
@row-click=${this._openEditDialog}
|
||||
.filter=${this._filter}
|
||||
@search-changed=${this._handleSearchChange}
|
||||
hasFab
|
||||
clickable
|
||||
.noDataText=${this.hass.localize(
|
||||
@@ -1080,10 +1070,6 @@ ${rejected
|
||||
this._activeCollapsed = ev.detail.value;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/web/divider/divider";
|
||||
import { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item-base";
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiBookshelf,
|
||||
@@ -27,13 +27,13 @@ import {
|
||||
} from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -41,15 +41,15 @@ import { until } from "lit/directives/until";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { isDevVersion } from "../../../common/config/version";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import { nextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu-new";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-list-item-new";
|
||||
import "../../../components/ha-list-new";
|
||||
import "../../../components/ha-menu-item";
|
||||
import "../../../components/ha-list-item-new";
|
||||
import "../../../components/ha-button-menu-new";
|
||||
import {
|
||||
deleteApplicationCredential,
|
||||
fetchApplicationCredentialsConfigEntry,
|
||||
@@ -57,13 +57,13 @@ import {
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import {
|
||||
ConfigEntry,
|
||||
DisableConfigEntryResult,
|
||||
ERROR_STATES,
|
||||
RECOVERABLE_STATES,
|
||||
deleteConfigEntry,
|
||||
disableConfigEntry,
|
||||
DisableConfigEntryResult,
|
||||
enableConfigEntry,
|
||||
ERROR_STATES,
|
||||
getConfigEntries,
|
||||
RECOVERABLE_STATES,
|
||||
reloadConfigEntry,
|
||||
updateConfigEntry,
|
||||
} from "../../../data/config_entries";
|
||||
@@ -80,13 +80,13 @@ import {
|
||||
} from "../../../data/entity_registry";
|
||||
import { getErrorLogDownloadUrl } from "../../../data/error_log";
|
||||
import {
|
||||
IntegrationLogInfo,
|
||||
IntegrationManifest,
|
||||
LogSeverity,
|
||||
domainToName,
|
||||
fetchIntegrationManifest,
|
||||
integrationIssuesUrl,
|
||||
IntegrationLogInfo,
|
||||
IntegrationManifest,
|
||||
integrationsWithPanel,
|
||||
LogSeverity,
|
||||
setIntegrationLogLevel,
|
||||
subscribeLogInfo,
|
||||
} from "../../../data/integration";
|
||||
@@ -453,7 +453,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
const attention = ATTENTION_SOURCES.includes(
|
||||
flow.context.source
|
||||
);
|
||||
return html` <ha-list-item-new
|
||||
return html`<ha-list-item-new
|
||||
class="config_entry ${attention ? "attention" : ""}"
|
||||
>
|
||||
${flow.localized_title}
|
||||
@@ -477,15 +477,8 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
></ha-button>
|
||||
</ha-list-item-new>`;
|
||||
})}
|
||||
${attentionEntries.map(
|
||||
(item, index) =>
|
||||
html`${this._renderConfigEntry(item)}
|
||||
${index < attentionEntries.length - 1
|
||||
? html` <md-divider
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></md-divider>`
|
||||
: ""} `
|
||||
${attentionEntries.map((item) =>
|
||||
this._renderConfigEntry(item)
|
||||
)}
|
||||
</ha-list-new>
|
||||
</ha-card>`
|
||||
@@ -509,16 +502,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
</div>`
|
||||
: nothing}
|
||||
<ha-list-new>
|
||||
${normalEntries.map(
|
||||
(item, index) =>
|
||||
html`${this._renderConfigEntry(item)}
|
||||
${index < normalEntries.length - 1
|
||||
? html` <md-divider
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></md-divider>`
|
||||
: ""} `
|
||||
)}
|
||||
${normalEntries.map((item) => this._renderConfigEntry(item))}
|
||||
</ha-list-new>
|
||||
<div class="card-actions">
|
||||
<ha-button @click=${this._addIntegration}>
|
||||
@@ -683,21 +667,19 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
const configPanel = this._configPanel(item.domain, this.hass.panels);
|
||||
|
||||
return html` <ha-list-item-new
|
||||
return html`<ha-list-item-new
|
||||
class=${classMap({
|
||||
config_entry: true,
|
||||
"state-not-loaded": item!.state === "not_loaded",
|
||||
"state-failed-unload": item!.state === "failed_unload",
|
||||
"state-setup": item!.state === "setup_in_progress",
|
||||
"state-error": ERROR_STATES.includes(item!.state),
|
||||
"state-disabled": item.disabled_by !== null,
|
||||
})}
|
||||
data-entry-id=${item.entry_id}
|
||||
.disabled=${item.disabled_by}
|
||||
.configEntry=${item}
|
||||
>
|
||||
<div slot="headline">
|
||||
${item.title || domainToName(this.hass.localize, item.domain)}
|
||||
</div>
|
||||
${item.title || domainToName(this.hass.localize, item.domain)}
|
||||
<div slot="supporting-text">
|
||||
<div>${devicesLine}</div>
|
||||
${stateText
|
||||
@@ -744,159 +726,170 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
${item.supports_options && stateText
|
||||
? html`<ha-menu-item @click=${this._showOptions}>
|
||||
<ha-svg-icon slot="start" .path=${mdiCog}></ha-svg-icon>
|
||||
? html`<ha-list-item
|
||||
@request-selected=${this._showOptions}
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiCog}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.configure"
|
||||
)}
|
||||
</ha-menu-item>`
|
||||
</ha-list-item>`
|
||||
: ""}
|
||||
${item.disabled_by && devices.length
|
||||
? html`
|
||||
<ha-menu-item
|
||||
href=${devices.length === 1
|
||||
? `/config/devices/device/${devices[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDevices} slot="start"></ha-svg-icon>
|
||||
? html`<a
|
||||
href=${devices.length === 1
|
||||
? `/config/devices/device/${devices[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon .path=${mdiDevices} slot="graphic"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.devices`,
|
||||
{ count: devices.length }
|
||||
)}
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-menu-item>
|
||||
`
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${item.disabled_by && services.length
|
||||
? html`<ha-menu-item
|
||||
? html`<a
|
||||
href=${services.length === 1
|
||||
? `/config/devices/device/${services[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${mdiHandExtendedOutline}
|
||||
slot="start"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.services`,
|
||||
{ count: services.length }
|
||||
)}
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-menu-item> `
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${mdiHandExtendedOutline}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.services`,
|
||||
{ count: services.length }
|
||||
)}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${item.disabled_by && entities.length
|
||||
? html`
|
||||
<ha-menu-item
|
||||
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>
|
||||
? html`<a
|
||||
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${mdiShapeOutline}
|
||||
slot="start"
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.entities`,
|
||||
{ count: entities.length }
|
||||
)}
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-menu-item>
|
||||
`
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${!item.disabled_by &&
|
||||
RECOVERABLE_STATES.includes(item.state) &&
|
||||
item.supports_unload &&
|
||||
item.source !== "system"
|
||||
? html`
|
||||
<ha-menu-item @click=${this._handleReload}>
|
||||
<ha-svg-icon slot="start" .path=${mdiReload}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.reload"
|
||||
)}
|
||||
</ha-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
? html`<ha-list-item
|
||||
@request-selected=${this._handleReload}
|
||||
graphic="icon"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.reload"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiReload}></ha-svg-icon>
|
||||
</ha-list-item>`
|
||||
: ""}
|
||||
|
||||
<ha-menu-item @click=${this._handleRename} graphic="icon">
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-list-item @request-selected=${this._handleRename} graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.rename"
|
||||
)}
|
||||
</ha-menu-item>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
|
||||
<md-divider role="separator" tabindex="-1"></md-divider>
|
||||
<li divider role="separator"></li>
|
||||
|
||||
${this._diagnosticHandler && item.state === "loaded"
|
||||
? html`
|
||||
<ha-menu-item
|
||||
href=${getConfigEntryDiagnosticsDownloadUrl(item.entry_id)}
|
||||
target="_blank"
|
||||
@click=${this._signUrl}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDownload}></ha-svg-icon>
|
||||
? html`<a
|
||||
href=${getConfigEntryDiagnosticsDownloadUrl(item.entry_id)}
|
||||
target="_blank"
|
||||
@click=${this._signUrl}
|
||||
>
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.download_diagnostics"
|
||||
)}
|
||||
</ha-menu-item>
|
||||
`
|
||||
<ha-svg-icon slot="graphic" .path=${mdiDownload}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${!item.disabled_by &&
|
||||
item.supports_reconfigure &&
|
||||
item.source !== "system"
|
||||
? html`
|
||||
<ha-menu-item @click=${this._handleReconfigure}>
|
||||
<ha-svg-icon slot="start" .path=${mdiWrench}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.reconfigure"
|
||||
)}
|
||||
</ha-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
? html`<ha-list-item
|
||||
@request-selected=${this._handleReconfigure}
|
||||
graphic="icon"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.reconfigure"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiWrench}></ha-svg-icon>
|
||||
</ha-list-item>`
|
||||
: ""}
|
||||
|
||||
<ha-menu-item @click=${this._handleSystemOptions} graphic="icon">
|
||||
<ha-svg-icon slot="start" .path=${mdiCog}></ha-svg-icon>
|
||||
<ha-list-item
|
||||
@request-selected=${this._handleSystemOptions}
|
||||
graphic="icon"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.system_options"
|
||||
)}
|
||||
</ha-menu-item>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiCog}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
${item.disabled_by === "user"
|
||||
? html`
|
||||
<ha-menu-item @click=${this._handleEnable}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiPlayCircleOutline}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</ha-menu-item>
|
||||
`
|
||||
? html`<ha-list-item
|
||||
@request-selected=${this._handleEnable}
|
||||
graphic="icon"
|
||||
>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiPlayCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>`
|
||||
: item.source !== "system"
|
||||
? html`
|
||||
<ha-menu-item
|
||||
class="warning"
|
||||
@click=${this._handleDisable}
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
class="warning"
|
||||
.path=${mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize("ui.common.disable")}
|
||||
</ha-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
${item.source !== "system"
|
||||
? html`
|
||||
<ha-menu-item class="warning" @click=${this._handleDelete}>
|
||||
? html`<ha-list-item
|
||||
class="warning"
|
||||
@request-selected=${this._handleDisable}
|
||||
graphic="icon"
|
||||
>
|
||||
${this.hass.localize("ui.common.disable")}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="graphic"
|
||||
class="warning"
|
||||
.path=${mdiDelete}
|
||||
.path=${mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete"
|
||||
)}
|
||||
</ha-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
</ha-list-item>`
|
||||
: ""}
|
||||
${item.source !== "system"
|
||||
? html`<ha-list-item
|
||||
class="warning"
|
||||
@request-selected=${this._handleDelete}
|
||||
graphic="icon"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
class="warning"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>`
|
||||
: ""}
|
||||
</ha-button-menu-new>
|
||||
</ha-list-item-new>`;
|
||||
}
|
||||
@@ -1062,43 +1055,64 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
}
|
||||
|
||||
private _handleRename(ev: Event): void {
|
||||
private _handleRename(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._editEntryName(
|
||||
((ev.target as HTMLElement).closest(".config_entry") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _handleReload(ev: Event): void {
|
||||
private _handleReload(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._reloadIntegration(
|
||||
((ev.target as HTMLElement).closest(".config_entry") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _handleReconfigure(ev: Event): void {
|
||||
private _handleReconfigure(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._reconfigureIntegration(
|
||||
((ev.target as HTMLElement).closest(".config_entry") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _handleDelete(ev: Event): void {
|
||||
private _handleDelete(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._removeIntegration(
|
||||
((ev.target as HTMLElement).closest(".config_entry") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _handleDisable(ev: Event): void {
|
||||
private _handleDisable(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._disableIntegration(
|
||||
((ev.target as HTMLElement).closest(".config_entry") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _handleEnable(ev: Event): void {
|
||||
private _handleEnable(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (ev.detail.source && !shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._enableIntegration(
|
||||
((ev.target as HTMLElement).closest(".config_entry") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _handleSystemOptions(ev: Event): void {
|
||||
private _handleSystemOptions(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._showSystemOptions(
|
||||
((ev.target as HTMLElement).closest(".config_entry") as any).configEntry
|
||||
);
|
||||
@@ -1313,7 +1327,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private async _signUrl(ev) {
|
||||
const anchor = ev.currentTarget;
|
||||
const anchor = ev.target.closest("a");
|
||||
ev.preventDefault();
|
||||
const signedUrl = await getSignedPath(
|
||||
this.hass,
|
||||
@@ -1414,22 +1428,32 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
ha-alert:first-of-type {
|
||||
margin-top: 16px;
|
||||
}
|
||||
ha-list-item-new {
|
||||
position: relative;
|
||||
}
|
||||
ha-list-item-new.discovered {
|
||||
--mdc-list-item-meta-size: auto;
|
||||
--mdc-list-item-meta-display: flex;
|
||||
height: 72px;
|
||||
}
|
||||
ha-list-item-new.config_entry {
|
||||
overflow: visible;
|
||||
--mdc-list-item-meta-size: auto;
|
||||
--mdc-list-item-meta-display: flex;
|
||||
}
|
||||
ha-button-menu-new ha-list-item {
|
||||
--mdc-list-item-meta-size: 24px;
|
||||
}
|
||||
ha-list-item-new.config_entry::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
top: 8px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
bottom: 8px;
|
||||
left: 0;
|
||||
opacity: 0.12;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
}
|
||||
ha-button-menu-new {
|
||||
flex: 0;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -1486,10 +1510,6 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.state-disabled [slot="headline"],
|
||||
.state-disabled [slot="supporting-text"] {
|
||||
opacity: var(--md-list-item-disabled-opacity, 0.3);
|
||||
}
|
||||
ha-list-new {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
@@ -36,8 +36,14 @@ class DialogThreadDataset extends LitElement implements HassDialog {
|
||||
dataset.extended_pan_id &&
|
||||
otbrInfo.active_dataset_tlvs?.includes(dataset.extended_pan_id);
|
||||
|
||||
const canImportKeychain =
|
||||
hasOTBR &&
|
||||
!this.hass.auth.external?.config.canTransferThreadCredentialsToKeychain &&
|
||||
network.routers?.length;
|
||||
|
||||
return html`<ha-dialog
|
||||
open
|
||||
.hideActions=${!canImportKeychain}
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(this.hass, network.name)}
|
||||
>
|
||||
@@ -53,8 +59,28 @@ class DialogThreadDataset extends LitElement implements HassDialog {
|
||||
Active dataset TLVs: ${otbrInfo.active_dataset_tlvs}`
|
||||
: nothing}
|
||||
</div>
|
||||
${canImportKeychain
|
||||
? html`<ha-button slot="primary-action" @click=${this._sendCredentials}
|
||||
>Send credentials to phone</ha-button
|
||||
>`
|
||||
: nothing}
|
||||
</ha-dialog>`;
|
||||
}
|
||||
|
||||
private _sendCredentials() {
|
||||
this.hass.auth.external!.fireMessage({
|
||||
type: "thread/store_in_platform_keychain",
|
||||
payload: {
|
||||
mac_extended_address:
|
||||
this._params?.network.dataset?.preferred_extended_address ||
|
||||
this._params!.network.routers![0]!.extended_address,
|
||||
border_agent_id:
|
||||
this._params?.network.dataset?.preferred_border_agent_id ||
|
||||
this._params!.network.routers![0]!.border_agent_id,
|
||||
active_operational_dataset: this._params!.otbrInfo!.active_dataset_tlvs,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -151,7 +151,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
||||
slot="fab"
|
||||
@click=${this._importExternalThreadCredentials}
|
||||
extended
|
||||
label="Send credentials to Home Assistant"
|
||||
label="Import credentials"
|
||||
><ha-svg-icon slot="icon" .path=${mdiCellphoneKey}></ha-svg-icon
|
||||
></ha-fab>`
|
||||
: nothing}
|
||||
@@ -160,14 +160,6 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _renderNetwork(network: ThreadNetwork) {
|
||||
const canImportKeychain =
|
||||
this.hass.auth.external?.config.canTransferThreadCredentialsToKeychain &&
|
||||
network.dataset?.extended_pan_id &&
|
||||
this._otbrInfo &&
|
||||
this._otbrInfo?.active_dataset_tlvs?.includes(
|
||||
network.dataset.extended_pan_id
|
||||
);
|
||||
|
||||
return html`<ha-card>
|
||||
<div class="card-header">
|
||||
${network.name}${network.dataset
|
||||
@@ -311,30 +303,9 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
</div>`
|
||||
: ""}
|
||||
${canImportKeychain
|
||||
? html`<div class="card-actions">
|
||||
<mwc-button @click=${this._sendCredentials}
|
||||
>Send credentials to phone</mwc-button
|
||||
>
|
||||
</div>`
|
||||
: ""}
|
||||
</ha-card>`;
|
||||
}
|
||||
|
||||
private _sendCredentials() {
|
||||
if (!this._otbrInfo) {
|
||||
return;
|
||||
}
|
||||
this.hass.auth.external!.fireMessage({
|
||||
type: "thread/store_in_platform_keychain",
|
||||
payload: {
|
||||
mac_extended_address: this._otbrInfo.extended_address,
|
||||
border_agent_id: this._otbrInfo.border_agent_id ?? "",
|
||||
active_operational_dataset: this._otbrInfo.active_dataset_tlvs ?? "",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async _showDatasetInfo(ev: Event) {
|
||||
const network = (ev.currentTarget as any).network as ThreadNetwork;
|
||||
showThreadDatasetDialog(this, { network, otbrInfo: this._otbrInfo });
|
||||
|
@@ -3,7 +3,6 @@ import { mdiAlertCircle, mdiCheckCircle, mdiQrcodeScan } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-alert";
|
||||
import type { HaCheckbox } from "../../../../../components/ha-checkbox";
|
||||
@@ -61,8 +60,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
| "finished"
|
||||
| "provisioned"
|
||||
| "validate_dsk_enter_pin"
|
||||
| "grant_security_classes"
|
||||
| "waiting_for_device";
|
||||
| "grant_security_classes";
|
||||
|
||||
@state() private _device?: ZWaveJSAddNodeDevice;
|
||||
|
||||
@@ -88,11 +86,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
|
||||
private _qrProcessing = false;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("beforeunload", this._onBeforeUnload);
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._unsubscribe();
|
||||
@@ -113,22 +106,14 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
// Prevent accidentally closing the dialog in certain stages
|
||||
const preventClose = this._shouldPreventClose();
|
||||
|
||||
const heading = this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.title"
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${preventClose
|
||||
? heading
|
||||
: createCloseHeading(this.hass, heading)}
|
||||
scrimClickAction=${ifDefined(preventClose ? "" : undefined)}
|
||||
escapeKeyAction=${ifDefined(preventClose ? "" : undefined)}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.panel.config.zwave_js.add_node.title")
|
||||
)}
|
||||
>
|
||||
${this._status === "loading"
|
||||
? html`<div style="display: flex; justify-content: center;">
|
||||
@@ -137,93 +122,81 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
</div>`
|
||||
: this._status === "waiting_for_device"
|
||||
? html`<div class="flex-container">
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.waiting_for_device"
|
||||
)}
|
||||
</p>
|
||||
</div>`
|
||||
: this._status === "choose_strategy"
|
||||
? html`<h3>Choose strategy</h3>
|
||||
<div class="flex-column">
|
||||
<ha-formfield
|
||||
.label=${html`<b>Secure if possible</b>
|
||||
<div class="secondary">
|
||||
Requires user interaction during inclusion. Fast and
|
||||
secure with S2 when supported. Fallback to legacy S0
|
||||
or no encryption when necessary.
|
||||
</div>`}
|
||||
>
|
||||
<ha-radio
|
||||
name="strategy"
|
||||
@change=${this._handleStrategyChange}
|
||||
.value=${InclusionStrategy.Default}
|
||||
.checked=${this._inclusionStrategy ===
|
||||
InclusionStrategy.Default ||
|
||||
this._inclusionStrategy === undefined}
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${html`<b>Legacy Secure</b>
|
||||
<div class="secondary">
|
||||
Uses the older S0 security that is secure, but slow
|
||||
due to a lot of overhead. Allows securely including S2
|
||||
capable devices which fail to be included with S2.
|
||||
</div>`}
|
||||
>
|
||||
<ha-radio
|
||||
name="strategy"
|
||||
@change=${this._handleStrategyChange}
|
||||
.value=${InclusionStrategy.Security_S0}
|
||||
.checked=${this._inclusionStrategy ===
|
||||
InclusionStrategy.Security_S0}
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${html`<b>Insecure</b>
|
||||
<div class="secondary">Do not use encryption.</div>`}
|
||||
>
|
||||
<ha-radio
|
||||
name="strategy"
|
||||
@change=${this._handleStrategyChange}
|
||||
.value=${InclusionStrategy.Insecure}
|
||||
.checked=${this._inclusionStrategy ===
|
||||
InclusionStrategy.Insecure}
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this._startManualInclusion}
|
||||
: this._status === "choose_strategy"
|
||||
? html`<h3>Choose strategy</h3>
|
||||
<div class="flex-column">
|
||||
<ha-formfield
|
||||
.label=${html`<b>Secure if possible</b>
|
||||
<div class="secondary">
|
||||
Requires user interaction during inclusion. Fast and
|
||||
secure with S2 when supported. Fallback to legacy S0 or
|
||||
no encryption when necessary.
|
||||
</div>`}
|
||||
>
|
||||
Search device
|
||||
</mwc-button>`
|
||||
: this._status === "qr_scan"
|
||||
? html`${this._error
|
||||
? html`<ha-alert alert-type="error"
|
||||
>${this._error}</ha-alert
|
||||
>`
|
||||
: ""}
|
||||
<ha-qr-scanner
|
||||
.localize=${this.hass.localize}
|
||||
@qr-code-scanned=${this._qrCodeScanned}
|
||||
></ha-qr-scanner>
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
@click=${this._startOver}
|
||||
<ha-radio
|
||||
name="strategy"
|
||||
@change=${this._handleStrategyChange}
|
||||
.value=${InclusionStrategy.Default}
|
||||
.checked=${this._inclusionStrategy ===
|
||||
InclusionStrategy.Default ||
|
||||
this._inclusionStrategy === undefined}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.common.back"
|
||||
)}
|
||||
</mwc-button>`
|
||||
: this._status === "validate_dsk_enter_pin"
|
||||
? html`
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${html`<b>Legacy Secure</b>
|
||||
<div class="secondary">
|
||||
Uses the older S0 security that is secure, but slow due
|
||||
to a lot of overhead. Allows securely including S2
|
||||
capable devices which fail to be included with S2.
|
||||
</div>`}
|
||||
>
|
||||
<ha-radio
|
||||
name="strategy"
|
||||
@change=${this._handleStrategyChange}
|
||||
.value=${InclusionStrategy.Security_S0}
|
||||
.checked=${this._inclusionStrategy ===
|
||||
InclusionStrategy.Security_S0}
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${html`<b>Insecure</b>
|
||||
<div class="secondary">Do not use encryption.</div>`}
|
||||
>
|
||||
<ha-radio
|
||||
name="strategy"
|
||||
@change=${this._handleStrategyChange}
|
||||
.value=${InclusionStrategy.Insecure}
|
||||
.checked=${this._inclusionStrategy ===
|
||||
InclusionStrategy.Insecure}
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this._startManualInclusion}
|
||||
>
|
||||
Search device
|
||||
</mwc-button>`
|
||||
: this._status === "qr_scan"
|
||||
? html`${this._error
|
||||
? html`<ha-alert alert-type="error"
|
||||
>${this._error}</ha-alert
|
||||
>`
|
||||
: ""}
|
||||
<ha-qr-scanner
|
||||
.localize=${this.hass.localize}
|
||||
@qr-code-scanned=${this._qrCodeScanned}
|
||||
></ha-qr-scanner>
|
||||
<mwc-button slot="secondaryAction" @click=${this._startOver}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.common.back"
|
||||
)}
|
||||
</mwc-button>`
|
||||
: this._status === "validate_dsk_enter_pin"
|
||||
? html`
|
||||
<p>
|
||||
Please enter the 5-digit PIN for your device and verify that
|
||||
the rest of the device-specific key matches the one that can
|
||||
@@ -252,160 +225,198 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: this._status === "grant_security_classes"
|
||||
? html`
|
||||
<h3>
|
||||
The device has requested the following security
|
||||
classes:
|
||||
</h3>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error"
|
||||
>${this._error}</ha-alert
|
||||
>`
|
||||
: ""}
|
||||
<div class="flex-column">
|
||||
${this._requestedGrant?.securityClasses
|
||||
.sort((a, b) => {
|
||||
// Put highest security classes at the top, S0 at the bottom
|
||||
if (a === SecurityClass.S0_Legacy) return 1;
|
||||
if (b === SecurityClass.S0_Legacy) return -1;
|
||||
return b - a;
|
||||
})
|
||||
.map(
|
||||
(securityClass) =>
|
||||
html`<ha-formfield
|
||||
.label=${html`<b
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${SecurityClass[securityClass]}.title`
|
||||
)}</b
|
||||
>
|
||||
<div class="secondary">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${SecurityClass[securityClass]}.description`
|
||||
)}
|
||||
</div>`}
|
||||
>
|
||||
<ha-checkbox
|
||||
@change=${this._handleSecurityClassChange}
|
||||
.value=${securityClass}
|
||||
.checked=${this._securityClasses.includes(
|
||||
securityClass
|
||||
)}
|
||||
: this._status === "grant_security_classes"
|
||||
? html`
|
||||
<h3>
|
||||
The device has requested the following security classes:
|
||||
</h3>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error"
|
||||
>${this._error}</ha-alert
|
||||
>`
|
||||
: ""}
|
||||
<div class="flex-column">
|
||||
${this._requestedGrant?.securityClasses
|
||||
.sort((a, b) => {
|
||||
// Put highest security classes at the top, S0 at the bottom
|
||||
if (a === SecurityClass.S0_Legacy) return 1;
|
||||
if (b === SecurityClass.S0_Legacy) return -1;
|
||||
return b - a;
|
||||
})
|
||||
.map(
|
||||
(securityClass) =>
|
||||
html`<ha-formfield
|
||||
.label=${html`<b
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${SecurityClass[securityClass]}.title`
|
||||
)}</b
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>`
|
||||
)}
|
||||
</div>
|
||||
<div class="secondary">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${SecurityClass[securityClass]}.description`
|
||||
)}
|
||||
</div>`}
|
||||
>
|
||||
<ha-checkbox
|
||||
@change=${this._handleSecurityClassChange}
|
||||
.value=${securityClass}
|
||||
.checked=${this._securityClasses.includes(
|
||||
securityClass
|
||||
)}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>`
|
||||
)}
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
.disabled=${!this._securityClasses.length}
|
||||
@click=${this._grantSecurityClasses}
|
||||
>
|
||||
Submit
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "timed_out"
|
||||
? html`
|
||||
<h3>Timed out!</h3>
|
||||
<p>
|
||||
We have not found any device in inclusion mode. Make
|
||||
sure the device is active and in inclusion mode.
|
||||
</p>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
.disabled=${!this._securityClasses.length}
|
||||
@click=${this._grantSecurityClasses}
|
||||
@click=${this._startOver}
|
||||
>
|
||||
Submit
|
||||
Retry
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "timed_out"
|
||||
? html`
|
||||
<h3>Timed out!</h3>
|
||||
: this._status === "started_specific"
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.searching_device"
|
||||
)}
|
||||
</h3>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
<p>
|
||||
We have not found any device in inclusion mode. Make
|
||||
sure the device is active and in inclusion mode.
|
||||
</p>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this._startOver}
|
||||
>
|
||||
Retry
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "started_specific"
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.searching_device"
|
||||
)}
|
||||
</h3>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.follow_device_instructions"
|
||||
)}
|
||||
</p>`
|
||||
: this._status === "started"
|
||||
? html`
|
||||
<div class="select-inclusion">
|
||||
<div class="outline">
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.follow_device_instructions"
|
||||
)}
|
||||
</p>`
|
||||
: this._status === "started"
|
||||
? html`
|
||||
<div class="select-inclusion">
|
||||
<div class="outline">
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.searching_device"
|
||||
)}
|
||||
</h2>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.follow_device_instructions"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<button
|
||||
class="link"
|
||||
@click=${this._chooseInclusionStrategy}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.searching_device"
|
||||
"ui.panel.config.zwave_js.add_node.choose_inclusion_strategy"
|
||||
)}
|
||||
</h2>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.follow_device_instructions"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<button
|
||||
class="link"
|
||||
@click=${this._chooseInclusionStrategy}
|
||||
>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
${this._supportsSmartStart
|
||||
? html` <div class="outline">
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.choose_inclusion_strategy"
|
||||
"ui.panel.config.zwave_js.add_node.qr_code"
|
||||
)}
|
||||
</button>
|
||||
</h2>
|
||||
<ha-svg-icon
|
||||
.path=${mdiQrcodeScan}
|
||||
></ha-svg-icon>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.qr_code_paragraph"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<mwc-button @click=${this._scanQRCode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.scan_qr_code"
|
||||
)}
|
||||
</mwc-button>
|
||||
</p>
|
||||
</div>`
|
||||
: ""}
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this.closeDialog}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "interviewing"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
<div class="status">
|
||||
<p>
|
||||
<b
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.interview_started"
|
||||
)}</b
|
||||
>
|
||||
</p>
|
||||
${this._stages
|
||||
? html` <div class="stages">
|
||||
${this._stages.map(
|
||||
(stage) => html`
|
||||
<span class="stage">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCheckCircle}
|
||||
class="success"
|
||||
></ha-svg-icon>
|
||||
${stage}
|
||||
</span>
|
||||
`
|
||||
)}
|
||||
</div>`
|
||||
: ""}
|
||||
</div>
|
||||
${this._supportsSmartStart
|
||||
? html` <div class="outline">
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.qr_code"
|
||||
)}
|
||||
</h2>
|
||||
<ha-svg-icon
|
||||
.path=${mdiQrcodeScan}
|
||||
></ha-svg-icon>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.qr_code_paragraph"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<mwc-button @click=${this._scanQRCode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.scan_qr_code"
|
||||
)}
|
||||
</mwc-button>
|
||||
</p>
|
||||
</div>`
|
||||
: ""}
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this.closeDialog}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "interviewing"
|
||||
: this._status === "failed"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
<div class="status">
|
||||
<p>
|
||||
<b
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.interview_started"
|
||||
)}</b
|
||||
>
|
||||
</p>
|
||||
<ha-alert
|
||||
alert-type="error"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.inclusion_failed"
|
||||
)}
|
||||
>
|
||||
${this._error ||
|
||||
this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.check_logs"
|
||||
)}
|
||||
</ha-alert>
|
||||
${this._stages
|
||||
? html` <div class="stages">
|
||||
${this._stages.map(
|
||||
@@ -430,21 +441,45 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "failed"
|
||||
: this._status === "finished"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${this._lowSecurity
|
||||
? mdiAlertCircle
|
||||
: mdiCheckCircle}
|
||||
class=${this._lowSecurity
|
||||
? "warning"
|
||||
: "success"}
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<ha-alert
|
||||
alert-type="error"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.inclusion_failed"
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.inclusion_finished"
|
||||
)}
|
||||
</p>
|
||||
${this._lowSecurity
|
||||
? html`<ha-alert
|
||||
alert-type="warning"
|
||||
title="The device was added insecurely"
|
||||
>
|
||||
There was an error during secure
|
||||
inclusion. You can try again by
|
||||
excluding the device and adding it
|
||||
again.
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
<a
|
||||
href=${`/config/devices/device/${
|
||||
this._device!.id
|
||||
}`}
|
||||
>
|
||||
${this._error ||
|
||||
this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.check_logs"
|
||||
)}
|
||||
</ha-alert>
|
||||
<mwc-button>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.view_device"
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
${this._stages
|
||||
? html` <div class="stages">
|
||||
${this._stages.map(
|
||||
@@ -469,60 +504,18 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "finished"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
: this._status === "provisioned"
|
||||
? html` <div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${this._lowSecurity
|
||||
? mdiAlertCircle
|
||||
: mdiCheckCircle}
|
||||
class=${this._lowSecurity
|
||||
? "warning"
|
||||
: "success"}
|
||||
.path=${mdiCheckCircle}
|
||||
class="success"
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.inclusion_finished"
|
||||
"ui.panel.config.zwave_js.add_node.provisioning_finished"
|
||||
)}
|
||||
</p>
|
||||
${this._lowSecurity
|
||||
? html`<ha-alert
|
||||
alert-type="warning"
|
||||
title="The device was added insecurely"
|
||||
>
|
||||
There was an error during secure
|
||||
inclusion. You can try again by
|
||||
excluding the device and adding it
|
||||
again.
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
<a
|
||||
href=${`/config/devices/device/${
|
||||
this._device?.id
|
||||
}`}
|
||||
>
|
||||
<mwc-button>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.view_device"
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
${this._stages
|
||||
? html` <div class="stages">
|
||||
${this._stages.map(
|
||||
(stage) => html`
|
||||
<span class="stage">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCheckCircle}
|
||||
class="success"
|
||||
></ha-svg-icon>
|
||||
${stage}
|
||||
</span>
|
||||
`
|
||||
)}
|
||||
</div>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button
|
||||
@@ -530,42 +523,12 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
@click=${this.closeDialog}
|
||||
>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: this._status === "provisioned"
|
||||
? html` <div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCheckCircle}
|
||||
class="success"
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.provisioning_finished"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this.closeDialog}
|
||||
>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _shouldPreventClose(): boolean {
|
||||
return (
|
||||
this._status === "started_specific" ||
|
||||
this._status === "validate_dsk_enter_pin" ||
|
||||
this._status === "grant_security_classes" ||
|
||||
this._status === "waiting_for_device"
|
||||
);
|
||||
}
|
||||
|
||||
private _chooseInclusionStrategy(): void {
|
||||
this._unsubscribe();
|
||||
this._status = "choose_strategy";
|
||||
@@ -676,7 +639,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
}
|
||||
|
||||
private async _validateDskAndEnterPin(): Promise<void> {
|
||||
this._status = "waiting_for_device";
|
||||
this._status = "loading";
|
||||
this._error = undefined;
|
||||
try {
|
||||
await zwaveValidateDskAndEnterPin(
|
||||
@@ -693,7 +656,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
}
|
||||
|
||||
private async _grantSecurityClasses(): Promise<void> {
|
||||
this._status = "waiting_for_device";
|
||||
this._status = "loading";
|
||||
this._error = undefined;
|
||||
try {
|
||||
await zwaveGrantSecurityClasses(
|
||||
@@ -756,12 +719,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
this._addNodeTimeoutHandle = undefined;
|
||||
}
|
||||
|
||||
if (message.event === "node found") {
|
||||
// The user may have to enter a PIN. Until then prevent accidentally
|
||||
// closing the dialog
|
||||
this._status = "waiting_for_device";
|
||||
}
|
||||
|
||||
if (message.event === "validate dsk and enter pin") {
|
||||
this._status = "validate_dsk_enter_pin";
|
||||
this._dsk = message.dsk;
|
||||
@@ -818,13 +775,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
}, 90000);
|
||||
}
|
||||
|
||||
private _onBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||
if (this._shouldPreventClose()) {
|
||||
event.preventDefault();
|
||||
}
|
||||
event.returnValue = true;
|
||||
};
|
||||
|
||||
private _unsubscribe(): void {
|
||||
if (this._subscribed) {
|
||||
this._subscribed.then((unsub) => unsub());
|
||||
@@ -841,7 +791,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
clearTimeout(this._addNodeTimeoutHandle);
|
||||
}
|
||||
this._addNodeTimeoutHandle = undefined;
|
||||
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
|
@@ -51,14 +51,6 @@ export class HaConfigLabels extends LitElement {
|
||||
|
||||
@state() private _labels: LabelRegistryEntry[] = [];
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "labels-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter = "";
|
||||
|
||||
@storage({
|
||||
key: "labels-table-sort",
|
||||
state: false,
|
||||
@@ -168,8 +160,6 @@ export class HaConfigLabels extends LitElement {
|
||||
hasFab
|
||||
.initialSorting=${this._activeSorting}
|
||||
@sorting-changed=${this._handleSortingChanged}
|
||||
.filter=${this._filter}
|
||||
@search-changed=${this._handleSearchChange}
|
||||
@row-click=${this._editLabel}
|
||||
clickable
|
||||
id="label_id"
|
||||
@@ -293,10 +283,6 @@ export class HaConfigLabels extends LitElement {
|
||||
private _handleSortingChanged(ev: CustomEvent) {
|
||||
this._activeSorting = ev.detail;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -2,21 +2,17 @@ import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||
import {
|
||||
mdiCheck,
|
||||
mdiCheckCircleOutline,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiPencil,
|
||||
mdiOpenInNew,
|
||||
mdiPlus,
|
||||
mdiStar,
|
||||
} from "@mdi/js";
|
||||
import { LitElement, PropertyValues, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoize from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { navigate } from "../../../../common/navigate";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
@@ -26,9 +22,6 @@ import "../../../../components/ha-clickable-list-item";
|
||||
import "../../../../components/ha-fab";
|
||||
import "../../../../components/ha-icon";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-menu";
|
||||
import type { HaMenu } from "../../../../components/ha-menu";
|
||||
import "../../../../components/ha-menu-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { LovelacePanelConfig } from "../../../../data/lovelace";
|
||||
import {
|
||||
@@ -48,11 +41,13 @@ import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-
|
||||
import "../../../../layouts/hass-loading-screen";
|
||||
import "../../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { HomeAssistant, Route } from "../../../../types";
|
||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import { getLovelaceStrategy } from "../../../lovelace/strategies/get-strategy";
|
||||
import { showNewDashboardDialog } from "../../dashboard/show-dialog-new-dashboard";
|
||||
import { lovelaceTabs } from "../ha-config-lovelace";
|
||||
import { showDashboardConfigureStrategyDialog } from "./show-dialog-lovelace-dashboard-configure-strategy";
|
||||
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
|
||||
type DataTableItem = Pick<
|
||||
LovelaceDashboard,
|
||||
@@ -75,14 +70,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
|
||||
@state() private _dashboards: LovelaceDashboard[] = [];
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "lovelace-dashboards-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter: string = "";
|
||||
|
||||
@storage({
|
||||
key: "lovelace-dashboards-table-sort",
|
||||
state: false,
|
||||
@@ -90,10 +77,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
})
|
||||
private _activeSorting?: SortingChangedEvent;
|
||||
|
||||
@state() private _overflowDashboard?: LovelaceDashboard;
|
||||
|
||||
@query("#overflow-menu") private _overflowMenu!: HaMenu;
|
||||
|
||||
public willUpdate() {
|
||||
if (!this.hasUpdated) {
|
||||
this.hass.loadFragmentTranslation("lovelace");
|
||||
@@ -219,36 +202,40 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
columns.actions = {
|
||||
columns.url_path = {
|
||||
title: "",
|
||||
width: "64px",
|
||||
type: "icon-button",
|
||||
template: (dashboard) => html`
|
||||
<ha-icon-button
|
||||
.dashboard=${dashboard}
|
||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
@click=${this._showOverflowMenu}
|
||||
></ha-icon-button>
|
||||
`,
|
||||
label: localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.headers.url"
|
||||
),
|
||||
filterable: true,
|
||||
width: "100px",
|
||||
template: (dashboard) =>
|
||||
narrow
|
||||
? html`
|
||||
<ha-icon-button
|
||||
.path=${mdiOpenInNew}
|
||||
.urlPath=${dashboard.url_path}
|
||||
@click=${this._navigate}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.open"
|
||||
)}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: html`
|
||||
<mwc-button
|
||||
.urlPath=${dashboard.url_path}
|
||||
@click=${this._navigate}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.open"
|
||||
)}</mwc-button
|
||||
>
|
||||
`,
|
||||
};
|
||||
|
||||
return columns;
|
||||
}
|
||||
);
|
||||
|
||||
private _showOverflowMenu = (ev) => {
|
||||
if (
|
||||
this._overflowMenu.open &&
|
||||
ev.target === this._overflowMenu.anchorElement
|
||||
) {
|
||||
this._overflowMenu.close();
|
||||
return;
|
||||
}
|
||||
this._overflowDashboard = ev.target.dashboard;
|
||||
this._overflowMenu.anchorElement = ev.target;
|
||||
this._overflowMenu.show();
|
||||
};
|
||||
|
||||
private _getItems = memoize((dashboards: LovelaceDashboard[]) => {
|
||||
const defaultMode = (
|
||||
this.hass.panels?.lovelace?.config as LovelacePanelConfig
|
||||
@@ -317,9 +304,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
.data=${this._getItems(this._dashboards)}
|
||||
.initialSorting=${this._activeSorting}
|
||||
@sorting-changed=${this._handleSortingChanged}
|
||||
.filter=${this._filter}
|
||||
@search-changed=${this._handleSearchChange}
|
||||
@row-click=${this._navigate}
|
||||
@row-click=${this._editDashboard}
|
||||
id="url_path"
|
||||
hasFab
|
||||
clickable
|
||||
@@ -351,22 +336,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<ha-menu id="overflow-menu" positioning="fixed">
|
||||
<ha-menu-item @click=${this._editDashboard}>
|
||||
<ha-svg-icon .path=${mdiPencil} slot="start"></ha-svg-icon>
|
||||
<div slot="headline">Edit</div>
|
||||
</ha-menu-item>
|
||||
|
||||
<ha-menu-item>
|
||||
<ha-svg-icon .path=${mdiStar} slot="start"></ha-svg-icon>
|
||||
<div slot="headline">Set to default</div>
|
||||
</ha-menu-item>
|
||||
<md-divider role="separator" tabindex="-1"></md-divider>
|
||||
<ha-menu-item class="warning">
|
||||
<ha-svg-icon .path=${mdiDelete} slot="start"></ha-svg-icon>
|
||||
<div slot="headline">Delete</div>
|
||||
</ha-menu-item>
|
||||
</ha-menu>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -379,23 +348,21 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
this._dashboards = await fetchDashboards(this.hass);
|
||||
}
|
||||
|
||||
private _navigate(ev: CustomEvent) {
|
||||
const urlPath = (ev.detail as RowClickedEvent).id;
|
||||
navigate(`/${urlPath}`);
|
||||
private _navigate(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
navigate(`/${(ev.target as any).urlPath}`);
|
||||
}
|
||||
|
||||
private _editDashboard = (ev) => {
|
||||
ev.stopPropagation();
|
||||
const dashboard = ev.currentTarget.parentElement.anchorElement.automation;
|
||||
|
||||
const urlPath = (ev.currentTarget as any).urlPath;
|
||||
private _editDashboard(ev: CustomEvent) {
|
||||
const urlPath = (ev.detail as RowClickedEvent).id;
|
||||
|
||||
if (urlPath === "energy") {
|
||||
navigate("/config/energy");
|
||||
return;
|
||||
}
|
||||
const dashboard = this._dashboards.find((res) => res.url_path === urlPath);
|
||||
this._openDetailDialog(dashboard, urlPath);
|
||||
};
|
||||
}
|
||||
|
||||
private async _addDashboard() {
|
||||
showNewDashboardDialog(this, {
|
||||
@@ -486,10 +453,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
private _handleSortingChanged(ev: CustomEvent) {
|
||||
this._activeSorting = ev.detail;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -52,14 +52,6 @@ export class HaConfigLovelaceRescources extends LitElement {
|
||||
|
||||
@state() private _resources: LovelaceResource[] = [];
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "lovelace-resources-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter = "";
|
||||
|
||||
@storage({
|
||||
key: "lovelace-resources-table-sort",
|
||||
state: false,
|
||||
@@ -146,8 +138,6 @@ export class HaConfigLovelaceRescources extends LitElement {
|
||||
)}
|
||||
.initialSorting=${this._activeSorting}
|
||||
@sorting-changed=${this._handleSortingChanged}
|
||||
.filter=${this._filter}
|
||||
@search-changed=${this._handleSearchChange}
|
||||
@row-click=${this._editResource}
|
||||
hasFab
|
||||
clickable
|
||||
@@ -262,10 +252,6 @@ export class HaConfigLovelaceRescources extends LitElement {
|
||||
this._activeSorting = ev.detail;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
|
@@ -8,7 +8,6 @@ import "../../../components/ha-formfield";
|
||||
import "../../../components/ha-picture-upload";
|
||||
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
|
||||
import "../../../components/ha-textfield";
|
||||
import { adminChangeUsername } from "../../../data/auth";
|
||||
import { PersonMutableParams } from "../../../data/person";
|
||||
import {
|
||||
deleteUser,
|
||||
@@ -20,11 +19,10 @@ import {
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
showPromptDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { CropOptions } from "../../../dialogs/image-cropper-dialog/show-image-cropper-dialog";
|
||||
import { ValueChangedEvent, HomeAssistant } from "../../../types";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import { HomeAssistant, ValueChangedEvent } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { showAddUserDialog } from "../users/show-dialog-add-user";
|
||||
import { showAdminChangePasswordDialog } from "../users/show-dialog-admin-change-password";
|
||||
@@ -138,9 +136,9 @@ class DialogPersonDetail extends LitElement {
|
||||
></ha-picture-upload>
|
||||
|
||||
<ha-formfield
|
||||
.label=${`${this.hass!.localize(
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.person.detail.allow_login"
|
||||
)}${this._user ? ` (${this._user.username})` : ""}`}
|
||||
)}
|
||||
>
|
||||
<ha-switch
|
||||
@change=${this._allowLoginChanged}
|
||||
@@ -246,21 +244,13 @@ class DialogPersonDetail extends LitElement {
|
||||
</mwc-button>
|
||||
${this._user && this.hass.user?.is_owner
|
||||
? html`<mwc-button
|
||||
slot="secondaryAction"
|
||||
@click=${this._changeUsername}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.editor.change_username"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
@click=${this._changePassword}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.editor.change_password"
|
||||
)}
|
||||
</mwc-button>`
|
||||
slot="secondaryAction"
|
||||
@click=${this._changePassword}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.editor.change_password"
|
||||
)}
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
`
|
||||
: nothing}
|
||||
@@ -302,14 +292,11 @@ class DialogPersonDetail extends LitElement {
|
||||
userAddedCallback: async (user?: User) => {
|
||||
if (user) {
|
||||
target.checked = true;
|
||||
if (this._params!.entry) {
|
||||
await this._params!.updateEntry({ user_id: user.id });
|
||||
}
|
||||
this._params?.refreshUsers();
|
||||
this._user = user;
|
||||
this._userId = user.id;
|
||||
this._isAdmin = user.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
|
||||
this._localOnly = user.local_only;
|
||||
this._params?.refreshUsers();
|
||||
}
|
||||
},
|
||||
name: this._name,
|
||||
@@ -333,9 +320,6 @@ class DialogPersonDetail extends LitElement {
|
||||
await deleteUser(this.hass, this._userId);
|
||||
this._params?.refreshUsers();
|
||||
this._userId = undefined;
|
||||
this._user = undefined;
|
||||
this._isAdmin = undefined;
|
||||
this._localOnly = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,53 +349,6 @@ class DialogPersonDetail extends LitElement {
|
||||
showAdminChangePasswordDialog(this, { userId: this._user.id });
|
||||
}
|
||||
|
||||
private async _changeUsername() {
|
||||
if (!this._user) {
|
||||
return;
|
||||
}
|
||||
const credential = this._user.credentials.find(
|
||||
(cred) => cred.type === "homeassistant"
|
||||
);
|
||||
if (!credential) {
|
||||
showAlertDialog(this, {
|
||||
title: "No Home Assistant credentials found.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const newUsername = await showPromptDialog(this, {
|
||||
inputLabel: this.hass.localize(
|
||||
"ui.panel.config.users.change_username.new_username"
|
||||
),
|
||||
confirmText: this.hass.localize(
|
||||
"ui.panel.config.users.change_username.change"
|
||||
),
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.users.change_username.caption"
|
||||
),
|
||||
defaultValue: this._user.username!,
|
||||
});
|
||||
if (newUsername) {
|
||||
try {
|
||||
await adminChangeUsername(this.hass, this._user.id, newUsername);
|
||||
this._params?.refreshUsers();
|
||||
this._user = { ...this._user, username: newUsername };
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.users.change_username.username_changed"
|
||||
),
|
||||
});
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.users.change_username.failed"
|
||||
),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateEntry() {
|
||||
this._submitting = true;
|
||||
try {
|
||||
|
@@ -31,6 +31,8 @@ class DialogIntegrationStartup extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
hideActions
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
|
@@ -143,6 +143,8 @@ class DialogSystemInformation extends LitElement {
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.panel.config.repairs.system_information")
|
||||
|
@@ -137,14 +137,6 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _filteredScenes?: string[] | null;
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "scene-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter = "";
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "scene-table-filters-full",
|
||||
@@ -551,8 +543,6 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
||||
"ui.panel.config.scene.picker.no_scenes"
|
||||
)}
|
||||
@clear-filter=${this._clearFilter}
|
||||
.filter=${this._filter}
|
||||
@search-changed=${this._handleSearchChange}
|
||||
hasFab
|
||||
clickable
|
||||
@row-click=${this._handleRowClicked}
|
||||
@@ -1151,10 +1141,6 @@ ${rejected
|
||||
this._activeCollapsed = ev.detail.value;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
|
@@ -141,14 +141,6 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _filteredScripts?: string[] | null;
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "script-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter = "";
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "script-table-filters-full",
|
||||
@@ -566,8 +558,6 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
||||
"ui.panel.config.script.picker.no_scripts"
|
||||
)}
|
||||
@clear-filter=${this._clearFilter}
|
||||
.filter=${this._filter}
|
||||
@search-changed=${this._handleSearchChange}
|
||||
hasFab
|
||||
clickable
|
||||
class=${this.narrow ? "narrow" : ""}
|
||||
@@ -810,10 +800,6 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
private _filterExpanded(ev) {
|
||||
if (ev.detail.expanded) {
|
||||
this._expandedFilter = ev.target.localName;
|
||||
|
@@ -35,7 +35,6 @@ import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showTagDetailDialog } from "./show-dialog-tag-detail";
|
||||
import "./tag-image";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
|
||||
export interface TagRowData extends Tag {
|
||||
display_name: string;
|
||||
@@ -58,14 +57,6 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
return this.hass.auth.external?.config.canWriteTag;
|
||||
}
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "tags-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter = "";
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean, _language, localize: LocalizeFunc) => {
|
||||
const columns: DataTableColumnContainer<TagRowData> = {
|
||||
@@ -198,8 +189,6 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
.data=${this._data(this._tags)}
|
||||
.noDataText=${this.hass.localize("ui.panel.config.tag.no_tags")}
|
||||
.filter=${this._filter}
|
||||
@search-changed=${this._handleSearchChange}
|
||||
hasFab
|
||||
>
|
||||
<ha-icon-button
|
||||
@@ -334,10 +323,6 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -10,16 +10,12 @@ import "../../../components/ha-label";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-textfield";
|
||||
import { adminChangeUsername } from "../../../data/auth";
|
||||
import {
|
||||
computeUserBadges,
|
||||
SYSTEM_GROUP_ID_ADMIN,
|
||||
SYSTEM_GROUP_ID_USER,
|
||||
} from "../../../data/user";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showPromptDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { showAdminChangePasswordDialog } from "./show-dialog-admin-change-password";
|
||||
@@ -176,15 +172,11 @@ class DialogUserDetail extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
${!user.system_generated && this.hass.user?.is_owner
|
||||
? html`<mwc-button @click=${this._changeUsername}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.editor.change_username"
|
||||
)} </mwc-button
|
||||
><mwc-button @click=${this._changePassword}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.editor.change_password"
|
||||
)}
|
||||
</mwc-button>`
|
||||
? html`<mwc-button @click=${this._changePassword}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.editor.change_password"
|
||||
)}
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
</div>
|
||||
|
||||
@@ -258,56 +250,6 @@ class DialogUserDetail extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _changeUsername() {
|
||||
const credential = this._params?.entry.credentials.find(
|
||||
(cred) => cred.type === "homeassistant"
|
||||
);
|
||||
if (!credential) {
|
||||
showAlertDialog(this, {
|
||||
title: "No Home Assistant credentials found.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const newUsername = await showPromptDialog(this, {
|
||||
inputLabel: this.hass.localize(
|
||||
"ui.panel.config.users.change_username.new_username"
|
||||
),
|
||||
confirmText: this.hass.localize(
|
||||
"ui.panel.config.users.change_username.change"
|
||||
),
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.users.change_username.caption"
|
||||
),
|
||||
defaultValue: this._params!.entry.username!,
|
||||
});
|
||||
if (newUsername) {
|
||||
try {
|
||||
await adminChangeUsername(
|
||||
this.hass,
|
||||
this._params!.entry.id,
|
||||
newUsername
|
||||
);
|
||||
this._params = {
|
||||
...this._params!,
|
||||
entry: { ...this._params!.entry, username: newUsername },
|
||||
};
|
||||
this._params.replaceEntry(this._params.entry);
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.users.change_username.username_changed"
|
||||
),
|
||||
});
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.users.change_username.failed"
|
||||
),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _changePassword() {
|
||||
const credential = this._params?.entry.credentials.find(
|
||||
(cred) => cred.type === "homeassistant"
|
||||
|
@@ -46,14 +46,6 @@ export class HaConfigUsers extends LitElement {
|
||||
@storage({ key: "users-table-grouping", state: false, subscribe: false })
|
||||
private _activeGrouping?: string;
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "users-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter = "";
|
||||
|
||||
@storage({
|
||||
key: "users-table-collapsed",
|
||||
state: false,
|
||||
@@ -182,7 +174,7 @@ export class HaConfigUsers extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
back-path="/config"
|
||||
backPath="/config"
|
||||
.tabs=${configSections.persons}
|
||||
.columns=${this._columns(this.narrow, this.hass.localize)}
|
||||
.data=${this._userData(this._users, this.hass.localize)}
|
||||
@@ -192,8 +184,6 @@ export class HaConfigUsers extends LitElement {
|
||||
@sorting-changed=${this._handleSortingChanged}
|
||||
@grouping-changed=${this._handleGroupingChanged}
|
||||
@collapsed-changed=${this._handleCollapseChanged}
|
||||
.filter=${this._filter}
|
||||
@search-changed=${this._handleSearchChange}
|
||||
@row-click=${this._editUser}
|
||||
hasFab
|
||||
clickable
|
||||
@@ -237,11 +227,6 @@ export class HaConfigUsers extends LitElement {
|
||||
|
||||
showUserDetailDialog(this, {
|
||||
entry,
|
||||
replaceEntry: (newEntry: User) => {
|
||||
this._users = this._users!.map((ent) =>
|
||||
ent.id === newEntry.id ? newEntry : ent
|
||||
);
|
||||
},
|
||||
updateEntry: async (values) => {
|
||||
const updated = await updateUser(this.hass!, entry!.id, values);
|
||||
this._users = this._users!.map((ent) =>
|
||||
@@ -298,10 +283,6 @@ export class HaConfigUsers extends LitElement {
|
||||
private _handleCollapseChanged(ev: CustomEvent) {
|
||||
this._activeCollapsed = ev.detail.value;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -4,7 +4,6 @@ import { UpdateUserParams, User } from "../../../data/user";
|
||||
export interface UserDetailDialogParams {
|
||||
entry: User;
|
||||
updateEntry: (updates: Partial<UpdateUserParams>) => Promise<unknown>;
|
||||
replaceEntry: (entry: User) => void;
|
||||
removeEntry: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
|
@@ -80,13 +80,7 @@ export class VoiceAssistantsExpose extends LitElement {
|
||||
|
||||
@state() private _extEntities?: Record<string, ExtEntityRegistryEntry>;
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "voice-expose-table-search",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _filter = "";
|
||||
@state() private _filter: string = history.state?.filter || "";
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
|
||||
@@ -640,6 +634,7 @@ export class VoiceAssistantsExpose extends LitElement {
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
history.replaceState({ filter: this._filter }, "");
|
||||
}
|
||||
|
||||
private _handleSelectionChanged(
|
||||
|
@@ -14,7 +14,7 @@ const SCHEMA = [
|
||||
{
|
||||
name: "location",
|
||||
required: true,
|
||||
selector: { location: { radius: true } },
|
||||
selector: { location: { radius: true, radius_readonly: true } },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -35,7 +35,6 @@ class DialogHomeZoneDetail extends LitElement {
|
||||
this._data = {
|
||||
latitude: this.hass.config.latitude,
|
||||
longitude: this.hass.config.longitude,
|
||||
radius: this.hass.config.radius,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -74,6 +73,11 @@ class DialogHomeZoneDetail extends LitElement {
|
||||
.computeLabel=${this._computeLabel}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
<p>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.no_edit_home_zone_radius"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@@ -91,7 +95,7 @@ class DialogHomeZoneDetail extends LitElement {
|
||||
location: {
|
||||
latitude: data.latitude,
|
||||
longitude: data.longitude,
|
||||
radius: data.radius || 100,
|
||||
radius: this.hass.states["zone.home"]?.attributes?.radius || 100,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -100,7 +104,6 @@ class DialogHomeZoneDetail extends LitElement {
|
||||
const value = { ...ev.detail.value };
|
||||
value.latitude = value.location.latitude;
|
||||
value.longitude = value.location.longitude;
|
||||
value.radius = value.location.radius;
|
||||
delete value.location;
|
||||
this._data = value;
|
||||
}
|
||||
|
@@ -101,8 +101,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
: zoneRadiusColor,
|
||||
location_editable:
|
||||
entityState.entity_id === "zone.home" && this._canEditCore,
|
||||
radius_editable:
|
||||
entityState.entity_id === "zone.home" && this._canEditCore,
|
||||
radius_editable: false,
|
||||
})
|
||||
);
|
||||
const storageLocations: MarkerLocation[] = storageItems.map((zone) => ({
|
||||
@@ -382,14 +381,8 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
private async _radiusUpdated(ev: CustomEvent) {
|
||||
private _radiusUpdated(ev: CustomEvent) {
|
||||
this._activeEntry = ev.detail.id;
|
||||
if (ev.detail.id === "zone.home" && this._canEditCore) {
|
||||
await saveCoreConfig(this.hass, {
|
||||
radius: Math.round(ev.detail.radius),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const entry = this._storageItems!.find((item) => item.id === ev.detail.id);
|
||||
if (!entry) {
|
||||
return;
|
||||
@@ -485,7 +478,6 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
await saveCoreConfig(this.hass, {
|
||||
latitude: values.latitude,
|
||||
longitude: values.longitude,
|
||||
radius: values.radius,
|
||||
});
|
||||
this._zoomZone("zone.home");
|
||||
}
|
||||
|
@@ -282,7 +282,6 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
|
||||
{ statistic_id: issue.data.statistic_id }
|
||||
)}`,
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
destructive: true,
|
||||
confirm: async () => {
|
||||
await clearStatistics(this.hass, [issue.data.statistic_id]);
|
||||
this._deletedStatistics.add(issue.data.statistic_id);
|
||||
@@ -315,7 +314,7 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
|
||||
});
|
||||
break;
|
||||
case "entity_no_longer_recorded":
|
||||
showConfirmationDialog(this, {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.statistics.fix_issue.entity_no_longer_recorded.title"
|
||||
),
|
||||
@@ -336,17 +335,7 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.statistics.fix_issue.entity_no_longer_recorded.info_text_3_link"
|
||||
)}</a
|
||||
><br /><br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.statistics.fix_issue.entity_no_longer_recorded.info_text_4"
|
||||
)}`,
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
destructive: true,
|
||||
confirm: async () => {
|
||||
await clearStatistics(this.hass, [issue.data.statistic_id]);
|
||||
this._deletedStatistics.add(issue.data.statistic_id);
|
||||
this._validateStatistics();
|
||||
},
|
||||
>`,
|
||||
});
|
||||
break;
|
||||
case "unsupported_state_class":
|
||||
@@ -392,7 +381,6 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
|
||||
{ statistic_id: issue.data.statistic_id }
|
||||
)}`,
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
destructive: true,
|
||||
confirm: async () => {
|
||||
await clearStatistics(this.hass, [issue.data.statistic_id]);
|
||||
this._deletedStatistics.add(issue.data.statistic_id);
|
||||
|
@@ -6,6 +6,7 @@ import {
|
||||
} from "@mdi/js";
|
||||
import { ActionDetail } from "@material/mwc-list";
|
||||
import { differenceInHours } from "date-fns";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
HassServiceTarget,
|
||||
UnsubscribeFunc,
|
||||
@@ -45,7 +46,11 @@ import {
|
||||
computeHistory,
|
||||
subscribeHistory,
|
||||
} from "../../data/history";
|
||||
import { Statistics, fetchStatistics } from "../../data/recorder";
|
||||
import {
|
||||
fetchStatistics,
|
||||
Statistics,
|
||||
getRecordedExcludedEntities,
|
||||
} from "../../data/recorder";
|
||||
import {
|
||||
expandAreaTarget,
|
||||
expandDeviceTarget,
|
||||
@@ -95,6 +100,8 @@ class HaPanelHistory extends LitElement {
|
||||
|
||||
private _interval?: number;
|
||||
|
||||
private _excludedEntities?: string[];
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
@@ -185,6 +192,7 @@ class HaPanelHistory extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${this._targetPickerValue}
|
||||
.disabled=${this._isLoading}
|
||||
.entityFilter=${this._entityFilter}
|
||||
addOnTop
|
||||
@value-changed=${this._targetsChanged}
|
||||
></ha-target-picker>
|
||||
@@ -211,6 +219,10 @@ class HaPanelHistory extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _entityFilter = (entity: HassEntity): boolean =>
|
||||
!this._excludedEntities ||
|
||||
!this._excludedEntities.includes(entity.entity_id);
|
||||
|
||||
private mergeHistoryResults(
|
||||
ltsResult: HistoryResult,
|
||||
historyResult: HistoryResult
|
||||
@@ -362,8 +374,17 @@ class HaPanelHistory extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _getRecordedExcludedEntities() {
|
||||
const { recorded_ids: _recordedIds, excluded_ids: excludedIds } =
|
||||
await getRecordedExcludedEntities(this.hass);
|
||||
this._excludedEntities = excludedIds;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
this._getRecordedExcludedEntities();
|
||||
|
||||
const searchParams = extractSearchParamsObject();
|
||||
if (searchParams.back === "1" && history.length > 1) {
|
||||
this._showBack = true;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
export const filterModes = <T extends string = string>(
|
||||
supportedModes: T[] | undefined,
|
||||
selectedModes: T[] | undefined
|
||||
): T[] =>
|
||||
export const filterModes = (
|
||||
supportedModes: string[] | undefined,
|
||||
selectedModes: string[] | undefined
|
||||
): string[] =>
|
||||
selectedModes
|
||||
? selectedModes.filter((mode) => (supportedModes || []).includes(mode))
|
||||
: supportedModes || [];
|
||||
|
@@ -6,6 +6,7 @@ import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-control-select";
|
||||
@@ -69,18 +70,37 @@ class HuiAlarmModeCardFeature
|
||||
}
|
||||
}
|
||||
|
||||
private _getCurrentMode = memoizeOne((stateObj: AlarmControlPanelEntity) => {
|
||||
const supportedModes = supportedAlarmModes(stateObj);
|
||||
return supportedModes.find((mode) => mode === stateObj.state);
|
||||
});
|
||||
private _modes = memoizeOne(
|
||||
(
|
||||
stateObj: AlarmControlPanelEntity,
|
||||
selectedModes: AlarmMode[] | undefined
|
||||
) => {
|
||||
if (!selectedModes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (Object.keys(ALARM_MODES) as AlarmMode[]).filter((mode) => {
|
||||
const feature = ALARM_MODES[mode].feature;
|
||||
return (
|
||||
(!feature || supportsFeature(stateObj, feature)) &&
|
||||
selectedModes.includes(mode)
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
private _getCurrentMode(stateObj: AlarmControlPanelEntity) {
|
||||
return this._modes(stateObj, this._config?.modes).find(
|
||||
(mode) => mode === stateObj.state
|
||||
);
|
||||
}
|
||||
|
||||
private async _valueChanged(ev: CustomEvent) {
|
||||
if (!this.stateObj) return;
|
||||
const mode = (ev.detail as any).value as AlarmMode;
|
||||
|
||||
if (mode === this.stateObj.state) return;
|
||||
if (mode === this.stateObj!.state) return;
|
||||
|
||||
const oldMode = this._getCurrentMode(this.stateObj);
|
||||
const oldMode = this._getCurrentMode(this.stateObj!);
|
||||
this._currentMode = mode;
|
||||
|
||||
try {
|
||||
@@ -133,7 +153,6 @@ class HuiAlarmModeCardFeature
|
||||
</ha-control-button-group>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="container">
|
||||
<ha-control-select
|
||||
|
@@ -24,7 +24,7 @@ import { calculateStatisticsSumGrowth } from "../../../../data/recorder";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { LovelaceCard } from "../../types";
|
||||
import type { EnergyGridNeutralityGaugeCardConfig } from "../types";
|
||||
import type { EnergyGridGaugeCardConfig } from "../types";
|
||||
import { hasConfigChanged } from "../../common/has-changed";
|
||||
|
||||
const LEVELS: LevelDefinition[] = [
|
||||
@@ -39,7 +39,7 @@ class HuiEnergyGridGaugeCard
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: EnergyGridNeutralityGaugeCardConfig;
|
||||
@state() private _config?: EnergyGridGaugeCardConfig;
|
||||
|
||||
@state() private _data?: EnergyData;
|
||||
|
||||
@@ -59,7 +59,7 @@ class HuiEnergyGridGaugeCard
|
||||
return 4;
|
||||
}
|
||||
|
||||
public setConfig(config: EnergyGridNeutralityGaugeCardConfig): void {
|
||||
public setConfig(config: EnergyGridGaugeCardConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { PropertyValueMap, PropertyValues, ReactiveElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { PropertyValues, ReactiveElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { MediaQueriesListener } from "../../../common/dom/media_query";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
@@ -11,41 +10,15 @@ import {
|
||||
checkConditionsMet,
|
||||
} from "../common/validate-condition";
|
||||
import { createCardElement } from "../create-element/create-card-element";
|
||||
import { createErrorCardConfig } from "../create-element/create-element-base";
|
||||
import type { LovelaceCard, LovelaceLayoutOptions } from "../types";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"card-visibility-changed": { value: boolean };
|
||||
"card-updated": undefined;
|
||||
}
|
||||
}
|
||||
import type { Lovelace, LovelaceCard, LovelaceLayoutOptions } from "../types";
|
||||
|
||||
@customElement("hui-card")
|
||||
export class HuiCard extends ReactiveElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public preview = false;
|
||||
@property({ attribute: false }) public lovelace!: Lovelace;
|
||||
|
||||
@property({ type: Boolean }) public isPanel = false;
|
||||
|
||||
set config(config: LovelaceCardConfig | undefined) {
|
||||
if (!config) return;
|
||||
if (config.type !== this._config?.type) {
|
||||
this._buildElement(config);
|
||||
} else if (config !== this.config) {
|
||||
this._element?.setConfig(config);
|
||||
fireEvent(this, "card-updated");
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
public get config() {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
private _config?: LovelaceCardConfig;
|
||||
@state() public _config?: LovelaceCardConfig;
|
||||
|
||||
private _element?: LovelaceCard;
|
||||
|
||||
@@ -63,7 +36,7 @@ export class HuiCard extends ReactiveElement {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._listenMediaQueries();
|
||||
this._updateVisibility();
|
||||
this._updateElement();
|
||||
}
|
||||
|
||||
public getCardSize(): number | Promise<number> {
|
||||
@@ -75,7 +48,7 @@ export class HuiCard extends ReactiveElement {
|
||||
}
|
||||
|
||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
||||
const configOptions = this.config?.layout_options ?? {};
|
||||
const configOptions = this._config?.layout_options ?? {};
|
||||
if (this._element) {
|
||||
const cardOptions = this._element.getLayoutOptions?.() ?? {};
|
||||
return {
|
||||
@@ -86,84 +59,37 @@ export class HuiCard extends ReactiveElement {
|
||||
return configOptions;
|
||||
}
|
||||
|
||||
public getElementLayoutOptions(): LovelaceLayoutOptions {
|
||||
return this._element?.getLayoutOptions?.() ?? {};
|
||||
}
|
||||
|
||||
private _createElement(config: LovelaceCardConfig) {
|
||||
const element = createCardElement(config);
|
||||
element.hass = this.hass;
|
||||
element.preview = this.preview;
|
||||
// For backwards compatibility
|
||||
(element as any).editMode = this.preview;
|
||||
// Update element when the visibility of the card changes (e.g. conditional card or filter card)
|
||||
element.addEventListener("card-visibility-changed", (ev: Event) => {
|
||||
ev.stopPropagation();
|
||||
this._updateVisibility();
|
||||
});
|
||||
element.addEventListener(
|
||||
"ll-upgrade",
|
||||
(ev: Event) => {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "card-updated");
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
element.addEventListener(
|
||||
"ll-rebuild",
|
||||
(ev: Event) => {
|
||||
ev.stopPropagation();
|
||||
this._buildElement(config);
|
||||
fireEvent(this, "card-updated");
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
return element;
|
||||
}
|
||||
|
||||
private _buildElement(config: LovelaceCardConfig) {
|
||||
this._element = this._createElement(config);
|
||||
public setConfig(config: LovelaceCardConfig): void {
|
||||
if (this._config === config) {
|
||||
return;
|
||||
}
|
||||
this._config = config;
|
||||
this._element = createCardElement(config);
|
||||
this._element.hass = this.hass;
|
||||
this._element.editMode = this.lovelace.editMode;
|
||||
|
||||
while (this.lastChild) {
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
this._updateVisibility();
|
||||
this.appendChild(this._element!);
|
||||
}
|
||||
|
||||
protected update(changedProps: PropertyValues<typeof this>) {
|
||||
super.update(changedProps);
|
||||
protected update(changedProperties: PropertyValues<typeof this>) {
|
||||
super.update(changedProperties);
|
||||
|
||||
if (this._element) {
|
||||
if (changedProps.has("hass")) {
|
||||
try {
|
||||
this._element.hass = this.hass;
|
||||
} catch (e: any) {
|
||||
this._buildElement(createErrorCardConfig(e.message, null));
|
||||
}
|
||||
if (changedProperties.has("hass")) {
|
||||
this._element.hass = this.hass;
|
||||
}
|
||||
if (changedProps.has("preview")) {
|
||||
try {
|
||||
this._element.preview = this.preview;
|
||||
// For backwards compatibility
|
||||
(this._element as any).editMode = this.preview;
|
||||
} catch (e: any) {
|
||||
this._buildElement(createErrorCardConfig(e.message, null));
|
||||
}
|
||||
if (changedProperties.has("lovelace")) {
|
||||
this._element.editMode = this.lovelace.editMode;
|
||||
}
|
||||
if (changedProps.has("isPanel")) {
|
||||
this._element.isPanel = this.isPanel;
|
||||
if (changedProperties.has("hass") || changedProperties.has("lovelace")) {
|
||||
this._updateElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected willUpdate(
|
||||
changedProps: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
||||
): void {
|
||||
if (changedProps.has("hass") || changedProps.has("preview")) {
|
||||
this._updateVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private _clearMediaQueries() {
|
||||
this._listeners.forEach((unsub) => unsub());
|
||||
this._listeners = [];
|
||||
@@ -171,50 +97,35 @@ export class HuiCard extends ReactiveElement {
|
||||
|
||||
private _listenMediaQueries() {
|
||||
this._clearMediaQueries();
|
||||
if (!this.config?.visibility) {
|
||||
if (!this._config?.visibility) {
|
||||
return;
|
||||
}
|
||||
const conditions = this.config.visibility;
|
||||
const conditions = this._config.visibility;
|
||||
const hasOnlyMediaQuery =
|
||||
conditions.length === 1 &&
|
||||
conditions[0].condition === "screen" &&
|
||||
!!conditions[0].media_query;
|
||||
|
||||
this._listeners = attachConditionMediaQueriesListeners(
|
||||
this.config.visibility,
|
||||
this._config.visibility,
|
||||
(matches) => {
|
||||
this._updateVisibility(hasOnlyMediaQuery && matches);
|
||||
this._updateElement(hasOnlyMediaQuery && matches);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _updateVisibility(forceVisible?: boolean) {
|
||||
if (!this._element || !this.hass) {
|
||||
private _updateElement(forceVisible?: boolean) {
|
||||
if (!this._element) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._element.hidden) {
|
||||
this._setElementVisibility(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const visible =
|
||||
forceVisible ||
|
||||
this.preview ||
|
||||
!this.config?.visibility ||
|
||||
checkConditionsMet(this.config.visibility, this.hass);
|
||||
this._setElementVisibility(visible);
|
||||
}
|
||||
|
||||
private _setElementVisibility(visible: boolean) {
|
||||
if (!this._element) return;
|
||||
|
||||
if (this.hidden !== !visible) {
|
||||
this.style.setProperty("display", visible ? "" : "none");
|
||||
this.toggleAttribute("hidden", !visible);
|
||||
fireEvent(this, "card-visibility-changed", { value: visible });
|
||||
}
|
||||
this.lovelace.editMode ||
|
||||
!this._config?.visibility ||
|
||||
checkConditionsMet(this._config.visibility, this.hass);
|
||||
|
||||
this.style.setProperty("display", visible ? "" : "none");
|
||||
this.toggleAttribute("hidden", !visible);
|
||||
if (!visible && this._element.parentElement) {
|
||||
this.removeChild(this._element);
|
||||
} else if (visible && !this._element.parentElement) {
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { customElement } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import { computeCardSize } from "../common/compute-card-size";
|
||||
import { HuiConditionalBase } from "../components/hui-conditional-base";
|
||||
import { createCardElement } from "../create-element/create-card-element";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { ConditionalCardConfig } from "./types";
|
||||
|
||||
@@ -37,19 +37,25 @@ class HuiConditionalCard extends HuiConditionalBase implements LovelaceCard {
|
||||
}
|
||||
|
||||
private _createCardElement(cardConfig: LovelaceCardConfig) {
|
||||
const element = document.createElement("hui-card");
|
||||
element.hass = this.hass;
|
||||
element.preview = this.preview;
|
||||
element.config = cardConfig;
|
||||
const element = createCardElement(cardConfig) as LovelaceCard;
|
||||
if (this.hass) {
|
||||
element.hass = this.hass;
|
||||
}
|
||||
element.addEventListener(
|
||||
"ll-rebuild",
|
||||
(ev) => {
|
||||
ev.stopPropagation();
|
||||
this._rebuildCard(cardConfig);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
return element;
|
||||
}
|
||||
|
||||
protected setVisibility(conditionMet: boolean): void {
|
||||
const visible = this.preview || conditionMet;
|
||||
const previouslyHidden = this.hidden;
|
||||
super.setVisibility(conditionMet);
|
||||
if (previouslyHidden !== this.hidden) {
|
||||
fireEvent(this, "card-visibility-changed", { value: visible });
|
||||
private _rebuildCard(config: LovelaceCardConfig): void {
|
||||
this._element = this._createCardElement(config);
|
||||
if (this.lastChild) {
|
||||
this.replaceChild(this._element, this.lastChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { PropertyValues, ReactiveElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { computeCardSize } from "../common/compute-card-size";
|
||||
@@ -12,9 +11,9 @@ import {
|
||||
checkConditionsMet,
|
||||
extractConditionEntityIds,
|
||||
} from "../common/validate-condition";
|
||||
import { createCardElement } from "../create-element/create-card-element";
|
||||
import { EntityFilterEntityConfig } from "../entity-rows/types";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { HuiCard } from "./hui-card";
|
||||
import { EntityFilterCardConfig } from "./types";
|
||||
|
||||
@customElement("hui-entity-filter-card")
|
||||
@@ -55,11 +54,11 @@ export class HuiEntityFilterCard
|
||||
|
||||
@property({ type: Boolean }) public isPanel = false;
|
||||
|
||||
@property({ type: Boolean }) public preview = false;
|
||||
@property({ type: Boolean }) public editMode = false;
|
||||
|
||||
@state() private _config?: EntityFilterCardConfig;
|
||||
|
||||
private _element?: HuiCard;
|
||||
private _element?: LovelaceCard;
|
||||
|
||||
private _configEntities?: EntityFilterEntityConfig[];
|
||||
|
||||
@@ -117,7 +116,7 @@ export class HuiEntityFilterCard
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
if (this._element) {
|
||||
this._element.hass = this.hass;
|
||||
this._element.preview = this.preview;
|
||||
this._element.editMode = this.editMode;
|
||||
this._element.isPanel = this.isPanel;
|
||||
}
|
||||
|
||||
@@ -164,21 +163,18 @@ export class HuiEntityFilterCard
|
||||
});
|
||||
|
||||
if (entitiesList.length === 0 && this._config.show_empty === false) {
|
||||
if (!this.hidden) {
|
||||
this.style.display = "none";
|
||||
this.toggleAttribute("hidden", true);
|
||||
fireEvent(this, "card-visibility-changed", { value: false });
|
||||
}
|
||||
this.style.display = "none";
|
||||
this.toggleAttribute("hidden", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.lastChild) {
|
||||
this._element.config = {
|
||||
this._element.setConfig({
|
||||
...this._baseCardConfig!,
|
||||
entities: entitiesList,
|
||||
};
|
||||
});
|
||||
this._oldEntities = entitiesList;
|
||||
} else {
|
||||
} else if (this._element.tagName !== "HUI-ERROR-CARD") {
|
||||
const isSame =
|
||||
this._oldEntities &&
|
||||
entitiesList.length === this._oldEntities.length &&
|
||||
@@ -186,10 +182,10 @@ export class HuiEntityFilterCard
|
||||
|
||||
if (!isSame) {
|
||||
this._oldEntities = entitiesList;
|
||||
this._element.config = {
|
||||
this._element.setConfig({
|
||||
...this._baseCardConfig!,
|
||||
entities: entitiesList,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,11 +194,8 @@ export class HuiEntityFilterCard
|
||||
this.appendChild(this._element);
|
||||
}
|
||||
|
||||
if (this.hidden) {
|
||||
this.style.display = "block";
|
||||
this.toggleAttribute("hidden", false);
|
||||
fireEvent(this, "card-visibility-changed", { value: true });
|
||||
}
|
||||
this.style.display = "block";
|
||||
this.toggleAttribute("hidden", false);
|
||||
}
|
||||
|
||||
private _haveEntitiesChanged(oldHass: HomeAssistant | null): boolean {
|
||||
@@ -245,12 +238,33 @@ export class HuiEntityFilterCard
|
||||
}
|
||||
|
||||
private _createCardElement(cardConfig: LovelaceCardConfig) {
|
||||
const element = document.createElement("hui-card");
|
||||
element.hass = this.hass;
|
||||
element.preview = this.preview;
|
||||
element.config = cardConfig;
|
||||
const element = createCardElement(cardConfig) as LovelaceCard;
|
||||
if (this.hass) {
|
||||
element.hass = this.hass;
|
||||
}
|
||||
element.isPanel = this.isPanel;
|
||||
element.editMode = this.editMode;
|
||||
element.addEventListener(
|
||||
"ll-rebuild",
|
||||
(ev) => {
|
||||
ev.stopPropagation();
|
||||
this._rebuildCard(element, cardConfig);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
return element;
|
||||
}
|
||||
|
||||
private _rebuildCard(
|
||||
cardElToReplace: LovelaceCard,
|
||||
config: LovelaceCardConfig
|
||||
): void {
|
||||
const newCardEl = this._createCardElement(config);
|
||||
if (cardElToReplace.parentElement) {
|
||||
cardElToReplace.parentElement!.replaceChild(newCardEl, cardElToReplace);
|
||||
}
|
||||
this._element = newCardEl;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { dump } from "js-yaml";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import "../../../components/ha-alert";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCard } from "../types";
|
||||
@@ -10,8 +10,6 @@ import { ErrorCardConfig } from "./types";
|
||||
export class HuiErrorCard extends LitElement implements LovelaceCard {
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public preview = false;
|
||||
|
||||
@state() private _config?: ErrorCardConfig;
|
||||
|
||||
public getCardSize(): number {
|
||||
|
@@ -38,7 +38,7 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public preview = false;
|
||||
@property({ type: Boolean }) public editMode = false;
|
||||
|
||||
@state() private _config?: MarkdownCardConfig;
|
||||
|
||||
@@ -163,12 +163,12 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
user: this.hass.user!.name,
|
||||
},
|
||||
strict: true,
|
||||
report_errors: this.preview,
|
||||
report_errors: this.editMode,
|
||||
}
|
||||
);
|
||||
await this._unsubRenderTemplate;
|
||||
} catch (e: any) {
|
||||
if (this.preview) {
|
||||
if (this.editMode) {
|
||||
this._error = e.message;
|
||||
this._errorLevel = undefined;
|
||||
}
|
||||
|
@@ -1,12 +1,19 @@
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { createCardElement } from "../create-element/create-card-element";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import "./hui-card";
|
||||
import type { HuiCard } from "./hui-card";
|
||||
import { StackCardConfig } from "./types";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
|
||||
export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
|
||||
extends LitElement
|
||||
@@ -23,9 +30,9 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public preview = false;
|
||||
@property({ type: Boolean }) public editMode = false;
|
||||
|
||||
@state() protected _cards?: HuiCard[];
|
||||
@state() protected _cards?: LovelaceCard[];
|
||||
|
||||
@state() protected _config?: T;
|
||||
|
||||
@@ -42,36 +49,30 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
|
||||
}
|
||||
this._config = config;
|
||||
this._cards = config.cards.map((card) => {
|
||||
const element = this._createCardElement(card);
|
||||
const element = this._createCardElement(card) as LovelaceCard;
|
||||
return element;
|
||||
});
|
||||
}
|
||||
|
||||
protected update(changedProperties) {
|
||||
super.update(changedProperties);
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (
|
||||
!this._cards ||
|
||||
(!changedProps.has("hass") && !changedProps.has("editMode"))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._cards) {
|
||||
if (changedProperties.has("hass")) {
|
||||
this._cards.forEach((card) => {
|
||||
card.hass = this.hass;
|
||||
});
|
||||
for (const element of this._cards) {
|
||||
if (this.hass) {
|
||||
element.hass = this.hass;
|
||||
}
|
||||
if (changedProperties.has("editMode")) {
|
||||
this._cards.forEach((card) => {
|
||||
card.preview = this.preview;
|
||||
});
|
||||
if (this.editMode !== undefined) {
|
||||
element.editMode = this.editMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _createCardElement(cardConfig: LovelaceCardConfig) {
|
||||
const element = document.createElement("hui-card");
|
||||
element.hass = this.hass;
|
||||
element.preview = this.preview;
|
||||
element.config = cardConfig;
|
||||
return element;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._config || !this._cards) {
|
||||
return nothing;
|
||||
@@ -109,4 +110,34 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _createCardElement(cardConfig: LovelaceCardConfig) {
|
||||
const element = createCardElement(cardConfig) as LovelaceCard;
|
||||
if (this.hass) {
|
||||
element.hass = this.hass;
|
||||
}
|
||||
element.addEventListener(
|
||||
"ll-rebuild",
|
||||
(ev) => {
|
||||
ev.stopPropagation();
|
||||
this._rebuildCard(element, cardConfig);
|
||||
fireEvent(this, "ll-rebuild");
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
return element;
|
||||
}
|
||||
|
||||
private _rebuildCard(
|
||||
cardElToReplace: LovelaceCard,
|
||||
config: LovelaceCardConfig
|
||||
): void {
|
||||
const newCardEl = this._createCardElement(config);
|
||||
if (cardElToReplace.parentElement) {
|
||||
cardElToReplace.parentElement.replaceChild(newCardEl, cardElToReplace);
|
||||
}
|
||||
this._cards = this._cards!.map((curCardEl) =>
|
||||
curCardEl === cardElToReplace ? newCardEl : curCardEl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,6 @@ import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-check-list-item";
|
||||
import "../../../components/ha-checkbox";
|
||||
@@ -105,7 +104,6 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._unsubItems?.then((unsub) => unsub());
|
||||
this._unsubItems = undefined;
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
@@ -244,7 +242,7 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
${this.todoListSupportsFeature(
|
||||
TodoListEntityFeature.MOVE_TODO_ITEM
|
||||
)
|
||||
? html`<ha-button-menu @closed=${stopPropagation}>
|
||||
? html`<ha-button-menu>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.path=${mdiDotsVertical}
|
||||
@@ -288,7 +286,7 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
${this.todoListSupportsFeature(
|
||||
TodoListEntityFeature.DELETE_TODO_ITEM
|
||||
)
|
||||
? html`<ha-button-menu @closed=${stopPropagation}>
|
||||
? html`<ha-button-menu>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.path=${mdiDotsVertical}
|
||||
|
@@ -118,74 +118,85 @@ export interface EnergyCardBaseConfig extends LovelaceCardConfig {
|
||||
collection_key?: string;
|
||||
}
|
||||
|
||||
export interface EnergySummaryCardConfig extends EnergyCardBaseConfig {
|
||||
export interface EnergySummaryCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-summary";
|
||||
title?: string;
|
||||
collection_key?: string;
|
||||
}
|
||||
|
||||
export interface EnergyDistributionCardConfig extends EnergyCardBaseConfig {
|
||||
export interface EnergyDistributionCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-distribution";
|
||||
title?: string;
|
||||
link_dashboard?: boolean;
|
||||
collection_key?: string;
|
||||
}
|
||||
export interface EnergyUsageGraphCardConfig extends EnergyCardBaseConfig {
|
||||
export interface EnergyUsageGraphCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-usage-graph";
|
||||
title?: string;
|
||||
collection_key?: string;
|
||||
}
|
||||
|
||||
export interface EnergySolarGraphCardConfig extends EnergyCardBaseConfig {
|
||||
export interface EnergySolarGraphCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-solar-graph";
|
||||
title?: string;
|
||||
collection_key?: string;
|
||||
}
|
||||
|
||||
export interface EnergyGasGraphCardConfig extends EnergyCardBaseConfig {
|
||||
export interface EnergyGasGraphCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-gas-graph";
|
||||
title?: string;
|
||||
collection_key?: string;
|
||||
}
|
||||
|
||||
export interface EnergyWaterGraphCardConfig extends EnergyCardBaseConfig {
|
||||
export interface EnergyWaterGraphCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-water-graph";
|
||||
title?: string;
|
||||
collection_key?: string;
|
||||
}
|
||||
|
||||
export interface EnergyDevicesGraphCardConfig extends EnergyCardBaseConfig {
|
||||
export interface EnergyDevicesGraphCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-devices-graph";
|
||||
title?: string;
|
||||
collection_key?: string;
|
||||
max_devices?: number;
|
||||
}
|
||||
|
||||
export interface EnergyDevicesDetailGraphCardConfig
|
||||
extends EnergyCardBaseConfig {
|
||||
export interface EnergyDevicesDetailGraphCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-devices-detail-graph";
|
||||
title?: string;
|
||||
collection_key?: string;
|
||||
max_devices?: number;
|
||||
}
|
||||
|
||||
export interface EnergySourcesTableCardConfig extends EnergyCardBaseConfig {
|
||||
export interface EnergySourcesTableCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-sources-table";
|
||||
title?: string;
|
||||
collection_key?: string;
|
||||
}
|
||||
|
||||
export interface EnergySolarGaugeCardConfig extends EnergyCardBaseConfig {
|
||||
export interface EnergySolarGaugeCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-solar-consumed-gauge";
|
||||
title?: string;
|
||||
collection_key?: string;
|
||||
}
|
||||
|
||||
export interface EnergySelfSufficiencyGaugeCardConfig
|
||||
extends EnergyCardBaseConfig {
|
||||
extends LovelaceCardConfig {
|
||||
type: "energy-self-sufficiency-gauge";
|
||||
title?: string;
|
||||
collection_key?: string;
|
||||
}
|
||||
|
||||
export interface EnergyGridNeutralityGaugeCardConfig
|
||||
extends EnergyCardBaseConfig {
|
||||
type: "energy-grid-neutrality-gauge";
|
||||
export interface EnergyGridGaugeCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-grid-result-gauge";
|
||||
title?: string;
|
||||
collection_key?: string;
|
||||
}
|
||||
|
||||
export interface EnergyCarbonGaugeCardConfig extends EnergyCardBaseConfig {
|
||||
export interface EnergyCarbonGaugeCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-carbon-consumed-gauge";
|
||||
title?: string;
|
||||
collection_key?: string;
|
||||
}
|
||||
|
||||
export interface EntityFilterCardConfig extends LovelaceCardConfig {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { HassEntities, HassEntity } from "home-assistant-js-websocket";
|
||||
import { SENSOR_ENTITIES, ASSIST_ENTITIES } from "../../../common/const";
|
||||
import { SENSOR_ENTITIES } from "../../../common/const";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
@@ -35,16 +35,17 @@ import { ButtonsHeaderFooterConfig } from "../header-footer/types";
|
||||
const HIDE_DOMAIN = new Set([
|
||||
"automation",
|
||||
"configurator",
|
||||
"conversation",
|
||||
"device_tracker",
|
||||
"event",
|
||||
"geo_location",
|
||||
"notify",
|
||||
"persistent_notification",
|
||||
"script",
|
||||
"sun",
|
||||
"todo",
|
||||
"zone",
|
||||
...ASSIST_ENTITIES,
|
||||
"event",
|
||||
"tts",
|
||||
"stt",
|
||||
"todo",
|
||||
]);
|
||||
|
||||
const HIDE_PLATFORM = new Set(["mobile_app"]);
|
||||
|
@@ -353,7 +353,7 @@ export class HuiCardOptions extends LitElement {
|
||||
allowDashboardChange: true,
|
||||
header: this.hass!.localize("ui.panel.lovelace.editor.move_card.header"),
|
||||
viewSelectedCallback: async (urlPath, selectedDashConfig, viewIndex) => {
|
||||
const view = selectedDashConfig.views[viewIndex];
|
||||
const view = this.lovelace!.config.views[viewIndex];
|
||||
|
||||
if (!isStrategyView(view) && view.type === SECTION_VIEW_LAYOUT) {
|
||||
showAlertDialog(this, {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user