20221003.0 (#13979)

This commit is contained in:
Bram Kragten 2022-10-03 20:02:17 +02:00 committed by GitHub
commit 3752336a9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 92 additions and 25 deletions

View File

@ -119,6 +119,7 @@ export class HassioBackups extends LitElement {
(narrow: boolean): DataTableColumnContainer => ({
name: {
title: this.supervisor.localize("backup.name"),
main: true,
sortable: true,
filterable: true,
grows: true,

View File

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

View File

@ -43,6 +43,7 @@ import {
mdiMoleculeCo,
mdiMoleculeCo2,
mdiPalette,
mdiProgressClock,
mdiRayVertex,
mdiRemote,
mdiRobot,
@ -125,6 +126,7 @@ export const FIXED_DEVICE_CLASS_ICONS = {
current: mdiCurrentAc,
date: mdiCalendar,
distance: mdiArrowLeftRight,
duration: mdiProgressClock,
energy: mdiLightningBolt,
frequency: mdiSineWave,
gas: mdiMeterGas,

View File

@ -69,6 +69,7 @@ export interface DataTableSortColumnData {
}
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
main?: boolean;
title: TemplateResult | string;
label?: TemplateResult | string;
type?: "numeric" | "icon" | "icon-button" | "overflow-menu";
@ -406,7 +407,7 @@ export class HaDataTable extends LitElement {
}
return html`
<div
role="cell"
role=${column.main ? "rowheader" : "cell"}
class="mdc-data-table__cell ${classMap({
"mdc-data-table__cell--numeric": column.type === "numeric",
"mdc-data-table__cell--icon": column.type === "icon",

View File

@ -15,6 +15,7 @@ import {
CAMERA_SUPPORT_STREAM,
computeMJPEGStreamUrl,
fetchStreamUrl,
fetchThumbnailUrlWithCache,
STREAM_TYPE_HLS,
STREAM_TYPE_WEB_RTC,
} from "../data/camera";
@ -37,6 +38,9 @@ class HaCameraStream extends LitElement {
@property({ type: Boolean, attribute: "allow-exoplayer" })
public allowExoPlayer = false;
// Video background image before its loaded
@state() private _posterUrl?: string;
// We keep track if we should force MJPEG if there was a failure
// to get the HLS stream url. This is reset if we change entities.
@state() private _forceMJPEG?: string;
@ -51,12 +55,14 @@ class HaCameraStream extends LitElement {
!this._shouldRenderMJPEG &&
this.stateObj &&
(changedProps.get("stateObj") as CameraEntity | undefined)?.entity_id !==
this.stateObj.entity_id &&
this.stateObj!.attributes.frontend_stream_type === STREAM_TYPE_HLS
this.stateObj.entity_id
) {
this._forceMJPEG = undefined;
this._url = undefined;
this._getStreamUrl();
this._getPosterUrl();
if (this.stateObj!.attributes.frontend_stream_type === STREAM_TYPE_HLS) {
this._forceMJPEG = undefined;
this._url = undefined;
this._getStreamUrl();
}
}
}
@ -94,6 +100,7 @@ class HaCameraStream extends LitElement {
.controls=${this.controls}
.hass=${this.hass}
.url=${this._url}
.posterUrl=${this._posterUrl}
></ha-hls-player>`
: html``;
}
@ -105,6 +112,7 @@ class HaCameraStream extends LitElement {
.controls=${this.controls}
.hass=${this.hass}
.entityid=${this.stateObj.entity_id}
.posterUrl=${this._posterUrl}
></ha-web-rtc-player>`;
}
return html``;
@ -129,6 +137,20 @@ class HaCameraStream extends LitElement {
return !isComponentLoaded(this.hass!, "stream");
}
private async _getPosterUrl(): Promise<void> {
try {
this._posterUrl = await fetchThumbnailUrlWithCache(
this.hass!,
this.stateObj!.entity_id,
this.clientWidth,
this.clientHeight
);
} catch (err: any) {
// poster url is optional
this._posterUrl = undefined;
}
}
private async _getStreamUrl(): Promise<void> {
try {
const { url } = await fetchStreamUrl(

View File

@ -297,7 +297,7 @@ export class HaComboBox extends LitElement {
const newValue = ev.detail.value;
if (newValue !== this.value) {
fireEvent(this, "value-changed", { value: newValue });
fireEvent(this, "value-changed", { value: newValue || undefined });
}
}

View File

@ -26,6 +26,7 @@ export class HaFormFloat extends LitElement implements HaFormElement {
protected render(): TemplateResult {
return html`
<ha-textfield
type="numeric"
inputMode="decimal"
.label=${this.label}
.value=${this.data !== undefined ? this.data : ""}
@ -55,6 +56,11 @@ export class HaFormFloat extends LitElement implements HaFormElement {
return;
}
// Allow user to start typing a negative value
if (rawValue === "-") {
return;
}
if (rawValue !== "") {
value = parseFloat(rawValue);
if (isNaN(value)) {

View File

@ -23,6 +23,8 @@ class HaHLSPlayer extends LitElement {
@property() public url!: string;
@property() public posterUrl!: string;
@property({ type: Boolean, attribute: "controls" })
public controls = false;
@ -78,6 +80,7 @@ class HaHLSPlayer extends LitElement {
: ""}
${!this._errorIsFatal
? html`<video
.poster=${this.posterUrl}
?autoplay=${this.autoPlay}
.muted=${this.muted}
?playsinline=${this.playsInline}

View File

@ -86,7 +86,10 @@ export class HaTextField extends TextFieldBase {
text-overflow: ellipsis;
width: inherit;
padding-right: 30px;
padding-inline-end: 30px;
padding-inline-start: initial;
box-sizing: border-box;
direction: var(--direction);
}
input {
@ -118,7 +121,7 @@ export class HaTextField extends TextFieldBase {
inset-inline-end: initial !important;
transform-origin: var(--float-start);
direction: var(--direction);
transform-origin: var(--float-start);
text-align: var(--float-start);
}
.mdc-text-field--with-leading-icon.mdc-text-field--filled

View File

@ -34,6 +34,8 @@ class HaWebRtcPlayer extends LitElement {
@property({ type: Boolean, attribute: "playsinline" })
public playsInline = false;
@property() public posterUrl!: string;
@state() private _error?: string;
// don't cache this, as we remove it on disconnects
@ -54,6 +56,7 @@ class HaWebRtcPlayer extends LitElement {
.muted=${this.muted}
?playsinline=${this.playsInline}
?controls=${this.controls}
.poster=${this.posterUrl}
></video>
`;
}

View File

@ -289,3 +289,6 @@ export const getDisplayUnit = (
? statisticsMetaData?.statistics_unit_of_measurement
: unit;
};
export const isExternalStatistic = (statisticsId: string): boolean =>
statisticsId.includes(":");

View File

@ -610,13 +610,15 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
if (
!(await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.areas.delete.confirmation_title"
"ui.panel.config.areas.delete.confirmation_title",
{ name: entry!.name }
),
text: this.hass.localize(
"ui.panel.config.areas.delete.confirmation_text"
),
dismissText: this.hass.localize("ui.common.cancel"),
confirmText: this.hass.localize("ui.common.delete"),
destructive: true,
}))
) {
return false;

View File

@ -100,6 +100,7 @@ class HaAutomationPicker extends LitElement {
title: this.hass.localize(
"ui.panel.config.automation.picker.headers.name"
),
main: true,
sortable: true,
filterable: true,
direction: "asc",

View File

@ -81,7 +81,11 @@ export class HaManualAutomationEditor extends LitElement {
`
: ""}
${this.config.description
? html`<p class="description">${this.config.description}</p>`
? html`<ha-markdown
class="description"
breaks
.content=${this.config.description}
></ha-markdown>`
: ""}
<div class="header">
<h2 id="triggers-heading" class="name">

View File

@ -291,6 +291,7 @@ export default class HaAutomationTriggerRow extends LitElement {
"ui.panel.config.automation.editor.triggers.id"
)}
.value=${this.trigger.id || ""}
.disabled=${this.disabled}
@change=${this._idChanged}
>
</ha-textfield>

View File

@ -51,6 +51,7 @@ class HaConfigBackup extends LitElement {
(narrow, _language): DataTableColumnContainer => ({
name: {
title: this.hass.localize("ui.panel.config.backup.name"),
main: true,
sortable: true,
filterable: true,
grows: true,

View File

@ -109,6 +109,7 @@ class HaBlueprintOverview extends LitElement {
title: this.hass.localize(
"ui.panel.config.blueprint.overview.headers.name"
),
main: true,
sortable: true,
filterable: true,
direction: "asc",

View File

@ -241,6 +241,7 @@ export class HaConfigDeviceDashboard extends LitElement {
title: this.hass.localize(
"ui.panel.config.devices.data_table.device"
),
main: true,
sortable: true,
filterable: true,
direction: "asc",
@ -258,6 +259,7 @@ export class HaConfigDeviceDashboard extends LitElement {
title: this.hass.localize(
"ui.panel.config.devices.data_table.device"
),
main: true,
sortable: true,
filterable: true,
grows: true,

View File

@ -21,6 +21,7 @@ import type { HaRadio } from "../../../../components/ha-radio";
import {
getStatisticMetadata,
getDisplayUnit,
isExternalStatistic,
} from "../../../../data/recorder";
@customElement("dialog-energy-gas-settings")
@ -86,7 +87,7 @@ export class DialogEnergyGasSettings
: "ft³ or m³");
const externalSource =
this._source.stat_cost && this._source.stat_cost.includes(":");
this._source.stat_cost && isExternalStatistic(this._source.stat_cost);
return html`
<ha-dialog
@ -102,7 +103,10 @@ export class DialogEnergyGasSettings
<ha-statistic-picker
.hass=${this.hass}
.includeUnitClass=${this._params.allowedGasUnitClass}
.includeUnitClass=${this._params.allowedGasUnitClass || [
"volume",
"energy",
]}
.value=${this._source.stat_energy_from}
.label=${`${this.hass.localize(
"ui.panel.config.energy.gas.dialog.gas_usage"
@ -271,7 +275,7 @@ export class DialogEnergyGasSettings
} else {
this._pickedDisplayUnit = undefined;
}
if (ev.detail.value.includes(":") && this._costs !== "statistic") {
if (isExternalStatistic(ev.detail.value) && this._costs !== "statistic") {
this._costs = "no-costs";
}
this._source = {

View File

@ -182,6 +182,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
`,
},
name: {
main: true,
title: this.hass.localize(
"ui.panel.config.entities.picker.headers.name"
),

View File

@ -91,6 +91,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
},
name: {
title: localize("ui.panel.config.helpers.picker.headers.name"),
main: true,
sortable: true,
filterable: true,
grows: true,

View File

@ -510,10 +510,6 @@ class AddIntegrationDialog extends LitElement {
const integrations =
this._integrations![integration.domain].integrations!;
let domains = Object.keys(integrations);
if (integration.iot_standards?.includes("homekit")) {
// if homekit is supported, also fetch the discovered homekit devices
domains.push("homekit_controller");
}
if (integration.domain === "apple") {
// we show discoverd homekit devices in their own brand section, dont show them at apple
domains = domains.filter((domain) => domain !== "homekit_controller");

View File

@ -78,6 +78,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
title: this.hass.localize(
"ui.panel.config.lovelace.dashboards.picker.headers.title"
),
main: true,
sortable: true,
filterable: true,
grows: true,

View File

@ -1,6 +1,7 @@
import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { formatDateNumeric } from "../../../common/datetime/format_date";
import { fireEvent } from "../../../common/dom/fire_event";
import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-markdown";
@ -92,8 +93,9 @@ class DialogRepairsIssue extends LitElement {
</span>
-
${this._issue.created
? new Date(this._issue.created).toLocaleDateString(
this.hass.language
? formatDateNumeric(
new Date(this._issue.created),
this.hass.locale
)
: ""}
</div>

View File

@ -95,6 +95,7 @@ class HaSceneDashboard extends LitElement {
title: this.hass.localize(
"ui.panel.config.scene.picker.headers.name"
),
main: true,
sortable: true,
filterable: true,
direction: "asc",

View File

@ -93,6 +93,7 @@ class HaScriptPicker extends LitElement {
},
name: {
title: this.hass.localize("ui.panel.config.script.picker.headers.name"),
main: true,
sortable: true,
filterable: true,
direction: "asc",

View File

@ -66,6 +66,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
},
display_name: {
title: this.hass.localize("ui.panel.config.tag.headers.name"),
main: true,
sortable: true,
filterable: true,
grows: true,

View File

@ -43,6 +43,7 @@ export class HaConfigUsers extends LitElement {
const columns: DataTableColumnContainer<User> = {
name: {
title: localize("ui.panel.config.users.picker.headers.name"),
main: true,
sortable: true,
filterable: true,
width: "25%",

View File

@ -32,6 +32,7 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { entitiesConfigStruct } from "../structs/entities-struct";
import {
getStatisticMetadata,
isExternalStatistic,
StatisticsMetaData,
statisticsMetaHasType,
} from "../../../../data/recorder";
@ -132,9 +133,8 @@ export class HuiStatisticsGraphCardEditor
disabled:
period === "5minute" &&
// External statistics don't support 5-minute statistics.
// External statistics is formatted as <domain>:<object_id>
statisticIds?.some((statistic_id) =>
statistic_id.includes(":")
isExternalStatistic(statistic_id)
),
})),
},
@ -240,7 +240,9 @@ export class HuiStatisticsGraphCardEditor
private async _entitiesChanged(ev: CustomEvent): Promise<void> {
const config = { ...this._config!, entities: ev.detail.value };
if (
config.entities?.some((statistic_id) => statistic_id.includes(":")) &&
config.entities?.some((statistic_id) =>
isExternalStatistic(statistic_id)
) &&
config.period === "5minute"
) {
delete config.period;

View File

@ -1313,8 +1313,8 @@
"no_linked_entities": "There are no entities linked to this area."
},
"delete": {
"confirmation_title": "Are you sure you want to delete this area?",
"confirmation_text": "This user will be permanently deleted."
"confirmation_title": "Delete {name}?",
"confirmation_text": "This area will be permanently deleted and all devices belonging to this area will become unassigned."
}
},
"backup": {