Merge pull request #10869 from home-assistant/dev

This commit is contained in:
Bram Kragten 2021-12-11 17:32:55 +01:00 committed by GitHub
commit bdd13db8cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 133 additions and 47 deletions

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup( setup(
name="home-assistant-frontend", name="home-assistant-frontend",
version="20211209.0", version="20211211.0",
description="The Home Assistant frontend", description="The Home Assistant frontend",
url="https://github.com/home-assistant/frontend", url="https://github.com/home-assistant/frontend",
author="The Home Assistant Authors", author="The Home Assistant Authors",

View File

@ -56,6 +56,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
return html` return html`
<ha-icon-button <ha-icon-button
@click=${this._handleClick} @click=${this._handleClick}
.label=${this.hass.localize("ui.components.related-filter-menu.filter")}
.path=${mdiFilterVariant} .path=${mdiFilterVariant}
></ha-icon-button> ></ha-icon-button>
<mwc-menu-surface <mwc-menu-surface

View File

@ -35,7 +35,7 @@ class HaCoverControls extends LitElement {
hidden: !supportsOpen(this.stateObj), hidden: !supportsOpen(this.stateObj),
})} })}
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.dialogs.more_info_control.open_cover" "ui.dialogs.more_info_control.cover.open_cover"
)} )}
@click=${this._onOpenTap} @click=${this._onOpenTap}
.disabled=${this._computeOpenDisabled()} .disabled=${this._computeOpenDisabled()}
@ -47,7 +47,7 @@ class HaCoverControls extends LitElement {
hidden: !supportsStop(this.stateObj), hidden: !supportsStop(this.stateObj),
})} })}
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.dialogs.more_info_control.stop_cover" "ui.dialogs.more_info_control.cover.stop_cover"
)} )}
.path=${mdiStop} .path=${mdiStop}
@click=${this._onStopTap} @click=${this._onStopTap}
@ -58,7 +58,7 @@ class HaCoverControls extends LitElement {
hidden: !supportsClose(this.stateObj), hidden: !supportsClose(this.stateObj),
})} })}
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.dialogs.more_info_control.close_cover" "ui.dialogs.more_info_control.cover.close_cover"
)} )}
@click=${this._onCloseTap} @click=${this._onCloseTap}
.disabled=${this._computeClosedDisabled()} .disabled=${this._computeClosedDisabled()}

View File

@ -30,7 +30,7 @@ class HaCoverTiltControls extends LitElement {
invisible: !supportsOpenTilt(this.stateObj), invisible: !supportsOpenTilt(this.stateObj),
})} })}
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.dialogs.more_info_control.open_tilt_cover" "ui.dialogs.more_info_control.cover.open_tilt_cover"
)} )}
.path=${mdiArrowTopRight} .path=${mdiArrowTopRight}
@click=${this._onOpenTiltTap} @click=${this._onOpenTiltTap}
@ -40,7 +40,9 @@ class HaCoverTiltControls extends LitElement {
class=${classMap({ class=${classMap({
invisible: !supportsStopTilt(this.stateObj), invisible: !supportsStopTilt(this.stateObj),
})} })}
.label=${this.hass.localize("ui.dialogs.more_info_control.stop_cover")} .label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.stop_cover"
)}
.path=${mdiStop} .path=${mdiStop}
@click=${this._onStopTiltTap} @click=${this._onStopTiltTap}
.disabled=${this.stateObj.state === UNAVAILABLE} .disabled=${this.stateObj.state === UNAVAILABLE}
@ -50,7 +52,7 @@ class HaCoverTiltControls extends LitElement {
invisible: !supportsCloseTilt(this.stateObj), invisible: !supportsCloseTilt(this.stateObj),
})} })}
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.dialogs.more_info_control.close_tilt_cover" "ui.dialogs.more_info_control.cover.close_tilt_cover"
)} )}
.path=${mdiArrowBottomLeft} .path=${mdiArrowBottomLeft}
@click=${this._onCloseTiltTap} @click=${this._onCloseTiltTap}

View File

