mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
20230629.0 (#17108)
This commit is contained in:
commit
3563541f8f
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20230628.0"
|
||||
version = "20230629.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@ -50,7 +50,7 @@ export const computeStateDisplay = (
|
||||
entities: HomeAssistant["entities"],
|
||||
state?: string
|
||||
): string => {
|
||||
const entity = entities[stateObj.entity_id] as
|
||||
const entity = entities?.[stateObj.entity_id] as
|
||||
| EntityRegistryDisplayEntry
|
||||
| undefined;
|
||||
|
||||
|
@ -17,6 +17,16 @@ import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
import {
|
||||
fuzzyFilterSort,
|
||||
ScorableTextItem,
|
||||
} from "../../common/string/filter/sequence-matching";
|
||||
|
||||
interface StatisticItem extends ScorableTextItem {
|
||||
id: string;
|
||||
name: string;
|
||||
state?: HassEntity;
|
||||
}
|
||||
|
||||
@customElement("ha-statistic-picker")
|
||||
export class HaStatisticPicker extends LitElement {
|
||||
@ -75,11 +85,11 @@ export class HaStatisticPicker extends LitElement {
|
||||
|
||||
private _init = false;
|
||||
|
||||
private _rowRenderer: ComboBoxLitRenderer<{
|
||||
id: string;
|
||||
name: string;
|
||||
state?: HassEntity;
|
||||
}> = (item) => html`<mwc-list-item graphic="avatar" twoline>
|
||||
private _statistics: StatisticItem[] = [];
|
||||
|
||||
private _rowRenderer: ComboBoxLitRenderer<StatisticItem> = (
|
||||
item
|
||||
) => html`<mwc-list-item graphic="avatar" twoline>
|
||||
${item.state
|
||||
? html`<state-badge slot="graphic" .stateObj=${item.state}></state-badge>`
|
||||
: ""}
|
||||
@ -105,7 +115,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
includeUnitClass?: string | string[],
|
||||
includeDeviceClass?: string | string[],
|
||||
entitiesOnly?: boolean
|
||||
): Array<{ id: string; name: string; state?: HassEntity }> => {
|
||||
): StatisticItem[] => {
|
||||
if (!statisticIds.length) {
|
||||
return [
|
||||
{
|
||||
@ -113,6 +123,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
name: this.hass.localize(
|
||||
"ui.components.statistic-picker.no_statistics"
|
||||
),
|
||||
strings: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -146,26 +157,28 @@ export class HaStatisticPicker extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
const output: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
state?: HassEntity;
|
||||
}> = [];
|
||||
const output: StatisticItem[] = [];
|
||||
statisticIds.forEach((meta) => {
|
||||
const entityState = this.hass.states[meta.statistic_id];
|
||||
if (!entityState) {
|
||||
if (!entitiesOnly) {
|
||||
const id = meta.statistic_id;
|
||||
const name = getStatisticLabel(this.hass, meta.statistic_id, meta);
|
||||
output.push({
|
||||
id: meta.statistic_id,
|
||||
name: getStatisticLabel(this.hass, meta.statistic_id, meta),
|
||||
id,
|
||||
name,
|
||||
strings: [id, name],
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
const id = meta.statistic_id;
|
||||
const name = getStatisticLabel(this.hass, meta.statistic_id, meta);
|
||||
output.push({
|
||||
id: meta.statistic_id,
|
||||
name: getStatisticLabel(this.hass, meta.statistic_id, meta),
|
||||
id,
|
||||
name,
|
||||
state: entityState,
|
||||
strings: [id, name],
|
||||
});
|
||||
});
|
||||
|
||||
@ -174,6 +187,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
{
|
||||
id: "",
|
||||
name: this.hass.localize("ui.components.statistic-picker.no_match"),
|
||||
strings: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -189,6 +203,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
name: this.hass.localize(
|
||||
"ui.components.statistic-picker.missing_entity"
|
||||
),
|
||||
strings: [],
|
||||
});
|
||||
|
||||
return output;
|
||||
@ -216,7 +231,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
) {
|
||||
this._init = true;
|
||||
if (this.hasUpdated) {
|
||||
(this.comboBox as any).items = this._getStatistics(
|
||||
this._statistics = this._getStatistics(
|
||||
this.statisticIds!,
|
||||
this.includeStatisticsUnitOfMeasurement,
|
||||
this.includeUnitClass,
|
||||
@ -225,7 +240,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
);
|
||||
} else {
|
||||
this.updateComplete.then(() => {
|
||||
(this.comboBox as any).items = this._getStatistics(
|
||||
this._statistics = this._getStatistics(
|
||||
this.statisticIds!,
|
||||
this.includeStatisticsUnitOfMeasurement,
|
||||
this.includeUnitClass,
|
||||
@ -248,11 +263,13 @@ export class HaStatisticPicker extends LitElement {
|
||||
.renderer=${this._rowRenderer}
|
||||
.disabled=${this.disabled}
|
||||
.allowCustomValue=${this.allowCustomEntity}
|
||||
.filteredItems=${this._statistics}
|
||||
item-value-path="id"
|
||||
item-id-path="id"
|
||||
item-label-path="name"
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._statisticChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
@ -281,6 +298,14 @@ export class HaStatisticPicker extends LitElement {
|
||||
this._opened = ev.detail.value;
|
||||
}
|
||||
|
||||
private _filterChanged(ev: CustomEvent): void {
|
||||
const target = ev.target as HaComboBox;
|
||||
const filterString = ev.detail.value.toLowerCase();
|
||||
target.filteredItems = filterString.length
|
||||
? fuzzyFilterSort<StatisticItem>(filterString, this._statistics)
|
||||
: this._statistics;
|
||||
}
|
||||
|
||||
private _setValue(value: string) {
|
||||
this.value = value;
|
||||
setTimeout(() => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { mdiMenu } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { subscribeNotifications } from "../data/persistent_notification";
|
||||
@ -17,6 +17,8 @@ class HaMenuButton extends LitElement {
|
||||
|
||||
@state() private _hasNotifications = false;
|
||||
|
||||
@state() private _show = false;
|
||||
|
||||
private _alwaysVisible = false;
|
||||
|
||||
private _attachNotifOnConnect = false;
|
||||
@ -40,7 +42,10 @@ class HaMenuButton extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
protected render() {
|
||||
if (!this._show) {
|
||||
return nothing;
|
||||
}
|
||||
const hasNotifications =
|
||||
this._hasNotifications &&
|
||||
(this.narrow || this.hass.dockedSidebar === "always_hidden");
|
||||
@ -66,8 +71,8 @@ class HaMenuButton extends LitElement {
|
||||
(Number((window.parent as any).frontendVersion) || 0) < 20190710;
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
protected willUpdate(changedProps) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!changedProps.has("narrow") && !changedProps.has("hass")) {
|
||||
return;
|
||||
@ -85,11 +90,11 @@ class HaMenuButton extends LitElement {
|
||||
const showButton =
|
||||
this.narrow || this.hass.dockedSidebar === "always_hidden";
|
||||
|
||||
if (oldShowButton === showButton) {
|
||||
if (this.hasUpdated && oldShowButton === showButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.style.display = showButton || this._alwaysVisible ? "initial" : "none";
|
||||
this._show = showButton || this._alwaysVisible;
|
||||
|
||||
if (!showButton) {
|
||||
if (this._unsubNotifications) {
|
||||
|
@ -81,6 +81,8 @@ class DialogBox extends LitElement {
|
||||
.type=${this._params.inputType
|
||||
? this._params.inputType
|
||||
: "text"}
|
||||
.min=${this._params.inputMin}
|
||||
.max=${this._params.inputMax}
|
||||
></ha-textfield>
|
||||
`
|
||||
: ""}
|
||||
|
@ -26,6 +26,8 @@ export interface PromptDialogParams extends BaseDialogBoxParams {
|
||||
placeholder?: string;
|
||||
confirm?: (out?: string) => void;
|
||||
cancel?: () => void;
|
||||
inputMin?: number | string;
|
||||
inputMax?: number | string;
|
||||
}
|
||||
|
||||
export interface DialogBoxParams
|
||||
|
@ -501,7 +501,7 @@ export class QuickBar extends LitElement {
|
||||
|
||||
private async _generateCommandItems(): Promise<CommandItem[]> {
|
||||
return [
|
||||
...this._generateReloadCommands(),
|
||||
...(await this._generateReloadCommands()),
|
||||
...this._generateServerControlCommands(),
|
||||
...(await this._generateNavigationCommands()),
|
||||
].sort((a, b) =>
|
||||
@ -513,17 +513,22 @@ export class QuickBar extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _generateReloadCommands(): CommandItem[] {
|
||||
private async _generateReloadCommands(): Promise<CommandItem[]> {
|
||||
// Get all domains that have a direct "reload" service
|
||||
const reloadableDomains = componentsWithService(this.hass, "reload");
|
||||
|
||||
const localize = await this.hass.loadBackendTranslation(
|
||||
"title",
|
||||
reloadableDomains
|
||||
);
|
||||
|
||||
const commands = reloadableDomains.map((domain) => ({
|
||||
primaryText:
|
||||
this.hass.localize(`ui.dialogs.quick-bar.commands.reload.${domain}`) ||
|
||||
this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.reload.reload",
|
||||
"domain",
|
||||
domainToName(this.hass.localize, domain)
|
||||
domainToName(localize, domain)
|
||||
),
|
||||
action: () => this.hass.callService(domain, "reload"),
|
||||
iconPath: mdiReload,
|
||||
|
@ -92,7 +92,11 @@ export class HaConversationTrigger
|
||||
|
||||
private async _updateOption(ev: Event) {
|
||||
const index = (ev.target as any).index;
|
||||
const command = [...this.trigger.command];
|
||||
const command = [
|
||||
...(Array.isArray(this.trigger.command)
|
||||
? this.trigger.command
|
||||
: [this.trigger.command]),
|
||||
];
|
||||
command.splice(index, 1, (ev.target as HaTextField).value);
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.trigger, command },
|
||||
|
@ -145,8 +145,15 @@ export class HaConfigDevicePage extends LitElement {
|
||||
);
|
||||
|
||||
private _integrations = memoizeOne(
|
||||
(device: DeviceRegistryEntry, entries: ConfigEntry[]): ConfigEntry[] =>
|
||||
entries.filter((entry) => device.config_entries.includes(entry.entry_id))
|
||||
(device: DeviceRegistryEntry, entries: ConfigEntry[]): ConfigEntry[] => {
|
||||
const entryLookup: { [entryId: string]: ConfigEntry } = {};
|
||||
for (const entry of entries) {
|
||||
entryLookup[entry.entry_id] = entry;
|
||||
}
|
||||
return device.config_entries
|
||||
.map((entry) => entryLookup[entry])
|
||||
.filter(Boolean);
|
||||
}
|
||||
);
|
||||
|
||||
private _entities = memoizeOne(
|
||||
|
@ -1,15 +1,10 @@
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import { mdiBookshelf, mdiCog, mdiDotsVertical, mdiOpenInNew } from "@mdi/js";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import {
|
||||
mdiBookshelf,
|
||||
mdiCog,
|
||||
mdiDotsVertical,
|
||||
mdiEyeOff,
|
||||
mdiOpenInNew,
|
||||
} from "@mdi/js";
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import {
|
||||
ATTENTION_SOURCES,
|
||||
DISCOVERY_SOURCES,
|
||||
@ -17,13 +12,12 @@ import {
|
||||
localizeConfigFlowTitle,
|
||||
} from "../../../data/config_flow";
|
||||
import type { IntegrationManifest } from "../../../data/integration";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import type { DataEntryFlowProgressExtended } from "./ha-config-integrations";
|
||||
import "./ha-integration-action-card";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
|
||||
@customElement("ha-config-flow-card")
|
||||
export class HaConfigFlowCard extends LitElement {
|
||||
@ -38,16 +32,10 @@ export class HaConfigFlowCard extends LitElement {
|
||||
return html`
|
||||
<ha-integration-action-card
|
||||
class=${classMap({
|
||||
discovered: !attention,
|
||||
attention: attention,
|
||||
})}
|
||||
.hass=${this.hass}
|
||||
.manifest=${this.manifest}
|
||||
.banner=${this.hass.localize(
|
||||
`ui.panel.config.integrations.${
|
||||
attention ? "attention" : "discovered"
|
||||
}`
|
||||
)}
|
||||
.domain=${this.flow.handler}
|
||||
.label=${this.flow.localized_title}
|
||||
>
|
||||
@ -60,72 +48,75 @@ export class HaConfigFlowCard extends LitElement {
|
||||
}`
|
||||
)}
|
||||
></mwc-button>
|
||||
<ha-button-menu slot="header-button">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
${this.flow.context.configuration_url
|
||||
? html`<a
|
||||
href=${this.flow.context.configuration_url.replace(
|
||||
/^homeassistant:\/\//,
|
||||
""
|
||||
)}
|
||||
rel="noreferrer"
|
||||
target=${this.flow.context.configuration_url.startsWith(
|
||||
"homeassistant://"
|
||||
)
|
||||
? "_self"
|
||||
: "_blank"}
|
||||
>
|
||||
<mwc-list-item graphic="icon" hasMeta>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.open_configuration_url"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiCog}></ha-svg-icon>
|
||||
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${this.manifest
|
||||
? html`<a
|
||||
href=${this.manifest.is_built_in
|
||||
? documentationUrl(
|
||||
this.hass,
|
||||
`/integrations/${this.manifest.domain}`
|
||||
${DISCOVERY_SOURCES.includes(this.flow.context.source) &&
|
||||
this.flow.context.unique_id
|
||||
? html`<mwc-button
|
||||
@click=${this._ignoreFlow}
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.integrations.ignore.ignore`
|
||||
)}
|
||||
></mwc-button>`
|
||||
: ""}
|
||||
${this.flow.context.configuration_url || this.manifest
|
||||
? html`<ha-button-menu slot="header-button">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
${this.flow.context.configuration_url
|
||||
? html`<a
|
||||
href=${this.flow.context.configuration_url.replace(
|
||||
/^homeassistant:\/\//,
|
||||
""
|
||||
)}
|
||||
rel="noreferrer"
|
||||
target=${this.flow.context.configuration_url.startsWith(
|
||||
"homeassistant://"
|
||||
)
|
||||
: this.manifest.documentation}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<mwc-list-item graphic="icon" hasMeta>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.documentation"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiBookshelf}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${DISCOVERY_SOURCES.includes(this.flow.context.source) &&
|
||||
this.flow.context.unique_id
|
||||
? html`
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
@request-selected=${this._ignoreFlow}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.ignore"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiEyeOff}></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
? "_self"
|
||||
: "_blank"}
|
||||
>
|
||||
<mwc-list-item graphic="icon" hasMeta>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.open_configuration_url"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiCog}></ha-svg-icon>
|
||||
<ha-svg-icon
|
||||
slot="meta"
|
||||
.path=${mdiOpenInNew}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${this.manifest
|
||||
? html`<a
|
||||
href=${this.manifest.is_built_in
|
||||
? documentationUrl(
|
||||
this.hass,
|
||||
`/integrations/${this.manifest.domain}`
|
||||
)
|
||||
: this.manifest.documentation}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<mwc-list-item graphic="icon" hasMeta>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.documentation"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiBookshelf}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon
|
||||
slot="meta"
|
||||
.path=${mdiOpenInNew}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
</ha-button-menu>`
|
||||
: ""}
|
||||
</ha-integration-action-card>
|
||||
`;
|
||||
}
|
||||
@ -174,14 +165,6 @@ export class HaConfigFlowCard extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.attention {
|
||||
--state-color: var(--error-color);
|
||||
--text-on-state-color: var(--text-primary-color);
|
||||
}
|
||||
.discovered {
|
||||
--state-color: var(--primary-color);
|
||||
--text-on-state-color: var(--text-primary-color);
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
|
@ -543,8 +543,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
];
|
||||
if (item.reason) {
|
||||
this.hass.loadBackendTranslation("config", item.domain);
|
||||
stateTextExtra = html`:
|
||||
${this.hass.localize(
|
||||
stateTextExtra = html`${this.hass.localize(
|
||||
`component.${item.domain}.config.error.${item.reason}`
|
||||
) || item.reason}`;
|
||||
} else {
|
||||
|
@ -377,46 +377,57 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
||||
</div>
|
||||
`}
|
||||
${this._showIgnored
|
||||
? html`<div class="container">
|
||||
${ignoredConfigEntries.map(
|
||||
(entry: ConfigEntryExtended) => html`
|
||||
<ha-ignored-config-entry-card
|
||||
.hass=${this.hass}
|
||||
.manifest=${this._manifests[entry.domain]}
|
||||
.entry=${entry}
|
||||
@change=${this._handleFlowUpdated}
|
||||
></ha-ignored-config-entry-card>
|
||||
`
|
||||
)}
|
||||
</div>`
|
||||
? html`<h1>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.ignored"
|
||||
)}
|
||||
</h1>
|
||||
<div class="container">
|
||||
${ignoredConfigEntries.map(
|
||||
(entry: ConfigEntryExtended) => html`
|
||||
<ha-ignored-config-entry-card
|
||||
.hass=${this.hass}
|
||||
.manifest=${this._manifests[entry.domain]}
|
||||
.entry=${entry}
|
||||
@change=${this._handleFlowUpdated}
|
||||
></ha-ignored-config-entry-card>
|
||||
`
|
||||
)}
|
||||
</div>`
|
||||
: ""}
|
||||
${configEntriesInProgress.length
|
||||
? html`<div class="container">
|
||||
${configEntriesInProgress.map(
|
||||
(flow: DataEntryFlowProgressExtended) => html`
|
||||
<ha-config-flow-card
|
||||
.hass=${this.hass}
|
||||
.manifest=${this._manifests[flow.handler]}
|
||||
.flow=${flow}
|
||||
@change=${this._handleFlowUpdated}
|
||||
></ha-config-flow-card>
|
||||
`
|
||||
)}
|
||||
</div>`
|
||||
? html`<h1>
|
||||
${this.hass.localize("ui.panel.config.integrations.discovered")}
|
||||
</h1>
|
||||
<div class="container">
|
||||
${configEntriesInProgress.map(
|
||||
(flow: DataEntryFlowProgressExtended) => html`
|
||||
<ha-config-flow-card
|
||||
.hass=${this.hass}
|
||||
.manifest=${this._manifests[flow.handler]}
|
||||
.flow=${flow}
|
||||
@change=${this._handleFlowUpdated}
|
||||
></ha-config-flow-card>
|
||||
`
|
||||
)}
|
||||
</div>`
|
||||
: ""}
|
||||
${this._showDisabled
|
||||
? html`<div class="container">
|
||||
${disabledConfigEntries.map(
|
||||
(entry: ConfigEntryExtended) => html`
|
||||
<ha-disabled-config-entry-card
|
||||
.hass=${this.hass}
|
||||
.entry=${entry}
|
||||
.manifest=${this._manifests[entry.domain]}
|
||||
.entityRegistryEntries=${this._entityRegistryEntries}
|
||||
></ha-disabled-config-entry-card>
|
||||
`
|
||||
)}
|
||||
</div>`
|
||||
? html`<h1>
|
||||
${this.hass.localize("ui.panel.config.integrations.disabled")}
|
||||
</h1>
|
||||
<div class="container">
|
||||
${disabledConfigEntries.map(
|
||||
(entry: ConfigEntryExtended) => html`
|
||||
<ha-disabled-config-entry-card
|
||||
.hass=${this.hass}
|
||||
.entry=${entry}
|
||||
.manifest=${this._manifests[entry.domain]}
|
||||
.entityRegistryEntries=${this._entityRegistryEntries}
|
||||
></ha-disabled-config-entry-card>
|
||||
`
|
||||
)}
|
||||
</div>`
|
||||
: ""}
|
||||
<div class="container">
|
||||
${integrations.length
|
||||
@ -746,19 +757,18 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
grid-gap: 16px 16px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
grid-gap: 8px 8px;
|
||||
padding: 8px 16px 16px;
|
||||
}
|
||||
.container:last-of-type {
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
.container > * {
|
||||
max-width: 500px;
|
||||
}
|
||||
.empty-message {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
grid-column-start: 1;
|
||||
grid-column-end: -1;
|
||||
}
|
||||
.empty-message h1 {
|
||||
margin-bottom: 0;
|
||||
@ -854,6 +864,9 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
||||
.menu-badge-container {
|
||||
position: relative;
|
||||
}
|
||||
h1 {
|
||||
margin: 8px 0 0 16px;
|
||||
}
|
||||
ha-button-menu {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
@ -1,9 +1,14 @@
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { IntegrationManifest } from "../../../data/integration";
|
||||
import {
|
||||
domainToName,
|
||||
type IntegrationManifest,
|
||||
} from "../../../data/integration";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "./ha-integration-header";
|
||||
import "../../../components/ha-card";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
|
||||
@customElement("ha-integration-action-card")
|
||||
export class HaIntegrationActionCard extends LitElement {
|
||||
@ -22,49 +27,94 @@ export class HaIntegrationActionCard extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card outlined>
|
||||
<ha-integration-header
|
||||
.hass=${this.hass}
|
||||
.banner=${this.banner}
|
||||
.domain=${this.domain}
|
||||
.label=${this.label}
|
||||
.localizedDomainName=${this.localizedDomainName}
|
||||
.manifest=${this.manifest}
|
||||
>
|
||||
<span slot="header-button"><slot name="header-button"></slot></span>
|
||||
</ha-integration-header>
|
||||
<div class="card-content">
|
||||
<img
|
||||
alt=""
|
||||
src=${brandsUrl({
|
||||
domain: this.domain,
|
||||
type: "icon",
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
<h2>${this.label}</h2>
|
||||
<h3>
|
||||
${this.localizedDomainName ||
|
||||
domainToName(this.hass.localize, this.domain, this.manifest)}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="filler"></div>
|
||||
<div class="actions"><slot></slot></div>
|
||||
<div class="card-actions"><slot></slot></div>
|
||||
<div class="header-button"><slot name="header-button"></slot></div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
--ha-card-border-color: var(--state-color);
|
||||
--mdc-theme-primary: var(--state-color);
|
||||
}
|
||||
.filler {
|
||||
flex: 1;
|
||||
}
|
||||
.attention {
|
||||
--state-color: var(--error-color);
|
||||
--text-on-state-color: var(--text-primary-color);
|
||||
}
|
||||
.discovered {
|
||||
--state-color: var(--primary-color);
|
||||
--text-on-state-color: var(--text-primary-color);
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 6px 0;
|
||||
height: 48px;
|
||||
}
|
||||
`;
|
||||
private _onImageLoad(ev) {
|
||||
ev.target.style.visibility = "initial";
|
||||
}
|
||||
|
||||
private _onImageError(ev) {
|
||||
ev.target.style.visibility = "hidden";
|
||||
}
|
||||
|
||||
static styles = [
|
||||
haStyle,
|
||||
css`
|
||||
ha-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
.header-button {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
.filler {
|
||||
flex: 1;
|
||||
}
|
||||
.attention {
|
||||
--state-color: var(--error-color);
|
||||
--text-on-state-color: var(--text-primary-color);
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.card-actions {
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
padding-bottom: 16px;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
:host ::slotted(*) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
:host ::slotted(:last-child) {
|
||||
margin-right: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -97,7 +97,13 @@ export class HaIntegrationCard extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.domain=${this.domain}
|
||||
.localizedDomainName=${this.items[0].localized_domain_name}
|
||||
.banner=${entryState !== "loaded"
|
||||
.error=${ERROR_STATES.includes(entryState)
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.state.${entryState}`
|
||||
)
|
||||
: undefined}
|
||||
.warning=${entryState !== "loaded" &&
|
||||
!ERROR_STATES.includes(entryState)
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.state.${entryState}`
|
||||
)
|
||||
@ -317,9 +323,12 @@ export class HaIntegrationCard extends LitElement {
|
||||
--text-on-state-color: var(--primary-text-color);
|
||||
}
|
||||
.state-not-loaded {
|
||||
opacity: 0.8;
|
||||
--state-color: var(--warning-color);
|
||||
--state-message-color: var(--primary-text-color);
|
||||
}
|
||||
.state-setup {
|
||||
opacity: 0.8;
|
||||
--state-message-color: var(--secondary-text-color);
|
||||
}
|
||||
:host(.highlight) ha-card {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { LitElement, TemplateResult, css, html } from "lit";
|
||||
import { mdiAlertCircleOutline, mdiAlertOutline } from "@mdi/js";
|
||||
import { LitElement, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { IntegrationManifest, domainToName } from "../../../data/integration";
|
||||
@ -9,9 +10,9 @@ import { brandsUrl } from "../../../util/brands-url";
|
||||
export class HaIntegrationHeader extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public banner?: string;
|
||||
@property() public error?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
@property() public warning?: string;
|
||||
|
||||
@property() public localizedDomainName?: string;
|
||||
|
||||
@ -20,25 +21,11 @@ export class HaIntegrationHeader extends LitElement {
|
||||
@property({ attribute: false }) public manifest?: IntegrationManifest;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
let primary: string;
|
||||
let secondary: string | undefined;
|
||||
|
||||
const domainName =
|
||||
this.localizedDomainName ||
|
||||
domainToName(this.hass.localize, this.domain, this.manifest);
|
||||
|
||||
if (this.label) {
|
||||
primary = this.label;
|
||||
secondary =
|
||||
primary.toLowerCase() === domainName.toLowerCase()
|
||||
? undefined
|
||||
: domainName;
|
||||
} else {
|
||||
primary = domainName;
|
||||
}
|
||||
|
||||
return html`
|
||||
${!this.banner ? "" : html`<div class="banner">${this.banner}</div>`}
|
||||
<div class="header">
|
||||
<img
|
||||
alt=""
|
||||
@ -52,8 +39,24 @@ export class HaIntegrationHeader extends LitElement {
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
<div class="info">
|
||||
<div class="primary" role="heading">${primary}</div>
|
||||
<div class="secondary">${secondary}</div>
|
||||
<div
|
||||
class="primary ${this.warning || this.error ? "hasError" : ""}"
|
||||
role="heading"
|
||||
aria-level="1"
|
||||
>
|
||||
${domainName}
|
||||
</div>
|
||||
${this.error
|
||||
? html`<div class="error">
|
||||
<ha-svg-icon .path=${mdiAlertCircleOutline}></ha-svg-icon>${this
|
||||
.error}
|
||||
</div>`
|
||||
: this.warning
|
||||
? html`<div class="warning">
|
||||
<ha-svg-icon .path=${mdiAlertOutline}></ha-svg-icon>${this
|
||||
.warning}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
<div class="header-button">
|
||||
<slot name="header-button"></slot>
|
||||
@ -71,16 +74,6 @@ export class HaIntegrationHeader extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.banner {
|
||||
background-color: var(--state-color);
|
||||
color: var(--text-on-state-color);
|
||||
text-align: center;
|
||||
padding: 2px;
|
||||
|
||||
/* Padding is subtracted for nested elements with border radiuses */
|
||||
border-top-left-radius: calc(var(--ha-card-border-radius, 12px) - 2px);
|
||||
border-top-right-radius: calc(var(--ha-card-border-radius, 12px) - 2px);
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
position: relative;
|
||||
@ -101,29 +94,46 @@ export class HaIntegrationHeader extends LitElement {
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
}
|
||||
.header .info div {
|
||||
.primary,
|
||||
.warning,
|
||||
.error {
|
||||
word-wrap: break-word;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.header-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
}
|
||||
.primary {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
word-break: break-word;
|
||||
color: var(--primary-text-color);
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
.secondary {
|
||||
.hasError {
|
||||
-webkit-line-clamp: 1;
|
||||
font-size: 14px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.warning,
|
||||
.error {
|
||||
line-height: 20px;
|
||||
--mdc-icon-size: 20px;
|
||||
-webkit-line-clamp: 1;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.error ha-svg-icon {
|
||||
margin-right: 4px;
|
||||
color: var(--error-color);
|
||||
}
|
||||
.warning ha-svg-icon {
|
||||
margin-right: 4px;
|
||||
color: var(--warning-color);
|
||||
}
|
||||
.header-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@ -119,6 +119,7 @@ export class ZHAGroupsDashboard extends LitElement {
|
||||
.data=${this._formattedGroups(this._groups)}
|
||||
@row-click=${this._handleRowClicked}
|
||||
clickable
|
||||
hasFab
|
||||
>
|
||||
<a href="/config/zha/group-add" slot="fab">
|
||||
<ha-fab
|
||||
|
@ -419,7 +419,9 @@ class HaPanelDevService extends LitElement {
|
||||
private _serviceChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
this._serviceData = { service: ev.detail.value || "", data: {} };
|
||||
this._response = undefined;
|
||||
this._yamlEditor?.setValue(this._serviceData);
|
||||
this._checkUiSupported();
|
||||
}
|
||||
|
||||
private _fillExampleData() {
|
||||
@ -441,6 +443,7 @@ class HaPanelDevService extends LitElement {
|
||||
});
|
||||
this._serviceData = { ...this._serviceData!, data: example };
|
||||
this._yamlEditor?.setValue(this._serviceData);
|
||||
this._checkUiSupported();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@ -628,14 +628,16 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
lastChangedString(entity) {
|
||||
return formatDateTimeWithSeconds(
|
||||
new Date(entity.last_changed),
|
||||
this.hass.locale
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
);
|
||||
}
|
||||
|
||||
lastUpdatedString(entity) {
|
||||
return formatDateTimeWithSeconds(
|
||||
new Date(entity.last_updated),
|
||||
this.hass.locale
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -240,6 +240,24 @@ export class HuiEnergyGasGraphCard
|
||||
context.parsed.y,
|
||||
locale
|
||||
)} ${unit}`,
|
||||
footer: (contexts) => {
|
||||
if (contexts.length < 2) {
|
||||
return [];
|
||||
}
|
||||
let total = 0;
|
||||
for (const context of contexts) {
|
||||
total += (context.dataset.data[context.dataIndex] as any).y;
|
||||
}
|
||||
if (total === 0) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_gas_graph.total_consumed",
|
||||
{ num: formatNumber(total, locale), unit }
|
||||
),
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
filler: {
|
||||
|
@ -236,6 +236,27 @@ export class HuiEnergySolarGraphCard
|
||||
context.parsed.y,
|
||||
locale
|
||||
)} kWh`,
|
||||
footer: (contexts) => {
|
||||
const production_contexts = contexts.filter(
|
||||
(c) => c.dataset?.stack === "solar"
|
||||
);
|
||||
if (production_contexts.length < 2) {
|
||||
return [];
|
||||
}
|
||||
let total = 0;
|
||||
for (const context of production_contexts) {
|
||||
total += (context.dataset.data[context.dataIndex] as any).y;
|
||||
}
|
||||
if (total === 0) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_solar_graph.total_produced",
|
||||
{ num: formatNumber(total, locale) }
|
||||
),
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
filler: {
|
||||
|
@ -240,6 +240,24 @@ export class HuiEnergyWaterGraphCard
|
||||
context.parsed.y,
|
||||
locale
|
||||
)} ${unit}`,
|
||||
footer: (contexts) => {
|
||||
if (contexts.length < 2) {
|
||||
return [];
|
||||
}
|
||||
let total = 0;
|
||||
for (const context of contexts) {
|
||||
total += (context.dataset.data[context.dataIndex] as any).y;
|
||||
}
|
||||
if (total === 0) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_water_graph.total_consumed",
|
||||
{ num: formatNumber(total, locale), unit }
|
||||
),
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
filler: {
|
||||
|
@ -46,7 +46,7 @@ export class HuiCardOptions extends LitElement {
|
||||
|
||||
@queryAssignedNodes() private _assignedNodes?: NodeListOf<LovelaceCard>;
|
||||
|
||||
@property({ type: Boolean }) public showPosition = false;
|
||||
@property({ type: Boolean }) public hidePosition = false;
|
||||
|
||||
@storage({
|
||||
key: "lovelaceClipboard",
|
||||
@ -82,36 +82,38 @@ export class HuiCardOptions extends LitElement {
|
||||
>
|
||||
<div class="right">
|
||||
<slot name="buttons"></slot>
|
||||
<ha-icon-button
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.move_down"
|
||||
)}
|
||||
.path=${mdiArrowDown}
|
||||
class="move-arrow"
|
||||
@click=${this._cardDown}
|
||||
.disabled=${this.lovelace!.config.views[this.path![0]].cards!
|
||||
.length ===
|
||||
this.path![1] + 1}
|
||||
></ha-icon-button>
|
||||
${this.showPosition
|
||||
? html`<ha-icon-button
|
||||
@click=${this._changeCardPosition}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.change_position"
|
||||
)}
|
||||
>
|
||||
<div class="position-badge">${this.path![1] + 1}</div>
|
||||
</ha-icon-button>`
|
||||
${!this.hidePosition
|
||||
? html`
|
||||
<ha-icon-button
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.move_down"
|
||||
)}
|
||||
.path=${mdiArrowDown}
|
||||
class="move-arrow"
|
||||
@click=${this._cardDown}
|
||||
.disabled=${this.lovelace!.config.views[this.path![0]]
|
||||
.cards!.length ===
|
||||
this.path![1] + 1}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
@click=${this._changeCardPosition}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.change_position"
|
||||
)}
|
||||
>
|
||||
<div class="position-badge">${this.path![1] + 1}</div>
|
||||
</ha-icon-button>
|
||||
<ha-icon-button
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.move_up"
|
||||
)}
|
||||
.path=${mdiArrowUp}
|
||||
class="move-arrow"
|
||||
@click=${this._cardUp}
|
||||
?disabled=${this.path![1] === 0}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
<ha-icon-button
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.move_up"
|
||||
)}
|
||||
.path=${mdiArrowUp}
|
||||
class="move-arrow"
|
||||
@click=${this._cardUp}
|
||||
?disabled=${this.path![1] === 0}
|
||||
></ha-icon-button>
|
||||
<ha-button-menu @action=${this._handleAction}>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@ -288,6 +290,7 @@ export class HuiCardOptions extends LitElement {
|
||||
"ui.panel.lovelace.editor.change_position.text"
|
||||
),
|
||||
inputType: "number",
|
||||
inputMin: "1",
|
||||
placeholder: String(path[1] + 1),
|
||||
});
|
||||
|
||||
|
@ -272,7 +272,6 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
|
||||
wrapper.hass = this.hass;
|
||||
wrapper.lovelace = this.lovelace;
|
||||
wrapper.path = [this.index!, index];
|
||||
wrapper.showPosition = true;
|
||||
card.editMode = true;
|
||||
wrapper.appendChild(card);
|
||||
columnEl.appendChild(wrapper);
|
||||
|
@ -121,6 +121,7 @@ export class PanelView extends LitElement implements LovelaceViewElement {
|
||||
wrapper.hass = this.hass;
|
||||
wrapper.lovelace = this.lovelace;
|
||||
wrapper.path = [this.index!, 0];
|
||||
wrapper.hidePosition = true;
|
||||
card.editMode = true;
|
||||
wrapper.appendChild(card);
|
||||
this._card = wrapper;
|
||||
|
@ -3301,6 +3301,7 @@
|
||||
"description": "Manage integrations with services or devices",
|
||||
"integration": "integration",
|
||||
"discovered": "Discovered",
|
||||
"disabled": "Disabled",
|
||||
"available_integrations": "Available integrations",
|
||||
"new_flow": "Setup another instance of {integration}",
|
||||
"attention": "Attention required",
|
||||
@ -3420,7 +3421,7 @@
|
||||
"loaded": "Loaded",
|
||||
"setup_error": "Failed to set up",
|
||||
"migration_error": "Migration error",
|
||||
"setup_retry": "Retrying setup",
|
||||
"setup_retry": "Failed setup, will retry",
|
||||
"not_loaded": "Not loaded",
|
||||
"failed_unload": "Failed to unload",
|
||||
"setup_in_progress": "Initializing"
|
||||
@ -4312,7 +4313,14 @@
|
||||
},
|
||||
"energy_solar_graph": {
|
||||
"production": "Production {name}",
|
||||
"forecast": "Forecast {name}"
|
||||
"forecast": "Forecast {name}",
|
||||
"total_produced": "Total produced {num} kWh"
|
||||
},
|
||||
"energy_gas_graph": {
|
||||
"total_consumed": "Total consumed {num} {unit}"
|
||||
},
|
||||
"energy_water_graph": {
|
||||
"total_consumed": "[%key:ui::panel::lovelace::cards::energy::energy_gas_graph::total_consumed%]"
|
||||
},
|
||||
"solar_consumed_gauge": {
|
||||
"card_indicates_solar_energy_used": "This card indicates how much of the solar energy you produced was used by your home instead of being returned to the grid.",
|
||||
|
@ -251,7 +251,7 @@ export interface HomeAssistant {
|
||||
callWS<T>(msg: MessageBase): Promise<T>;
|
||||
loadBackendTranslation(
|
||||
category: Parameters<typeof getHassTranslations>[2],
|
||||
integration?: Parameters<typeof getHassTranslations>[3],
|
||||
integrations?: Parameters<typeof getHassTranslations>[3],
|
||||
configFlow?: Parameters<typeof getHassTranslations>[4]
|
||||
): Promise<LocalizeFunc>;
|
||||
loadFragmentTranslation(fragment: string): Promise<LocalizeFunc | undefined>;
|
||||
|
Loading…
x
Reference in New Issue
Block a user