mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Merge branch 'dev' into rc
This commit is contained in:
commit
add0b55657
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -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",
|
||||
});
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -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];
|
||||
|
@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user