@ -1,6 +1,7 @@
import { Formfield } from "@material/mwc-formfield"; import { Formfield } from "@material/mwc-formfield";
import { css, CSSResultGroup } from "lit"; import { css, CSSResultGroup } from "lit";
import { customElement } from "lit/decorators"; import { customElement } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
@customElement("ha-formfield") @customElement("ha-formfield")
// @ts-expect-error // @ts-expect-error
@ -13,6 +14,7 @@ export class HaFormfield extends Formfield {
case "HA-CHECKBOX": case "HA-CHECKBOX":
case "HA-RADIO": case "HA-RADIO":
(input as any).checked = !(input as any).checked; (input as any).checked = !(input as any).checked;
fireEvent(input, "change");
break; break;
default: default:
input.click(); input.click();

View File

@ -29,7 +29,7 @@ export class HaIconOverflowMenu extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
${this.narrow ${this.narrow
? html` <!-- Collapsed Representation for Small Screens --> ? html` <!-- Collapsed representation for small screens -->
<ha-button-menu <ha-button-menu
@click=${this._handleIconOverflowMenuOpened} @click=${this._handleIconOverflowMenuOpened}
@closed=${this._handleIconOverflowMenuClosed} @closed=${this._handleIconOverflowMenuClosed}
@ -59,8 +59,7 @@ export class HaIconOverflowMenu extends LitElement {
)} )}
</ha-button-menu>` </ha-button-menu>`
: html` : html`
<!-- Icon Representation for Big Screens --> <!-- Icon representation for big screens -->
${this.items.map((item) => ${this.items.map((item) =>
item.narrowOnly item.narrowOnly
? "" ? ""
@ -70,13 +69,12 @@ export class HaIconOverflowMenu extends LitElement {
${item.tooltip} ${item.tooltip}
</paper-tooltip>` </paper-tooltip>`
: ""} : ""}
<mwc-icon-button <ha-icon-button
@click=${item.action} @click=${item.action}
.label=${item.label} .label=${item.label}
.path=${item.path}
.disabled=${item.disabled} .disabled=${item.disabled}
> ></ha-icon-button>
<ha-svg-icon .path=${item.path}></ha-svg-icon>
</mwc-icon-button>
</div> ` </div> `
)} )}
`} `}

View File

