mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 07:16:39 +00:00
commit
f6d6fd179f
8
.github/dependabot.yml
vendored
Normal file
8
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
time: "06:00"
|
||||
open-pull-requests-limit: 10
|
16
.github/workflows/ci.yaml
vendored
16
.github/workflows/ci.yaml
vendored
@ -19,9 +19,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
@ -43,9 +43,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
@ -62,9 +62,9 @@ jobs:
|
||||
needs: [lint, test]
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
@ -81,9 +81,9 @@ jobs:
|
||||
needs: [lint, test]
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
@ -36,14 +36,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@ -57,4 +57,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
4
.github/workflows/demo.yaml
vendored
4
.github/workflows/demo.yaml
vendored
@ -14,9 +14,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2.0.1
|
||||
- uses: dessant/lock-threads@v3.0.0
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: "30"
|
||||
|
63
.github/workflows/nightly.yaml
vendored
Normal file
63
.github/workflows/nightly.yaml
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
name: Nightly
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: 3.8
|
||||
NODE_VERSION: 14
|
||||
NODE_OPTIONS: --max_old_space_size=6144
|
||||
|
||||
permissions:
|
||||
actions: none
|
||||
|
||||
jobs:
|
||||
nightly:
|
||||
name: Nightly
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Download translations
|
||||
run: ./script/translations_download
|
||||
env:
|
||||
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
||||
|
||||
- name: Bump version
|
||||
run: script/version_bump.js nightly
|
||||
|
||||
- name: Build nightly Python wheels
|
||||
run: |
|
||||
pip install build
|
||||
yarn install
|
||||
|
||||
script/build_frontend
|
||||
|
||||
rm -rf dist home_assistant_frontend.egg-info
|
||||
python3 -m build
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist/home_assistant_frontend*.whl
|
||||
if-no-files-found: error
|
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@ -24,18 +24,18 @@ jobs:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Verify version
|
||||
uses: home-assistant/actions/helpers/verify-version@master
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
2
.github/workflows/translations.yaml
vendored
2
.github/workflows/translations.yaml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Upload Translations
|
||||
run: |
|
||||
|
@ -27,7 +27,7 @@ module.exports = {
|
||||
version() {
|
||||
const version = fs
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8")
|
||||
.match(/version\W+=\W"(\d{8}\.\d)"/);
|
||||
.match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
|
||||
if (!version) {
|
||||
throw Error("Version not found");
|
||||
}
|
||||
|
@ -797,7 +797,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
||||
attributes: {
|
||||
battery_level: 34,
|
||||
on: true,
|
||||
friendly_name: "altan_motion_sensor",
|
||||
friendly_name: "Porch motion sensor",
|
||||
device_class: "motion",
|
||||
},
|
||||
},
|
||||
@ -818,7 +818,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
||||
attributes: {
|
||||
battery_level: 74,
|
||||
on: true,
|
||||
friendly_name: "badrumssensor",
|
||||
friendly_name: "Bathroom motion sensor",
|
||||
device_class: "motion",
|
||||
},
|
||||
},
|
||||
@ -829,7 +829,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
||||
battery_level: 47,
|
||||
on: true,
|
||||
dark: true,
|
||||
friendly_name: "R\u00f6relsesensor k\u00e4llaren 1",
|
||||
friendly_name: "Basement motion sensor",
|
||||
device_class: "motion",
|
||||
icon: "mdi:walk",
|
||||
},
|
||||
@ -863,7 +863,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
||||
attributes: {
|
||||
battery_level: 60,
|
||||
on: true,
|
||||
friendly_name: "R\u00f6relsesensor skafferiet",
|
||||
friendly_name: "Pantry motion sensor",
|
||||
device_class: "motion",
|
||||
icon: "mdi:walk",
|
||||
},
|
||||
@ -875,7 +875,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
||||
battery_level: 60,
|
||||
on: true,
|
||||
dark: true,
|
||||
friendly_name: "R\u00f6relsesensor k\u00e4llaren 2",
|
||||
friendly_name: "Stair motion sensor",
|
||||
device_class: "motion",
|
||||
icon: "mdi:walk",
|
||||
},
|
||||
@ -887,7 +887,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
||||
battery_level: 47,
|
||||
on: true,
|
||||
dark: true,
|
||||
friendly_name: "B\u00e4nksensor",
|
||||
friendly_name: "Bench sensor",
|
||||
device_class: "motion",
|
||||
},
|
||||
},
|
||||
|
@ -277,7 +277,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
||||
],
|
||||
show_header_toggle: false,
|
||||
type: "entities",
|
||||
title: "Bandbredd",
|
||||
title: "Bandwidth",
|
||||
},
|
||||
// {
|
||||
// title: "Updater",
|
||||
|
@ -72,8 +72,8 @@
|
||||
"@material/mwc-textfield": "0.25.3",
|
||||
"@material/mwc-top-app-bar-fixed": "^0.25.3",
|
||||
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
|
||||
"@mdi/js": "6.7.96",
|
||||
"@mdi/svg": "6.7.96",
|
||||
"@mdi/js": "6.9.96",
|
||||
"@mdi/svg": "6.9.96",
|
||||
"@polymer/app-layout": "^3.1.0",
|
||||
"@polymer/iron-flex-layout": "^3.0.1",
|
||||
"@polymer/iron-icon": "^3.0.1",
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20220630.0"
|
||||
version = "20220705.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@ -24,10 +24,15 @@ function auto(version) {
|
||||
return patch(version);
|
||||
}
|
||||
|
||||
function nightly() {
|
||||
return `${today()}.dev`;
|
||||
}
|
||||
|
||||
const methods = {
|
||||
patch,
|
||||
today,
|
||||
auto,
|
||||
nightly,
|
||||
};
|
||||
|
||||
async function main(args) {
|
||||
@ -57,7 +62,11 @@ async function main(args) {
|
||||
console.log("Current version:", version);
|
||||
console.log("New version:", newVersion);
|
||||
|
||||
fs.writeFileSync("pyproject.toml", setup.replace(version, newVersion), "utf-8");
|
||||
fs.writeFileSync(
|
||||
"pyproject.toml",
|
||||
setup.replace(version, newVersion),
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
if (!commit) {
|
||||
return;
|
||||
|
@ -186,6 +186,7 @@ class StateHistoryCharts extends LitElement {
|
||||
line-height: 60px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-height: var(--history-max-height);
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
private _entityCompletions(
|
||||
context: CompletionContext
|
||||
): CompletionResult | null | Promise<CompletionResult | null> {
|
||||
const entityWord = context.matchBefore(/[a-z_]{3,}\./);
|
||||
const entityWord = context.matchBefore(/[a-z_]{3,}\.\w*/);
|
||||
|
||||
if (
|
||||
!entityWord ||
|
||||
@ -227,7 +227,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
return {
|
||||
from: Number(entityWord.from),
|
||||
options: states,
|
||||
span: /^\w*.\w*$/,
|
||||
span: /^[a-z_]{3,}\.\w*$/,
|
||||
};
|
||||
}
|
||||
|
||||
@ -257,7 +257,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
private async _mdiCompletions(
|
||||
context: CompletionContext
|
||||
): Promise<CompletionResult | null> {
|
||||
const match = context.matchBefore(/mdi:/);
|
||||
const match = context.matchBefore(/mdi:\S*/);
|
||||
|
||||
if (!match || (match.from === match.to && !context.explicit)) {
|
||||
return null;
|
||||
@ -268,7 +268,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
return {
|
||||
from: Number(match.from),
|
||||
options: iconItems,
|
||||
span: /^\w*.\w*$/,
|
||||
span: /^mdi:\S*$/,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -42,8 +42,8 @@ import "./entity/ha-entity-picker";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
|
||||
import "./ha-area-picker";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-input-helper-text";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-target-picker")
|
||||
export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
@ -119,15 +119,26 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
if (!this._areas || !this._devices || !this._entities) {
|
||||
return html``;
|
||||
}
|
||||
return html`<div class=${this.horizontal ? "horizontal-container" : ""}>
|
||||
${this.horizontal ? this._renderChips() : this._renderItems()}
|
||||
${this._renderPicker()}
|
||||
${this.horizontal ? this._renderItems() : this._renderChips()}
|
||||
</div>`;
|
||||
return html`
|
||||
${this.horizontal
|
||||
? html`
|
||||
<div class="horizontal-container">
|
||||
${this._renderChips()} ${this._renderPicker()}
|
||||
</div>
|
||||
${this._renderItems()}
|
||||
`
|
||||
: html`
|
||||
<div>
|
||||
${this._renderItems()} ${this._renderPicker()}
|
||||
${this._renderChips()}
|
||||
</div>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderItems() {
|
||||
return html`<div class="mdc-chip-set items">
|
||||
return html`
|
||||
<div class="mdc-chip-set items">
|
||||
${this.value?.area_id
|
||||
? ensureArray(this.value.area_id).map((area_id) => {
|
||||
const area = this._areas![area_id];
|
||||
@ -163,11 +174,13 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
</div>`;
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderChips() {
|
||||
return html`<div class="mdc-chip-set">
|
||||
return html`
|
||||
<div class="mdc-chip-set">
|
||||
<div
|
||||
class="mdc-chip area_id add"
|
||||
.type=${"area_id"}
|
||||
@ -231,7 +244,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
</div>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""} `;
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private async _showPicker(ev) {
|
||||
@ -320,7 +334,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
private _renderPicker() {
|
||||
switch (this._addMode) {
|
||||
case "area_id":
|
||||
return html`<ha-area-picker
|
||||
return html`
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
id="input"
|
||||
.type=${"area_id"}
|
||||
@ -332,11 +347,12 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
.entityFilter=${this.entityRegFilter}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeDomains=${this.includeDomains}
|
||||
class=${this.horizontal ? "hidden-picker" : ""}
|
||||
@value-changed=${this._targetPicked}
|
||||
></ha-area-picker>`;
|
||||
></ha-area-picker>
|
||||
`;
|
||||
case "device_id":
|
||||
return html`<ha-device-picker
|
||||
return html`
|
||||
<ha-device-picker
|
||||
.hass=${this.hass}
|
||||
id="input"
|
||||
.type=${"device_id"}
|
||||
@ -347,11 +363,12 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
.entityFilter=${this.entityRegFilter}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeDomains=${this.includeDomains}
|
||||
class=${this.horizontal ? "hidden-picker" : ""}
|
||||
@value-changed=${this._targetPicked}
|
||||
></ha-device-picker>`;
|
||||
></ha-device-picker>
|
||||
`;
|
||||
case "entity_id":
|
||||
return html`<ha-entity-picker
|
||||
return html`
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
id="input"
|
||||
.type=${"entity_id"}
|
||||
@ -361,10 +378,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
.entityFilter=${this.entityFilter}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeDomains=${this.includeDomains}
|
||||
class=${this.horizontal ? "hidden-picker" : ""}
|
||||
@value-changed=${this._targetPicked}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>`;
|
||||
></ha-entity-picker>
|
||||
`;
|
||||
}
|
||||
return html``;
|
||||
}
|
||||
@ -553,15 +570,11 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
${unsafeCSS(chipStyles)}
|
||||
.hidden-picker {
|
||||
height: 0px;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
.horizontal-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
min-height: 56px;
|
||||
align-items: center;
|
||||
}
|
||||
.mdc-chip {
|
||||
color: var(--primary-text-color);
|
||||
|
@ -124,7 +124,6 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchOnboardingSteps();
|
||||
this._fetchInstallationType();
|
||||
import("./onboarding-integrations");
|
||||
import("./onboarding-core-config");
|
||||
registerServiceWorker(this, false);
|
||||
@ -215,6 +214,9 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
});
|
||||
history.replaceState(null, "", location.pathname);
|
||||
await this._connectHass(auth);
|
||||
} else {
|
||||
// User creating screen needs to know the installation type.
|
||||
this._fetchInstallationType();
|
||||
}
|
||||
|
||||
this._steps = steps;
|
||||
|
@ -378,6 +378,10 @@ class OnboardingCoreConfig extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-locations-editor {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
flex: 1;
|
||||
}
|
||||
|
@ -30,7 +30,13 @@ import { HomeAssistant } from "../types";
|
||||
import "./action-badge";
|
||||
import "./integration-badge";
|
||||
|
||||
const HIDDEN_DOMAINS = new Set(["hassio", "met", "radio_browser", "rpi_power"]);
|
||||
const HIDDEN_DOMAINS = new Set([
|
||||
"hassio",
|
||||
"met",
|
||||
"radio_browser",
|
||||
"rpi_power",
|
||||
"sun",
|
||||
]);
|
||||
|
||||
@customElement("onboarding-integrations")
|
||||
class OnboardingIntegrations extends LitElement {
|
||||
@ -75,7 +81,10 @@ class OnboardingIntegrations extends LitElement {
|
||||
// Render discovered and existing entries together sorted by localized title.
|
||||
const entries: Array<[string, TemplateResult]> = this._entries.map(
|
||||
(entry) => {
|
||||
const title = domainToName(this.hass.localize, entry.domain);
|
||||
const title =
|
||||
entry.title ||
|
||||
domainToName(this.hass.localize, entry.domain) ||
|
||||
entry.domain;
|
||||
return [
|
||||
title,
|
||||
html`
|
||||
|
@ -1,7 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import { css, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@ -11,9 +9,7 @@ import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import { debounce } from "../../../../common/util/debounce";
|
||||
import "../../../../components/buttons/ha-call-api-button";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import {
|
||||
cloudLogout,
|
||||
CloudStatusLoggedIn,
|
||||
@ -23,6 +19,7 @@ import {
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import "../../ha-config-section";
|
||||
import "./cloud-alexa-pref";
|
||||
@ -52,23 +49,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
.narrow=${this.narrow}
|
||||
header="Home Assistant Cloud"
|
||||
>
|
||||
<ha-button-menu
|
||||
slot="toolbar-icon"
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handleMenuAction}
|
||||
activatable
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<mwc-list-item>
|
||||
${this.hass.localize("ui.panel.config.cloud.account.sign_out")}
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
|
||||
<div class="content">
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">Home Assistant Cloud</span>
|
||||
@ -156,6 +136,11 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
<mwc-button @click=${this._signOut} class="warning">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.sign_out"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
@ -279,9 +264,7 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
private async _signOut() {
|
||||
showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.cloud.account.sign_out_confirm"
|
||||
@ -291,7 +274,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
confirm: () => this._logoutFromCloud(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _logoutFromCloud() {
|
||||
await cloudLogout(this.hass);
|
||||
@ -303,7 +285,9 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
[slot="introduction"] {
|
||||
margin: -1em 0;
|
||||
}
|
||||
@ -320,9 +304,7 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
.card-actions {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.card-actions a {
|
||||
text-decoration: none;
|
||||
justify-content: space-between;
|
||||
}
|
||||
mwc-button {
|
||||
align-self: center;
|
||||
@ -334,10 +316,8 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
text-transform: capitalize;
|
||||
padding: 16px;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
`;
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,6 +185,10 @@ export class CloudTTSPref extends LitElement {
|
||||
right: auto;
|
||||
left: 24px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import { getConfigEntries } from "../../../../../../data/config_entries";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
fetchZwaveIsAnyFirmwareUpdateInProgress,
|
||||
fetchZwaveNodeFirmwareUpdateCapabilities,
|
||||
fetchZwaveNodeIsFirmwareUpdateInProgress,
|
||||
fetchZwaveNodeStatus,
|
||||
} from "../../../../../../data/zwave_js";
|
||||
@ -87,20 +86,13 @@ export const getZwaveDeviceActions = async (
|
||||
return actions;
|
||||
}
|
||||
|
||||
const [
|
||||
firmwareUpdateCapabilities,
|
||||
isAnyFirmwareUpdateInProgress,
|
||||
isNodeFirmwareUpdateInProgress,
|
||||
] = await Promise.all([
|
||||
fetchZwaveNodeFirmwareUpdateCapabilities(hass, device.id),
|
||||
const [isAnyFirmwareUpdateInProgress, isNodeFirmwareUpdateInProgress] =
|
||||
await Promise.all([
|
||||
fetchZwaveIsAnyFirmwareUpdateInProgress(hass, entryId),
|
||||
fetchZwaveNodeIsFirmwareUpdateInProgress(hass, device.id),
|
||||
]);
|
||||
|
||||
if (
|
||||
firmwareUpdateCapabilities.firmware_upgradable &&
|
||||
(!isAnyFirmwareUpdateInProgress || isNodeFirmwareUpdateInProgress)
|
||||
) {
|
||||
if (!isAnyFirmwareUpdateInProgress || isNodeFirmwareUpdateInProgress) {
|
||||
actions.push({
|
||||
label: hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.update_firmware"
|
||||
@ -117,7 +109,6 @@ export const getZwaveDeviceActions = async (
|
||||
) {
|
||||
showZWaveJUpdateFirmwareNodeDialog(el, {
|
||||
device,
|
||||
firmwareUpdateCapabilities,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -165,13 +165,6 @@ class HaConfigInfo extends LitElement {
|
||||
`
|
||||
)}
|
||||
</mwc-list>
|
||||
<p class="config-path">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.info.path_configuration",
|
||||
"path",
|
||||
hass.config.config_dir
|
||||
)}
|
||||
</p>
|
||||
${!customUiList.length
|
||||
? ""
|
||||
: html`
|
||||
@ -202,7 +195,7 @@ class HaConfigInfo extends LitElement {
|
||||
if (((window as any).CUSTOM_UI_LIST || []).length !== customUI.length) {
|
||||
this.requestUpdate();
|
||||
}
|
||||
}, 1000);
|
||||
}, 2000);
|
||||
|
||||
if (isComponentLoaded(this.hass, "hassio")) {
|
||||
this._loadSupervisorInfo();
|
||||
@ -291,12 +284,6 @@ class HaConfigInfo extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
.config-path {
|
||||
color: var(--secondary-text-color);
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.custom-ui {
|
||||
color: var(--secondary-text-color);
|
||||
text-align: center;
|
||||
|
@ -766,7 +766,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
margin-right: 8px;
|
||||
align-items: center;
|
||||
height: 56px;
|
||||
position: sticky;
|
||||
|
@ -6,7 +6,6 @@ import "@material/mwc-linear-progress/mwc-linear-progress";
|
||||
import { mdiCheckCircle, mdiCloseCircle, mdiFileUpload } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
@ -26,7 +25,6 @@ import {
|
||||
ZWaveJSNodeFirmwareUpdateFinishedMessage,
|
||||
ZWaveJSNodeFirmwareUpdateProgressMessage,
|
||||
ZWaveJSNodeStatusUpdatedMessage,
|
||||
ZWaveJSNodeFirmwareUpdateCapabilities,
|
||||
ZWaveJSNodeStatus,
|
||||
} from "../../../../../data/zwave_js";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
@ -66,12 +64,9 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
|
||||
|
||||
private _deviceName?: string;
|
||||
|
||||
private _firmwareUpdateCapabilities?: ZWaveJSNodeFirmwareUpdateCapabilities;
|
||||
|
||||
public showDialog(params: ZWaveJSUpdateFirmwareNodeDialogParams): void {
|
||||
this._deviceName = computeDeviceName(params.device, this.hass!);
|
||||
this.device = params.device;
|
||||
this._firmwareUpdateCapabilities = params.firmwareUpdateCapabilities;
|
||||
this._fetchData();
|
||||
this._subscribeNodeStatus();
|
||||
}
|
||||
@ -84,7 +79,6 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
|
||||
this._updateFinishedMessage =
|
||||
this._firmwareFile =
|
||||
this._nodeStatus =
|
||||
this._firmwareUpdateCapabilities =
|
||||
undefined;
|
||||
this._firmwareTarget = 0;
|
||||
this._uploading = this._updateInProgress = false;
|
||||
@ -92,34 +86,21 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(
|
||||
firmwareUpdateCapabilities: ZWaveJSNodeFirmwareUpdateCapabilities
|
||||
): HaFormIntegerSchema => {
|
||||
if (!firmwareUpdateCapabilities.firmware_upgradable) {
|
||||
// We should never get here, this is to pass type checks
|
||||
throw new Error();
|
||||
}
|
||||
return {
|
||||
name: "firmware_target",
|
||||
type: "integer",
|
||||
valueMin: Math.min(...firmwareUpdateCapabilities.firmware_targets),
|
||||
valueMax: Math.max(...firmwareUpdateCapabilities.firmware_targets),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (
|
||||
!this.device ||
|
||||
!this._nodeStatus ||
|
||||
!this._firmwareUpdateCapabilities ||
|
||||
!this._firmwareUpdateCapabilities.firmware_upgradable ||
|
||||
this._updateInProgress === undefined
|
||||
) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const schema: HaFormIntegerSchema = {
|
||||
name: "firmware_target",
|
||||
type: "integer",
|
||||
valueMin: 0,
|
||||
};
|
||||
|
||||
const beginFirmwareUpdateHTML = html`<ha-file-upload
|
||||
.hass=${this.hass}
|
||||
.uploading=${this._uploading}
|
||||
@ -130,8 +111,7 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
|
||||
)}
|
||||
@file-picked=${this._uploadFile}
|
||||
></ha-file-upload>
|
||||
${this._firmwareUpdateCapabilities.firmware_targets.length > 1
|
||||
? html`<p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.firmware_target_intro"
|
||||
)}
|
||||
@ -139,10 +119,9 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${{ firmware_target: this._firmwareTarget }}
|
||||
.schema=${[this._schema(this._firmwareUpdateCapabilities)]}
|
||||
.schema=${[schema]}
|
||||
@value-changed=${this._firmwareTargetChanged}
|
||||
></ha-form>`
|
||||
: ""}
|
||||
></ha-form>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this._beginFirmwareUpdate}
|
||||
@ -285,12 +264,6 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
|
||||
)}
|
||||
</p>
|
||||
${beginFirmwareUpdateHTML}`}
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.finished_status.try_again"
|
||||
)}
|
||||
</p>
|
||||
${beginFirmwareUpdateHTML}
|
||||
`}
|
||||
</ha-dialog>
|
||||
`;
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { DeviceRegistryEntry } from "../../../../../data/device_registry";
|
||||
import { ZWaveJSNodeFirmwareUpdateCapabilities } from "../../../../../data/zwave_js";
|
||||
|
||||
export interface ZWaveJSUpdateFirmwareNodeDialogParams {
|
||||
device: DeviceRegistryEntry;
|
||||
firmwareUpdateCapabilities: ZWaveJSNodeFirmwareUpdateCapabilities;
|
||||
}
|
||||
|
||||
export const loadUpdateFirmwareNodeDialog = () =>
|
||||
|
@ -107,6 +107,8 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
|
||||
@state() private _entities: string[] = [];
|
||||
|
||||
private _single_entities: string[] = [];
|
||||
|
||||
@state() private _devices: string[] = [];
|
||||
|
||||
@state()
|
||||
@ -121,7 +123,7 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
|
||||
private _unsubscribeEvents?: () => void;
|
||||
|
||||
@state() private _deviceEntityLookup: DeviceEntitiesLookup = {};
|
||||
private _deviceEntityLookup: DeviceEntitiesLookup = {};
|
||||
|
||||
private _activateContextId?: string;
|
||||
|
||||
@ -520,9 +522,11 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
}
|
||||
|
||||
if (changedProps.has("_entityRegistryEntries")) {
|
||||
this._deviceEntityLookup = {};
|
||||
for (const entity of this._entityRegistryEntries) {
|
||||
if (
|
||||
!entity.device_id ||
|
||||
entity.entity_category ||
|
||||
SCENE_IGNORED_DOMAINS.includes(computeDomain(entity.entity_id))
|
||||
) {
|
||||
continue;
|
||||
@ -530,13 +534,10 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
if (!(entity.device_id in this._deviceEntityLookup)) {
|
||||
this._deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
if (
|
||||
!this._deviceEntityLookup[entity.device_id].includes(entity.entity_id)
|
||||
) {
|
||||
this._deviceEntityLookup[entity.device_id].push(entity.entity_id);
|
||||
}
|
||||
if (
|
||||
this._entities.includes(entity.entity_id) &&
|
||||
!this._single_entities.includes(entity.device_id) &&
|
||||
!this._devices.includes(entity.device_id)
|
||||
) {
|
||||
this._devices = [...this._devices, entity.device_id];
|
||||
@ -625,12 +626,24 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
private _initEntities(config: SceneConfig) {
|
||||
this._entities = Object.keys(config.entities);
|
||||
this._entities.forEach((entity) => this._storeState(entity));
|
||||
this._single_entities = [];
|
||||
|
||||
const filteredEntityReg = this._entityRegistryEntries.filter((entityReg) =>
|
||||
this._entities.includes(entityReg.entity_id)
|
||||
);
|
||||
const newDevices: string[] = [];
|
||||
|
||||
if (config.metadata) {
|
||||
Object.keys(config.entities).forEach((entity) => {
|
||||
if (
|
||||
!this._single_entities.includes(entity) &&
|
||||
config.metadata![entity].entity_only
|
||||
) {
|
||||
this._single_entities.push(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const entityReg of filteredEntityReg) {
|
||||
if (!entityReg.device_id) {
|
||||
continue;
|
||||
@ -654,6 +667,7 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
return;
|
||||
}
|
||||
this._entities = [...this._entities, entityId];
|
||||
this._single_entities.push(entityId);
|
||||
this._storeState(entityId);
|
||||
this._dirty = true;
|
||||
}
|
||||
@ -664,6 +678,9 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
this._entities = this._entities.filter(
|
||||
(entityId) => entityId !== deleteEntityId
|
||||
);
|
||||
this._single_entities = this._single_entities.filter(
|
||||
(entityId) => entityId !== deleteEntityId
|
||||
);
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
@ -815,19 +832,15 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
private _calculateMetaData(): SceneMetaData {
|
||||
const output: SceneMetaData = {};
|
||||
|
||||
for (const entityReg of this._entityRegistryEntries) {
|
||||
if (!this._entities.includes(entityReg.entity_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const entityState = this._getCurrentState(entityReg.entity_id);
|
||||
for (const entityId of this._single_entities) {
|
||||
const entityState = this._getCurrentState(entityId);
|
||||
|
||||
if (!entityState) {
|
||||
continue;
|
||||
}
|
||||
|
||||
output[entityReg.entity_id] = {
|
||||
entity_only: !this._devices.includes(entityReg.device_id!),
|
||||
output[entityId] = {
|
||||
entity_only: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { mdiRefresh } from "@mdi/js";
|
||||
import { mdiCollapseAll, mdiRefresh } from "@mdi/js";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import {
|
||||
@ -10,9 +10,12 @@ import {
|
||||
startOfWeek,
|
||||
startOfYesterday,
|
||||
} from "date-fns/esm";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket/dist/types";
|
||||
import { css, html, LitElement, PropertyValues } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket/dist/types";
|
||||
import { LocalStorage } from "../../common/decorators/local-storage";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import {
|
||||
createSearchParam,
|
||||
@ -20,46 +23,62 @@ import {
|
||||
} from "../../common/url/search-params";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import "../../components/chart/state-history-charts";
|
||||
import "../../components/ha-target-picker";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-date-range-picker";
|
||||
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-menu-button";
|
||||
import { computeHistory, fetchDateWS } from "../../data/history";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../../components/ha-target-picker";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import { computeHistory, fetchDateWS } from "../../data/history";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
@property() hass!: HomeAssistant;
|
||||
@property({ attribute: false }) hass!: HomeAssistant;
|
||||
|
||||
@property({ reflect: true, type: Boolean }) narrow!: boolean;
|
||||
|
||||
@property() _startDate: Date;
|
||||
|
||||
@property() _endDate: Date;
|
||||
|
||||
@property() _targetPickerValue?;
|
||||
|
||||
@property() _isLoading = false;
|
||||
|
||||
@property() _stateHistory?;
|
||||
|
||||
@property({ reflect: true, type: Boolean }) rtl = false;
|
||||
|
||||
@state() private _startDate: Date;
|
||||
|
||||
@state() private _endDate: Date;
|
||||
|
||||
@LocalStorage("historyPickedValue", true, false) private _targetPickerValue?;
|
||||
|
||||
@state() private _isLoading = false;
|
||||
|
||||
@state() private _stateHistory?;
|
||||
|
||||
@state() private _ranges?: DateRangePickerRanges;
|
||||
|
||||
@state() private _entities?: EntityRegistryEntry[];
|
||||
@state() private _devices?: { [deviceId: string]: DeviceRegistryEntry };
|
||||
|
||||
@state() private _stateEntities?: EntityRegistryEntry[];
|
||||
@state() private _entities?: { [entityId: string]: EntityRegistryEntry };
|
||||
|
||||
@state() private _stateEntities?: { [entityId: string]: EntityRegistryEntry };
|
||||
|
||||
@state() private _deviceIdToEntities?: {
|
||||
[deviceId: string]: EntityRegistryEntry[];
|
||||
};
|
||||
|
||||
@state() private _areaIdToEntities?: {
|
||||
[areaId: string]: EntityRegistryEntry[];
|
||||
};
|
||||
|
||||
@state() private _areaIdToDevices?: {
|
||||
[areaId: string]: DeviceRegistryEntry[];
|
||||
};
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
@ -76,7 +95,52 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities;
|
||||
this._entities = entities.reduce((accumulator, current) => {
|
||||
accumulator[current.entity_id] = current;
|
||||
return accumulator;
|
||||
}, {});
|
||||
this._deviceIdToEntities = entities.reduce((accumulator, current) => {
|
||||
if (!current.device_id) {
|
||||
return accumulator;
|
||||
}
|
||||
let found = accumulator[current.device_id];
|
||||
if (found === undefined) {
|
||||
found = [];
|
||||
accumulator[current.device_id] = found;
|
||||
}
|
||||
found.push(current);
|
||||
return accumulator;
|
||||
}, {});
|
||||
this._areaIdToEntities = entities.reduce((accumulator, current) => {
|
||||
if (!current.area_id) {
|
||||
return accumulator;
|
||||
}
|
||||
let found = accumulator[current.area_id];
|
||||
if (found === undefined) {
|
||||
found = [];
|
||||
accumulator[current.area_id] = found;
|
||||
}
|
||||
found.push(current);
|
||||
return accumulator;
|
||||
}, {});
|
||||
}),
|
||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
||||
this._devices = devices.reduce((accumulator, current) => {
|
||||
accumulator[current.id] = current;
|
||||
return accumulator;
|
||||
}, {});
|
||||
this._areaIdToDevices = devices.reduce((accumulator, current) => {
|
||||
if (!current.area_id) {
|
||||
return accumulator;
|
||||
}
|
||||
let found = accumulator[current.area_id];
|
||||
if (found === undefined) {
|
||||
found = [];
|
||||
accumulator[current.area_id] = found;
|
||||
}
|
||||
found.push(current);
|
||||
return accumulator;
|
||||
}, {});
|
||||
}),
|
||||
];
|
||||
}
|
||||
@ -91,8 +155,18 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
<div main-title>${this.hass.localize("panel.history")}</div>
|
||||
${this._targetPickerValue
|
||||
? html`
|
||||
<ha-icon-button
|
||||
@click=${this._refreshHistory}
|
||||
@click=${this._removeAll}
|
||||
.disabled=${this._isLoading}
|
||||
.path=${mdiCollapseAll}
|
||||
.label=${this.hass.localize("ui.panel.history.remove_all")}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
<ha-icon-button
|
||||
@click=${this._getHistory}
|
||||
.disabled=${this._isLoading}
|
||||
.path=${mdiRefresh}
|
||||
.label=${this.hass.localize("ui.common.refresh")}
|
||||
@ -125,6 +199,10 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
alt=${this.hass.localize("ui.common.loading")}
|
||||
></ha-circular-progress>
|
||||
</div>`
|
||||
: !this._targetPickerValue
|
||||
? html`<div class="start-search">
|
||||
${this.hass.localize("ui.panel.history.start_search")}
|
||||
</div>`
|
||||
: html`
|
||||
<state-history-charts
|
||||
.hass=${this.hass}
|
||||
@ -135,24 +213,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
</state-history-charts>
|
||||
`}
|
||||
</div>
|
||||
${this._isLoading
|
||||
? html`<div class="progress-wrapper">
|
||||
<ha-circular-progress
|
||||
active
|
||||
alt=${this.hass.localize("ui.common.loading")}
|
||||
></ha-circular-progress>
|
||||
</div>`
|
||||
: html`
|
||||
<state-history-charts
|
||||
virtualize
|
||||
.hass=${this.hass}
|
||||
.historyData=${this._stateHistory}
|
||||
.endTime=${this._endDate}
|
||||
.narrow=${this.narrow}
|
||||
no-single
|
||||
>
|
||||
</state-history-charts>
|
||||
`}
|
||||
</ha-app-layout>
|
||||
`;
|
||||
}
|
||||
@ -197,29 +257,38 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (
|
||||
changedProps.has("_startDate") ||
|
||||
this._targetPickerValue &&
|
||||
(changedProps.has("_startDate") ||
|
||||
changedProps.has("_endDate") ||
|
||||
changedProps.has("_targetPickerValue") ||
|
||||
changedProps.has("_entities")
|
||||
(!this._stateHistory &&
|
||||
(changedProps.has("_entities") ||
|
||||
changedProps.has("_devices") ||
|
||||
changedProps.has("_stateEntities") ||
|
||||
changedProps.has("_deviceIdToEntities") ||
|
||||
changedProps.has("_areaIdToEntities") ||
|
||||
changedProps.has("_areaIdToDevices"))))
|
||||
) {
|
||||
this._getHistory();
|
||||
}
|
||||
|
||||
if (changedProps.has("hass") || changedProps.has("_entities")) {
|
||||
if (!changedProps.has("hass") && !changedProps.has("_entities")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.language !== this.hass.language) {
|
||||
this.rtl = computeRTL(this.hass);
|
||||
}
|
||||
|
||||
if (this._entities) {
|
||||
const stateEntities: EntityRegistryEntry[] = [];
|
||||
const regEntityIds = new Set(
|
||||
this._entities.map((entity) => entity.entity_id)
|
||||
);
|
||||
const stateEntities: { [entityId: string]: EntityRegistryEntry } = {};
|
||||
const regEntityIds = new Set(Object.keys(this._entities));
|
||||
for (const entityId of Object.keys(this.hass.states)) {
|
||||
if (regEntityIds.has(entityId)) {
|
||||
continue;
|
||||
}
|
||||
stateEntities.push({
|
||||
stateEntities[entityId] = {
|
||||
name: computeStateName(this.hass.states[entityId]),
|
||||
entity_id: entityId,
|
||||
platform: computeDomain(entityId),
|
||||
@ -230,29 +299,33 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
device_id: null,
|
||||
icon: null,
|
||||
entity_category: null,
|
||||
});
|
||||
};
|
||||
}
|
||||
this._stateEntities = stateEntities;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshHistory() {
|
||||
this._getHistory();
|
||||
private _removeAll() {
|
||||
this._targetPickerValue = undefined;
|
||||
this._updatePath();
|
||||
}
|
||||
|
||||
private async _getHistory() {
|
||||
this._isLoading = true;
|
||||
const entityIds = this._getEntityIds();
|
||||
const dateHistory =
|
||||
entityIds.length === 0
|
||||
? {}
|
||||
: await fetchDateWS(
|
||||
|
||||
if (!entityIds.length) {
|
||||
this._stateHistory = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const dateHistory = await fetchDateWS(
|
||||
this.hass,
|
||||
this._startDate,
|
||||
this._endDate,
|
||||
entityIds
|
||||
);
|
||||
|
||||
this._stateHistory = computeHistory(
|
||||
this.hass,
|
||||
dateHistory,
|
||||
@ -261,50 +334,90 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
this._isLoading = false;
|
||||
}
|
||||
|
||||
private _filterEntity(entity: EntityRegistryEntry): boolean {
|
||||
const { area_id, device_id, entity_id } = this._targetPickerValue;
|
||||
if (area_id !== undefined) {
|
||||
if (typeof area_id === "string" && area_id === entity.area_id) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(area_id) && area_id.includes(entity.area_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (device_id !== undefined) {
|
||||
if (typeof device_id === "string" && device_id === entity.device_id) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(device_id) && device_id.includes(entity.device_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (entity_id !== undefined) {
|
||||
if (typeof entity_id === "string" && entity_id === entity.entity_id) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(entity_id) && entity_id.includes(entity.entity_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _getEntityIds(): string[] {
|
||||
if (
|
||||
this._targetPickerValue === undefined ||
|
||||
this._entities === undefined ||
|
||||
this._stateEntities === undefined
|
||||
this._stateEntities === undefined ||
|
||||
this._devices === undefined ||
|
||||
this._deviceIdToEntities === undefined ||
|
||||
this._areaIdToEntities === undefined ||
|
||||
this._areaIdToDevices === undefined
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
const entityIds = this._entities
|
||||
.filter((entity) => this._filterEntity(entity))
|
||||
.map((entity) => entity.entity_id);
|
||||
const stateEntityIds = this._stateEntities
|
||||
.filter((entity) => this._filterEntity(entity))
|
||||
.map((entity) => entity.entity_id);
|
||||
return [...entityIds, ...stateEntityIds];
|
||||
const entityIds = new Set<string>();
|
||||
let {
|
||||
area_id: searchingAreaId,
|
||||
device_id: searchingDeviceId,
|
||||
entity_id: searchingEntityId,
|
||||
} = this._targetPickerValue;
|
||||
|
||||
if (searchingAreaId !== undefined) {
|
||||
searchingAreaId =
|
||||
typeof searchingAreaId === "string"
|
||||
? [searchingAreaId]
|
||||
: searchingAreaId;
|
||||
for (const singleSearchingAreaId of searchingAreaId) {
|
||||
const foundEntities = this._areaIdToEntities[singleSearchingAreaId];
|
||||
if (!foundEntities) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const foundEntity of foundEntities) {
|
||||
if (foundEntity.entity_category === null) {
|
||||
entityIds.add(foundEntity.entity_id);
|
||||
}
|
||||
}
|
||||
|
||||
const foundDevices = this._areaIdToDevices[singleSearchingAreaId];
|
||||
if (foundDevices !== undefined) {
|
||||
for (const foundDevice of foundDevices) {
|
||||
const foundDeviceEntities =
|
||||
this._deviceIdToEntities[foundDevice.id];
|
||||
for (const foundDeviceEntity of foundDeviceEntities) {
|
||||
if (
|
||||
(!foundDeviceEntity.area_id ||
|
||||
foundDeviceEntity.area_id === singleSearchingAreaId) &&
|
||||
foundDeviceEntity.entity_category === null
|
||||
) {
|
||||
entityIds.add(foundDeviceEntity.entity_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (searchingDeviceId !== undefined) {
|
||||
searchingDeviceId =
|
||||
typeof searchingDeviceId === "string"
|
||||
? [searchingDeviceId]
|
||||
: searchingDeviceId;
|
||||
for (const singleSearchingDeviceId of searchingDeviceId) {
|
||||
const foundEntities = this._deviceIdToEntities[singleSearchingDeviceId];
|
||||
if (!foundEntities) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const foundEntity of foundEntities) {
|
||||
if (foundEntity.entity_category === null) {
|
||||
entityIds.add(foundEntity.entity_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (searchingEntityId !== undefined) {
|
||||
searchingEntityId =
|
||||
typeof searchingEntityId === "string"
|
||||
? [searchingEntityId]
|
||||
: searchingEntityId;
|
||||
for (const singleSearchingEntityId of searchingEntityId) {
|
||||
entityIds.add(singleSearchingEntityId);
|
||||
}
|
||||
}
|
||||
return [...entityIds];
|
||||
}
|
||||
|
||||
private _dateRangeChanged(ev) {
|
||||
@ -321,7 +434,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _entitiesChanged(ev) {
|
||||
this._targetPickerValue = ev.detail.value;
|
||||
|
||||
this._updatePath();
|
||||
}
|
||||
|
||||
@ -389,7 +501,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
align-items: flex-start;
|
||||
padding: 8px 16px 0;
|
||||
}
|
||||
|
||||
@ -429,9 +541,21 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.start-search {
|
||||
padding-top: 16px;
|
||||
text-align: center;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-panel-history", HaPanelHistory);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-panel-history": HaPanelHistory;
|
||||
}
|
||||
}
|
||||
|
@ -1593,7 +1593,6 @@
|
||||
"copy_github": "For GitHub",
|
||||
"description": "Version, loaded integrations and links to documentation",
|
||||
"home_assistant_logo": "Home Assistant logo",
|
||||
"path_configuration": "Path to configuration.yaml: {path}",
|
||||
"developed_by": "Developed by a bunch of awesome people.",
|
||||
"license": "Published under the Apache 2.0 license",
|
||||
"source": "Source:",
|
||||
@ -4012,7 +4011,8 @@
|
||||
"month": "Month",
|
||||
"year": "Year",
|
||||
"previous": "Previous",
|
||||
"next": "Next"
|
||||
"next": "Next",
|
||||
"compare": "Compare Data"
|
||||
}
|
||||
},
|
||||
"reload_lovelace": "Reload UI"
|
||||
@ -4537,6 +4537,9 @@
|
||||
}
|
||||
},
|
||||
"energy": {
|
||||
"compare": {
|
||||
"info": "You are comparing the period {start} with the period {end}"
|
||||
},
|
||||
"setup": {
|
||||
"next": "Next",
|
||||
"back": "Back",
|
||||
@ -4556,6 +4559,11 @@
|
||||
"energy_sources_table_title": "Sources",
|
||||
"energy_devices_graph_title": "Monitor individual devices"
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
"start_search": "Start by selecting an area, device or entity above",
|
||||
"add_all": "Add all entities",
|
||||
"remove_all": "Remove all selections"
|
||||
}
|
||||
},
|
||||
"tips": {
|
||||
|
20
yarn.lock
20
yarn.lock
@ -2975,17 +2975,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mdi/js@npm:6.7.96":
|
||||
version: 6.7.96
|
||||
resolution: "@mdi/js@npm:6.7.96"
|
||||
checksum: 8c8f6acb8fd3f856a92ffe2405e258ee5aa84cf541fda1c0a564c9c8bbf935cf2b6a6100cf97d41e9ada1ccb59e4b138d4c712e075f759d7595e21ef1cff84b5
|
||||
"@mdi/js@npm:6.9.96":
|
||||
version: 6.9.96
|
||||
resolution: "@mdi/js@npm:6.9.96"
|
||||
checksum: 94c43271585981e7ebf8cc5e3ead11915eea1339b79e849b351fbaae58e9626aaca0a8b031f0e80754d880ea6b08fd715906588f3cf980603bb5f6871bff12cc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mdi/svg@npm:6.7.96":
|
||||
version: 6.7.96
|
||||
resolution: "@mdi/svg@npm:6.7.96"
|
||||
checksum: 959332009b8833d0347e2dfac86028362a6d11996db850025b7da8c493d7fd341a14d8716ef775dd2ed0492158c77236bcf09adfc4ae77b31044a0a8a26fc74b
|
||||
"@mdi/svg@npm:6.9.96":
|
||||
version: 6.9.96
|
||||
resolution: "@mdi/svg@npm:6.9.96"
|
||||
checksum: 3e45f9a6632b0aa22f02ab0023d44131e3aea308bae85160445fe1f8f0b6d61df5c01e2756d18bd1461f7314fbf7c00a397bf99670f55803d90de5fa8e3fe33e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -9047,8 +9047,8 @@ fsevents@^1.2.7:
|
||||
"@material/mwc-textfield": 0.25.3
|
||||
"@material/mwc-top-app-bar-fixed": ^0.25.3
|
||||
"@material/top-app-bar": 14.0.0-canary.261f2db59.0
|
||||
"@mdi/js": 6.7.96
|
||||
"@mdi/svg": 6.7.96
|
||||
"@mdi/js": 6.9.96
|
||||
"@mdi/svg": 6.9.96
|
||||
"@open-wc/dev-server-hmr": ^0.0.2
|
||||
"@polymer/app-layout": ^3.1.0
|
||||
"@polymer/iron-flex-layout": ^3.0.1
|
||||
|
Loading…
x
Reference in New Issue
Block a user