Merge pull request #10818 from home-assistant/dev

This commit is contained in:
Paulus Schoutsen 2021-12-06 15:21:37 -08:00 committed by GitHub
commit 8f729e2a95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 250 additions and 116 deletions

View File

@ -48,7 +48,6 @@ import "../../../src/layouts/hass-subpage";
import "../../../src/layouts/hass-tabs-subpage";
import { SUPERVISOR_UPDATE_NAMES } from "../../../src/panels/config/dashboard/ha-config-updates";
import { HomeAssistant, Route } from "../../../src/types";
import { documentationUrl } from "../../../src/util/documentation-url";
import { addonArchIsSupported, extractChangelog } from "../util/addon";
declare global {
@ -60,7 +59,6 @@ declare global {
type updateType = "os" | "supervisor" | "core" | "addon";
const changelogUrl = (
hass: HomeAssistant,
entry: updateType,
version: string
): string | undefined => {
@ -68,17 +66,19 @@ const changelogUrl = (
return undefined;
}
if (entry === "core") {
return version?.includes("dev")
return version.includes("dev")
? "https://github.com/home-assistant/core/commits/dev"
: documentationUrl(hass, "/latest-release-notes/");
: version.includes("b")
? "https://next.home-assistant.io/latest-release-notes/"
: "https://www.home-assistant.io/latest-release-notes/";
}
if (entry === "os") {
return version?.includes("dev")
return version.includes("dev")
? "https://github.com/home-assistant/operating-system/commits/dev"
: `https://github.com/home-assistant/operating-system/releases/tag/${version}`;
}
if (entry === "supervisor") {
return version?.includes("dev")
return version.includes("dev")
? "https://github.com/home-assistant/supervisor/commits/main"
: `https://github.com/home-assistant/supervisor/releases/tag/${version}`;
}
@ -120,7 +120,7 @@ class UpdateAvailableCard extends LitElement {
return html``;
}
const changelog = changelogUrl(this.hass, this._updateType, this._version);
const changelog = changelogUrl(this._updateType, this._version);
return html`
<ha-card

View File

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

View File

@ -221,6 +221,7 @@ export const DOMAINS_INPUT_ROW = [
"scene",
"script",
"select",
"switch",
];
/** Domains that should have the history hidden in the more info dialog. */

View File

@ -96,7 +96,11 @@ export class HaDateInput extends LitElement {
attr-for-value="value"
.i18n=${i18n}
>
<paper-input .label=${this.label} no-label-float>
<paper-input
.label=${this.label}
.disabled=${this.disabled}
no-label-float
>
<ha-svg-icon slot="suffix" .path=${mdiCalendar}></ha-svg-icon>
</paper-input>
</vaadin-date-picker-light>`;

View File

@ -39,6 +39,7 @@ export class HaPictureUpload extends LitElement {
.uploading=${this._uploading}
.value=${this.value ? html`<img .src=${this.value} />` : ""}
@file-picked=${this._handleFilePicked}
@change=${this._handleFileCleared}
accept="image/png, image/jpeg, image/gif"
></ha-file-upload>
`;
@ -53,6 +54,10 @@ export class HaPictureUpload extends LitElement {
}
}
private async _handleFileCleared() {
this.value = null;
}
private async _cropFile(file: File) {
if (!["image/png", "image/jpeg", "image/gif"].includes(file.type)) {
showAlertDialog(this, {

View File

@ -208,11 +208,11 @@ export const enum NodeStatus {
export interface ZwaveJSProvisioningEntry {
/** The device specific key (DSK) in the form aaaaa-bbbbb-ccccc-ddddd-eeeee-fffff-11111-22222 */
dsk: string;
securityClasses: SecurityClass[];
/**
* Additional properties to be stored in this provisioning entry, e.g. the device ID from a scanned QR code
*/
[prop: string]: any;
security_classes: SecurityClass[];
additional_properties: {
nodeId?: number;
[prop: string]: any;
};
}
export interface RequestedGrant {
@ -278,7 +278,7 @@ export const setZwaveDataCollectionPreference = (
export const fetchZwaveProvisioningEntries = (
hass: HomeAssistant,
entry_id: string
): Promise<any> =>
): Promise<ZwaveJSProvisioningEntry[]> =>
hass.callWS({
type: "zwave_js/get_provisioning_entries",
entry_id,

View File

@ -1,17 +1,27 @@
import "@polymer/paper-input/paper-input";
import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { assert, literal, object, optional, string, union } from "superstruct";
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/entity/ha-entity-attribute-picker";
import "../../../../../components/entity/ha-entity-picker";
import "../../../../../components/ha-duration-input";
import { StateCondition } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import { forDictStruct } from "../../structs";
import {
ConditionElement,
handleChangeEvent,
} from "../ha-automation-condition-row";
import "../../../../../components/ha-duration-input";
import { fireEvent } from "../../../../../common/dom/fire_event";
const stateConditionStruct = object({
condition: literal("state"),
entity_id: string(),
attribute: optional(string()),
state: string(),
for: optional(union([string(), forDictStruct])),
});
@customElement("ha-automation-condition-state")
export class HaStateCondition extends LitElement implements ConditionElement {
@ -23,19 +33,14 @@ export class HaStateCondition extends LitElement implements ConditionElement {
return { entity_id: "", state: "" };
}
public willUpdate(changedProperties: PropertyValues): boolean {
if (
changedProperties.has("condition") &&
Array.isArray(this.condition?.state)
) {
fireEvent(
this,
"ui-mode-not-available",
Error(this.hass.localize("ui.errors.config.no_state_array_support"))
);
// We have to stop the update if state is an array.
// Otherwise the state will be changed to a comma-separated string by the input element.
return false;
public shouldUpdate(changedProperties: PropertyValues) {
if (changedProperties.has("condition")) {
try {
assert(this.condition, stateConditionStruct);
} catch (e: any) {
fireEvent(this, "ui-mode-not-available", e);
return false;
}
}
return true;
}

View File

@ -0,0 +1,8 @@
import { object, optional, number } from "superstruct";
export const forDictStruct = object({
days: optional(number()),
hours: optional(number()),
minutes: optional(number()),
seconds: optional(number()),
});

View File

@ -68,7 +68,7 @@ export const handleChangeEvent = (element: TriggerElement, ev: CustomEvent) => {
}
let newTrigger: Trigger;
if (!newVal) {
if (newVal === undefined || newVal === "") {
newTrigger = { ...element.trigger };
delete newTrigger[name];
} else {

View File

@ -1,19 +1,30 @@
import "@polymer/paper-input/paper-input";
import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { assert, literal, object, optional, string, union } from "superstruct";
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { hasTemplate } from "../../../../../common/string/has-template";
import "../../../../../components/entity/ha-entity-attribute-picker";
import "../../../../../components/entity/ha-entity-picker";
import "../../../../../components/ha-duration-input";
import { StateTrigger } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import "../../../../../components/ha-duration-input";
import { forDictStruct } from "../../structs";
import {
handleChangeEvent,
TriggerElement,
} from "../ha-automation-trigger-row";
const stateTriggerStruct = object({
platform: literal("state"),
entity_id: string(),
attribute: optional(string()),
from: optional(string()),
to: optional(string()),
for: optional(union([string(), forDictStruct])),
});
@customElement("ha-automation-trigger-state")
export class HaStateTrigger extends LitElement implements TriggerElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@ -24,9 +35,9 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
return { entity_id: "" };
}
public willUpdate(changedProperties: PropertyValues) {
public shouldUpdate(changedProperties: PropertyValues) {
if (!changedProperties.has("trigger")) {
return;
return true;
}
// Check for templates in trigger. If found, revert to YAML mode.
if (this.trigger && hasTemplate(this.trigger)) {
@ -35,7 +46,15 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
"ui-mode-not-available",
Error(this.hass.localize("ui.errors.config.no_template_editor_support"))
);
return false;
}
try {
assert(this.trigger, stateTriggerStruct);
} catch (e: any) {
fireEvent(this, "ui-mode-not-available", e);
return false;
}
return true;
}
protected render() {

View File

@ -224,7 +224,7 @@ class HaBlueprintOverview extends LitElement {
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.automations}
.tabs=${configSections.blueprints}
.columns=${this._columns(this.narrow, this.hass.language)}
.data=${this._processedBlueprints(this.blueprints)}
id="entity_id"

View File

@ -1,4 +1,4 @@
import { mdiCellphoneCog, mdiCloudLock } from "@mdi/js";
import { mdiCloudLock } from "@mdi/js";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import {
@ -110,29 +110,10 @@ class HaConfigDashboard extends LitElement {
></ha-config-navigation>
`
: ""}
${this._externalConfig?.hasSettingsScreen
? html`
<ha-config-navigation
.hass=${this.hass}
.narrow=${this.narrow}
.showAdvanced=${this.showAdvanced}
.pages=${[
{
path: "#external-app-configuration",
name: "Companion App",
description: "Location and notifications",
iconPath: mdiCellphoneCog,
iconColor: "#37474F",
core: true,
},
]}
@click=${this._handleExternalAppConfiguration}
></ha-config-navigation>
`
: ""}
<ha-config-navigation
.hass=${this.hass}
.narrow=${this.narrow}
.externalConfig=${this._externalConfig}
.showAdvanced=${this.showAdvanced}
.pages=${configSections.dashboard}
></ha-config-navigation>
@ -142,13 +123,6 @@ class HaConfigDashboard extends LitElement {
`;
}
private _handleExternalAppConfiguration(ev: Event) {
ev.preventDefault();
this.hass.auth.external!.fireMessage({
type: "config_screen/show",
});
}
static get styles(): CSSResultGroup {
return [
haStyle,

View File

@ -6,6 +6,7 @@ import { canShowPage } from "../../../common/config/can_show_page";
import "../../../components/ha-card";
import "../../../components/ha-icon-next";
import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
import { ExternalConfig } from "../../../external_app/external_config";
import { PageNavigation } from "../../../layouts/hass-tabs-subpage";
import { HomeAssistant } from "../../../types";
@ -19,10 +20,16 @@ class HaConfigNavigation extends LitElement {
@property() public pages!: PageNavigation[];
@property() public externalConfig?: ExternalConfig;
protected render(): TemplateResult {
return html`
${this.pages.map((page) =>
canShowPage(this.hass, page)
(
page.path === "#external-app-configuration"
? this.externalConfig?.hasSettingsScreen
: canShowPage(this.hass, page)
)
? html`
<a href=${page.path} aria-role="option" tabindex="-1">
<paper-icon-item @click=${this._entryClicked}>
@ -77,6 +84,16 @@ class HaConfigNavigation extends LitElement {
private _entryClicked(ev) {
ev.currentTarget.blur();
if (
ev.currentTarget.parentElement.href.endsWith(
"#external-app-configuration"
)
) {
ev.preventDefault();
this.hass.auth.external!.fireMessage({
type: "config_screen/show",
});
}
}
static get styles(): CSSResultGroup {

View File

@ -1,6 +1,7 @@
import {
mdiAccount,
mdiBadgeAccountHorizontal,
mdiCellphoneCog,
mdiCog,
mdiDevices,
mdiHomeAssistant,
@ -57,22 +58,22 @@ export const configSections: { [name: string]: PageNavigation[] } = {
{
path: "/config/automation",
name: "Automations & Scenes",
description: "Automations, blueprints, scenes and scripts",
description: "Manage automations, scenes, scripts and helpers",
iconPath: mdiRobot,
iconColor: "#518C43",
components: ["automation", "blueprint", "scene", "script"],
},
{
path: "/config/helpers",
name: "Automation Helpers",
description: "Elements that help build automations",
iconPath: mdiTools,
iconColor: "#4D2EA4",
core: true,
},
{
path: "/config/blueprint",
name: "Blueprints",
description: "Manage blueprints",
iconPath: mdiPaletteSwatch,
iconColor: "#64B5F6",
component: "blueprint",
},
{
path: "/hassio",
name: "Add-ons & Backups (Supervisor)",
name: "Add-ons, Backups & Supervisor",
description: "Create backups, check logs or reboot your system",
iconPath: mdiHomeAssistant,
iconColor: "#4084CD",
@ -111,6 +112,13 @@ export const configSections: { [name: string]: PageNavigation[] } = {
iconColor: "#E48629",
components: ["person", "zone", "users"],
},
{
path: "#external-app-configuration",
name: "Companion App",
description: "Location and notifications",
iconPath: mdiCellphoneCog,
iconColor: "#8E24AA",
},
{
path: "/config/core",
name: "Settings",
@ -155,13 +163,6 @@ export const configSections: { [name: string]: PageNavigation[] } = {
},
],
automations: [
{
component: "blueprint",
path: "/config/blueprint",
translationKey: "ui.panel.config.blueprint.caption",
iconPath: mdiPaletteSwatch,
iconColor: "#518C43",
},
{
component: "automation",
path: "/config/automation",
@ -183,8 +184,6 @@ export const configSections: { [name: string]: PageNavigation[] } = {
iconPath: mdiScriptText,
iconColor: "#518C43",
},
],
helpers: [
{
component: "helpers",
path: "/config/helpers",
@ -194,6 +193,15 @@ export const configSections: { [name: string]: PageNavigation[] } = {
core: true,
},
],
blueprints: [
{
component: "blueprint",
path: "/config/blueprint",
translationKey: "ui.panel.config.blueprint.caption",
iconPath: mdiPaletteSwatch,
iconColor: "#518C43",
},
],
tags: [
{
component: "tag",
@ -447,9 +455,19 @@ class HaPanelConfig extends HassRouterPage {
this.hass.loadBackendTranslation("title");
if (isComponentLoaded(this.hass, "cloud")) {
this._updateCloudStatus();
this.addEventListener("connection-status", (ev) => {
if (ev.detail === "connected") {
this._updateCloudStatus();
}
});
}
if (isComponentLoaded(this.hass, "hassio")) {
this._loadSupervisorUpdates();
this.addEventListener("connection-status", (ev) => {
if (ev.detail === "connected") {
this._loadSupervisorUpdates();
}
});
} else {
this._supervisorUpdates = null;
}

View File

@ -132,7 +132,7 @@ export class HaConfigHelpers extends LitElement {
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.helpers}
.tabs=${configSections.automations}
.columns=${this._columns(this.narrow, this.hass.language)}
.data=${this._getItems(this._stateItems)}
@row-click=${this._openEditDialog}

View File

@ -1,6 +1,7 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { property } from "lit/decorators";
import "../../../layouts/hass-tabs-subpage";
import "../../../components/ha-logo-svg";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
@ -40,13 +41,14 @@ class HaConfigInfo extends LitElement {
href=${documentationUrl(this.hass, "")}
target="_blank"
rel="noreferrer"
><img
src="/static/icons/favicon-192x192.png"
height="192"
alt=${this.hass.localize(
>
<ha-logo-svg
title=${this.hass.localize(
"ui.panel.config.info.home_assistant_logo"
)}
/></a>
>
</ha-logo-svg>
</a>
<br />
<h2>Home Assistant ${hass.connection.haVersion}</h2>
<p>
@ -193,6 +195,11 @@ class HaConfigInfo extends LitElement {
margin: 0 auto;
padding-bottom: 16px;
}
ha-logo-svg {
padding: 12px;
height: 180px;
width: 180px;
}
`,
];
}

View File

@ -45,6 +45,8 @@ export interface ZWaveJSAddNodeDevice {
class DialogZWaveJSAddNode extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: ZWaveJSAddNodeDialogParams;
@state() private _entryId?: string;
@state() private _status?:
@ -91,6 +93,7 @@ class DialogZWaveJSAddNode extends LitElement {
}
public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise<void> {
this._params = params;
this._entryId = params.entry_id;
this._status = "loading";
this._checkSmartStartSupport();
@ -562,6 +565,9 @@ class DialogZWaveJSAddNode extends LitElement {
provisioningInfo
);
this._status = "provisioned";
if (this._params?.addedCallback) {
this._params.addedCallback();
}
} catch (err: any) {
this._error = err.message;
this._status = "failed";
@ -693,6 +699,9 @@ class DialogZWaveJSAddNode extends LitElement {
if (message.event === "interview completed") {
this._unsubscribe();
this._status = "finished";
if (this._params?.addedCallback) {
this._params.addedCallback();
}
}
if (message.event === "interview stage completed") {

View File

@ -2,6 +2,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
export interface ZWaveJSAddNodeDialogParams {
entry_id: string;
addedCallback?: () => void;
}
export const loadAddNodeDialog = () => import("./dialog-zwave_js-add-node");

View File

@ -411,6 +411,7 @@ class ZWaveJSConfigDashboard extends LitElement {
private async _addNodeClicked() {
showZWaveJSAddNodeDialog(this, {
entry_id: this.configEntryId!,
addedCallback: () => this._fetchData(),
});
}

View File

@ -327,6 +327,9 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
if (!("states" in item.metadata)) {
return false;
}
if (Object.keys(item.metadata.states).length !== 2) {
return false;
}
if (!(0 in item.metadata.states) || !(1 in item.metadata.states)) {
return false;
}

View File

@ -1,4 +1,4 @@
import { mdiDelete } from "@mdi/js";
import { mdiCheckCircle, mdiCloseCircleOutline, mdiDelete } from "@mdi/js";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
@ -42,17 +42,42 @@ class ZWaveJSProvisioned extends LitElement {
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({
included: {
title: this.hass.localize(
"ui.panel.config.zwave_js.provisioned.included"
),
type: "icon",
width: "100px",
template: (_info, provisioningEntry: any) =>
provisioningEntry.additional_properties.nodeId
? html`
<ha-svg-icon
.label=${this.hass.localize(
"ui.panel.config.zwave_js.provisioned.included"
)}
.path=${mdiCheckCircle}
></ha-svg-icon>
`
: html`
<ha-svg-icon
.label=${this.hass.localize(
"ui.panel.config.zwave_js.provisioned.not_included"
)}
.path=${mdiCloseCircleOutline}
></ha-svg-icon>
`,
},
dsk: {
title: this.hass.localize("ui.panel.config.zwave_js.provisioned.dsk"),
sortable: true,
filterable: true,
grows: true,
},
securityClasses: {
security_classes: {
title: this.hass.localize(
"ui.panel.config.zwave_js.provisioned.security_classes"
),
width: "15%",
width: "30%",
hidden: narrow,
filterable: true,
sortable: true,
@ -60,7 +85,7 @@ class ZWaveJSProvisioned extends LitElement {
securityClasses
.map((secClass) =>
this.hass.localize(
`ui.panel.config.zwave_js.security_classes.${SecurityClass[secClass]}`
`ui.panel.config.zwave_js.security_classes.${SecurityClass[secClass]}.title`
)
)
.join(", "),
@ -70,6 +95,7 @@ class ZWaveJSProvisioned extends LitElement {
"ui.panel.config.zwave_js.provisioned.unprovison"
),
type: "icon-button",
width: "100px",
template: (_info, provisioningEntry: any) => html`
<ha-icon-button
.label=${this.hass.localize(
@ -97,6 +123,8 @@ class ZWaveJSProvisioned extends LitElement {
}
private _unprovision = async (ev) => {
const dsk = ev.currentTarget.provisioningEntry.dsk;
const confirm = await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.zwave_js.provisioned.confirm_unprovision_title"
@ -113,11 +141,8 @@ class ZWaveJSProvisioned extends LitElement {
return;
}
await unprovisionZwaveSmartStartNode(
this.hass,
this.configEntryId,
ev.currentTarget.provisioningEntry.dsk
);
await unprovisionZwaveSmartStartNode(this.hass, this.configEntryId, dsk);
this._fetchData();
};
}

View File

@ -25,6 +25,7 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import { domainIcon } from "../../../common/entity/domain_icon";
import { navigate } from "../../../common/navigate";
import { formatNumber } from "../../../common/number/format_number";
import { subscribeOne } from "../../../common/util/subscribe-one";
import "../../../components/entity/state-badge";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
@ -83,8 +84,11 @@ export class HuiAreaCard
return document.createElement("hui-area-card-editor");
}
public static getStubConfig(): AreaCardConfig {
return { type: "area", area: "" };
public static async getStubConfig(
hass: HomeAssistant
): Promise<AreaCardConfig> {
const areas = await subscribeOne(hass.connection, subscribeAreaRegistry);
return { type: "area", area: areas[0]?.area_id || "" };
}
@property({ attribute: false }) public hass!: HomeAssistant;
@ -358,12 +362,12 @@ export class HuiAreaCard
});
let cameraEntityId: string | undefined;
if ("camera" in entitiesByDomain) {
if (this._config.show_camera && "camera" in entitiesByDomain) {
cameraEntityId = entitiesByDomain.camera[0].entity_id;
}
return html`
<ha-card class=${area.picture ? "image" : ""}>
<ha-card class=${area.picture || cameraEntityId ? "image" : ""}>
${area.picture || cameraEntityId
? html`<hui-image
.config=${this._config}

View File

@ -79,6 +79,7 @@ export interface EntitiesCardConfig extends LovelaceCardConfig {
export interface AreaCardConfig extends LovelaceCardConfig {
area: string;
navigation_path?: string;
show_camera?: boolean;
}
export interface ButtonCardConfig extends LovelaceCardConfig {

View File

@ -1,7 +1,7 @@
import "@polymer/paper-input/paper-input";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, object, optional, string } from "superstruct";
import { assert, assign, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-area-picker";
import { HomeAssistant } from "../../../../types";
@ -11,6 +11,8 @@ import { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { EditorTarget } from "../types";
import { configElementStyle } from "./config-elements-style";
import "../../../../components/ha-formfield";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
@ -18,6 +20,7 @@ const cardConfigStruct = assign(
area: optional(string()),
navigation_path: optional(string()),
theme: optional(string()),
show_camera: optional(boolean()),
})
);
@ -47,6 +50,10 @@ export class HuiAreaCardEditor
return this._config!.theme || "";
}
get _show_camera(): boolean {
return this._config!.show_camera || false;
}
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
@ -59,9 +66,23 @@ export class HuiAreaCardEditor
.value=${this._area}
.placeholder=${this._area}
.configValue=${"area"}
.label=${this.hass.localize("ui.dialogs.entity_registry.editor.area")}
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.area.name"
)}
@value-changed=${this._valueChanged}
></ha-area-picker>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.area.show_camera"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.checked=${this._show_camera}
.configValue=${"show_camera"}
@change=${this._valueChanged}
></ha-switch>
</ha-formfield>
<paper-input
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.action-editor.navigation_path"
@ -86,7 +107,8 @@ export class HuiAreaCardEditor
return;
}
const target = ev.target! as EditorTarget;
const value = ev.detail.value;
const value =
target.checked !== undefined ? target.checked : ev.detail.value;
if (this[`_${target.configValue}`] === value) {
return;

View File

@ -13,7 +13,7 @@ export const getCardStubConfig = async (
const elClass = await getCardElementClass(type);
if (elClass && elClass.getStubConfig) {
const classStubConfig = elClass.getStubConfig(
const classStubConfig = await elClass.getStubConfig(
hass,
entities,
entitiesFallback

View File

@ -13,7 +13,7 @@ export const getHeaderFooterStubConfig = async (
const elClass = await getHeaderFooterElementClass(type);
if (elClass && elClass.getStubConfig) {
const classStubConfig = elClass.getStubConfig(
const classStubConfig = await elClass.getStubConfig(
hass,
entities,
entitiesFallback

View File

@ -91,6 +91,7 @@ export const coreCards: Card[] = [
},
{
type: "area",
showElement: true,
},
{
type: "conditional",

View File

@ -1,7 +1,7 @@
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { UNAVAILABLE } from "../../../data/entity";
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity";
import { setValue } from "../../../data/input_text";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@ -67,6 +67,12 @@ class HuiInputTextEntityRow extends LitElement implements LovelaceRow {
const element = this._inputEl;
const stateObj = this.hass!.states[this._config!.entity];
// Filter out invalid text states
if (element.value && UNAVAILABLE_STATES.includes(element.value)) {
element.value = stateObj.state;
return;
}
if (element.value !== stateObj.state) {
setValue(this.hass!, stateObj.entity_id, element.value!);
}

View File

@ -95,6 +95,7 @@ export const derivedStyles = {
"mdc-theme-text-disabled-on-light": "var(--disabled-text-color)",
"mdc-theme-text-primary-on-background": "var(--primary-text-color)",
"mdc-theme-text-secondary-on-background": "var(--secondary-text-color)",
"mdc-theme-text-hint-on-background": "var(--secondary-text-color)",
"mdc-theme-text-icon-on-background": "var(--secondary-text-color)",
"mdc-theme-error": "var(--error-color)",
"app-header-text-color": "var(--text-primary-color)",

View File

@ -878,8 +878,7 @@
"key_missing": "Required key ''{key}'' is missing.",
"key_not_expected": "Key ''{key}'' is not expected or not supported by the visual editor.",
"key_wrong_type": "The provided value for ''{key}'' is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong}).",
"no_template_editor_support": "Templates not supported in visual editor",
"no_state_array_support": "Multiple state values not supported in visual editor"
"no_template_editor_support": "Templates not supported in visual editor"
},
"supervisor": {
"title": "Could not load the Supervisor panel!",
@ -1514,7 +1513,7 @@
"extra_fields": {
"above": "Above",
"below": "Below",
"for": "Duration",
"for": "Duration (optional)",
"zone": "[%key:ui::panel::config::automation::editor::triggers::type::zone::label%]"
}
},
@ -1537,9 +1536,9 @@
"state": {
"label": "State",
"attribute": "Attribute (optional)",
"from": "From",
"for": "For",
"to": "To"
"from": "From (optional)",
"for": "For (optional)",
"to": "To (optional)"
},
"homeassistant": {
"label": "Home Assistant",
@ -2897,6 +2896,8 @@
"dsk": "DSK",
"security_classes": "Security classes",
"unprovison": "Unprovison",
"included": "Included",
"not_included": "Not Included",
"confirm_unprovision_title": "Are you sure you want to unprovision the device?",
"confirm_unprovision_text": "If you unprovision the device it will not be added to Home Assistant when it is powered on. If it is already added to Home Assistant, removing the provisioned device will not remove it from Home Assistant."
},
@ -3251,11 +3252,12 @@
"alarm-panel": {
"name": "Alarm Panel",
"available_states": "Available States",
"description": "The Alarm Panel card allows you to Arm and Disarm your alarm control panel integrations."
"description": "The Alarm Panel card allows you to arm and disarm your alarm control panel integrations."
},
"area": {
"name": "Area",
"description": "The Area card automatically displays entities of a specific area."
"description": "The Area card automatically displays entities of a specific area.",
"show_camera": "Show camera feed instead of area picture"
},
"calendar": {
"name": "Calendar",