Merge branch 'dev' into rc

This commit is contained in:
Bram Kragten 2024-02-07 11:21:56 +01:00
commit add0b55657
No known key found for this signature in database
GPG Key ID: FBE2DFDB363EF55B
12 changed files with 174 additions and 70 deletions

View File

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

View File

@ -133,9 +133,9 @@ export class HaStateLabelBadge extends LitElement {
entityState,
this._timerTimeRemaining
)}
.description=${this.showName === false
? undefined
: this.name ?? computeStateName(entityState)}
.description=${this.showName
? this.name ?? computeStateName(entityState)
: undefined}
>
${!image && showIcon
? html`<ha-state-icon

View File

@ -144,15 +144,6 @@ export class CloudLogin extends LitElement {
"ui.panel.config.cloud.login.password_error_msg"
)}
></ha-textfield>
<button
class="link pwd-forgot-link"
.disabled=${this._requestInProgress}
@click=${this._handleForgotPassword}
>
${this.hass.localize(
"ui.panel.config.cloud.login.forgot_password"
)}
</button>
</div>
<div class="card-actions">
<ha-progress-button
@ -162,6 +153,15 @@ export class CloudLogin extends LitElement {
"ui.panel.config.cloud.login.sign_in"
)}</ha-progress-button
>
<button
class="link pwd-forgot-link"
.disabled=${this._requestInProgress}
@click=${this._handleForgotPassword}
>
${this.hass.localize(
"ui.panel.config.cloud.login.forgot_password"
)}
</button>
</div>
</ha-card>
@ -311,11 +311,6 @@ export class CloudLogin extends LitElement {
display: flex;
flex-direction: column;
}
.pwd-forgot-link {
color: var(--secondary-text-color) !important;
text-align: right !important;
align-self: flex-end;
}
`,
];
}

View File

@ -1,18 +1,21 @@
import "@material/mwc-button/mwc-button";
import { mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { mdiCloseCircle } from "@mdi/js";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-circular-progress";
import "../../../../../components/ha-qr-code";
import { createCloseHeading } from "../../../../../components/ha-dialog";
import "../../../../../components/ha-qr-code";
import { domainToName } from "../../../../../data/integration";
import {
openMatterCommissioningWindow,
MatterCommissioningParameters,
openMatterCommissioningWindow,
} from "../../../../../data/matter";
import { haStyleDialog } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import { brandsUrl } from "../../../../../util/brands-url";
import { MatterOpenCommissioningWindowDialogParams } from "./show-dialog-matter-open-commissioning-window";
import { copyToClipboard } from "../../../../../common/util/copy-clipboard";
@customElement("dialog-matter-open-commissioning-window")
class DialogMatterOpenCommissioningWindow extends LitElement {
@ -52,28 +55,46 @@ class DialogMatterOpenCommissioningWindow extends LitElement {
${this.hass.localize(
"ui.panel.config.matter.open_commissioning_window.success"
)}
<br />
${this.hass.localize(
"ui.panel.config.matter.open_commissioning_window.scan_code"
)}
</p>
<div class="flex-container">
<ha-svg-icon
.path=${mdiCheckCircle}
class="success"
></ha-svg-icon>
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.matter.open_commissioning_window.sharing_code"
)}: <b>${this._commissionParams.setup_manual_code}</b>
</p>
<div class="sharing-code-container">
<div class="sharing-code">
<img
crossorigin="anonymous"
referrerpolicy="no-referrer"
alt=${domainToName(this.hass.localize, "matter")}
src=${brandsUrl({
domain: "matter",
type: "logo",
darkOptimized: this.hass.themes?.darkMode,
})}
/>
<ha-qr-code
.data=${this._commissionParams.setup_qr_code}
errorCorrectionLevel="quartile"
scale="6"
margin="1"
></ha-qr-code>
<span class="code"
>${this._commissionParams.setup_manual_code.substring(
0,
4
)}-${this._commissionParams.setup_manual_code.substring(
4,
7
)}-${this._commissionParams.setup_manual_code.substring(
7
)}</span
>
</div>
</div>
<ha-qr-code
.data=${this._commissionParams.setup_qr_code}
errorCorrectionLevel="quartile"
scale="6"
></ha-qr-code>
<div></div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.common.close")}
<mwc-button slot="primaryAction" @click=${this._copyCode}>
${this.hass.localize(
"ui.panel.config.matter.open_commissioning_window.copy_code"
)}
</mwc-button>
`
: this._status === "started"
@ -145,9 +166,18 @@ class DialogMatterOpenCommissioningWindow extends LitElement {
}
}
private async _copyCode() {
if (!this._commissionParams) {
return;
}
await copyToClipboard(this._commissionParams.setup_manual_code);
this.closeDialog();
}
public closeDialog(): void {
this.device_id = undefined;
this._status = undefined;
this._commissionParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@ -193,6 +223,30 @@ class DialogMatterOpenCommissioningWindow extends LitElement {
.flex-container ha-svg-icon {
margin-right: 20px;
}
.sharing-code-container {
display: flex;
justify-content: center;
padding-top: 16px;
}
.sharing-code {
display: flex;
flex-direction: column;
align-items: center;
border: 2px solid;
border-radius: 16px;
padding: 16px;
}
.sharing-code img {
width: 160px;
margin-bottom: 8px;
}
.code {
font-family: monospace;
}
`,
];
}

