mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
20221003.0 (#13979)
This commit is contained in:
commit
3752336a9a
@ -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,
|
||||
|
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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(
|
||||
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)) {
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
`;
|
||||
}
|
||||
|
@ -289,3 +289,6 @@ export const getDisplayUnit = (
|
||||
? statisticsMetaData?.statistics_unit_of_measurement
|
||||
: unit;
|
||||
};
|
||||
|
||||
export const isExternalStatistic = (statisticsId: string): boolean =>
|
||||
statisticsId.includes(":");
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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 = {
|
||||
|
@ -182,6 +182,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
`,
|
||||
},
|
||||
name: {
|
||||
main: true,
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.entities.picker.headers.name"
|
||||
),
|
||||
|
@ -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,
|
||||
|
@ -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");
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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%",
|
||||
|
@ -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;
|
||||
|
@ -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": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user