Merge pull request #13099 from home-assistant/dev

Bump to 20220705.0
This commit is contained in:
Bram Kragten 2022-07-05 18:55:51 +02:00 committed by GitHub
commit f6d6fd179f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 611 additions and 425 deletions

8
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: weekly
time: "06:00"
open-pull-requests-limit: 10

View File

@ -19,9 +19,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
@ -43,9 +43,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
@ -62,9 +62,9 @@ jobs:
needs: [lint, test] needs: [lint, test]
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
@ -81,9 +81,9 @@ jobs:
needs: [lint, test] needs: [lint, test]
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn

View File

@ -23,7 +23,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
# We must fetch at least the immediate parents so that if this is # We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head. # a pull request then we can checkout the head.
@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v1 uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@ -57,4 +57,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v2

View File

@ -14,9 +14,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn

View File

@ -9,7 +9,7 @@ jobs:
lock: lock:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v2.0.1 - uses: dessant/lock-threads@v3.0.0
with: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
issue-lock-inactive-days: "30" issue-lock-inactive-days: "30"

63
.github/workflows/nightly.yaml vendored Normal file
View 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

View File

@ -24,18 +24,18 @@ jobs:
contents: write # Required to upload release assets contents: write # Required to upload release assets
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Verify version - name: Verify version
uses: home-assistant/actions/helpers/verify-version@master uses: home-assistant/actions/helpers/verify-version@master
- name: Set up Python ${{ env.PYTHON_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Upload Translations - name: Upload Translations
run: | run: |

View File

@ -27,7 +27,7 @@ module.exports = {
version() { version() {
const version = fs const version = fs
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8") .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) { if (!version) {
throw Error("Version not found"); throw Error("Version not found");
} }

View File

@ -797,7 +797,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: { attributes: {
battery_level: 34, battery_level: 34,
on: true, on: true,
friendly_name: "altan_motion_sensor", friendly_name: "Porch motion sensor",
device_class: "motion", device_class: "motion",
}, },
}, },
@ -818,7 +818,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: { attributes: {
battery_level: 74, battery_level: 74,
on: true, on: true,
friendly_name: "badrumssensor", friendly_name: "Bathroom motion sensor",
device_class: "motion", device_class: "motion",
}, },
}, },
@ -829,7 +829,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 47, battery_level: 47,
on: true, on: true,
dark: true, dark: true,
friendly_name: "R\u00f6relsesensor k\u00e4llaren 1", friendly_name: "Basement motion sensor",
device_class: "motion", device_class: "motion",
icon: "mdi:walk", icon: "mdi:walk",
}, },
@ -863,7 +863,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: { attributes: {
battery_level: 60, battery_level: 60,
on: true, on: true,
friendly_name: "R\u00f6relsesensor skafferiet", friendly_name: "Pantry motion sensor",
device_class: "motion", device_class: "motion",
icon: "mdi:walk", icon: "mdi:walk",
}, },
@ -875,7 +875,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 60, battery_level: 60,
on: true, on: true,
dark: true, dark: true,
friendly_name: "R\u00f6relsesensor k\u00e4llaren 2", friendly_name: "Stair motion sensor",
device_class: "motion", device_class: "motion",
icon: "mdi:walk", icon: "mdi:walk",
}, },
@ -887,7 +887,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 47, battery_level: 47,
on: true, on: true,
dark: true, dark: true,
friendly_name: "B\u00e4nksensor", friendly_name: "Bench sensor",
device_class: "motion", device_class: "motion",
}, },
}, },

View File

@ -277,7 +277,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
], ],
show_header_toggle: false, show_header_toggle: false,
type: "entities", type: "entities",
title: "Bandbredd", title: "Bandwidth",
}, },
// { // {
// title: "Updater", // title: "Updater",

View File

@ -72,8 +72,8 @@
"@material/mwc-textfield": "0.25.3", "@material/mwc-textfield": "0.25.3",
"@material/mwc-top-app-bar-fixed": "^0.25.3", "@material/mwc-top-app-bar-fixed": "^0.25.3",
"@material/top-app-bar": "14.0.0-canary.261f2db59.0", "@material/top-app-bar": "14.0.0-canary.261f2db59.0",
"@mdi/js": "6.7.96", "@mdi/js": "6.9.96",
"@mdi/svg": "6.7.96", "@mdi/svg": "6.9.96",
"@polymer/app-layout": "^3.1.0", "@polymer/app-layout": "^3.1.0",
"@polymer/iron-flex-layout": "^3.0.1", "@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1", "@polymer/iron-icon": "^3.0.1",

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "home-assistant-frontend" name = "home-assistant-frontend"
version = "20220630.0" version = "20220705.0"
license = {text = "Apache-2.0"} license = {text = "Apache-2.0"}
description = "The Home Assistant frontend" description = "The Home Assistant frontend"
readme = "README.md" readme = "README.md"

View File