@ -1,5 +1,7 @@
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import "@material/mwc-select/mwc-select"; import "@material/mwc-select/mwc-select";
import "@material/mwc-textfield/mwc-textfield";
import type { TextField } from "@material/mwc-textfield/mwc-textfield";
import { mdiCamera } from "@mdi/js"; import { mdiCamera } from "@mdi/js";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
@ -9,6 +11,7 @@ import { stopPropagation } from "../common/dom/stop_propagation";
import { LocalizeFunc } from "../common/translations/localize"; import { LocalizeFunc } from "../common/translations/localize";
import "./ha-alert"; import "./ha-alert";
import "./ha-button-menu"; import "./ha-button-menu";
import "@material/mwc-button/mwc-button";
@customElement("ha-qr-scanner") @customElement("ha-qr-scanner")
class HaQrScanner extends LitElement { class HaQrScanner extends LitElement {
@ -26,6 +29,8 @@ class HaQrScanner extends LitElement {
@query("#canvas-container", true) private _canvasContainer!: HTMLDivElement; @query("#canvas-container", true) private _canvasContainer!: HTMLDivElement;
@query("mwc-textfield") private _manualInput?: TextField;
public disconnectedCallback(): void { public disconnectedCallback(): void {
super.disconnectedCallback(); super.disconnectedCallback();
this._qrNotFoundCount = 0; this._qrNotFoundCount = 0;
@ -74,7 +79,7 @@ class HaQrScanner extends LitElement {
<ha-icon-button <ha-icon-button
slot="trigger" slot="trigger"
.label=${this.localize( .label=${this.localize(
"ui.panel.config.zwave_js.add_node.select_camera" "ui.components.qr-scanner.select_camera"
)} )}
.path=${mdiCamera} .path=${mdiCamera}
></ha-icon-button> ></ha-icon-button>
@ -90,11 +95,22 @@ class HaQrScanner extends LitElement {
</ha-button-menu>` </ha-button-menu>`
: ""} : ""}
</div>` </div>`
: html`<ha-alert alert-type="warning" : html`<ha-alert alert-type="warning">
>${!window.isSecureContext ${!window.isSecureContext
? "You can only use your camera to scan a QR core when using HTTPS." ? this.localize("ui.components.qr-scanner.only_https_supported")
: "Your browser doesn't support QR scanning."}</ha-alert : this.localize("ui.components.qr-scanner.not_supported")}
>`}`; </ha-alert>
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
<div class="row">
<mwc-textfield
.label=${this.localize("ui.components.qr-scanner.enter_qr_code")}
@keyup=${this._manualKeyup}
@paste=${this._manualPaste}
></mwc-textfield>
<mwc-button @click=${this._manualSubmit}
>${this.localize("ui.common.submit")}</mwc-button
>
</div>`}`;
} }
private async _loadQrScanner() { private async _loadQrScanner() {
@ -143,6 +159,23 @@ class HaQrScanner extends LitElement {
fireEvent(this, "qr-code-scanned", { value: qrCodeString }); fireEvent(this, "qr-code-scanned", { value: qrCodeString });
}; };
private _manualKeyup(ev: KeyboardEvent) {
if (ev.key === "Enter") {
this._qrCodeScanned((ev.target as TextField).value);
}
}
private _manualPaste(ev: ClipboardEvent) {
this._qrCodeScanned(
// @ts-ignore
(ev.clipboardData || window.clipboardData).getData("text")
);
}
private _manualSubmit() {
this._qrCodeScanned(this._manualInput!.value);
}
private _cameraChanged(ev: CustomEvent): void { private _cameraChanged(ev: CustomEvent): void {
this._qrScanner?.setCamera((ev.target as any).value); this._qrScanner?.setCamera((ev.target as any).value);
} }
@ -162,6 +195,14 @@ class HaQrScanner extends LitElement {
color: white; color: white;
border-radius: 50%; border-radius: 50%;
} }
.row {
display: flex;
align-items: center;
}
mwc-textfield {
flex: 1;
margin-right: 8px;
}
`; `;
} }

View File

@ -99,6 +99,8 @@ export class QuickBar extends LitElement {
private _focusSet = false; private _focusSet = false;
private _focusListElement?: ListItem | null;
public async showDialog(params: QuickBarParams) { public async showDialog(params: QuickBarParams) {
this._commandMode = params.commandMode || this._toggleIfAlreadyOpened(); this._commandMode = params.commandMode || this._toggleIfAlreadyOpened();
this._initializeItemsIfNeeded(); this._initializeItemsIfNeeded();
@ -317,7 +319,8 @@ export class QuickBar extends LitElement {
} else if (ev.code === "ArrowDown") { } else if (ev.code === "ArrowDown") {
ev.preventDefault(); ev.preventDefault();
this._getItemAtIndex(0)?.focus(); this._getItemAtIndex(0)?.focus();
this._getItemAtIndex(1)?.focus(); this._focusSet = true;
this._focusListElement = this._getItemAtIndex(0);
} }
} }
@ -350,6 +353,11 @@ export class QuickBar extends LitElement {
this._initializeItemsIfNeeded(); this._initializeItemsIfNeeded();
this._filter = this._search; this._filter = this._search;
} else { } else {
if (this._focusSet && this._focusListElement) {
this._focusSet = false;
// @ts-ignore
this._focusListElement.rippleHandlers.endFocus();
}
this._debouncedSetFilter(this._search); this._debouncedSetFilter(this._search);
} }
} }
@ -366,12 +374,14 @@ export class QuickBar extends LitElement {
private _setFocusFirstListItem() { private _setFocusFirstListItem() {
// @ts-ignore // @ts-ignore
this._getItemAtIndex(0)?.rippleHandlers.startFocus(); this._getItemAtIndex(0)?.rippleHandlers.startFocus();
this._focusListElement = this._getItemAtIndex(0);
} }
private _handleListItemKeyDown(ev: KeyboardEvent) { private _handleListItemKeyDown(ev: KeyboardEvent) {
const isSingleCharacter = ev.key.length === 1; const isSingleCharacter = ev.key.length === 1;
const isFirstListItem = const isFirstListItem =
(ev.target as HTMLElement).getAttribute("index") === "0"; (ev.target as HTMLElement).getAttribute("index") === "0";
this._focusListElement = ev.target as ListItem;
if (ev.key === "ArrowUp") { if (ev.key === "ArrowUp") {
if (isFirstListItem) { if (isFirstListItem) {
this._filterInputField?.focus(); this._filterInputField?.focus();
@ -511,7 +521,13 @@ export class QuickBar extends LitElement {
if (page.component) { if (page.component) {
const info = this._getNavigationInfoFromConfig(page); const info = this._getNavigationInfoFromConfig(page);
if (info) { // Add to list, but only if we do not already have an entry for the same path and component
if (
info &&
!items.some(
(e) => e.path === info.path && e.component === info.component
)
) {
items.push(info); items.push(info);
} }
} }

View File

@ -1,4 +1,9 @@
import { object, optional, number } from "superstruct"; import { object, optional, number, string } from "superstruct";
export const baseTriggerStruct = object({
platform: string(),
id: optional(string()),
});
export const forDictStruct = object({ export const forDictStruct = object({
days: optional(number()), days: optional(number()),

View File

@ -1,7 +1,15 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { html, LitElement, PropertyValues } from "lit"; import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { assert, literal, object, optional, string, union } from "superstruct"; import {
assert,
assign,
literal,
object,
optional,
string,
union,
} from "superstruct";
import { createDurationData } from "../../../../../common/datetime/create_duration_data"; import { createDurationData } from "../../../../../common/datetime/create_duration_data";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import { hasTemplate } from "../../../../../common/string/has-template"; import { hasTemplate } from "../../../../../common/string/has-template";
@ -10,20 +18,23 @@ import "../../../../../components/entity/ha-entity-picker";
import "../../../../../components/ha-duration-input"; import "../../../../../components/ha-duration-input";
import { StateTrigger } from "../../../../../data/automation"; import { StateTrigger } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
import { forDictStruct } from "../../structs"; import { baseTriggerStruct, forDictStruct } from "../../structs";
import { import {
handleChangeEvent, handleChangeEvent,
TriggerElement, TriggerElement,
} from "../ha-automation-trigger-row"; } from "../ha-automation-trigger-row";
const stateTriggerStruct = object({ const stateTriggerStruct = assign(
platform: literal("state"), baseTriggerStruct,
entity_id: string(), object({
attribute: optional(string()), platform: literal("state"),
from: optional(string()), entity_id: string(),
to: optional(string()), attribute: optional(string()),
for: optional(union([string(), forDictStruct])), from: optional(string()),
}); to: optional(string()),
for: optional(union([string(), forDictStruct])),
})
);
@customElement("ha-automation-trigger-state") @customElement("ha-automation-trigger-state")
export class HaStateTrigger extends LitElement implements TriggerElement { export class HaStateTrigger extends LitElement implements TriggerElement {

View File

@ -178,7 +178,10 @@ class DialogZWaveJSAddNode extends LitElement {
Search device Search device
</mwc-button>` </mwc-button>`
: this._status === "qr_scan" : this._status === "qr_scan"
? html`<ha-qr-scanner ? html`${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<ha-qr-scanner
.localize=${this.hass.localize} .localize=${this.hass.localize}
@qr-code-scanned=${this._qrCodeScanned} @qr-code-scanned=${this._qrCodeScanned}
></ha-qr-scanner> ></ha-qr-scanner>
@ -194,9 +197,9 @@ class DialogZWaveJSAddNode extends LitElement {
</p> </p>
${ ${
this._error this._error
? html`<ha-alert alert-type="error" ? html`<ha-alert alert-type="error">
>${this._error}</ha-alert ${this._error}
>` </ha-alert>`
: "" : ""
} }
<div class="flex-container"> <div class="flex-container">
@ -614,7 +617,6 @@ class DialogZWaveJSAddNode extends LitElement {
ZWaveFeature.SmartStart ZWaveFeature.SmartStart
) )
).supported; ).supported;
this._supportsSmartStart = true;
} }
private _startOver(_ev: Event) { private _startOver(_ev: Event) {

View File

@ -291,12 +291,14 @@
"undo": "Undo", "undo": "Undo",
"move": "Move", "move": "Move",
"save": "Save", "save": "Save",
"submit": "Submit",
"rename": "Rename", "rename": "Rename",
"yes": "Yes", "yes": "Yes",
"no": "No", "no": "No",
"not_now": "Not now", "not_now": "Not now",
"skip": "Skip", "skip": "Skip",
"menu": "Menu", "menu": "Menu",
"overflow_menu": "Overflow menu",
"help": "Help", "help": "Help",
"successfully_saved": "Successfully saved", "successfully_saved": "Successfully saved",
"successfully_deleted": "Successfully deleted", "successfully_deleted": "Successfully deleted",
@ -421,6 +423,7 @@
} }
}, },
"related-filter-menu": { "related-filter-menu": {
"filter": "Filter",
"filter_by_entity": "Filter by entity", "filter_by_entity": "Filter by entity",
"filter_by_device": "Filter by device", "filter_by_device": "Filter by device",
"filter_by_area": "Filter by area", "filter_by_area": "Filter by area",
@ -538,6 +541,13 @@
}, },
"attributes": { "attributes": {
"expansion_header": "Attributes" "expansion_header": "Attributes"
},
"qr-scanner": {
"select_camera": "Select camera",
"only_https_supported": "You can only use your camera to scan a QR code when using HTTPS.",
"not_supported": "Your browser doesn't support QR scanning.",
"manual_input": "You can scan the QR code with another QR scanner and paste the code in the input below",
"enter_qr_code": "Enter QR code value"
} }
}, },
"dialogs": { "dialogs": {
@ -939,7 +949,7 @@
}, },
"blueprints": { "blueprints": {
"title": "Blueprints", "title": "Blueprints",
"description": "Manage blueprints" "description": "Pre-made automations and scripts by the community"
}, },
"supervisor": { "supervisor": {
"title": "Add-ons, Backups & Supervisor", "title": "Add-ons, Backups & Supervisor",
@ -1146,7 +1156,7 @@
"cost_number": "Use a static price", "cost_number": "Use a static price",
"cost_number_input": "Price per {unit}", "cost_number_input": "Price per {unit}",
"gas_usage": "Gas usage", "gas_usage": "Gas usage",
"m3_or_kWh": "m³ or kWh" "m3_or_kWh": "ft³, m³, Wh, kWh or MWh"
} }
}, },
"device_consumption": { "device_consumption": {
@ -1187,19 +1197,19 @@
}, },
"entity_unexpected_unit_energy": { "entity_unexpected_unit_energy": {
"title": "Unexpected unit of measurement", "title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement 'kWh' or 'Wh':" "description": "The following entities do not have the expected units of measurement 'Wh', 'kWh' or 'MWh':"
}, },
"entity_unexpected_unit_gas": { "entity_unexpected_unit_gas": {
"title": "Unexpected unit of measurement", "title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement 'kWh', 'm³' or 'ft³':" "description": "The following entities do not have the expected units of measurement 'Wh', 'kWh' or 'MWh' for an energy sensor or 'm³' or 'ft³' for a gas sensor:"
}, },
"entity_unexpected_unit_energy_price": { "entity_unexpected_unit_energy_price": {
"title": "Unexpected unit of measurement", "title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'' or ''{currency}/Wh'':" "description": "The following entities do not have the expected units of measurement ''{currency}/kWh'', ''{currency}/Wh'' or ''{currency}/MWh'':"
}, },
"entity_unexpected_unit_gas_price": { "entity_unexpected_unit_gas_price": {
"title": "Unexpected unit of measurement", "title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'', ''{currency}/Wh'', ''{currency}/m³'' or ''{currency}/ft³'':" "description": "The following entities do not have the expected units of measurement ''{currency}/kWh'', ''{currency}/Wh'', ''{currency}/MWh'', ''{currency}/m³'' or ''{currency}/ft³'':"
}, },
"entity_unexpected_state_class": { "entity_unexpected_state_class": {
"title": "Unexpected state class", "title": "Unexpected state class",
@ -2515,7 +2525,7 @@
"admin": "Administrator", "admin": "Administrator",
"group": "Group", "group": "Group",
"active": "Active", "active": "Active",
"local_only": "Can only login from the local network", "local_only": "Can only log in from the local network",
"system_generated": "System generated", "system_generated": "System generated",
"system_generated_users_not_removable": "Unable to remove system generated users.", "system_generated_users_not_removable": "Unable to remove system generated users.",
"system_generated_users_not_editable": "Unable to update system generated users.", "system_generated_users_not_editable": "Unable to update system generated users.",
@ -2920,8 +2930,6 @@
"qr_code": "QR Code", "qr_code": "QR Code",
"qr_code_paragraph": "If your device supports SmartStart you can scan the QR code for easy pairing.", "qr_code_paragraph": "If your device supports SmartStart you can scan the QR code for easy pairing.",
"scan_qr_code": "Scan QR code", "scan_qr_code": "Scan QR code",
"enter_qr_code": "Enter QR code value",
"select_camera": "Select camera",
"inclusion_failed": "The device could not be added.", "inclusion_failed": "The device could not be added.",
"check_logs": "Please check the logs for more information.", "check_logs": "Please check the logs for more information.",
"inclusion_finished": "The device has been added.", "inclusion_finished": "The device has been added.",