Compare commits

..

4 Commits

Author SHA1 Message Date
Paul Bottein
a1d69eb94f Add should regenerate for view and section 2026-03-02 14:20:19 +01:00
Paul Bottein
7193102634 Rename method 2026-03-02 14:20:19 +01:00
Paul Bottein
003511bc9c Check for floors 2026-03-02 14:20:19 +01:00
Paul Bottein
c7f583461c Add should update method to strategies 2026-03-02 14:20:19 +01:00
37 changed files with 367 additions and 486 deletions

View File

@@ -118,6 +118,8 @@ export class HaDataTable extends LitElement {
@property({ type: Boolean }) public clickable = false;
@property({ attribute: "has-fab", type: Boolean }) public hasFab = false;
/**
* Add an extra row at the bottom of the data table
* @type {TemplateResult}
@@ -517,6 +519,7 @@ export class HaDataTable extends LitElement {
this._filteredData,
localize,
this.appendRow,
this.hasFab,
this.groupColumn,
this.groupOrder,
this._collapsedGroups,
@@ -713,13 +716,14 @@ export class HaDataTable extends LitElement {
data: DataTableRowData[],
localize: LocalizeFunc,
appendRow,
hasFab: boolean,
groupColumn: string | undefined,
groupOrder: string[] | undefined,
collapsedGroups: string[],
sortColumn: string | undefined,
sortDirection: SortingDirection
) => {
if (appendRow || groupColumn) {
if (appendRow || hasFab || groupColumn) {
let items = [...data];
if (groupColumn) {
@@ -809,11 +813,13 @@ export class HaDataTable extends LitElement {
items.push({ append: true, selectable: false, content: appendRow });
}
items.push({ empty: true });
if (hasFab) {
items.push({ empty: true });
}
return items;
}
return [...data, { empty: true }];
return data;
}
);
@@ -865,6 +871,7 @@ export class HaDataTable extends LitElement {
this._filteredData,
this.localizeFunc || this.hass.localize,
this.appendRow,
this.hasFab,
this.groupColumn,
this.groupOrder,
this._collapsedGroups,
@@ -1082,8 +1089,11 @@ export class HaDataTable extends LitElement {
}
.mdc-data-table__row.empty-row {
height: var(
--data-table-empty-row-height,
height: max(
var(
--data-table-empty-row-height,
var(--data-table-row-height, 52px)
),
var(--safe-area-inset-bottom, 0px)
);
}

View File

@@ -84,9 +84,6 @@ export class HaCodeEditor extends ReactiveElement {
@property({ type: Boolean, attribute: "disable-fullscreen" })
public disableFullscreen = false;
@property({ type: Boolean, attribute: "in-dialog" })
public inDialog = false;
@property({ type: Boolean, attribute: "has-toolbar" })
public hasToolbar = true;
@@ -135,7 +132,6 @@ export class HaCodeEditor extends ReactiveElement {
public connectedCallback() {
super.connectedCallback();
this.classList.toggle("in-dialog", this.inDialog);
// Force update on reconnection so editor is recreated
if (this.hasUpdated) {
this.requestUpdate();
@@ -154,7 +150,6 @@ export class HaCodeEditor extends ReactiveElement {
}
public disconnectedCallback() {
fireEvent(this, "dialog-set-fullscreen", false);
super.disconnectedCallback();
this.removeEventListener("keydown", stopPropagation);
this.removeEventListener("keydown", this._handleKeyDown);
@@ -221,9 +216,6 @@ export class HaCodeEditor extends ReactiveElement {
if (changedProps.has("error")) {
this.classList.toggle("error-state", this.error);
}
if (changedProps.has("inDialog")) {
this.classList.toggle("in-dialog", this.inDialog);
}
if (changedProps.has("_isFullscreen")) {
this.classList.toggle("fullscreen", this._isFullscreen);
this._updateToolbarButtons();
@@ -442,19 +434,10 @@ export class HaCodeEditor extends ReactiveElement {
private _updateFullscreenState(
fullscreen: boolean = this._isFullscreen
): boolean {
const previousFullscreen = this._isFullscreen;
this.classList.toggle("in-dialog", this.inDialog);
// Update the current fullscreen state based on selected value. If fullscreen
// is disabled, or we have no toolbar, ensure we are not in fullscreen mode.
this._isFullscreen =
fullscreen && !this.disableFullscreen && this.hasToolbar;
if (previousFullscreen !== this._isFullscreen) {
fireEvent(this, "dialog-set-fullscreen", this._isFullscreen);
}
// Return whether successfully in requested state
return this._isFullscreen === fullscreen;
}
@@ -863,10 +846,10 @@ export class HaCodeEditor extends ReactiveElement {
:host(.fullscreen) {
position: fixed !important;
top: calc(var(--header-height, 56px) + var(--ha-space-2)) !important;
left: var(--ha-space-2) !important;
right: var(--ha-space-2) !important;
bottom: var(--ha-space-2) !important;
top: calc(var(--header-height, 56px) + 8px) !important;
left: 8px !important;
right: 8px !important;
bottom: 8px !important;
z-index: 6;
border-radius: var(--ha-border-radius-lg) !important;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important;
@@ -884,17 +867,6 @@ export class HaCodeEditor extends ReactiveElement {
display: block !important;
}
:host(.in-dialog.fullscreen) {
position: absolute !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
border-radius: 0 !important;
box-shadow: none !important;
padding: 0 !important;
}
:host(.hasToolbar) .cm-editor {
padding-top: var(--code-editor-toolbar-height);
}

View File

@@ -10,9 +10,7 @@ import {
state,
} from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import type { HASSDomEvent } from "../common/dom/fire_event";
import { fireEvent } from "../common/dom/fire_event";
import { withViewTransition } from "../common/util/view-transition";
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant } from "../types";
@@ -129,14 +127,6 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
private _escapePressed = false;
public connectedCallback(): void {
super.connectedCallback();
this.addEventListener(
"dialog-set-fullscreen",
this._handleFullscreenChanged as EventListener
);
}
protected get scrollableElement(): HTMLElement | null {
return this.bodyContainer;
}
@@ -243,36 +233,15 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
private _handleAfterHide = (ev: DialogHideEvent) => {
if (ev.eventPhase === Event.AT_TARGET) {
this._open = false;
this._setFullscreen(false);
fireEvent(this, "closed");
}
};
public disconnectedCallback(): void {
this.removeEventListener(
"dialog-set-fullscreen",
this._handleFullscreenChanged as EventListener
);
this._setFullscreen(false);
super.disconnectedCallback();
this._open = false;
}
private _handleFullscreenChanged(ev: HASSDomEvent<boolean>): void {
if (!this._open) {
this._setFullscreen(ev.detail);
return;
}
withViewTransition(() => {
this._setFullscreen(ev.detail);
});
}
private _setFullscreen(fullscreen: boolean): void {
this.toggleAttribute("fullscreen", fullscreen);
}
@eventOptions({ passive: true })
private _handleBodyScroll(ev: Event) {
this._bodyScrolled = (ev.target as HTMLDivElement).scrollTop > 0;
@@ -338,27 +307,10 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
--width: min(var(--ha-dialog-width-lg, 1024px), var(--full-width));
}
:host([width="full"]) wa-dialog,
:host([fullscreen]) wa-dialog {
:host([width="full"]) wa-dialog {
--width: var(--full-width);
}
:host([fullscreen]) wa-dialog::part(dialog) {
min-height: var(--safe-height);
max-height: var(--safe-height);
margin-top: 0;
transform: none;
}
:host([fullscreen]) .content-wrapper {
overflow: hidden;
}
:host([fullscreen]) .body {
overflow: hidden;
padding: 0;
}
wa-dialog::part(dialog) {
-webkit-backdrop-filter: var(
--ha-dialog-surface-backdrop-filter,
@@ -519,7 +471,6 @@ declare global {
}
interface HASSDomEvents {
"dialog-set-fullscreen": boolean;
opened: undefined;
"after-show": undefined;
closed: undefined;

View File

@@ -47,9 +47,6 @@ export class HaYamlEditor extends LitElement {
@property({ type: Boolean, attribute: "disable-fullscreen" })
public disableFullscreen = false;
@property({ type: Boolean, attribute: "in-dialog" })
public inDialog = false;
@property({ type: Boolean }) public required = false;
@property({ attribute: "copy-clipboard", type: Boolean })
@@ -104,13 +101,6 @@ export class HaYamlEditor extends LitElement {
}
}
public disableCodeEditorFullscreen(): void {
this.disableFullscreen = true;
if (this._codeEditor) {
this._codeEditor.disableFullscreen = true;
}
}
protected render() {
if (this._yaml === undefined) {
return nothing;
@@ -124,7 +114,6 @@ export class HaYamlEditor extends LitElement {
.value=${this._yaml}
.readOnly=${this.readOnly}
.disableFullscreen=${this.disableFullscreen}
.inDialog=${this.inDialog}
mode="yaml"
autocomplete-entities
autocomplete-icons

View File

@@ -61,7 +61,6 @@ class HaMoreInfoDetails extends LitElement {
.value=${yamlData}
read-only
auto-update
in-dialog
></ha-yaml-editor>`
: html`
<section class="section">

View File

@@ -119,8 +119,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
@query(".content") private _contentElement?: HTMLDivElement;
@query("ha-adaptive-dialog") private _dialogElement?: HTMLElement;
@state() private _entityId?: string | null;
@state() private _data?: Record<string, any>;
@@ -179,10 +177,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
}
public closeDialog() {
const dialog = this._dialogElement?.shadowRoot?.querySelector("ha-dialog");
if (dialog) {
fireEvent(dialog as HTMLElement, "dialog-set-fullscreen", false);
}
this._open = false;
}
@@ -260,11 +254,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
private _goBack() {
if (this._childView) {
const dialog =
this._dialogElement?.shadowRoot?.querySelector("ha-dialog");
if (dialog) {
fireEvent(dialog as HTMLElement, "dialog-set-fullscreen", false);
}
this._childView = undefined;
this._detailsYamlMode = false;
return;
@@ -331,10 +320,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
}
private _toggleDetailsYamlMode() {
const dialog = this._dialogElement?.shadowRoot?.querySelector("ha-dialog");
if (dialog) {
fireEvent(dialog as HTMLElement, "dialog-set-fullscreen", false);
}
this._detailsYamlMode = !this._detailsYamlMode;
}
@@ -764,21 +749,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
const previousChildView = changedProps.get("_childView") as
| ChildView
| undefined;
if (
previousChildView?.viewTag === "ha-more-info-details" &&
this._childView?.viewTag !== "ha-more-info-details"
) {
const dialog =
this._dialogElement?.shadowRoot?.querySelector("ha-dialog");
if (dialog) {
fireEvent(dialog as HTMLElement, "dialog-set-fullscreen", false);
}
}
if (changedProps.has("_currView")) {
this._childView = undefined;
this._infoEditMode = false;

View File

@@ -12,11 +12,10 @@ import {
mdiUnfoldLessHorizontal,
mdiUnfoldMoreHorizontal,
} from "@mdi/js";
import type { TemplateResult, PropertyValues } from "lit";
import type { TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { canShowPage } from "../common/config/can_show_page";
import { fireEvent } from "../common/dom/fire_event";
import type { LocalizeFunc } from "../common/translations/localize";
import "../components/chips/ha-assist-chip";
@@ -84,15 +83,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
* Do we need to add padding for a fab.
* @type {Boolean}
*/
@property({ attribute: "has-fab", type: Boolean, reflect: true })
public hasFab = false;
/**
* Show tabs on top or at bottom (narrow) of the page.
* @type {Boolean}
*/
@property({ attribute: "show-tabs", type: Boolean, reflect: true })
public showTabs = false;
@property({ attribute: "has-fab", type: Boolean }) public hasFab = false;
/**
* Add an extra row at the bottom of the data table
@@ -209,19 +200,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
this._dataTable.clearSelection();
}
protected willUpdate(changedProperties: PropertyValues) {
if (
changedProperties.has("tabs") ||
(changedProperties.has("hass") &&
(this.hass?.config.components !==
changedProperties.get("hass")?.config.components ||
this.hass?.userData?.showAdvanced !==
changedProperties.get("hass")?.userData?.showAdvanced))
) {
this.showTabs =
this.tabs.filter((page) => canShowPage(this.hass, page)).length > 1;
}
protected willUpdate() {
if (this.hasUpdated) {
return;
}
@@ -512,6 +491,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
.noDataText=${this.noDataText}
.filter=${this.filter}
.selectable=${this._selectMode}
.hasFab=${this.hasFab}
.id=${this.id}
.clickable=${this.clickable}
.appendRow=${this.appendRow}
@@ -733,7 +713,6 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
width: 100%;
height: 100%;
--data-table-border-width: 0;
--data-table-empty-row-height: var(--safe-area-inset-bottom, 0px);
}
:host(:not([narrow])) ha-data-table,
.pane {
@@ -746,23 +725,6 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
);
display: block;
}
/* Last content row should keep the same padding above the fab as the fab
has to the bottom (16px standard fab bottom padding) + the safe-area inset. */
:host([has-fab]) ha-data-table {
--data-table-empty-row-height: calc(
48px + 16px * 2 + var(--safe-area-inset-bottom, 0px)
);
}
/* In narrow view with tabs shown at the bottom, the tab bar already
accounts for safe-area-inset-bottom. No extra empty-row height is needed. */
:host([narrow][show-tabs]:not([has-fab])) ha-data-table {
--data-table-empty-row-height: 0px;
}
/* Reserve space for fab + doubled narrow-mode bottom padding (28px * 2)
when using narrow layout with bottom tabs. */
:host([narrow][show-tabs][has-fab]) ha-data-table {
--data-table-empty-row-height: calc(48px + 28px * 2);
}
.pane-content {
height: calc(

View File

@@ -37,7 +37,7 @@ export interface PageNavigation {
}
@customElement("hass-tabs-subpage")
export class HassTabsSubpage extends LitElement {
class HassTabsSubpage extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public localizeFunc?: LocalizeFunc;
@@ -65,14 +65,6 @@ export class HassTabsSubpage extends LitElement {
*/
@property({ type: Boolean, attribute: "has-fab" }) public hasFab = false;
/**
* Whether tabs are shown (2 or more tabs visible).
* When both, show-tabs and narrow are true, tabs are shown as bottom bar.
* @type {Boolean}
*/
@property({ type: Boolean, attribute: "show-tabs", reflect: true })
public showTabs = false;
@state() private _activeTab?: PageNavigation;
// @ts-ignore
@@ -91,7 +83,6 @@ export class HassTabsSubpage extends LitElement {
const shownTabs = tabs.filter((page) => canShowPage(this.hass, page));
if (shownTabs.length < 2) {
this.showTabs = false;
if (shownTabs.length === 1) {
const page = shownTabs[0];
return [
@@ -101,7 +92,6 @@ export class HassTabsSubpage extends LitElement {
return [""];
}
this.showTabs = true;
return shownTabs.map(
(page) => html`
<a href=${page.path} @click=${this._tabClicked}>
@@ -145,6 +135,7 @@ export class HassTabsSubpage extends LitElement {
this.narrow,
this.localizeFunc || this.hass.localize
);
const showTabs = tabs.length > 1;
return html`
<div class="toolbar ${classMap({ narrow: this.narrow })}">
<slot name="toolbar">
@@ -169,12 +160,12 @@ export class HassTabsSubpage extends LitElement {
@click=${this._backTapped}
></ha-icon-button-arrow-prev>
`}
${this.narrow || !this.showTabs
${this.narrow || !showTabs
? html`<div class="main-title">
<slot name="header">${!this.showTabs ? tabs[0] : ""}</slot>
<slot name="header">${!showTabs ? tabs[0] : ""}</slot>
</div>`
: ""}
${this.showTabs && !this.narrow
${showTabs && !this.narrow
? html`<div id="tabbar">${tabs}</div>`
: ""}
<div id="toolbar-icon">
@@ -182,11 +173,13 @@ export class HassTabsSubpage extends LitElement {
</div>
</div>
</slot>
${this.showTabs && this.narrow
${showTabs && this.narrow
? html`<div id="tabbar" class="bottom-bar">${tabs}</div>`
: ""}
</div>
<div class="container">
<div
class=${classMap({ container: true, tabs: showTabs && this.narrow })}
>
${this.pane
? html`<div class="pane">
<div class="shadow-container"></div>
@@ -195,12 +188,15 @@ export class HassTabsSubpage extends LitElement {
</div>
</div>`
: nothing}
<div class="content ha-scrollbar" @scroll=${this._saveScrollPos}>
<div
class="content ha-scrollbar ${classMap({ tabs: showTabs })}"
@scroll=${this._saveScrollPos}
>
<slot></slot>
${this.hasFab ? html`<div class="fab-bottom-space"></div>` : nothing}
</div>
</div>
<div id="fab">
<div id="fab" class=${classMap({ tabs: showTabs })}>
<slot name="fab"></slot>
</div>
`;
@@ -377,7 +373,7 @@ export class HassTabsSubpage extends LitElement {
margin-left: var(--safe-area-inset-left);
margin-inline-start: var(--safe-area-inset-left);
}
:host([narrow][show-tabs]) .content {
:host([narrow]) .content.tabs {
/* Bottom bar reuses header height */
margin-bottom: calc(
var(--header-height, 0px) + var(--safe-area-inset-bottom, 0px)
@@ -388,7 +384,7 @@ export class HassTabsSubpage extends LitElement {
height: calc(64px + var(--safe-area-inset-bottom, 0px));
}
:host([narrow][show-tabs]) .content .fab-bottom-space {
:host([narrow]) .content.tabs .fab-bottom-space {
height: calc(80px + var(--safe-area-inset-bottom, 0px));
}
@@ -404,7 +400,7 @@ export class HassTabsSubpage extends LitElement {
justify-content: flex-end;
gap: var(--ha-space-2);
}
:host([narrow][show-tabs]) #fab {
:host([narrow]) #fab.tabs {
bottom: calc(84px + var(--safe-area-inset-bottom, 0px));
}
#fab[is-wide] {

View File

@@ -2,10 +2,10 @@ import type { LitElement, PropertyValues } from "lit";
import { property, state } from "lit/decorators";
import type { LocalizeFunc } from "../common/translations/localize";
import { computeLocalize } from "../common/translations/localize";
import { computeDirectionStyles } from "../common/util/compute_rtl";
import { translationMetadata } from "../resources/translations-metadata";
import type { Constructor, Resources } from "../types";
import { getLocalLanguage, getTranslation } from "../util/common-translation";
import { translationMetadata } from "../resources/translations-metadata";
import { computeDirectionStyles } from "../common/util/compute_rtl";
const empty = () => "";
@@ -28,16 +28,16 @@ export const litLocalizeLiteMixin = <T extends Constructor<LitElement>>(
this._initializeLocalizeLite();
}
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
computeDirectionStyles(
translationMetadata.translations[this.language!].isRTL,
this
);
}
protected willUpdate(changedProperties: PropertyValues) {
super.willUpdate(changedProperties);
if (!this.updated || changedProperties.has("language")) {
computeDirectionStyles(
translationMetadata.translations[this.language!].isRTL,
this
);
}
if (changedProperties.get("language")) {
this._resources = undefined;
this._initializeLocalizeLite();

View File

@@ -1,6 +1,7 @@
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { navigate } from "../../common/navigate";
import type { LocalizeKeys } from "../../common/translations/localize";
import "../../components/ha-alert";
@@ -10,9 +11,13 @@ import "../../components/ha-top-app-bar-fixed";
import type { EnergyPreferences } from "../../data/energy";
import { getEnergyDataCollection } from "../../data/energy";
import type { LovelaceConfig } from "../../data/lovelace/config/types";
import type { LovelaceViewConfig } from "../../data/lovelace/config/view";
import {
isStrategyView,
type LovelaceViewConfig,
} from "../../data/lovelace/config/view";
import { haStyle } from "../../resources/styles";
import type { HomeAssistant, PanelInfo } from "../../types";
import "../lovelace/components/hui-energy-period-selector";
import "../lovelace/hui-root";
import type { Lovelace } from "../lovelace/types";
import "../lovelace/views/hui-view";
@@ -32,6 +37,7 @@ const OVERVIEW_VIEW = {
strategy: {
type: "energy-overview",
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
show_period_selector: true,
},
} as LovelaceViewConfig;
@@ -40,6 +46,7 @@ const ENERGY_VIEW = {
strategy: {
type: "energy",
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
show_period_selector: true,
},
} as LovelaceViewConfig;
@@ -48,6 +55,7 @@ const WATER_VIEW = {
strategy: {
type: "water",
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
show_period_selector: true,
},
} as LovelaceViewConfig;
@@ -56,6 +64,7 @@ const GAS_VIEW = {
strategy: {
type: "gas",
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
show_period_selector: true,
},
} as LovelaceViewConfig;
@@ -201,6 +210,16 @@ class PanelEnergy extends LitElement {
return nothing;
}
const routePath = this.route?.path?.split("/")[1] || "";
const currentView = this._lovelace.config.views.find(
(view) => view.path === routePath
);
const showEnergySelector =
currentView &&
isStrategyView(currentView) &&
currentView.strategy?.show_period_selector;
return html`
<hui-root
.hass=${this.hass}
@@ -211,8 +230,22 @@ class PanelEnergy extends LitElement {
.backButton=${this._searchParms.has("historyBack")}
.backPath=${this._searchParms.get("backPath") || "/"}
@reload-energy-panel=${this._reloadConfig}
class=${classMap({ "has-period-selector": showEnergySelector })}
>
</hui-root>
${showEnergySelector
? html`
<ha-card class="period-selector">
<hui-energy-period-selector
.hass=${this.hass}
.collectionKey=${DEFAULT_ENERGY_COLLECTION_KEY}
opening-direction="right"
vertical-opening-direction="up"
fixed
></hui-energy-period-selector>
</ha-card>
`
: nothing}
`;
}
@@ -323,6 +356,50 @@ class PanelEnergy extends LitElement {
align-items: center;
justify-content: center;
}
hui-root.has-period-selector {
--view-container-padding-bottom: var(--ha-space-18);
}
.period-selector {
position: fixed;
z-index: 4;
bottom: max(var(--ha-space-4), var(--safe-area-inset-bottom, 0px));
left: max(
var(--mdc-drawer-width, 0px),
var(--safe-area-inset-left, 0px)
);
right: var(--safe-area-inset-right, 0);
inset-inline-start: max(
var(--mdc-drawer-width, 0px),
var(--safe-area-inset-left, 0px)
);
inset-inline-end: var(--safe-area-inset-right, 0);
transition:
left var(--ha-animation-duration-normal) ease,
right var(--ha-animation-duration-normal) ease,
inset-inline-start var(--ha-animation-duration-normal) ease,
inset-inline-end var(--ha-animation-duration-normal) ease;
margin: 0 auto;
max-width: calc(min(470px, 100% - var(--ha-space-4)));
box-sizing: border-box;
padding-left: var(--ha-space-2);
padding-right: 0;
padding-inline-start: var(--ha-space-4);
padding-inline-end: 0;
--ha-card-box-shadow:
0px 3px 5px -1px rgba(0, 0, 0, 0.2),
0px 6px 10px 0px rgba(0, 0, 0, 0.14),
0px 1px 18px 0px rgba(0, 0, 0, 0.12);
--ha-card-border-color: var(--divider-color);
--ha-card-border-width: var(--ha-card-border-width, 1px);
}
@media all and (max-width: 450px), all and (max-height: 500px) {
hui-root.has-period-selector {
--view-container-padding-bottom: var(--ha-space-14);
}
.period-selector {
bottom: max(var(--ha-space-2), var(--safe-area-inset-bottom, 0px));
}
}
`,
];
}

View File

@@ -13,23 +13,16 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
_config: LovelaceStrategyConfig,
hass: HomeAssistant
): Promise<LovelaceViewConfig> {
const collectionKey =
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
const view: LovelaceViewConfig = {
type: "sections",
sections: [],
dense_section_placement: true,
max_columns: 3,
footer: {
column_span: 1.1,
card: {
type: "energy-date-selection",
collection_key: collectionKey,
},
},
max_columns: 2,
};
const collectionKey =
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
const energyCollection = getEnergyDataCollection(hass, {
key: collectionKey,
});

View File

@@ -2,7 +2,6 @@ import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import type { GridSourceTypeEnergyPreference } from "../../../data/energy";
import { getEnergyDataCollection } from "../../../data/energy";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../types";
@@ -15,22 +14,11 @@ export class EnergyViewStrategy extends ReactiveElement {
_config: LovelaceStrategyConfig,
hass: HomeAssistant
): Promise<LovelaceViewConfig> {
const view: LovelaceViewConfig = { cards: [] };
const collectionKey =
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
const view: LovelaceViewConfig = {
type: "sections",
max_columns: 3,
sections: [],
footer: {
column_span: 1.1,
card: {
type: "energy-date-selection",
collection_key: collectionKey,
},
},
};
const energyCollection = getEnergyDataCollection(hass, {
key: collectionKey,
});
@@ -48,6 +36,8 @@ export class EnergyViewStrategy extends ReactiveElement {
return view;
}
view.type = "sidebar";
const hasGrid = prefs.energy_sources.find(
(source): source is GridSourceTypeEnergyPreference =>
source.type === "grid" &&
@@ -60,95 +50,83 @@ export class EnergyViewStrategy extends ReactiveElement {
const hasBattery = prefs.energy_sources.some(
(source) => source.type === "battery"
);
const mainCards: LovelaceCardConfig[] = [];
const gaugeCards: LovelaceCardConfig[] = [];
// Only include if we have a grid source & return.
if (hasReturn) {
const card = {
type: "energy-grid-neutrality-gauge",
collection_key: collectionKey,
};
gaugeCards.push(card);
}
// Only include if we have a solar source.
if (hasSolar) {
if (hasReturn) {
const card = {
type: "energy-solar-consumed-gauge",
collection_key: collectionKey,
};
gaugeCards.push(card);
}
if (hasGrid) {
const card = {
type: "energy-self-sufficiency-gauge",
collection_key: collectionKey,
};
gaugeCards.push(card);
}
}
// Only include if we have a grid
if (hasGrid) {
const card = {
type: "energy-carbon-consumed-gauge",
collection_key: collectionKey,
};
gaugeCards.push(card);
}
if (gaugeCards.length) {
view.sections!.push({
type: "grid",
column_span: 3,
cards:
gaugeCards.length === 1
? [gaugeCards[0]]
: gaugeCards.map((card) => ({
...card,
grid_options: { columns: 6 },
})),
});
}
mainCards.push({
view.cards!.push({
type: "energy-compare",
collection_key: collectionKey,
grid_options: { columns: 36 },
});
// Only include if we have a grid or battery.
if (hasGrid || hasBattery) {
mainCards.push({
view.cards!.push({
title: hass.localize("ui.panel.energy.cards.energy_usage_graph_title"),
type: "energy-usage-graph",
collection_key: collectionKey,
grid_options: { columns: 36 },
});
}
// Only include if we have a solar source.
if (hasSolar) {
mainCards.push({
view.cards!.push({
title: hass.localize("ui.panel.energy.cards.energy_solar_graph_title"),
type: "energy-solar-graph",
collection_key: collectionKey,
grid_options: { columns: 36 },
});
}
// Only include if we have a grid or battery.
if (hasGrid || hasBattery) {
view.cards!.push({
title: hass.localize("ui.panel.energy.cards.energy_distribution_title"),
type: "energy-distribution",
view_layout: { position: "sidebar" },
collection_key: collectionKey,
});
}
if (hasGrid || hasSolar || hasBattery) {
mainCards.push({
view.cards!.push({
title: hass.localize(
"ui.panel.energy.cards.energy_sources_table_title"
),
type: "energy-sources-table",
collection_key: collectionKey,
types: ["grid", "solar", "battery"],
grid_options: { columns: 36 },
});
}
// Only include if we have a grid source & return.
if (hasReturn) {
view.cards!.push({
type: "energy-grid-neutrality-gauge",
view_layout: { position: "sidebar" },
collection_key: collectionKey,
});
}
// Only include if we have a solar source.
if (hasSolar) {
if (hasReturn) {
view.cards!.push({
type: "energy-solar-consumed-gauge",
view_layout: { position: "sidebar" },
collection_key: collectionKey,
});
}
if (hasGrid) {
view.cards!.push({
type: "energy-self-sufficiency-gauge",
view_layout: { position: "sidebar" },
collection_key: collectionKey,
});
}
}
// Only include if we have a grid
if (hasGrid) {
view.cards!.push({
type: "energy-carbon-consumed-gauge",
view_layout: { position: "sidebar" },
collection_key: collectionKey,
});
}
@@ -159,38 +137,29 @@ export class EnergyViewStrategy extends ReactiveElement {
hass,
(d) => d.stat_consumption
);
mainCards.push({
view.cards!.push({
title: hass.localize(
"ui.panel.energy.cards.energy_devices_detail_graph_title"
),
type: "energy-devices-detail-graph",
collection_key: collectionKey,
grid_options: { columns: 36 },
});
mainCards.push({
view.cards!.push({
title: hass.localize(
"ui.panel.energy.cards.energy_devices_graph_title"
),
type: "energy-devices-graph",
collection_key: collectionKey,
grid_options: { columns: 36 },
});
mainCards.push({
view.cards!.push({
title: hass.localize("ui.panel.energy.cards.energy_sankey_title"),
type: "energy-sankey",
collection_key: collectionKey,
group_by_floor: showFloorsAndAreas,
group_by_area: showFloorsAndAreas,
grid_options: { columns: 36 },
});
}
view.sections!.push({
type: "grid",
column_span: 3,
cards: mainCards,
});
return view;
}
}

View File

@@ -13,22 +13,14 @@ export class GasViewStrategy extends ReactiveElement {
_config: LovelaceStrategyConfig,
hass: HomeAssistant
): Promise<LovelaceViewConfig> {
const collectionKey =
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
const view: LovelaceViewConfig = {
type: "sections",
max_columns: 3,
sections: [{ type: "grid", cards: [], column_span: 3 }],
footer: {
column_span: 1.1,
card: {
type: "energy-date-selection",
collection_key: collectionKey,
},
},
sections: [{ type: "grid", cards: [] }],
};
const collectionKey =
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
const energyCollection = getEnergyDataCollection(hass, {
key: collectionKey,
});
@@ -57,9 +49,6 @@ export class GasViewStrategy extends ReactiveElement {
title: hass.localize("ui.panel.energy.cards.energy_gas_graph_title"),
type: "energy-gas-graph",
collection_key: collectionKey,
grid_options: {
columns: 24,
},
});
section.cards!.push({
@@ -67,9 +56,6 @@ export class GasViewStrategy extends ReactiveElement {
type: "energy-sources-table",
collection_key: collectionKey,
types: ["gas"],
grid_options: {
columns: 12,
},
});
return view;

View File

@@ -14,22 +14,14 @@ export class WaterViewStrategy extends ReactiveElement {
_config: LovelaceStrategyConfig,
hass: HomeAssistant
): Promise<LovelaceViewConfig> {
const collectionKey =
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
const view: LovelaceViewConfig = {
type: "sections",
max_columns: 3,
sections: [{ type: "grid", cards: [], column_span: 3 }],
footer: {
column_span: 1.1,
card: {
type: "energy-date-selection",
collection_key: collectionKey,
},
},
sections: [{ type: "grid", cards: [] }],
};
const collectionKey =
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
const energyCollection = getEnergyDataCollection(hass, {
key: collectionKey,
});
@@ -60,9 +52,6 @@ export class WaterViewStrategy extends ReactiveElement {
title: hass.localize("ui.panel.energy.cards.energy_water_graph_title"),
type: "energy-water-graph",
collection_key: collectionKey,
grid_options: {
columns: 24,
},
});
section.cards!.push({
title: hass.localize(
@@ -71,9 +60,6 @@ export class WaterViewStrategy extends ReactiveElement {
type: "energy-sources-table",
collection_key: collectionKey,
types: ["water"],
grid_options: {
columns: 12,
},
});
}
@@ -90,9 +76,6 @@ export class WaterViewStrategy extends ReactiveElement {
collection_key: collectionKey,
group_by_floor: showFloorsAndAreas,
group_by_area: showFloorsAndAreas,
grid_options: {
columns: 24,
},
});
}

View File

@@ -131,10 +131,9 @@ export class HuiDiscoveredDevicesCard
}
// Update visibility based on admin status and discovered devices count
const shouldBeHidden = Boolean(
const shouldBeHidden =
!this.hass.user?.is_admin ||
(this._config.hide_empty && this._discoveredFlows.length === 0)
);
(this._config.hide_empty && this._discoveredFlows.length === 0);
if (shouldBeHidden !== this.hidden) {
this.style.display = shouldBeHidden ? "none" : "";

View File

@@ -97,10 +97,9 @@ export class HuiRepairsCard
}
// Update visibility based on admin status and repairs count
const shouldBeHidden = Boolean(
const shouldBeHidden =
!this.hass.user?.is_admin ||
(this._config.hide_empty && this._repairsIssues.length === 0)
);
(this._config.hide_empty && this._repairsIssues.length === 0);
if (shouldBeHidden !== this.hidden) {
this.style.display = shouldBeHidden ? "none" : "";

View File

@@ -91,10 +91,9 @@ export class HuiUpdatesCard extends LitElement implements LovelaceCard {
const updateEntities = this._getUpdateEntities();
// Update visibility based on admin status and updates count
const shouldBeHidden = Boolean(
const shouldBeHidden =
!this.hass.user?.is_admin ||
(this._config.hide_empty && updateEntities.length === 0)
);
(this._config.hide_empty && updateEntities.length === 0);
if (shouldBeHidden !== this.hidden) {
this.style.display = shouldBeHidden ? "none" : "";
@@ -104,7 +103,7 @@ export class HuiUpdatesCard extends LitElement implements LovelaceCard {
}
protected render(): TemplateResult | typeof nothing {
if (!this._config || !this.hass) {
if (!this._config || !this.hass || this.hidden) {
return nothing;
}

View File

@@ -125,7 +125,7 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
}
private _measure() {
this.narrow = this.offsetWidth < 425;
this.narrow = this.offsetWidth < 450;
this._collapseButtons = this.offsetWidth < 320;
}

View File

@@ -226,7 +226,6 @@ export class HuiDialogEditBadge
.hass=${this.hass}
.lovelace=${this._params.lovelaceConfig}
.value=${this._badgeConfig}
in-dialog
@config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged}
@editor-save=${this._save}
@@ -315,9 +314,7 @@ export class HuiDialogEditBadge
}
private _toggleMode(): void {
withViewTransition(() => {
this._badgeEditorEl?.toggleMode();
});
this._badgeEditorEl?.toggleMode();
}
private _opened() {

View File

@@ -97,7 +97,6 @@ export class HuiDialogSuggestBadge extends LitElement {
<ha-yaml-editor
.hass=${this.hass}
.defaultValue=${this._badgeConfig}
in-dialog
></ha-yaml-editor>
</div>
`

View File

@@ -203,7 +203,6 @@ export class HuiDialogEditCard
.hass=${this.hass}
.lovelace=${this._params.lovelaceConfig}
.value=${this._cardConfig}
in-dialog
@config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged}
@editor-save=${this._save}
@@ -298,9 +297,7 @@ export class HuiDialogEditCard
}
private _toggleMode(): void {
withViewTransition(() => {
this._cardEditorEl?.toggleMode();
});
this._cardEditorEl?.toggleMode();
}
private _opened() {

View File

@@ -133,7 +133,6 @@ export class HuiDialogSuggestCard extends LitElement {
<ha-yaml-editor
.hass=${this.hass}
.defaultValue=${this._cardConfig}
in-dialog
></ha-yaml-editor>
</div>
`

View File

@@ -57,9 +57,6 @@ export abstract class HuiElementEditor<
@property({ attribute: false }) public context?: C;
@property({ type: Boolean, attribute: "in-dialog" })
public inDialog = false;
@state() private _config?: T;
@state() private _configElement?: LovelaceGenericElementEditor;
@@ -153,9 +150,6 @@ export abstract class HuiElementEditor<
}
public toggleMode() {
if (!this.GUImode) {
this._yamlEditor?.disableCodeEditorFullscreen();
}
this.GUImode = !this.GUImode;
}
@@ -249,7 +243,6 @@ export abstract class HuiElementEditor<
.defaultValue=${this._config}
autofocus
.hass=${this.hass}
.inDialog=${this.inDialog}
@value-changed=${this._handleYAMLChanged}
@blur=${this._onBlurYaml}
@keydown=${this._ignoreKeydown}

View File

@@ -127,7 +127,6 @@ export class HuiDialogEditSection
<ha-yaml-editor
.hass=${this.hass}
autofocus
in-dialog
@value-changed=${this._viewYamlChanged}
></ha-yaml-editor>
`;

View File

@@ -162,7 +162,6 @@ export class HuiDialogEditView extends LitElement {
<ha-yaml-editor
.hass=${this.hass}
autofocus
in-dialog
@value-changed=${this._viewYamlChanged}
></ha-yaml-editor>
`;

View File

@@ -84,7 +84,6 @@ export class HuiDialogEditViewHeader extends LitElement {
<ha-yaml-editor
.hass=${this.hass}
autofocus
in-dialog
@value-changed=${this._viewYamlChanged}
></ha-yaml-editor>
`;

View File

@@ -12,7 +12,6 @@ import {
removeSearchParam,
} from "../../common/url/search-params";
import { debounce } from "../../common/util/debounce";
import { deepEqual } from "../../common/util/deep-equal";
import "../../components/ha-button";
import { domainToName } from "../../data/integration";
import { subscribeLovelaceUpdates } from "../../data/lovelace";
@@ -36,7 +35,10 @@ import { checkLovelaceConfig } from "./common/check-lovelace-config";
import { loadLovelaceResources } from "./common/load-resources";
import { showSaveDialog } from "./editor/show-save-config-dialog";
import "./hui-root";
import { generateLovelaceDashboardStrategy } from "./strategies/get-strategy";
import {
checkStrategyShouldRegenerate,
generateLovelaceDashboardStrategy,
} from "./strategies/get-strategy";
import type { Lovelace } from "./types";
import { generateDefaultView } from "./views/default-view";
import { fetchDashboards } from "../../data/lovelace/dashboard";
@@ -50,12 +52,6 @@ interface LovelacePanelConfig {
let editorLoaded = false;
let resourcesLoaded = false;
declare global {
interface HASSDomEvents {
"strategy-config-changed": undefined;
}
}
@customElement("ha-panel-lovelace")
export class LovelacePanel extends LitElement {
@property({ attribute: false }) public panel?: PanelInfo<
@@ -129,7 +125,6 @@ export class LovelacePanel extends LitElement {
.route=${this.route}
.narrow=${this.narrow}
@config-refresh=${this._forceFetchConfig}
@strategy-config-changed=${this._strategyConfigChanged}
></hui-root>
`;
}
@@ -195,61 +190,25 @@ export class LovelacePanel extends LitElement {
this.lovelace &&
isStrategyDashboard(this.lovelace.rawConfig)
) {
// If the entity registry changed, ask the user if they want to refresh the config
if (
oldHass.entities !== this.hass.entities ||
oldHass.devices !== this.hass.devices ||
oldHass.areas !== this.hass.areas ||
oldHass.floors !== this.hass.floors
) {
if (this.hass.config.state === "RUNNING") {
this._debounceRegistriesChanged();
}
}
// If ha started, refresh the config
if (
this.hass.config.state === "RUNNING" &&
oldHass.config.state !== "RUNNING"
checkStrategyShouldRegenerate(
"dashboard",
this.lovelace.rawConfig.strategy,
oldHass,
this.hass
)
) {
this._regenerateStrategyConfig();
this._debounceRegenerateStrategy();
}
}
}
private _debounceRegistriesChanged = debounce(
() => this._registriesChanged(),
private _debounceRegenerateStrategy = debounce(
() => this._regenerateStrategyConfig(),
200
);
private _registriesChanged = async () => {
if (!this.hass || !this.lovelace) {
return;
}
const rawConfig = this.lovelace.rawConfig;
if (!isStrategyDashboard(rawConfig)) {
return;
}
const oldConfig = this.lovelace.config;
const generatedConfig = await generateLovelaceDashboardStrategy(
rawConfig,
this.hass!
);
const newConfig = checkLovelaceConfig(generatedConfig) as LovelaceConfig;
// Regenerate if the config changed
if (!deepEqual(newConfig, oldConfig)) {
this._regenerateStrategyConfig();
}
};
private _strategyConfigChanged = (ev: CustomEvent) => {
ev.stopPropagation();
this._regenerateStrategyConfig();
};
private async _regenerateStrategyConfig() {
if (!this.hass || !this.lovelace) {
return;

View File

@@ -4,6 +4,7 @@ import { ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { storage } from "../../../common/decorators/storage";
import { fireEvent } from "../../../common/dom/fire_event";
import { debounce } from "../../../common/util/debounce";
import "../../../components/ha-svg-icon";
import type { LovelaceSectionElement } from "../../../data/lovelace";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
@@ -23,7 +24,10 @@ import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"
import { addCard, replaceCard } from "../editor/config-util";
import { performDeleteCard } from "../editor/delete-card";
import { parseLovelaceCardPath } from "../editor/lovelace-path";
import { generateLovelaceSectionStrategy } from "../strategies/get-strategy";
import {
checkStrategyShouldRegenerate,
generateLovelaceSectionStrategy,
} from "../strategies/get-strategy";
import type { Lovelace } from "../types";
import { DEFAULT_SECTION_LAYOUT } from "./const";
@@ -104,9 +108,35 @@ export class HuiSection extends ConditionalListenerMixin<LovelaceSectionConfig>(
(!oldConfig || this.config !== oldConfig)
) {
this._initializeConfig();
return;
}
if (!changedProperties.has("hass")) {
return;
}
const oldHass = changedProperties.get("hass") as HomeAssistant | undefined;
if (
oldHass &&
this.hass &&
isStrategySection(this.config) &&
this.hass.config.state === "RUNNING" &&
checkStrategyShouldRegenerate(
"section",
this.config.strategy,
oldHass,
this.hass
)
) {
this._debounceRefreshConfig();
}
}
private _debounceRefreshConfig = debounce(
() => this._initializeConfig(),
200
);
public disconnectedCallback() {
super.disconnectedCallback();
}

View File

@@ -31,6 +31,17 @@ export interface AreasDashboardStrategyConfig {
@customElement("areas-dashboard-strategy")
export class AreasDashboardStrategy extends ReactiveElement {
static shouldRegenerate(
_config: AreasDashboardStrategyConfig,
oldHass: HomeAssistant,
newHass: HomeAssistant
): boolean {
return (
oldHass.areas !== newHass.areas ||
oldHass.config.state !== newHass.config.state
);
}
static async generate(
config: AreasDashboardStrategyConfig,
hass: HomeAssistant

View File

@@ -235,6 +235,50 @@ export const generateLovelaceSectionStrategy = async (
};
};
/**
* Synchronously checks whether a strategy needs regeneration.
* Falls back to checking common registries if the strategy doesn't implement shouldRegenerate.
*/
export const checkStrategyShouldRegenerate = (
configType: LovelaceStrategyConfigType,
strategyConfig: LovelaceStrategyConfig,
oldHass: HomeAssistant,
newHass: HomeAssistant
): boolean => {
const strategyType = strategyConfig.type;
if (!strategyType) {
return false;
}
let strategy: LovelaceStrategy | undefined;
if (strategyType in STRATEGIES[configType]) {
const tag = `${strategyType}-${configType}-strategy`;
strategy = customElements.get(tag) as unknown as
| LovelaceStrategy
| undefined;
} else if (strategyType.startsWith(CUSTOM_PREFIX)) {
const name = strategyType.slice(CUSTOM_PREFIX.length);
const tag = `ll-strategy-${configType}-${name}`;
const legacyTag = `ll-strategy-${name}`;
strategy = (customElements.get(tag) ??
customElements.get(legacyTag)) as unknown as LovelaceStrategy | undefined;
}
if (strategy?.shouldRegenerate) {
return strategy.shouldRegenerate(strategyConfig, oldHass, newHass);
}
// Default: check common registries for strategies without shouldRegenerate
return (
oldHass.entities !== newHass.entities ||
oldHass.devices !== newHass.devices ||
oldHass.areas !== newHass.areas ||
oldHass.floors !== newHass.floors ||
oldHass.config.state !== newHass.config.state
);
};
/**
* Find all references to strategies and replaces them with the generated output
*/

View File

@@ -21,6 +21,17 @@ export interface HomeDashboardStrategyConfig {
@customElement("home-dashboard-strategy")
export class HomeDashboardStrategy extends ReactiveElement {
static shouldRegenerate(
_config: HomeDashboardStrategyConfig,
oldHass: HomeAssistant,
newHass: HomeAssistant
): boolean {
return (
oldHass.areas !== newHass.areas ||
oldHass.config.state !== newHass.config.state
);
}
static async generate(
config: HomeDashboardStrategyConfig,
hass: HomeAssistant

View File

@@ -1,6 +1,7 @@
import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
import type { HomeAssistant } from "../../../../types";
import type { LovelaceStrategyEditor } from "../types";
import type { IframeViewStrategyConfig } from "./iframe-view-strategy";
@@ -8,6 +9,14 @@ export type IframeDashboardStrategyConfig = IframeViewStrategyConfig;
@customElement("iframe-dashboard-strategy")
export class IframeDashboardStrategy extends ReactiveElement {
static shouldRegenerate(
_config: IframeDashboardStrategyConfig,
_oldHass: HomeAssistant,
_newHass: HomeAssistant
): boolean {
return false;
}
static async generate(
config: IframeDashboardStrategyConfig
): Promise<LovelaceConfig> {

View File

@@ -1,12 +1,21 @@
import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
import type { HomeAssistant } from "../../../../types";
import type { MapViewStrategyConfig } from "./map-view-strategy";
export type MapDashboardStrategyConfig = MapViewStrategyConfig;
@customElement("map-dashboard-strategy")
export class MapDashboardStrategy extends ReactiveElement {
static shouldRegenerate(
_config: MapDashboardStrategyConfig,
_oldHass: HomeAssistant,
_newHass: HomeAssistant
): boolean {
return false;
}
static async generate(
config: MapDashboardStrategyConfig
): Promise<LovelaceConfig> {

View File

@@ -1,6 +1,7 @@
import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
import type { HomeAssistant } from "../../../../types";
import type { LovelaceStrategyEditor } from "../types";
import type { OriginalStatesViewStrategyConfig } from "./original-states-view-strategy";
@@ -9,6 +10,14 @@ export type OriginalStatesDashboardStrategyConfig =
@customElement("original-states-dashboard-strategy")
export class OriginalStatesDashboardStrategy extends ReactiveElement {
static shouldRegenerate(
_config: OriginalStatesDashboardStrategyConfig,
_oldHass: HomeAssistant,
_newHass: HomeAssistant
): boolean {
return false;
}
static async generate(
config: OriginalStatesDashboardStrategyConfig
): Promise<LovelaceConfig> {

View File

@@ -7,6 +7,11 @@ import type { LovelaceGenericElementEditor } from "../types";
export interface LovelaceStrategy<T = any> {
generate(config: LovelaceStrategyConfig, hass: HomeAssistant): Promise<T>;
shouldRegenerate?(
config: LovelaceStrategyConfig,
oldHass: HomeAssistant,
newHass: HomeAssistant
): boolean;
getConfigElement?: () => LovelaceStrategyEditor;
noEditor?: boolean;
configRequired?: boolean;

View File

@@ -241,9 +241,8 @@ export class HuiViewFooter extends LitElement {
box-sizing: content-box;
margin: 0 auto;
max-width: calc(
var(--footer-column-span, 1) / var(--column-count, 1) * 100% +
(var(--footer-column-span, 1) - var(--column-count, 1)) /
var(--column-count, 1) * var(--column-gap, 32px)
var(--footer-column-span, 1) * var(--column-max-width, 500px) +
(var(--footer-column-span, 1) - 1) * var(--column-gap, 32px)
);
}

View File

@@ -3,9 +3,9 @@ import type { PropertyValues } from "lit";
import { ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { storage } from "../../../common/decorators/storage";
import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { debounce } from "../../../common/util/debounce";
import { deepEqual } from "../../../common/util/deep-equal";
import "../../../components/entity/ha-state-label-badge";
import "../../../components/ha-svg-icon";
import type { LovelaceViewElement } from "../../../data/lovelace";
@@ -42,7 +42,10 @@ import { parseLovelaceCardPath } from "../editor/lovelace-path";
import { createErrorSectionConfig } from "../sections/hui-error-section";
import "../sections/hui-section";
import type { HuiSection } from "../sections/hui-section";
import { generateLovelaceViewStrategy } from "../strategies/get-strategy";
import {
checkStrategyShouldRegenerate,
generateLovelaceViewStrategy,
} from "../strategies/get-strategy";
import type { Lovelace } from "../types";
import { getViewType } from "./get-view-type";
@@ -90,10 +93,6 @@ export class HUIView extends ReactiveElement {
private _layoutElement?: LovelaceViewElement;
private _layoutElementConfig?: LovelaceViewConfig;
private _rendered = false;
@storage({
key: "dashboardCardClipboard",
state: false,
@@ -154,18 +153,6 @@ export class HUIView extends ReactiveElement {
return this;
}
connectedCallback(): void {
super.connectedCallback();
this.updateComplete.then(() => {
this._rendered = true;
});
}
disconnectedCallback(): void {
super.disconnectedCallback();
this._rendered = false;
}
public willUpdate(changedProperties: PropertyValues<typeof this>): void {
super.willUpdate(changedProperties);
@@ -201,51 +188,24 @@ export class HUIView extends ReactiveElement {
const viewConfig = this.lovelace.config.views[this.index];
if (oldHass && this.hass && this.lovelace && isStrategyView(viewConfig)) {
if (
oldHass.entities !== this.hass.entities ||
oldHass.devices !== this.hass.devices ||
oldHass.areas !== this.hass.areas ||
oldHass.floors !== this.hass.floors
this.hass.config.state === "RUNNING" &&
checkStrategyShouldRegenerate(
"view",
viewConfig.strategy,
oldHass,
this.hass
)
) {
if (this.hass.config.state === "RUNNING") {
// If the page is not rendered yet, we can force the refresh
if (this._rendered) {
this._debounceRefreshConfig(false);
} else {
this._refreshConfig(true);
}
}
this._debounceRefreshConfig();
}
}
}
private _debounceRefreshConfig = debounce(
(force: boolean) => this._refreshConfig(force),
() => this._initializeConfig(),
200
);
private _refreshConfig = async (force: boolean) => {
if (!this.hass || !this.lovelace) {
return;
}
const viewConfig = this.lovelace.config.views[this.index];
if (!isStrategyView(viewConfig)) {
return;
}
const oldConfig = this._layoutElementConfig;
const newConfig = await this._generateConfig(viewConfig);
// Don't ask if the config is the same
if (!deepEqual(newConfig, oldConfig)) {
if (force) {
this._setConfig(newConfig, true);
} else {
fireEvent(this, "strategy-config-changed");
}
}
};
protected update(changedProperties: PropertyValues) {
super.update(changedProperties);
@@ -332,7 +292,6 @@ export class HUIView extends ReactiveElement {
addLayoutElement = true;
this._createLayoutElement(viewConfig);
}
this._layoutElementConfig = viewConfig;
this._createBadges(viewConfig);
this._createCards(viewConfig);
this._createSections(viewConfig);