20241106.0 (#22695)

This commit is contained in:
Bram Kragten 2024-11-06 14:05:23 +01:00 committed by GitHub
commit ce39b1a2c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 487 additions and 223 deletions

View File

@ -38,12 +38,13 @@ class HassioAddonLogDashboard extends LitElement {
@value-changed=${this._filterChanged}
.hass=${this.hass}
.filter=${this._filter}
.label=${this.hass.localize("ui.panel.config.logs.search")}
.label=${this.supervisor.localize("ui.panel.config.logs.search")}
></search-input>
</div>
<div class="content">
<error-log-card
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.header=${this.addon.name}
.provider=${this.addon.slug}
show

View File

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

View File

@ -12,6 +12,7 @@ import {
query,
state as litState,
} from "lit/decorators";
import { classMap } from "lit/directives/class-map";
interface State {
bold: boolean;
@ -26,12 +27,15 @@ interface State {
export class HaAnsiToHtml extends LitElement {
@property() public content!: string;
@property({ type: Boolean, attribute: "wrap-disabled" }) public wrapDisabled =
false;
@query("pre") private _pre?: HTMLPreElement;
@litState() private _filter = "";
protected render(): TemplateResult | void {
return html`<pre></pre>`;
return html`<pre class=${classMap({ wrap: !this.wrapDisabled })}></pre>`;
}
protected firstUpdated(_changedProperties: PropertyValues): void {
@ -47,9 +51,11 @@ export class HaAnsiToHtml extends LitElement {
return css`
pre {
overflow-x: auto;
margin: 0;
}
pre.wrap {
white-space: pre-wrap;
overflow-wrap: break-word;
margin: 0;
}
.bold {
font-weight: bold;

View File

@ -26,7 +26,10 @@ export class HaTraceLogbook extends LitElement {
.entries=${this.logbookEntries}
.narrow=${this.narrow}
></ha-logbook-renderer>
<hat-logbook-note .domain=${this.trace.domain}></hat-logbook-note>
<hat-logbook-note
.hass=${this.hass}
.domain=${this.trace.domain}
></hat-logbook-note>
`
: html`<div class="padded-box">
No Logbook entries found for this step.

View File

@ -291,7 +291,10 @@ export class HaTracePathDetails extends LitElement {
.entries=${entries}
.narrow=${this.narrow}
></ha-logbook-renderer>
<hat-logbook-note .domain=${this.trace.domain}></hat-logbook-note>
<hat-logbook-note
.hass=${this.hass}
.domain=${this.trace.domain}
></hat-logbook-note>
`
: html`<div class="padded-box">
${this.hass!.localize(

View File

@ -28,7 +28,10 @@ export class HaTraceTimeline extends LitElement {
allowPick
>
</hat-trace-timeline>
<hat-logbook-note .domain=${this.trace.domain}></hat-logbook-note>
<hat-logbook-note
.hass=${this.hass}
.domain=${this.trace.domain}
></hat-logbook-note>
`;
}

View File

@ -1,14 +1,22 @@
import { css, html, LitElement } from "lit";
import { css, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import type { HomeAssistant } from "../../types";
@customElement("hat-logbook-note")
class HatLogbookNote extends LitElement {
@property() public domain = "automation";
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public domain: "automation" | "script" = "automation";
render() {
return html`
Not all shown logbook entries might be related to this ${this.domain}.
`;
if (this.domain === "script") {
return this.hass.localize(
"ui.panel.config.automation.trace.messages.not_all_entries_are_related_script_note"
);
}
return this.hass.localize(
"ui.panel.config.automation.trace.messages.not_all_entries_are_related_automation_note"
);
}
static styles = css`

View File

@ -52,59 +52,61 @@ class MoreInfoUpdate extends LitElement {
return html`
<div class="content">
${this.stateObj.attributes.in_progress
? supportsFeature(this.stateObj, UpdateEntityFeature.PROGRESS) &&
this.stateObj.attributes.update_percentage !== null
? html`<mwc-linear-progress
.progress=${this.stateObj.attributes.update_percentage / 100}
buffer=""
></mwc-linear-progress>`
: html`<mwc-linear-progress indeterminate></mwc-linear-progress>`
: nothing}
<h3>${this.stateObj.attributes.title}</h3>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: nothing}
<div class="row">
<div class="key">
${this.hass.formatEntityAttributeName(
this.stateObj,
"installed_version"
)}
<div class="summary">
${this.stateObj.attributes.in_progress
? supportsFeature(this.stateObj, UpdateEntityFeature.PROGRESS) &&
this.stateObj.attributes.update_percentage !== null
? html`<mwc-linear-progress
.progress=${this.stateObj.attributes.update_percentage / 100}
buffer=""
></mwc-linear-progress>`
: html`<mwc-linear-progress indeterminate></mwc-linear-progress>`
: nothing}
<h3>${this.stateObj.attributes.title}</h3>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: nothing}
<div class="row">
<div class="key">
${this.hass.formatEntityAttributeName(
this.stateObj,
"installed_version"
)}
</div>
<div class="value">
${this.stateObj.attributes.installed_version ??
this.hass.localize("state.default.unavailable")}
</div>
</div>
<div class="value">
${this.stateObj.attributes.installed_version ??
this.hass.localize("state.default.unavailable")}
<div class="row">
<div class="key">
${this.hass.formatEntityAttributeName(
this.stateObj,
"latest_version"
)}
</div>
<div class="value">
${this.stateObj.attributes.latest_version ??
this.hass.localize("state.default.unavailable")}
</div>
</div>
</div>
<div class="row">
<div class="key">
${this.hass.formatEntityAttributeName(
this.stateObj,
"latest_version"
)}
</div>
<div class="value">
${this.stateObj.attributes.latest_version ??
this.hass.localize("state.default.unavailable")}
</div>
</div>
${this.stateObj.attributes.release_url
? html`<div class="row">
<div class="key">
<a
href=${this.stateObj.attributes.release_url}
target="_blank"
rel="noreferrer"
>
${this.hass.localize(
"ui.dialogs.more_info_control.update.release_announcement"
)}
</a>
</div>
</div>`
: nothing}
${this.stateObj.attributes.release_url
? html`<div class="row">
<div class="key">
<a
href=${this.stateObj.attributes.release_url}
target="_blank"
rel="noreferrer"
>
${this.hass.localize(
"ui.dialogs.more_info_control.update.release_announcement"
)}
</a>
</div>
</div>`
: nothing}
</div>
${supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES) &&
!this._error
? this._releaseNotes === undefined
@ -293,6 +295,11 @@ class MoreInfoUpdate extends LitElement {
ha-expansion-panel {
margin: 16px 0;
}
.summary {
margin-bottom: 16px;
}
.row {
margin: 0;
display: flex;
@ -308,7 +315,9 @@ class MoreInfoUpdate extends LitElement {
);
position: sticky;
bottom: 0;
margin: 0 -24px -24px -24px;
margin: 0 -24px 0 -24px;
margin-bottom: calc(-1 * max(env(safe-area-inset-bottom), 24px));
padding-bottom: env(safe-area-inset-bottom);
box-sizing: border-box;
display: flex;
flex-direction: column;

View File

@ -264,6 +264,7 @@ export interface ExternalConfig {
hasAssist: boolean;
hasBarCodeScanner: number;
canSetupImprov: boolean;
downloadFileSupported: boolean;
}
export class ExternalMessaging {

View File

@ -370,7 +370,7 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
}`,
description:
this.hass.localize(
`component.${domain}.services.${service}.description`
`component.${dmn}.services.${service}.description`
) || services[dmn][service]?.description,
});
}

View File

@ -17,19 +17,21 @@ import type { HomeAssistant } from "../../../types";
import { fileDownload } from "../../../util/file_download";
import type { DownloadLogsDialogParams } from "./show-dialog-download-logs";
const DEFAULT_LINE_COUNT = 500;
@customElement("dialog-download-logs")
class DownloadLogsDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: DownloadLogsDialogParams;
@state() private _lineCount = 100;
@state() private _lineCount = DEFAULT_LINE_COUNT;
@query("ha-md-dialog") private _dialogElement!: HaMdDialog;
public showDialog(dialogParams: DownloadLogsDialogParams) {
this._dialogParams = dialogParams;
this._lineCount = this._dialogParams?.defaultLineCount ?? 100;
this._lineCount = this._dialogParams?.defaultLineCount || 500;
}
public closeDialog() {
@ -38,7 +40,7 @@ class DownloadLogsDialog extends LitElement {
private _dialogClosed() {
this._dialogParams = undefined;
this._lineCount = 100;
this._lineCount = DEFAULT_LINE_COUNT;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@ -48,7 +50,7 @@ class DownloadLogsDialog extends LitElement {
}
const numberOfLinesOptions = [100, 500, 1000, 5000, 10000];
if (!numberOfLinesOptions.includes(this._lineCount)) {
if (!numberOfLinesOptions.includes(this._lineCount) && this._lineCount) {
numberOfLinesOptions.push(this._lineCount);
numberOfLinesOptions.sort((a, b) => a - b);
}
@ -63,7 +65,7 @@ class DownloadLogsDialog extends LitElement {
.path=${mdiClose}
></ha-icon-button>
<span slot="title" id="dialog-light-color-favorite-title">
${this.hass.localize("ui.panel.config.logs.download_full_log")}
${this.hass.localize("ui.panel.config.logs.download_logs")}
</span>
<span slot="subtitle">
${this._dialogParams.header}${this._dialogParams.boot === 0
@ -95,7 +97,7 @@ class DownloadLogsDialog extends LitElement {
<ha-button @click=${this.closeDialog}>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button @click=${this._dowloadLogs}>
<ha-button @click=${this._downloadLogs}>
${this.hass.localize("ui.common.download")}
</ha-button>
</div>
@ -103,7 +105,7 @@ class DownloadLogsDialog extends LitElement {
`;
}
private async _dowloadLogs() {
private async _downloadLogs() {
const provider = this._dialogParams!.provider;
const boot = this._dialogParams!.boot;

View File

@ -1,9 +1,16 @@
import "@material/mwc-list/mwc-list-item";
import type { ActionDetail } from "@material/mwc-list";
import {
mdiArrowCollapseDown,
mdiDotsVertical,
mdiCircle,
mdiDownload,
mdiFormatListNumbered,
mdiMenuDown,
mdiRefresh,
mdiWrap,
mdiWrapDisabled,
} from "@mdi/js";
import {
css,
@ -31,6 +38,8 @@ import "../../../components/chips/ha-assist-chip";
import "../../../components/ha-menu";
import "../../../components/ha-md-menu-item";
import "../../../components/ha-md-divider";
import "../../../components/ha-button-menu";
import "../../../components/ha-list-item";
import { getSignedPath } from "../../../data/auth";
@ -40,10 +49,14 @@ import {
fetchHassioBoots,
fetchHassioLogs,
fetchHassioLogsFollow,
getHassioLogDownloadLinesUrl,
getHassioLogDownloadUrl,
} from "../../../data/hassio/supervisor";
import type { HomeAssistant } from "../../../types";
import { fileDownload } from "../../../util/file_download";
import {
downloadFileSupported,
fileDownload,
} from "../../../util/file_download";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import type { ConnectionStatus } from "../../../data/connection-status";
import { atLeastVersion } from "../../../common/config/version";
@ -51,6 +64,7 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { debounce } from "../../../common/util/debounce";
import { showDownloadLogsDialog } from "./show-dialog-download-logs";
import type { HaMenu } from "../../../components/ha-menu";
import type { LocalizeFunc } from "../../../common/translations/localize";
const NUMBER_OF_LINES = 100;
@ -58,6 +72,8 @@ const NUMBER_OF_LINES = 100;
class ErrorLogCard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public localizeFunc?: LocalizeFunc<any>;
@property() public filter = "";
@property() public header?: string;
@ -109,30 +125,42 @@ class ErrorLogCard extends LitElement {
@state() private _boots?: number[];
@state() private _showBootsSelect = false;
@state() private _wrapLines = true;
@state() private _downloadSupported;
@state() private _logsFileLink;
protected render(): TemplateResult {
const localize = this.localizeFunc || this.hass.localize;
return html`
<div class="error-log-intro">
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
: nothing}
<ha-card outlined class=${classMap({ hidden: this.show === false })}>
<div class="header">
<h1 class="card-header">
${this.header ||
this.hass.localize("ui.panel.config.logs.show_full_logs")}
${this.header || localize("ui.panel.config.logs.show_full_logs")}
</h1>
<div class="action-buttons">
${this._streamSupported && Array.isArray(this._boots)
${this._streamSupported &&
Array.isArray(this._boots) &&
this._showBootsSelect
? html`
<ha-assist-chip
.title=${localize(
"ui.panel.config.logs.haos_boots_title"
)}
.label=${this._boot === 0
? this.hass.localize("ui.panel.config.logs.current")
? localize("ui.panel.config.logs.current")
: this._boot === -1
? this.hass.localize("ui.panel.config.logs.previous")
: this.hass.localize(
"ui.panel.config.logs.startups_ago",
{ boot: this._boot * -1 }
)}
? localize("ui.panel.config.logs.previous")
: localize("ui.panel.config.logs.startups_ago", {
boot: this._boot * -1,
})}
id="boots-anchor"
@click=${this._toggleBootsMenu}
>
@ -154,14 +182,10 @@ class ErrorLogCard extends LitElement {
.selected=${boot === this._boot}
>
${boot === 0
? this.hass.localize(
"ui.panel.config.logs.current"
)
? localize("ui.panel.config.logs.current")
: boot === -1
? this.hass.localize(
"ui.panel.config.logs.previous"
)
: this.hass.localize(
? localize("ui.panel.config.logs.previous")
: localize(
"ui.panel.config.logs.startups_ago",
{ boot: boot * -1 }
)}
@ -176,20 +200,61 @@ class ErrorLogCard extends LitElement {
</ha-menu>
`
: nothing}
${this._downloadSupported
? html`
<ha-icon-button
.path=${mdiDownload}
@click=${this._downloadLogs}
.label=${localize("ui.panel.config.logs.download_logs")}
></ha-icon-button>
`
: this._logsFileLink
? html`
<a
href=${this._logsFileLink}
target="_blank"
class="download-link"
>
<ha-icon-button
.path=${mdiDownload}
.label=${localize(
"ui.panel.config.logs.download_logs"
)}
></ha-icon-button>
</a>
`
: nothing}
<ha-icon-button
.path=${mdiDownload}
@click=${this._downloadFullLog}
.label=${this.hass.localize(
"ui.panel.config.logs.download_full_log"
.path=${this._wrapLines ? mdiWrapDisabled : mdiWrap}
@click=${this._toggleLineWrap}
.label=${localize(
`ui.panel.config.logs.${this._wrapLines ? "full_width" : "wrap_lines"}`
)}
></ha-icon-button>
${!this._streamSupported || this._error
? html`<ha-icon-button
.path=${mdiRefresh}
@click=${this._loadLogs}
.label=${this.hass.localize("ui.common.refresh")}
.label=${localize("ui.common.refresh")}
></ha-icon-button>`
: nothing}
${this._streamSupported && Array.isArray(this._boots)
? html`
<ha-button-menu @action=${this._handleOverflowAction}>
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
</ha-icon-button>
<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiFormatListNumbered}
></ha-svg-icon>
${localize(
`ui.panel.config.logs.${this._showBootsSelect ? "hide" : "show"}_haos_boots`
)}
</ha-list-item>
</ha-button-menu>
`
: nothing}
</div>
</div>
<div class="card-content error-log">
@ -202,25 +267,22 @@ class ErrorLogCard extends LitElement {
</div>`
: nothing}
${this._loadingState === "loading"
? html`<div>
${this.hass.localize("ui.panel.config.logs.loading_log")}
</div>`
? html`<div>${localize("ui.panel.config.logs.loading_log")}</div>`
: this._loadingState === "empty"
? html`<div>
${this.hass.localize("ui.panel.config.logs.no_errors")}
</div>`
? html`<div>${localize("ui.panel.config.logs.no_errors")}</div>`
: nothing}
${this._loadingState === "loaded" &&
this.filter &&
this._noSearchResults
? html`<div>
${this.hass.localize(
"ui.panel.config.logs.no_issues_search",
{ term: this.filter }
)}
${localize("ui.panel.config.logs.no_issues_search", {
term: this.filter,
})}
</div>`
: nothing}
<ha-ansi-to-html></ha-ansi-to-html>
<ha-ansi-to-html
?wrap-disabled=${!this._wrapLines}
></ha-ansi-to-html>
<div id="scroll-bottom-marker"></div>
</div>
<ha-button
@ -236,24 +298,36 @@ class ErrorLogCard extends LitElement {
.path=${mdiArrowCollapseDown}
slot="icon"
></ha-svg-icon>
${this.hass.localize("ui.panel.config.logs.scroll_down_button")}
${localize("ui.panel.config.logs.scroll_down_button")}
<ha-svg-icon
.path=${mdiArrowCollapseDown}
slot="trailingIcon"
></ha-svg-icon>
</ha-button>
${this._streamSupported &&
this._loadingState !== "loading" &&
!this._error
? html`<div class="live-indicator">
<ha-svg-icon path=${mdiCircle}></ha-svg-icon>
Live
</div>`
: nothing}
</ha-card>
${this.show === false
? html`
<ha-button outlined @click=${this._downloadFullLog}>
<ha-svg-icon .path=${mdiDownload}></ha-svg-icon>
${this.hass.localize("ui.panel.config.logs.download_full_log")}
</ha-button>
${this._downloadSupported
? html`
<ha-button outlined @click=${this._downloadLogs}>
<ha-svg-icon .path=${mdiDownload}></ha-svg-icon>
${localize("ui.panel.config.logs.download_logs")}
</ha-button>
`
: nothing}
<mwc-button raised @click=${this._showLogs}>
${this.hass.localize("ui.panel.config.logs.load_logs")}
${localize("ui.panel.config.logs.load_logs")}
</mwc-button>
`
: ""}
: nothing}
</div>
`;
}
@ -268,6 +342,9 @@ class ErrorLogCard extends LitElement {
11
);
}
if (this._downloadSupported === undefined && this.hass) {
this._downloadSupported = downloadFileSupported(this.hass);
}
}
protected firstUpdated(changedProps: PropertyValues) {
@ -331,7 +408,7 @@ class ErrorLogCard extends LitElement {
);
}
private async _downloadFullLog(): Promise<void> {
private async _downloadLogs(): Promise<void> {
if (this._streamSupported) {
showDownloadLogsDialog(this, {
header: this.header,
@ -378,6 +455,18 @@ class ErrorLogCard extends LitElement {
isComponentLoaded(this.hass, "hassio") &&
this.provider
) {
// check if there are any logs at all
const testResponse = await fetchHassioLogs(
this.hass,
this.provider,
`entries=:-1:`,
this._boot
);
const testLogs = await testResponse.text();
if (!testLogs.trim()) {
this._loadingState = "empty";
}
const response = await fetchHassioLogsFollow(
this.hass,
this.provider,
@ -438,6 +527,17 @@ class ErrorLogCard extends LitElement {
} else {
this._newLogsIndicator = true;
}
if (!this._downloadSupported) {
const downloadUrl = getHassioLogDownloadLinesUrl(
this.provider,
this._numberOfLines,
this._boot
);
getSignedPath(this.hass, downloadUrl).then((signedUrl) => {
this._logsFileLink = signedUrl.path;
});
}
}
}
} else {
@ -461,10 +561,13 @@ class ErrorLogCard extends LitElement {
if (err.name === "AbortError") {
return;
}
this._error = this.hass.localize("ui.panel.config.logs.failed_get_logs", {
provider: this.provider,
error: extractApiErrorMessage(err),
});
this._error = (this.localizeFunc || this.hass.localize)(
"ui.panel.config.logs.failed_get_logs",
{
provider: this.provider,
error: extractApiErrorMessage(err),
}
);
}
}
@ -585,6 +688,18 @@ class ErrorLogCard extends LitElement {
}
}
private _toggleLineWrap() {
this._wrapLines = !this._wrapLines;
}
private _handleOverflowAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._showBootsSelect = !this._showBootsSelect;
break;
}
}
private _toggleBootsMenu() {
if (this._bootsMenu) {
this._bootsMenu.open = !this._bootsMenu.open;
@ -597,6 +712,9 @@ class ErrorLogCard extends LitElement {
}
static styles: CSSResultGroup = css`
:host {
direction: var(--direction);
}
.error-log-intro {
text-align: center;
margin: 16px;
@ -646,7 +764,7 @@ class ErrorLogCard extends LitElement {
position: relative;
font-family: var(--code-font-family, monospace);
clear: both;
text-align: left;
text-align: start;
padding-top: 12px;
padding-bottom: 12px;
overflow-y: scroll;
@ -713,6 +831,36 @@ class ErrorLogCard extends LitElement {
--ha-assist-chip-container-shape: 10px;
--md-assist-chip-trailing-space: 8px;
}
@keyframes breathe {
from {
opacity: 0.8;
}
to {
opacity: 0;
}
}
.live-indicator {
position: absolute;
bottom: 0;
inset-inline-end: 16px;
border-top-right-radius: 8px;
border-top-left-radius: 8px;
background-color: var(--primary-color);
color: var(--text-primary-color);
padding: 4px 8px;
opacity: 0.8;
}
.live-indicator ha-svg-icon {
animation: breathe 1s cubic-bezier(0.5, 0, 1, 1) infinite alternate;
height: 14px;
width: 14px;
}
.download-link {
color: var(--text-color);
}
`;
}

View File

@ -1,11 +1,11 @@
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../components/ha-card";
import type { HomeAssistant } from "../../../../types";
import { hasConfigChanged } from "../../common/has-changed";
import "../../components/hui-energy-period-selector";
import "../../../../components/ha-card";
import type { LovelaceCard, LovelaceLayoutOptions } from "../../types";
import type { LovelaceCard, LovelaceGridOptions } from "../../types";
import type { EnergyCardBaseConfig } from "../types";
@customElement("hui-energy-date-selection-card")
@ -21,10 +21,10 @@ export class HuiEnergyDateSelectionCard
return 1;
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_rows: 1,
grid_columns: 4,
rows: 1,
columns: 12,
};
}

View File

@ -45,7 +45,7 @@ import "../components/hui-warning";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { AreaCardConfig } from "./types";
@ -534,10 +534,11 @@ export class HuiAreaCard
forwardHaptic("light");
}
getLayoutOptions(): LovelaceLayoutOptions {
getGridOptions(): LovelaceGridOptions {
return {
grid_columns: 4,
grid_rows: 3,
columns: 12,
rows: 3,
min_columns: 3,
};
}

View File

@ -46,7 +46,7 @@ import { createEntityNotFoundWarning } from "../components/hui-warning";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { ButtonCardConfig } from "./types";
@ -134,20 +134,23 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
);
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
if (
this._config?.show_icon &&
(this._config?.show_name || this._config?.show_state)
) {
return {
grid_rows: 2,
grid_columns: 2,
grid_min_rows: 2,
rows: 2,
columns: 6,
min_columns: 2,
min_rows: 2,
};
}
return {
grid_rows: 1,
grid_columns: 1,
rows: 1,
columns: 3,
min_columns: 2,
min_rows: 1,
};
}

View File

@ -33,8 +33,8 @@ import { createEntityNotFoundWarning } from "../components/hui-warning";
import { createHeaderFooterElement } from "../create-element/create-header-footer-element";
import type {
LovelaceCard,
LovelaceGridOptions,
LovelaceHeaderFooter,
LovelaceLayoutOptions,
} from "../types";
import type { HuiErrorCard } from "./hui-error-card";
import type { EntityCardConfig } from "./types";
@ -249,12 +249,12 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_columns: 2,
grid_rows: 2,
grid_min_columns: 2,
grid_min_rows: 2,
columns: 6,
rows: 2,
min_columns: 6,
min_rows: 2,
};
}

View File

@ -16,7 +16,7 @@ import "../heading-badges/hui-heading-badge";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { HeadingCardConfig } from "./types";
@ -65,10 +65,11 @@ export class HuiHeadingCard extends LitElement implements LovelaceCard {
return 1;
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_columns: "full",
grid_rows: this._config?.heading_style === "subtitle" ? "auto" : 1,
columns: "full",
rows: this._config?.heading_style === "subtitle" ? "auto" : 1,
min_columns: 3,
};
}

View File

@ -19,7 +19,7 @@ import { createEntityNotFoundWarning } from "../components/hui-warning";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { HumidifierCardConfig } from "./types";
@ -171,21 +171,21 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
`;
}
public getLayoutOptions(): LovelaceLayoutOptions {
const grid_columns = 4;
let grid_rows = 5;
let grid_min_rows = 2;
const grid_min_columns = 2;
public getGridOptions(): LovelaceGridOptions {
const columns = 12;
let rows = 5;
let min_rows = 2;
const min_columns = 6;
if (this._config?.features?.length) {
const featureHeight = Math.ceil((this._config.features.length * 2) / 3);
grid_rows += featureHeight;
grid_min_rows += featureHeight;
rows += featureHeight;
min_rows += featureHeight;
}
return {
grid_columns,
grid_rows,
grid_min_rows,
grid_min_columns,
columns,
rows,
min_columns,
min_rows,
};
}

View File

@ -11,7 +11,7 @@ import { IFRAME_SANDBOX } from "../../../util/iframe";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { IframeCardConfig } from "./types";
@ -113,11 +113,12 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
`;
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_columns: "full",
grid_rows: 4,
grid_min_rows: 2,
columns: "full",
rows: 4,
min_columns: 3,
min_rows: 2,
};
}

View File

@ -11,8 +11,8 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { deepEqual } from "../../../common/util/deep-equal";
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
import "../../../components/ha-card";
import "../../../components/ha-alert";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import "../../../components/map/ha-map";
import type {
@ -23,15 +23,15 @@ import type {
} from "../../../components/map/ha-map";
import type { HistoryStates } from "../../../data/history";
import { subscribeHistoryStatesTimeWindow } from "../../../data/history";
import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities";
import {
hasConfigChanged,
hasConfigOrEntitiesChanged,
} from "../common/has-changed";
import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities";
import { processConfigEntities } from "../common/process-config-entities";
import type { EntityConfig } from "../entity-rows/types";
import type { LovelaceCard, LovelaceLayoutOptions } from "../types";
import type { LovelaceCard, LovelaceGridOptions } from "../types";
import type { MapCardConfig } from "./types";
export const DEFAULT_HOURS_TO_SHOW = 0;
@ -431,12 +431,12 @@ class HuiMapCard extends LitElement implements LovelaceCard {
}
);
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_columns: "full",
grid_rows: 4,
grid_min_columns: 2,
grid_min_rows: 2,
columns: "full",
rows: 4,
min_columns: 6,
min_rows: 2,
};
}

View File

@ -6,7 +6,7 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities";
import type { GraphHeaderFooterConfig } from "../header-footer/types";
import type { LovelaceCardEditor, LovelaceLayoutOptions } from "../types";
import type { LovelaceCardEditor, LovelaceGridOptions } from "../types";
import { HuiEntityCard } from "./hui-entity-card";
import type { EntityCardConfig, SensorCardConfig } from "./types";
@ -73,12 +73,12 @@ class HuiSensorCard extends HuiEntityCard {
super.setConfig(entityCardConfig);
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_columns: 2,
grid_rows: 2,
grid_min_columns: 2,
grid_min_rows: 2,
columns: 6,
rows: 2,
min_columns: 6,
min_rows: 2,
};
}

View File

@ -26,7 +26,7 @@ import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceHeaderFooter,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { HuiErrorCard } from "./hui-error-card";
import type { EntityCardConfig, StatisticCardConfig } from "./types";
@ -249,12 +249,12 @@ export class HuiStatisticCard extends LitElement implements LovelaceCard {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_columns: 2,
grid_rows: 2,
grid_min_columns: 2,
grid_min_rows: 2,
columns: 6,
rows: 2,
min_columns: 6,
min_rows: 2,
};
}

View File

@ -19,7 +19,7 @@ import { createEntityNotFoundWarning } from "../components/hui-warning";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { ThermostatCardConfig } from "./types";
@ -163,21 +163,21 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
`;
}
public getLayoutOptions(): LovelaceLayoutOptions {
const grid_columns = 4;
let grid_rows = 5;
let grid_min_rows = 2;
const grid_min_columns = 2;
public getGridOptions(): LovelaceGridOptions {
const columns = 12;
let rows = 5;
let min_rows = 2;
const min_columns = 6;
if (this._config?.features?.length) {
const featureHeight = Math.ceil((this._config.features.length * 2) / 3);
grid_rows += featureHeight;
grid_min_rows += featureHeight;
rows += featureHeight;
min_rows += featureHeight;
}
return {
grid_columns,
grid_rows,
grid_min_rows,
grid_min_columns,
columns,
rows,
min_columns,
min_rows,
};
}

View File

@ -34,7 +34,7 @@ import { hasAction } from "../common/has-action";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import { renderTileBadge } from "./tile/badges/tile-badge";
import type { ThermostatCardConfig, TileCardConfig } from "./types";
@ -109,22 +109,22 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
);
}
public getLayoutOptions(): LovelaceLayoutOptions {
const grid_columns = 2;
let grid_min_columns = 2;
let grid_rows = 1;
public getGridOptions(): LovelaceGridOptions {
const columns = 6;
let min_columns = 6;
let rows = 1;
if (this._config?.features?.length) {
grid_rows += this._config.features.length;
rows += this._config.features.length;
}
if (this._config?.vertical) {
grid_rows++;
grid_min_columns = 1;
rows++;
min_columns = 3;
}
return {
grid_columns,
grid_rows,
grid_min_rows: grid_rows,
grid_min_columns,
columns,
rows,
min_columns,
min_rows: rows,
};
}

View File

@ -34,7 +34,7 @@ import { createEntityNotFoundWarning } from "../components/hui-warning";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { WeatherForecastCardConfig } from "./types";
@ -418,31 +418,31 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
return typeof item !== "undefined" && item !== null;
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
if (
this._config?.show_current !== false &&
this._config?.show_forecast !== false
) {
return {
grid_columns: 4,
grid_min_columns: 2,
grid_rows: 4,
grid_min_rows: 4,
columns: 12,
rows: 4,
min_columns: 6,
min_rows: 4,
};
}
if (this._config?.show_forecast !== false) {
return {
grid_columns: 4,
grid_min_columns: 2,
grid_rows: 3,
grid_min_rows: 3,
columns: 12,
rows: 3,
min_columns: 6,
min_rows: 3,
};
}
return {
grid_columns: 4,
grid_min_columns: 2,
grid_rows: 2,
grid_min_rows: 2,
columns: 12,
rows: 2,
min_columns: 6,
min_rows: 2,
};
}

View File

@ -233,7 +233,7 @@ export class HuiCardEditMode extends LitElement {
}
private _handleAction(ev) {
switch (ev.target.action) {
switch (ev.currentTarget.action) {
case "edit":
this._editCard();
break;

View File

@ -2492,10 +2492,15 @@
"show_full_logs": "Show full logs",
"select_number_of_lines": "Select number of lines to download",
"lines": "Lines",
"download_full_log": "Download full log",
"download_logs": "Download logs",
"scroll_down_button": "New logs - Click to scroll",
"provider_not_found": "Log provider not found",
"provider_not_available": "Logs for ''{provider}'' are not available on your system.",
"haos_boots_title": "Logs of HAOS startup",
"show_haos_boots": "Show HAOS startups",
"hide_haos_boots": "Hide HAOS startups",
"full_width": "Full width",
"wrap_lines": "Wrap lines",
"current": "Current",
"previous": "Previous",
"startups_ago": "{boot} startups ago",
@ -3581,7 +3586,9 @@
"stopped_unknown_reason": "Stopped because of unknown reason {reason} at {time} (runtime: {executiontime} seconds)",
"disabled": "(disabled)",
"triggered_by": "{triggeredBy, select, \n alias {{alias} triggered}\n other {Triggered} \n} {triggeredPath, select, \n trigger {by the {trigger}}\n other {manually} \n} at {time}",
"path_error": "Unable to extract path {path}. Download trace and report as bug."
"path_error": "Unable to extract path {path}. Download trace and report as bug.",
"not_all_entries_are_related_automation_note": "Not all shown logbook entries might be related to this automation.",
"not_all_entries_are_related_script_note": "Not all shown logbook entries might be related to this script."
}
}
},
@ -7848,6 +7855,62 @@
"restore": "[%key:ui::components::data-table::settings::restore%]"
}
}
},
"panel": {
"config": {
"logs": {
"caption": "[%key:ui::panel::config::logs::caption%]",
"description": "[%key:ui::panel::config::logs::description%]",
"details": "[%key:ui::panel::config::logs::details%]",
"search": "[%key:ui::panel::config::logs::search%]",
"failed_get_logs": "[%key:ui::panel::config::logs::failed_get_logs%]",
"no_issues_search": "[%key:ui::panel::config::logs::no_issues_search%]",
"load_logs": "[%key:ui::panel::config::logs::load_logs%]",
"nr_of_lines": "[%key:ui::panel::config::logs::nr_of_lines%]",
"loading_log": "[%key:ui::panel::config::logs::loading_log%]",
"no_errors": "[%key:ui::panel::config::logs::no_errors%]",
"no_issues": "[%key:ui::panel::config::logs::no_issues%]",
"clear": "[%key:ui::panel::config::logs::clear%]",
"refresh": "[%key:ui::panel::config::logs::refresh%]",
"copy": "[%key:ui::panel::config::logs::copy%]",
"log_provider": "[%key:ui::panel::config::logs::log_provider%]",
"multiple_messages": "[%key:ui::panel::config::logs::multiple_messages%]",
"level": {
"critical": "[%key:ui::panel::config::logs::level::critical%]",
"error": "[%key:ui::panel::config::logs::level::error%]",
"warning": "[%key:ui::panel::config::logs::level::warning%]",
"info": "[%key:ui::panel::config::logs::level::info%]",
"debug": "[%key:ui::panel::config::logs::level::debug%]"
},
"custom_integration": "[%key:ui::panel::config::logs::custom_integration%]",
"error_from_custom_integration": "[%key:ui::panel::config::logs::error_from_custom_integration%]",
"show_full_logs": "[%key:ui::panel::config::logs::show_full_logs%]",
"select_number_of_lines": "[%key:ui::panel::config::logs::select_number_of_lines%]",
"lines": "[%key:ui::panel::config::logs::lines%]",
"download_logs": "[%key:ui::panel::config::logs::download_logs%]",
"scroll_down_button": "[%key:ui::panel::config::logs::scroll_down_button%]",
"provider_not_found": "[%key:ui::panel::config::logs::provider_not_found%]",
"provider_not_available": "[%key:ui::panel::config::logs::provider_not_available%]",
"haos_boots_title": "[%key:ui::panel::config::logs::haos_boots_title%]",
"show_haos_boots": "[%key:ui::panel::config::logs::show_haos_boots%]",
"hide_haos_boots": "[%key:ui::panel::config::logs::hide_haos_boots%]",
"full_width": "[%key:ui::panel::config::logs::full_width%]",
"wrap_lines": "[%key:ui::panel::config::logs::wrap_lines%]",
"current": "[%key:ui::panel::config::logs::current%]",
"previous": "[%key:ui::panel::config::logs::previous%]",
"startups_ago": "[%key:ui::panel::config::logs::startups_ago%]",
"detail": {
"logger": "[%key:ui::panel::config::logs::detail::logger%]",
"source": "[%key:ui::panel::config::logs::detail::source%]",
"integration": "[%key:ui::panel::config::integrations::integration%]",
"documentation": "[%key:ui::panel::config::logs::detail::documentation%]",
"issues": "[%key:ui::panel::config::logs::detail::issues%]",
"first_occurred": "[%key:ui::panel::config::logs::detail::first_occurred%]",
"occurrences": "[%key:ui::panel::config::logs::detail::occurrences%]",
"last_logged": "[%key:ui::panel::config::logs::detail::last_logged%]"
}
}
}
}
}
}

View File

@ -1,3 +1,6 @@
import type { HomeAssistant } from "../types";
import { isIosApp } from "./is_ios";
export const fileDownload = (href: string, filename = ""): void => {
const a = document.createElement("a");
a.target = "_blank";
@ -8,3 +11,6 @@ export const fileDownload = (href: string, filename = ""): void => {
a.dispatchEvent(new MouseEvent("click"));
document.body.removeChild(a);
};
export const downloadFileSupported = (hass: HomeAssistant): boolean =>
!isIosApp(hass) || !!hass.auth.external?.config.downloadFileSupported;

5
src/util/is_ios.ts Normal file
View File

@ -0,0 +1,5 @@
import type { HomeAssistant } from "../types";
import { isSafari } from "./is_safari";
export const isIosApp = (hass: HomeAssistant): boolean =>
isSafari && !!hass.auth.external;