View File

@ -55,6 +55,7 @@ import { haStyle } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import { fileDownload } from "../../util/file_download";
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
import { computeDomain } from "../../common/entity/compute_domain";
class HaPanelHistory extends SubscribeMixin(LitElement) {
@property({ attribute: false }) hass!: HomeAssistant;
@ -657,7 +658,8 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
}
private _downloadHistory() {
const entities = this._getEntityIds();
// Make a copy because getEntityIDs is memoized and sort works in-place
const entities = [...this._getEntityIds()].sort();
if (entities.length === 0 || !this._mungedStateHistory) {
showAlertDialog(this, {
title: this.hass.localize("ui.panel.history.download_data_error"),
@ -667,12 +669,46 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
return;
}
const csv: string[] = ["entity_id,state,last_changed\n"];
const csv: string[] = [""]; // headers will be replaced later.
const headers = ["entity_id", "state", "last_changed"];
const processedDomainAttributes = new Set<string>();
const domainAttributes: Record<string, Record<string, number>> = {
climate: {
current_temperature: 0,
hvac_action: 0,
target_temp_high: 0,
target_temp_low: 0,
temperature: 0,
},
humidifier: {
action: 0,
current_humidity: 0,
humidity: 0,
},
water_heater: {
current_temperature: 0,
operation_mode: 0,
temperature: 0,
},
};
const formatDate = (number) => new Date(number).toISOString();
for (const line of this._mungedStateHistory.line) {
for (const entity of line.data) {
const entityId = entity.entity_id;
const domain = computeDomain(entityId);
const extraAttributes = domainAttributes[domain];
// Add extra attributes to headers if needed
if (extraAttributes && !processedDomainAttributes.has(domain)) {
processedDomainAttributes.add(domain);
let index = headers.length;
for (const attr of Object.keys(extraAttributes)) {
headers.push(attr);
extraAttributes[attr] = index;
index += 1;
}
}
if (entity.statistics) {
for (const s of entity.statistics) {
@ -681,7 +717,19 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
}
for (const s of entity.states) {
csv.push(`${entityId},${s.state},${formatDate(s.last_changed)}\n`);
const lastChanged = formatDate(s.last_changed);
const data = [entityId, s.state, lastChanged];
if (s.attributes && extraAttributes) {
const attrs = s.attributes;
for (const [attr, index] of Object.entries(extraAttributes)) {
if (attr in attrs) {
data[index] = attrs[attr];
}
}
}
csv.push(data.join(",") + "\n");
}
}
}
@ -691,6 +739,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
csv.push(`${entityId},${s.state},${formatDate(s.last_changed)}\n`);
}
}
csv[0] = headers.join(",") + "\n";
const blob = new Blob(csv, {
type: "text/csv",
});

View File

@ -34,7 +34,7 @@ export class HuiStateLabelBadge extends LitElement implements LovelaceBadge {
.name=${this._config.name}
.icon=${this._config.icon}
.image=${this._config.image}
.showName=${this._config.show_name}
.showName=${this._config.show_name ?? true}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action),

View File

@ -17,6 +17,7 @@ export interface StateLabelBadgeConfig extends LovelaceBadgeConfig {
name?: string;
icon?: string;
image?: string;
show_name?: boolean;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;

View File

@ -29,26 +29,25 @@ export class HuiHorizontalStackCard extends HuiStackCard {
display: flex;
height: 100%;
}
#root {
--stack-card-side-margin: 4px;
}
#root > * {
flex: 1 1 0;
margin: var(
--horizontal-stack-card-margin,
var(--stack-card-margin, 0 var(--stack-card-side-margin))
var(--stack-card-margin, 0 4px)
);
min-width: 0;
}
#root > *:first-child {
#root[dir="ltr"] > *:first-child {
margin-left: 0;
margin-inline-start: 0;
margin-inline-end: var(--stack-card-side-margin);
}
#root > *:last-child {
#root[dir="ltr"] > *:last-child {
margin-right: 0;
margin-inline-end: 0;
margin-inline-start: var(--stack-card-side-margin);
}
#root[dir="rtl"] > *:first-child {
margin-right: 0;
}
#root[dir="rtl"] > *:last-child {
margin-left: 0;
}
`,
];

View File

@ -138,10 +138,14 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard {
tabindex="0"
.value=${item}
>
<div>
<ha-svg-icon
.path=${this.computeIcon(item, stateObj.attributes.battery)}
></ha-svg-icon>
<div class="icon">
${item === "battery"
? html`<ha-icon
.icon=${batteryLevelIcon(stateObj.attributes.battery)}
></ha-icon>`
: html`<ha-svg-icon
.path=${SENSOR_ICONS[item]}
></ha-svg-icon>`}
</div>
<div
class=${stateObj.attributes.problem.indexOf(item) === -1
@ -214,9 +218,13 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard {
padding-bottom: 16px;
}
.icon {
margin-bottom: 8px;
}
ha-icon,
ha-svg-icon {
color: var(--paper-item-icon-color);
margin-bottom: 8px;
}
.attributes {
@ -250,13 +258,6 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard {
);
}
private computeIcon(attr: string, batLvl: number): string {
if (attr === "battery") {
return batteryLevelIcon(batLvl);
}
return SENSOR_ICONS[attr];
}
private _handleMoreInfo(ev: Event): void {
const target = ev.currentTarget! as PlantAttributeTarget;
const stateObj = this.hass!.states[this._config!.entity];

View File

@ -12,6 +12,7 @@ import { HomeAssistant } from "../../../types";
import { createCardElement } from "../create-element/create-card-element";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { StackCardConfig } from "./types";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
extends LitElement
@ -77,7 +78,9 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
${this._config.title
? html`<h1 class="card-header">${this._config.title}</h1>`
: ""}
<div id="root">${this._cards}</div>
<div id="root" dir=${this.hass ? computeRTLDirection(this.hass) : "ltr"}>
${this._cards}
</div>
`;
}

View File

@ -58,6 +58,7 @@ export class HuiStateBadgeElement
: this._config.title === null
? ""
: this._config.title}
showName
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action),

View File

@ -4662,8 +4662,9 @@
"start_commissioning": "Share device",
"in_progress": "We're communicating with the device. This may take some time.",
"failed": "The command failed. Additional information may be available in the logs.",
"success": "Your device can now be added to another Matter controller, scan the QR code below or enter the sharing code in the app of the controller you want to add your device to.",
"sharing_code": "Sharing code"
"success": "Your device is ready to be added to another Matter platform.",
"scan_code": "With their app, scan the QR code or enter the sharing code below to finish set up.",
"copy_code": "Copy code"
}
},
"tips": {