@ -24,10 +24,15 @@ function auto(version) {
return patch(version); return patch(version);
} }
function nightly() {
return `${today()}.dev`;
}
const methods = { const methods = {
patch, patch,
today, today,
auto, auto,
nightly,
}; };
async function main(args) { async function main(args) {
@ -57,7 +62,11 @@ async function main(args) {
console.log("Current version:", version); console.log("Current version:", version);
console.log("New version:", newVersion); 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) { if (!commit) {
return; return;

View File

@ -186,6 +186,7 @@ class StateHistoryCharts extends LitElement {
line-height: 60px; line-height: 60px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
.container { .container {
max-height: var(--history-max-height); max-height: var(--history-max-height);
} }

View File

@ -209,7 +209,7 @@ export class HaCodeEditor extends ReactiveElement {
private _entityCompletions( private _entityCompletions(
context: CompletionContext context: CompletionContext
): CompletionResult | null | Promise<CompletionResult | null> { ): CompletionResult | null | Promise<CompletionResult | null> {
const entityWord = context.matchBefore(/[a-z_]{3,}\./); const entityWord = context.matchBefore(/[a-z_]{3,}\.\w*/);
if ( if (
!entityWord || !entityWord ||
@ -227,7 +227,7 @@ export class HaCodeEditor extends ReactiveElement {
return { return {
from: Number(entityWord.from), from: Number(entityWord.from),
options: states, options: states,
span: /^\w*.\w*$/, span: /^[a-z_]{3,}\.\w*$/,
}; };
} }
@ -257,7 +257,7 @@ export class HaCodeEditor extends ReactiveElement {
private async _mdiCompletions( private async _mdiCompletions(
context: CompletionContext context: CompletionContext
): Promise<CompletionResult | null> { ): Promise<CompletionResult | null> {
const match = context.matchBefore(/mdi:/); const match = context.matchBefore(/mdi:\S*/);
if (!match || (match.from === match.to && !context.explicit)) { if (!match || (match.from === match.to && !context.explicit)) {
return null; return null;
@ -268,7 +268,7 @@ export class HaCodeEditor extends ReactiveElement {
return { return {
from: Number(match.from), from: Number(match.from),
options: iconItems, options: iconItems,
span: /^\w*.\w*$/, span: /^mdi:\S*$/,
}; };
} }

View File

@ -42,8 +42,8 @@ import "./entity/ha-entity-picker";
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker"; import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
import "./ha-area-picker"; import "./ha-area-picker";
import "./ha-icon-button"; import "./ha-icon-button";
import "./ha-svg-icon";
import "./ha-input-helper-text"; import "./ha-input-helper-text";
import "./ha-svg-icon";
@customElement("ha-target-picker") @customElement("ha-target-picker")
export class HaTargetPicker extends SubscribeMixin(LitElement) { export class HaTargetPicker extends SubscribeMixin(LitElement) {
@ -119,15 +119,26 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
if (!this._areas || !this._devices || !this._entities) { if (!this._areas || !this._devices || !this._entities) {
return html``; return html``;
} }
return html`<div class=${this.horizontal ? "horizontal-container" : ""}> return html`
${this.horizontal ? this._renderChips() : this._renderItems()} ${this.horizontal
${this._renderPicker()} ? html`
${this.horizontal ? this._renderItems() : this._renderChips()} <div class="horizontal-container">
</div>`; ${this._renderChips()} ${this._renderPicker()}
</div>
${this._renderItems()}
`
: html`
<div>
${this._renderItems()} ${this._renderPicker()}
${this._renderChips()}
</div>
`}
`;
} }
private _renderItems() { private _renderItems() {
return html`<div class="mdc-chip-set items"> return html`
<div class="mdc-chip-set items">
${this.value?.area_id ${this.value?.area_id
? ensureArray(this.value.area_id).map((area_id) => { ? ensureArray(this.value.area_id).map((area_id) => {
const area = this._areas![area_id]; const area = this._areas![area_id];
@ -163,11 +174,13 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
); );
}) })
: ""} : ""}
</div>`; </div>
`;
} }
private _renderChips() { private _renderChips() {
return html`<div class="mdc-chip-set"> return html`
<div class="mdc-chip-set">
<div <div
class="mdc-chip area_id add" class="mdc-chip area_id add"
.type=${"area_id"} .type=${"area_id"}
@ -231,7 +244,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
</div> </div>
${this.helper ${this.helper
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>` ? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
: ""} `; : ""}
`;
} }
private async _showPicker(ev) { private async _showPicker(ev) {
@ -320,7 +334,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
private _renderPicker() { private _renderPicker() {
switch (this._addMode) { switch (this._addMode) {
case "area_id": case "area_id":
return html`<ha-area-picker return html`
<ha-area-picker
.hass=${this.hass} .hass=${this.hass}
id="input" id="input"
.type=${"area_id"} .type=${"area_id"}
@ -332,11 +347,12 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
.entityFilter=${this.entityRegFilter} .entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses} .includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains} .includeDomains=${this.includeDomains}
class=${this.horizontal ? "hidden-picker" : ""}
@value-changed=${this._targetPicked} @value-changed=${this._targetPicked}
></ha-area-picker>`; ></ha-area-picker>
`;
case "device_id": case "device_id":
return html`<ha-device-picker return html`
<ha-device-picker
.hass=${this.hass} .hass=${this.hass}
id="input" id="input"
.type=${"device_id"} .type=${"device_id"}
@ -347,11 +363,12 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
.entityFilter=${this.entityRegFilter} .entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses} .includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains} .includeDomains=${this.includeDomains}
class=${this.horizontal ? "hidden-picker" : ""}
@value-changed=${this._targetPicked} @value-changed=${this._targetPicked}
></ha-device-picker>`; ></ha-device-picker>
`;
case "entity_id": case "entity_id":
return html`<ha-entity-picker return html`
<ha-entity-picker
.hass=${this.hass} .hass=${this.hass}
id="input" id="input"
.type=${"entity_id"} .type=${"entity_id"}
@ -361,10 +378,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
.entityFilter=${this.entityFilter} .entityFilter=${this.entityFilter}
.includeDeviceClasses=${this.includeDeviceClasses} .includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains} .includeDomains=${this.includeDomains}
class=${this.horizontal ? "hidden-picker" : ""}
@value-changed=${this._targetPicked} @value-changed=${this._targetPicked}
allow-custom-entity allow-custom-entity
></ha-entity-picker>`; ></ha-entity-picker>
`;
} }
return html``; return html``;
} }
@ -553,15 +570,11 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
${unsafeCSS(chipStyles)} ${unsafeCSS(chipStyles)}
.hidden-picker {
height: 0px;
display: inline-block;
overflow: hidden;
position: absolute;
}
.horizontal-container { .horizontal-container {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
min-height: 56px;
align-items: center;
} }
.mdc-chip { .mdc-chip {
color: var(--primary-text-color); color: var(--primary-text-color);

View File

@ -124,7 +124,6 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
protected firstUpdated(changedProps: PropertyValues) { protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
this._fetchOnboardingSteps(); this._fetchOnboardingSteps();
this._fetchInstallationType();
import("./onboarding-integrations"); import("./onboarding-integrations");
import("./onboarding-core-config"); import("./onboarding-core-config");
registerServiceWorker(this, false); registerServiceWorker(this, false);
@ -215,6 +214,9 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
}); });
history.replaceState(null, "", location.pathname); history.replaceState(null, "", location.pathname);
await this._connectHass(auth); await this._connectHass(auth);
} else {
// User creating screen needs to know the installation type.
this._fetchInstallationType();
} }
this._steps = steps; this._steps = steps;

View File

@ -378,6 +378,10 @@ class OnboardingCoreConfig extends LitElement {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
ha-locations-editor {
height: 200px;
}
.flex { .flex {
flex: 1; flex: 1;
} }

View File

@ -30,7 +30,13 @@ import { HomeAssistant } from "../types";
import "./action-badge"; import "./action-badge";
import "./integration-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") @customElement("onboarding-integrations")
class OnboardingIntegrations extends LitElement { class OnboardingIntegrations extends LitElement {
@ -75,7 +81,10 @@ class OnboardingIntegrations extends LitElement {
// Render discovered and existing entries together sorted by localized title. // Render discovered and existing entries together sorted by localized title.
const entries: Array<[string, TemplateResult]> = this._entries.map( const entries: Array<[string, TemplateResult]> = this._entries.map(
(entry) => { (entry) => {
const title = domainToName(this.hass.localize, entry.domain); const title =
entry.title ||
domainToName(this.hass.localize, entry.domain) ||
entry.domain;
return [ return [
title, title,
html` html`

View File

@ -1,7 +1,5 @@
import "@material/mwc-button"; import "@material/mwc-button";
import type { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import { css, html, LitElement, PropertyValues } from "lit"; import { css, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators"; 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 { debounce } from "../../../../common/util/debounce";
import "../../../../components/buttons/ha-call-api-button"; import "../../../../components/buttons/ha-call-api-button";
import "../../../../components/ha-alert"; import "../../../../components/ha-alert";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
import "../../../../components/ha-icon-button";
import { import {
cloudLogout, cloudLogout,
CloudStatusLoggedIn, CloudStatusLoggedIn,
@ -23,6 +19,7 @@ import {
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import "../../../../layouts/hass-subpage"; import "../../../../layouts/hass-subpage";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import { haStyle } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import "../../ha-config-section"; import "../../ha-config-section";
import "./cloud-alexa-pref"; import "./cloud-alexa-pref";
@ -52,23 +49,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
.narrow=${this.narrow} .narrow=${this.narrow}
header="Home Assistant Cloud" 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"> <div class="content">
<ha-config-section .isWide=${this.isWide}> <ha-config-section .isWide=${this.isWide}>
<span slot="header">Home Assistant Cloud</span> <span slot="header">Home Assistant Cloud</span>
@ -156,6 +136,11 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
)} )}
</mwc-button> </mwc-button>
</a> </a>
<mwc-button @click=${this._signOut} class="warning">
${this.hass.localize(
"ui.panel.config.cloud.account.sign_out"
)}
</mwc-button>
</div> </div>
</ha-card> </ha-card>
</ha-config-section> </ha-config-section>
@ -279,9 +264,7 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
} }
} }
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) { private async _signOut() {
switch (ev.detail.index) {
case 0:
showConfirmationDialog(this, { showConfirmationDialog(this, {
text: this.hass.localize( text: this.hass.localize(
"ui.panel.config.cloud.account.sign_out_confirm" "ui.panel.config.cloud.account.sign_out_confirm"
@ -291,7 +274,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
confirm: () => this._logoutFromCloud(), confirm: () => this._logoutFromCloud(),
}); });
} }
}
private async _logoutFromCloud() { private async _logoutFromCloud() {
await cloudLogout(this.hass); await cloudLogout(this.hass);
@ -303,7 +285,9 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
} }
static get styles() { static get styles() {
return css` return [
haStyle,
css`
[slot="introduction"] { [slot="introduction"] {
margin: -1em 0; margin: -1em 0;
} }
@ -320,9 +304,7 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
.card-actions { .card-actions {
display: flex; display: flex;
flex-direction: row-reverse; flex-direction: row-reverse;
} justify-content: space-between;
.card-actions a {
text-decoration: none;
} }
mwc-button { mwc-button {
align-self: center; align-self: center;
@ -334,10 +316,8 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
text-transform: capitalize; text-transform: capitalize;
padding: 16px; padding: 16px;
} }
a { `,
color: var(--primary-color); ];
}
`;
} }
} }

View File

@ -185,6 +185,10 @@ export class CloudTTSPref extends LitElement {
right: auto; right: auto;
left: 24px; left: 24px;
} }
.card-actions {
display: flex;
flex-direction: row-reverse;
}
`; `;
} }
} }

View File

@ -2,7 +2,6 @@ import { getConfigEntries } from "../../../../../../data/config_entries";
import { DeviceRegistryEntry } from "../../../../../../data/device_registry"; import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
import { import {
fetchZwaveIsAnyFirmwareUpdateInProgress, fetchZwaveIsAnyFirmwareUpdateInProgress,
fetchZwaveNodeFirmwareUpdateCapabilities,
fetchZwaveNodeIsFirmwareUpdateInProgress, fetchZwaveNodeIsFirmwareUpdateInProgress,
fetchZwaveNodeStatus, fetchZwaveNodeStatus,
} from "../../../../../../data/zwave_js"; } from "../../../../../../data/zwave_js";
@ -87,20 +86,13 @@ export const getZwaveDeviceActions = async (
return actions; return actions;
} }
const [ const [isAnyFirmwareUpdateInProgress, isNodeFirmwareUpdateInProgress] =
firmwareUpdateCapabilities, await Promise.all([
isAnyFirmwareUpdateInProgress,
isNodeFirmwareUpdateInProgress,
] = await Promise.all([
fetchZwaveNodeFirmwareUpdateCapabilities(hass, device.id),
fetchZwaveIsAnyFirmwareUpdateInProgress(hass, entryId), fetchZwaveIsAnyFirmwareUpdateInProgress(hass, entryId),
fetchZwaveNodeIsFirmwareUpdateInProgress(hass, device.id), fetchZwaveNodeIsFirmwareUpdateInProgress(hass, device.id),
]); ]);
if ( if (!isAnyFirmwareUpdateInProgress || isNodeFirmwareUpdateInProgress) {
firmwareUpdateCapabilities.firmware_upgradable &&
(!isAnyFirmwareUpdateInProgress || isNodeFirmwareUpdateInProgress)
) {
actions.push({ actions.push({
label: hass.localize( label: hass.localize(
"ui.panel.config.zwave_js.device_info.update_firmware" "ui.panel.config.zwave_js.device_info.update_firmware"
@ -117,7 +109,6 @@ export const getZwaveDeviceActions = async (
) { ) {
showZWaveJUpdateFirmwareNodeDialog(el, { showZWaveJUpdateFirmwareNodeDialog(el, {
device, device,
firmwareUpdateCapabilities,
}); });
} }
}, },

View File

@ -165,13 +165,6 @@ class HaConfigInfo extends LitElement {
` `
)} )}
</mwc-list> </mwc-list>
<p class="config-path">
${this.hass.localize(
"ui.panel.config.info.path_configuration",
"path",
hass.config.config_dir
)}
</p>
${!customUiList.length ${!customUiList.length
? "" ? ""
: html` : html`
@ -202,7 +195,7 @@ class HaConfigInfo extends LitElement {
if (((window as any).CUSTOM_UI_LIST || []).length !== customUI.length) { if (((window as any).CUSTOM_UI_LIST || []).length !== customUI.length) {
this.requestUpdate(); this.requestUpdate();
} }
}, 1000); }, 2000);
if (isComponentLoaded(this.hass, "hassio")) { if (isComponentLoaded(this.hass, "hassio")) {
this._loadSupervisorInfo(); 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 { .custom-ui {
color: var(--secondary-text-color); color: var(--secondary-text-color);
text-align: center; text-align: center;

View File

@ -766,7 +766,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
width: 100%; width: 100%;
margin-right: 8px;
align-items: center; align-items: center;
height: 56px; height: 56px;
position: sticky; position: sticky;

View File

@ -6,7 +6,6 @@ import "@material/mwc-linear-progress/mwc-linear-progress";
import { mdiCheckCircle, mdiCloseCircle, mdiFileUpload } from "@mdi/js"; import { mdiCheckCircle, mdiCloseCircle, mdiFileUpload } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import { createCloseHeading } from "../../../../../components/ha-dialog"; import { createCloseHeading } from "../../../../../components/ha-dialog";
@ -26,7 +25,6 @@ import {
ZWaveJSNodeFirmwareUpdateFinishedMessage, ZWaveJSNodeFirmwareUpdateFinishedMessage,
ZWaveJSNodeFirmwareUpdateProgressMessage, ZWaveJSNodeFirmwareUpdateProgressMessage,
ZWaveJSNodeStatusUpdatedMessage, ZWaveJSNodeStatusUpdatedMessage,
ZWaveJSNodeFirmwareUpdateCapabilities,
ZWaveJSNodeStatus, ZWaveJSNodeStatus,
} from "../../../../../data/zwave_js"; } from "../../../../../data/zwave_js";
import { haStyleDialog } from "../../../../../resources/styles"; import { haStyleDialog } from "../../../../../resources/styles";
@ -66,12 +64,9 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
private _deviceName?: string; private _deviceName?: string;
private _firmwareUpdateCapabilities?: ZWaveJSNodeFirmwareUpdateCapabilities;
public showDialog(params: ZWaveJSUpdateFirmwareNodeDialogParams): void { public showDialog(params: ZWaveJSUpdateFirmwareNodeDialogParams): void {
this._deviceName = computeDeviceName(params.device, this.hass!); this._deviceName = computeDeviceName(params.device, this.hass!);
this.device = params.device; this.device = params.device;
this._firmwareUpdateCapabilities = params.firmwareUpdateCapabilities;
this._fetchData(); this._fetchData();
this._subscribeNodeStatus(); this._subscribeNodeStatus();
} }
@ -84,7 +79,6 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
this._updateFinishedMessage = this._updateFinishedMessage =
this._firmwareFile = this._firmwareFile =
this._nodeStatus = this._nodeStatus =
this._firmwareUpdateCapabilities =
undefined; undefined;
this._firmwareTarget = 0; this._firmwareTarget = 0;
this._uploading = this._updateInProgress = false; this._uploading = this._updateInProgress = false;
@ -92,34 +86,21 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
fireEvent(this, "dialog-closed", { dialog: this.localName }); 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 { protected render(): TemplateResult {
if ( if (
!this.device || !this.device ||
!this._nodeStatus || !this._nodeStatus ||
!this._firmwareUpdateCapabilities ||
!this._firmwareUpdateCapabilities.firmware_upgradable ||
this._updateInProgress === undefined this._updateInProgress === undefined
) { ) {
return html``; return html``;
} }
const schema: HaFormIntegerSchema = {
name: "firmware_target",
type: "integer",
valueMin: 0,
};
const beginFirmwareUpdateHTML = html`<ha-file-upload const beginFirmwareUpdateHTML = html`<ha-file-upload
.hass=${this.hass} .hass=${this.hass}
.uploading=${this._uploading} .uploading=${this._uploading}
@ -130,8 +111,7 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
)} )}
@file-picked=${this._uploadFile} @file-picked=${this._uploadFile}
></ha-file-upload> ></ha-file-upload>
${this._firmwareUpdateCapabilities.firmware_targets.length > 1 <p>
? html`<p>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.zwave_js.update_firmware.firmware_target_intro" "ui.panel.config.zwave_js.update_firmware.firmware_target_intro"
)} )}
@ -139,10 +119,9 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
<ha-form <ha-form
.hass=${this.hass} .hass=${this.hass}
.data=${{ firmware_target: this._firmwareTarget }} .data=${{ firmware_target: this._firmwareTarget }}
.schema=${[this._schema(this._firmwareUpdateCapabilities)]} .schema=${[schema]}
@value-changed=${this._firmwareTargetChanged} @value-changed=${this._firmwareTargetChanged}
></ha-form>` ></ha-form>
: ""}
<mwc-button <mwc-button
slot="primaryAction" slot="primaryAction"
@click=${this._beginFirmwareUpdate} @click=${this._beginFirmwareUpdate}
@ -285,12 +264,6 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
)} )}
</p> </p>
${beginFirmwareUpdateHTML}`} ${beginFirmwareUpdateHTML}`}
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.update_firmware.finished_status.try_again"
)}
</p>
${beginFirmwareUpdateHTML}
`} `}
</ha-dialog> </ha-dialog>
`; `;

View File

@ -1,10 +1,8 @@
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import { DeviceRegistryEntry } from "../../../../../data/device_registry"; import { DeviceRegistryEntry } from "../../../../../data/device_registry";
import { ZWaveJSNodeFirmwareUpdateCapabilities } from "../../../../../data/zwave_js";
export interface ZWaveJSUpdateFirmwareNodeDialogParams { export interface ZWaveJSUpdateFirmwareNodeDialogParams {
device: DeviceRegistryEntry; device: DeviceRegistryEntry;
firmwareUpdateCapabilities: ZWaveJSNodeFirmwareUpdateCapabilities;
} }
export const loadUpdateFirmwareNodeDialog = () => export const loadUpdateFirmwareNodeDialog = () =>

View File

@ -107,6 +107,8 @@ export class HaSceneEditor extends SubscribeMixin(
@state() private _entities: string[] = []; @state() private _entities: string[] = [];
private _single_entities: string[] = [];
@state() private _devices: string[] = []; @state() private _devices: string[] = [];
@state() @state()
@ -121,7 +123,7 @@ export class HaSceneEditor extends SubscribeMixin(
private _unsubscribeEvents?: () => void; private _unsubscribeEvents?: () => void;
@state() private _deviceEntityLookup: DeviceEntitiesLookup = {}; private _deviceEntityLookup: DeviceEntitiesLookup = {};
private _activateContextId?: string; private _activateContextId?: string;
@ -520,9 +522,11 @@ export class HaSceneEditor extends SubscribeMixin(
} }
if (changedProps.has("_entityRegistryEntries")) { if (changedProps.has("_entityRegistryEntries")) {
this._deviceEntityLookup = {};
for (const entity of this._entityRegistryEntries) { for (const entity of this._entityRegistryEntries) {
if ( if (
!entity.device_id || !entity.device_id ||
entity.entity_category ||
SCENE_IGNORED_DOMAINS.includes(computeDomain(entity.entity_id)) SCENE_IGNORED_DOMAINS.includes(computeDomain(entity.entity_id))
) { ) {
continue; continue;
@ -530,13 +534,10 @@ export class HaSceneEditor extends SubscribeMixin(
if (!(entity.device_id in this._deviceEntityLookup)) { if (!(entity.device_id in this._deviceEntityLookup)) {
this._deviceEntityLookup[entity.device_id] = []; this._deviceEntityLookup[entity.device_id] = [];
} }
if (
!this._deviceEntityLookup[entity.device_id].includes(entity.entity_id)
) {
this._deviceEntityLookup[entity.device_id].push(entity.entity_id); this._deviceEntityLookup[entity.device_id].push(entity.entity_id);
}
if ( if (
this._entities.includes(entity.entity_id) && this._entities.includes(entity.entity_id) &&
!this._single_entities.includes(entity.device_id) &&
!this._devices.includes(entity.device_id) !this._devices.includes(entity.device_id)
) { ) {
this._devices = [...this._devices, entity.device_id]; this._devices = [...this._devices, entity.device_id];
@ -625,12 +626,24 @@ export class HaSceneEditor extends SubscribeMixin(
private _initEntities(config: SceneConfig) { private _initEntities(config: SceneConfig) {
this._entities = Object.keys(config.entities); this._entities = Object.keys(config.entities);
this._entities.forEach((entity) => this._storeState(entity)); this._entities.forEach((entity) => this._storeState(entity));
this._single_entities = [];
const filteredEntityReg = this._entityRegistryEntries.filter((entityReg) => const filteredEntityReg = this._entityRegistryEntries.filter((entityReg) =>
this._entities.includes(entityReg.entity_id) this._entities.includes(entityReg.entity_id)
); );
const newDevices: string[] = []; 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) { for (const entityReg of filteredEntityReg) {
if (!entityReg.device_id) { if (!entityReg.device_id) {
continue; continue;
@ -654,6 +667,7 @@ export class HaSceneEditor extends SubscribeMixin(
return; return;
} }
this._entities = [...this._entities, entityId]; this._entities = [...this._entities, entityId];
this._single_entities.push(entityId);
this._storeState(entityId); this._storeState(entityId);
this._dirty = true; this._dirty = true;
} }
@ -664,6 +678,9 @@ export class HaSceneEditor extends SubscribeMixin(
this._entities = this._entities.filter( this._entities = this._entities.filter(
(entityId) => entityId !== deleteEntityId (entityId) => entityId !== deleteEntityId
); );
this._single_entities = this._single_entities.filter(
(entityId) => entityId !== deleteEntityId
);
this._dirty = true; this._dirty = true;
} }
@ -815,19 +832,15 @@ export class HaSceneEditor extends SubscribeMixin(
private _calculateMetaData(): SceneMetaData { private _calculateMetaData(): SceneMetaData {
const output: SceneMetaData = {}; const output: SceneMetaData = {};
for (const entityReg of this._entityRegistryEntries) { for (const entityId of this._single_entities) {
if (!this._entities.includes(entityReg.entity_id)) { const entityState = this._getCurrentState(entityId);
continue;
}
const entityState = this._getCurrentState(entityReg.entity_id);
if (!entityState) { if (!entityState) {
continue; continue;
} }
output[entityReg.entity_id] = { output[entityId] = {
entity_only: !this._devices.includes(entityReg.device_id!), entity_only: true,
}; };
} }

View File

@ -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-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/app-layout/app-toolbar/app-toolbar";
import { import {
@ -10,9 +10,12 @@ import {
startOfWeek, startOfWeek,
startOfYesterday, startOfYesterday,
} from "date-fns/esm"; } from "date-fns/esm";
import { UnsubscribeFunc } from "home-assistant-js-websocket/dist/types";
import { css, html, LitElement, PropertyValues } from "lit"; import { css, html, LitElement, PropertyValues } from "lit";
import { property, state } from "lit/decorators"; 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 { navigate } from "../../common/navigate";
import { import {
createSearchParam, createSearchParam,
@ -20,46 +23,62 @@ import {
} from "../../common/url/search-params"; } from "../../common/url/search-params";
import { computeRTL } from "../../common/util/compute_rtl"; import { computeRTL } from "../../common/util/compute_rtl";
import "../../components/chart/state-history-charts"; import "../../components/chart/state-history-charts";
import "../../components/ha-target-picker";
import "../../components/ha-circular-progress"; import "../../components/ha-circular-progress";
import "../../components/ha-date-range-picker"; import "../../components/ha-date-range-picker";
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker"; import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
import "../../components/ha-icon-button"; import "../../components/ha-icon-button";
import "../../components/ha-menu-button"; import "../../components/ha-menu-button";
import { computeHistory, fetchDateWS } from "../../data/history"; import "../../components/ha-target-picker";
import "../../layouts/ha-app-layout"; import {
import { haStyle } from "../../resources/styles"; DeviceRegistryEntry,
import { HomeAssistant } from "../../types"; subscribeDeviceRegistry,
} from "../../data/device_registry";
import { import {
EntityRegistryEntry, EntityRegistryEntry,
subscribeEntityRegistry, subscribeEntityRegistry,
} from "../../data/entity_registry"; } from "../../data/entity_registry";
import { computeHistory, fetchDateWS } from "../../data/history";
import "../../layouts/ha-app-layout";
import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { computeStateName } from "../../common/entity/compute_state_name"; import { haStyle } from "../../resources/styles";
import { computeDomain } from "../../common/entity/compute_domain"; import { HomeAssistant } from "../../types";
class HaPanelHistory extends SubscribeMixin(LitElement) { class HaPanelHistory extends SubscribeMixin(LitElement) {
@property() hass!: HomeAssistant; @property({ attribute: false }) hass!: HomeAssistant;
@property({ reflect: true, type: Boolean }) narrow!: boolean; @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; @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 _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() { public constructor() {
super(); super();
@ -76,7 +95,52 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
public hassSubscribe(): UnsubscribeFunc[] { public hassSubscribe(): UnsubscribeFunc[] {
return [ return [
subscribeEntityRegistry(this.hass.connection!, (entities) => { 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} .narrow=${this.narrow}
></ha-menu-button> ></ha-menu-button>
<div main-title>${this.hass.localize("panel.history")}</div> <div main-title>${this.hass.localize("panel.history")}</div>
${this._targetPickerValue
? html`
<ha-icon-button <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} .disabled=${this._isLoading}
.path=${mdiRefresh} .path=${mdiRefresh}
.label=${this.hass.localize("ui.common.refresh")} .label=${this.hass.localize("ui.common.refresh")}
@ -125,6 +199,10 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
alt=${this.hass.localize("ui.common.loading")} alt=${this.hass.localize("ui.common.loading")}
></ha-circular-progress> ></ha-circular-progress>
</div>` </div>`
: !this._targetPickerValue
? html`<div class="start-search">
${this.hass.localize("ui.panel.history.start_search")}
</div>`
: html` : html`
<state-history-charts <state-history-charts
.hass=${this.hass} .hass=${this.hass}
@ -135,24 +213,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
</state-history-charts> </state-history-charts>
`} `}
</div> </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> </ha-app-layout>
`; `;
} }
@ -197,29 +257,38 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
protected updated(changedProps: PropertyValues) { protected updated(changedProps: PropertyValues) {
if ( if (
changedProps.has("_startDate") || this._targetPickerValue &&
(changedProps.has("_startDate") ||
changedProps.has("_endDate") || changedProps.has("_endDate") ||
changedProps.has("_targetPickerValue") || 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(); 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; const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.language !== this.hass.language) { if (!oldHass || oldHass.language !== this.hass.language) {
this.rtl = computeRTL(this.hass); this.rtl = computeRTL(this.hass);
} }
if (this._entities) { if (this._entities) {
const stateEntities: EntityRegistryEntry[] = []; const stateEntities: { [entityId: string]: EntityRegistryEntry } = {};
const regEntityIds = new Set( const regEntityIds = new Set(Object.keys(this._entities));
this._entities.map((entity) => entity.entity_id)
);
for (const entityId of Object.keys(this.hass.states)) { for (const entityId of Object.keys(this.hass.states)) {
if (regEntityIds.has(entityId)) { if (regEntityIds.has(entityId)) {
continue; continue;
} }
stateEntities.push({ stateEntities[entityId] = {
name: computeStateName(this.hass.states[entityId]), name: computeStateName(this.hass.states[entityId]),
entity_id: entityId, entity_id: entityId,
platform: computeDomain(entityId), platform: computeDomain(entityId),
@ -230,29 +299,33 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
device_id: null, device_id: null,
icon: null, icon: null,
entity_category: null, entity_category: null,
}); };
} }
this._stateEntities = stateEntities; this._stateEntities = stateEntities;
} }
} }
}
private _refreshHistory() { private _removeAll() {
this._getHistory(); this._targetPickerValue = undefined;
this._updatePath();
} }
private async _getHistory() { private async _getHistory() {
this._isLoading = true; this._isLoading = true;
const entityIds = this._getEntityIds(); const entityIds = this._getEntityIds();
const dateHistory =
entityIds.length === 0 if (!entityIds.length) {
? {} this._stateHistory = undefined;
: await fetchDateWS( return;
}
const dateHistory = await fetchDateWS(
this.hass, this.hass,
this._startDate, this._startDate,
this._endDate, this._endDate,
entityIds entityIds
); );
this._stateHistory = computeHistory( this._stateHistory = computeHistory(
this.hass, this.hass,
dateHistory, dateHistory,
@ -261,50 +334,90 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
this._isLoading = false; 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[] { private _getEntityIds(): string[] {
if ( if (
this._targetPickerValue === undefined || this._targetPickerValue === undefined ||
this._entities === undefined || this._entities === undefined ||
this._stateEntities === undefined this._stateEntities === undefined ||
this._devices === undefined ||
this._deviceIdToEntities === undefined ||
this._areaIdToEntities === undefined ||
this._areaIdToDevices === undefined
) { ) {
return []; return [];
} }
const entityIds = this._entities const entityIds = new Set<string>();
.filter((entity) => this._filterEntity(entity)) let {
.map((entity) => entity.entity_id); area_id: searchingAreaId,
const stateEntityIds = this._stateEntities device_id: searchingDeviceId,
.filter((entity) => this._filterEntity(entity)) entity_id: searchingEntityId,
.map((entity) => entity.entity_id); } = this._targetPickerValue;
return [...entityIds, ...stateEntityIds];
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) { private _dateRangeChanged(ev) {
@ -321,7 +434,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
private _entitiesChanged(ev) { private _entitiesChanged(ev) {
this._targetPickerValue = ev.detail.value; this._targetPickerValue = ev.detail.value;
this._updatePath(); this._updatePath();
} }
@ -389,7 +501,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
.filters { .filters {
display: flex; display: flex;
align-items: flex-end; align-items: flex-start;
padding: 8px 16px 0; padding: 8px 16px 0;
} }
@ -429,9 +541,21 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
max-width: none; max-width: none;
width: 100%; width: 100%;
} }
.start-search {
padding-top: 16px;
text-align: center;
color: var(--secondary-text-color);
}
`, `,
]; ];
} }
} }
customElements.define("ha-panel-history", HaPanelHistory); customElements.define("ha-panel-history", HaPanelHistory);
declare global {
interface HTMLElementTagNameMap {
"ha-panel-history": HaPanelHistory;
}
}

View File

@ -1593,7 +1593,6 @@
"copy_github": "For GitHub", "copy_github": "For GitHub",
"description": "Version, loaded integrations and links to documentation", "description": "Version, loaded integrations and links to documentation",
"home_assistant_logo": "Home Assistant logo", "home_assistant_logo": "Home Assistant logo",
"path_configuration": "Path to configuration.yaml: {path}",
"developed_by": "Developed by a bunch of awesome people.", "developed_by": "Developed by a bunch of awesome people.",
"license": "Published under the Apache 2.0 license", "license": "Published under the Apache 2.0 license",
"source": "Source:", "source": "Source:",
@ -4012,7 +4011,8 @@
"month": "Month", "month": "Month",
"year": "Year", "year": "Year",
"previous": "Previous", "previous": "Previous",
"next": "Next" "next": "Next",
"compare": "Compare Data"
} }
}, },
"reload_lovelace": "Reload UI" "reload_lovelace": "Reload UI"
@ -4537,6 +4537,9 @@
} }
}, },
"energy": { "energy": {
"compare": {
"info": "You are comparing the period {start} with the period {end}"
},
"setup": { "setup": {
"next": "Next", "next": "Next",
"back": "Back", "back": "Back",
@ -4556,6 +4559,11 @@
"energy_sources_table_title": "Sources", "energy_sources_table_title": "Sources",
"energy_devices_graph_title": "Monitor individual devices" "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": { "tips": {

View File

@ -2975,17 +2975,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@mdi/js@npm:6.7.96": "@mdi/js@npm:6.9.96":
version: 6.7.96 version: 6.9.96
resolution: "@mdi/js@npm:6.7.96" resolution: "@mdi/js@npm:6.9.96"
checksum: 8c8f6acb8fd3f856a92ffe2405e258ee5aa84cf541fda1c0a564c9c8bbf935cf2b6a6100cf97d41e9ada1ccb59e4b138d4c712e075f759d7595e21ef1cff84b5 checksum: 94c43271585981e7ebf8cc5e3ead11915eea1339b79e849b351fbaae58e9626aaca0a8b031f0e80754d880ea6b08fd715906588f3cf980603bb5f6871bff12cc
languageName: node languageName: node
linkType: hard linkType: hard
"@mdi/svg@npm:6.7.96": "@mdi/svg@npm:6.9.96":
version: 6.7.96 version: 6.9.96
resolution: "@mdi/svg@npm:6.7.96" resolution: "@mdi/svg@npm:6.9.96"
checksum: 959332009b8833d0347e2dfac86028362a6d11996db850025b7da8c493d7fd341a14d8716ef775dd2ed0492158c77236bcf09adfc4ae77b31044a0a8a26fc74b checksum: 3e45f9a6632b0aa22f02ab0023d44131e3aea308bae85160445fe1f8f0b6d61df5c01e2756d18bd1461f7314fbf7c00a397bf99670f55803d90de5fa8e3fe33e
languageName: node languageName: node
linkType: hard linkType: hard
@ -9047,8 +9047,8 @@ fsevents@^1.2.7:
"@material/mwc-textfield": 0.25.3 "@material/mwc-textfield": 0.25.3
"@material/mwc-top-app-bar-fixed": ^0.25.3 "@material/mwc-top-app-bar-fixed": ^0.25.3
"@material/top-app-bar": 14.0.0-canary.261f2db59.0 "@material/top-app-bar": 14.0.0-canary.261f2db59.0
"@mdi/js": 6.7.96 "@mdi/js": 6.9.96
"@mdi/svg": 6.7.96 "@mdi/svg": 6.9.96
"@open-wc/dev-server-hmr": ^0.0.2 "@open-wc/dev-server-hmr": ^0.0.2
"@polymer/app-layout": ^3.1.0 "@polymer/app-layout": ^3.1.0
"@polymer/iron-flex-layout": ^3.0.1 "@polymer/iron-flex-layout": ^3.0.1