Move card loading logic into hui-card (#21018)

* Move card rebuild to hui-card

* Use hui card in stack card

* add once to event

* Do not use state

* Use hui card in conditional card

* Use editMode instead of lovelace in hui card

* Fix edit mode

* Use hui-card in card dialog and panel todo

* Fix edit mode

* Fix types

* Migrate entity filter card

* Update demo card

* Fix UI view

* Allow edit mode attribute

* Remove unused condition

* Remove unused section preview code

* Remove useless check for config
This commit is contained in:
Paul Bottein 2024-06-12 13:38:21 +02:00 committed by GitHub
parent a497f42f73
commit 433c00b73a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 267 additions and 569 deletions

View File

@ -1,8 +1,9 @@
import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { until } from "lit/directives/until"; import { until } from "lit/directives/until";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-button";
import "../../../src/components/ha-circular-progress"; import "../../../src/components/ha-circular-progress";
import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card"; import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
@ -11,7 +12,6 @@ import {
demoConfigs, demoConfigs,
selectedDemoConfig, selectedDemoConfig,
selectedDemoConfigIndex, selectedDemoConfigIndex,
setDemoConfig,
} from "../configs/demo-configs"; } from "../configs/demo-configs";
@customElement("ha-demo-card") @customElement("ha-demo-card")
@ -64,9 +64,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
)} )}
</div> </div>
<mwc-button @click=${this._nextConfig} .disabled=${this._switching}> <ha-button @click=${this._nextConfig} .disabled=${this._switching}>
${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")} ${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")}
</mwc-button> </ha-button>
</div> </div>
<div class="content"> <div class="content">
<p class="small-hidden"> <p class="small-hidden">
@ -87,9 +87,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
</div> </div>
<div class="actions small-hidden"> <div class="actions small-hidden">
<a href="https://www.home-assistant.io" target="_blank"> <a href="https://www.home-assistant.io" target="_blank">
<mwc-button> <ha-button>
${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")} ${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
</mwc-button> </ha-button>
</a> </a>
</div> </div>
</ha-card> </ha-card>
@ -113,13 +113,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
private async _updateConfig(index: number) { private async _updateConfig(index: number) {
this._switching = true; this._switching = true;
try { fireEvent(this, "set-demo-config" as any, { index });
await setDemoConfig(this.hass, this.lovelace!, index);
} catch (err: any) {
alert("Failed to switch config :-(");
} finally {
this._switching = false;
}
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
@ -149,7 +143,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
height: 60px; height: 60px;
} }
.picker mwc-button { .picker ha-button {
margin-right: 8px; margin-right: 8px;
} }

View File

@ -1,9 +1,12 @@
import type { LocalizeFunc } from "../../../src/common/translations/localize"; import type { LocalizeFunc } from "../../../src/common/translations/localize";
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { selectedDemoConfig } from "../configs/demo-configs"; import {
selectedDemoConfig,
selectedDemoConfigIndex,
setDemoConfig,
} from "../configs/demo-configs";
import "../custom-cards/cast-demo-row"; import "../custom-cards/cast-demo-row";
import "../custom-cards/ha-demo-card"; import "../custom-cards/ha-demo-card";
import type { HADemoCard } from "../custom-cards/ha-demo-card";
export const mockLovelace = ( export const mockLovelace = (
hass: MockHomeAssistant, hass: MockHomeAssistant,
@ -19,17 +22,22 @@ export const mockLovelace = (
hass.mockWS("lovelace/resources", () => Promise.resolve([])); hass.mockWS("lovelace/resources", () => Promise.resolve([]));
}; };
customElements.whenDefined("hui-card").then(() => { customElements.whenDefined("hui-root").then(() => {
// eslint-disable-next-line // eslint-disable-next-line
const HUIView = customElements.get("hui-card"); const HUIRoot = customElements.get("hui-root")!;
// Patch HUI-VIEW to make the lovelace object available to the demo card
const oldCreateCard = HUIView!.prototype.createElement;
HUIView!.prototype.createElement = function (config) { const oldFirstUpdated = HUIRoot.prototype.firstUpdated;
const el = oldCreateCard.call(this, config);
if (config.type === "custom:ha-demo-card") { HUIRoot.prototype.firstUpdated = function (changedProperties) {
(el as HADemoCard).lovelace = this.lovelace; oldFirstUpdated.call(this, changedProperties);
} this.addEventListener("set-demo-config", async (ev) => {
return el; const index = (ev as CustomEvent).detail.index;
try {
await setDemoConfig(this.hass, this.lovelace!, index);
} catch (err: any) {
setDemoConfig(this.hass, this.lovelace!, selectedDemoConfigIndex);
alert("Failed to switch config :-(");
}
});
}; };
}); });

View File

@ -1,7 +1,9 @@
import { load } from "js-yaml"; import { load } from "js-yaml";
import { html, css, LitElement, PropertyValues } from "lit"; import { LitElement, PropertyValueMap, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element"; import memoizeOne from "memoize-one";
import "../../../src/panels/lovelace/cards/hui-card";
import type { HuiCard } from "../../../src/panels/lovelace/cards/hui-card";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
export interface DemoCardConfig { export interface DemoCardConfig {
@ -19,7 +21,12 @@ class DemoCard extends LitElement {
@state() private _size?: number; @state() private _size?: number;
@query("#card") private _card!: HTMLElement; @query("hui-card", false) private _card?: HuiCard;
private _config = memoizeOne((config: string) => {
const c = (load(config) as any)[0];
return c;
});
render() { render() {
return html` return html`
@ -30,63 +37,32 @@ class DemoCard extends LitElement {
: ""} : ""}
</h2> </h2>
<div class="root"> <div class="root">
<div id="card"></div> <hui-card
${this.showConfig ? html`<pre>${this.config.config.trim()}</pre>` : ""} .config=${this._config(this.config.config)}
.hass=${this.hass}
@card-updated=${this._cardUpdated}
></hui-card>
${this.showConfig
? html`<pre>${this.config.config.trim()}</pre>`
: nothing}
</div> </div>
`; `;
} }
updated(changedProps: PropertyValues) { private async _cardUpdated(ev) {
super.updated(changedProps); ev.stopPropagation();
this._updateSize();
if (changedProps.has("config")) {
const card = this._card;
while (card.lastChild) {
card.removeChild(card.lastChild);
}
const el = this._createCardElement((load(this.config.config) as any)[0]);
card.appendChild(el);
this._getSize(el);
}
if (changedProps.has("hass")) {
const card = this._card.lastChild;
if (card) {
(card as any).hass = this.hass;
}
}
} }
async _getSize(el) { private async _updateSize() {
await customElements.whenDefined(el.localName); this._size = await this._card?.getCardSize();
if (!("getCardSize" in el)) {
this._size = undefined;
return;
}
this._size = await el.getCardSize();
} }
_createCardElement(cardConfig) { protected update(
const element = createCardElement(cardConfig); _changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
if (this.hass) { ): void {
element.hass = this.hass; super.update(_changedProperties);
} this._updateSize();
element.addEventListener(
"ll-rebuild",
(ev) => {
ev.stopPropagation();
this._rebuildCard(element, cardConfig);
},
{ once: true }
);
return element;
}
_rebuildCard(cardElToReplace, config) {
const newCardEl = this._createCardElement(config);
cardElToReplace.parentElement.replaceChild(newCardEl, cardElToReplace);
} }
static styles = css` static styles = css`
@ -101,7 +77,7 @@ class DemoCard extends LitElement {
font-size: 0.5em; font-size: 0.5em;
color: var(--primary-text-color); color: var(--primary-text-color);
} }
#card { hui-card {
max-width: 400px; max-width: 400px;
width: 100vw; width: 100vw;
} }

View File

@ -1,5 +1,6 @@
import { PropertyValues, ReactiveElement } from "lit"; import { PropertyValueMap, PropertyValues, ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { MediaQueriesListener } from "../../../common/dom/media_query"; import { MediaQueriesListener } from "../../../common/dom/media_query";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
@ -10,23 +11,41 @@ import {
checkConditionsMet, checkConditionsMet,
} from "../common/validate-condition"; } from "../common/validate-condition";
import { createCardElement } from "../create-element/create-card-element"; import { createCardElement } from "../create-element/create-card-element";
import type { Lovelace, LovelaceCard, LovelaceLayoutOptions } from "../types"; import { createErrorCardConfig } from "../create-element/create-element-base";
import type { LovelaceCard, LovelaceLayoutOptions } from "../types";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
"card-visibility-changed": { value: boolean }; "card-visibility-changed": { value: boolean };
"card-updated": undefined;
} }
} }
@customElement("hui-card") @customElement("hui-card")
export class HuiCard extends ReactiveElement { export class HuiCard extends ReactiveElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public lovelace?: Lovelace; @property({ type: Boolean }) public editMode = false;
@property({ attribute: false }) public isPanel = false; @property({ type: Boolean }) public isPanel = false;
@state() public _config?: LovelaceCardConfig; set config(config: LovelaceCardConfig | undefined) {
if (!config) return;
if (config.type !== this._config?.type) {
this._buildElement(config);
} else if (config !== this.config) {
this._element?.setConfig(config);
fireEvent(this, "card-updated");
}
this._config = config;
}
@property({ attribute: false })
public get config() {
return this._config;
}
private _config?: LovelaceCardConfig;
private _element?: LovelaceCard; private _element?: LovelaceCard;
@ -44,7 +63,7 @@ export class HuiCard extends ReactiveElement {
public connectedCallback() { public connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this._listenMediaQueries(); this._listenMediaQueries();
this._updateElement(); this._updateVisibility();
} }
public getCardSize(): number | Promise<number> { public getCardSize(): number | Promise<number> {
@ -56,7 +75,7 @@ export class HuiCard extends ReactiveElement {
} }
public getLayoutOptions(): LovelaceLayoutOptions { public getLayoutOptions(): LovelaceLayoutOptions {
const configOptions = this._config?.layout_options ?? {}; const configOptions = this.config?.layout_options ?? {};
if (this._element) { if (this._element) {
const cardOptions = this._element.getLayoutOptions?.() ?? {}; const cardOptions = this._element.getLayoutOptions?.() ?? {};
return { return {
@ -67,51 +86,76 @@ export class HuiCard extends ReactiveElement {
return configOptions; return configOptions;
} }
// Public to make demo happy private _createElement(config: LovelaceCardConfig) {
public createElement(config: LovelaceCardConfig) { const element = createCardElement(config);
const element = createCardElement(config) as LovelaceCard;
element.hass = this.hass; element.hass = this.hass;
element.editMode = this.lovelace?.editMode; element.editMode = this.editMode;
// Update element when the visibility of the card changes (e.g. conditional card or filter card) // Update element when the visibility of the card changes (e.g. conditional card or filter card)
element.addEventListener("card-visibility-changed", (ev) => { element.addEventListener("card-visibility-changed", (ev: Event) => {
ev.stopPropagation(); ev.stopPropagation();
this._updateElement(); this._updateVisibility();
}); });
element.addEventListener(
"ll-upgrade",
(ev: Event) => {
ev.stopPropagation();
fireEvent(this, "card-updated");
},
{ once: true }
);
element.addEventListener(
"ll-rebuild",
(ev: Event) => {
ev.stopPropagation();
this._buildElement(config);
fireEvent(this, "card-updated");
},
{ once: true }
);
return element; return element;
} }
public setConfig(config: LovelaceCardConfig): void { private _buildElement(config: LovelaceCardConfig) {
if (this._config === config) { this._element = this._createElement(config);
return;
}
this._config = config;
this._element = this.createElement(config);
while (this.lastChild) { while (this.lastChild) {
this.removeChild(this.lastChild); this.removeChild(this.lastChild);
} }
this.appendChild(this._element!); this._updateVisibility();
} }
protected update(changedProperties: PropertyValues<typeof this>) { protected update(changedProps: PropertyValues<typeof this>) {
super.update(changedProperties); super.update(changedProps);
if (this._element) { if (this._element) {
if (changedProperties.has("hass")) { if (changedProps.has("hass")) {
this._element.hass = this.hass; try {
this._element.hass = this.hass;
} catch (e: any) {
this._buildElement(createErrorCardConfig(e.message, null));
}
} }
if (changedProperties.has("lovelace")) { if (changedProps.has("editMode")) {
this._element.editMode = this.lovelace?.editMode; try {
this._element.editMode = this.editMode;
} catch (e: any) {
this._buildElement(createErrorCardConfig(e.message, null));
}
} }
if (changedProperties.has("hass") || changedProperties.has("lovelace")) { if (changedProps.has("isPanel")) {
this._updateElement();
}
if (changedProperties.has("isPanel")) {
this._element.isPanel = this.isPanel; this._element.isPanel = this.isPanel;
} }
} }
} }
protected willUpdate(
changedProps: PropertyValueMap<any> | Map<PropertyKey, unknown>
): void {
if (changedProps.has("hass") || changedProps.has("lovelace")) {
this._updateVisibility();
}
}
private _clearMediaQueries() { private _clearMediaQueries() {
this._listeners.forEach((unsub) => unsub()); this._listeners.forEach((unsub) => unsub());
this._listeners = []; this._listeners = [];
@ -119,42 +163,50 @@ export class HuiCard extends ReactiveElement {
private _listenMediaQueries() { private _listenMediaQueries() {
this._clearMediaQueries(); this._clearMediaQueries();
if (!this._config?.visibility) { if (!this.config?.visibility) {
return; return;
} }
const conditions = this._config.visibility; const conditions = this.config.visibility;
const hasOnlyMediaQuery = const hasOnlyMediaQuery =
conditions.length === 1 && conditions.length === 1 &&
conditions[0].condition === "screen" && conditions[0].condition === "screen" &&
!!conditions[0].media_query; !!conditions[0].media_query;
this._listeners = attachConditionMediaQueriesListeners( this._listeners = attachConditionMediaQueriesListeners(
this._config.visibility, this.config.visibility,
(matches) => { (matches) => {
this._updateElement(hasOnlyMediaQuery && matches); this._updateVisibility(hasOnlyMediaQuery && matches);
} }
); );
} }
private _updateElement(forceVisible?: boolean) { private _updateVisibility(forceVisible?: boolean) {
if (!this._element) { if (!this._element || !this.hass) {
return; return;
} }
if (this._element.hidden) { if (this._element.hidden) {
this.style.setProperty("display", "none"); this._setElementVisibility(false);
this.toggleAttribute("hidden", true);
return; return;
} }
const visible = const visible =
forceVisible || forceVisible ||
this.lovelace?.editMode || this.editMode ||
!this._config?.visibility || !this.config?.visibility ||
checkConditionsMet(this._config.visibility, this.hass); checkConditionsMet(this.config.visibility, this.hass);
this._setElementVisibility(visible);
}
private _setElementVisibility(visible: boolean) {
if (!this._element) return;
if (this.hidden !== !visible) {
this.style.setProperty("display", visible ? "" : "none");
this.toggleAttribute("hidden", !visible);
fireEvent(this, "card-visibility-changed", { value: visible });
}
this.style.setProperty("display", visible ? "" : "none");
this.toggleAttribute("hidden", !visible);
if (!visible && this._element.parentElement) { if (!visible && this._element.parentElement) {
this.removeChild(this._element); this.removeChild(this._element);
} else if (visible && !this._element.parentElement) { } else if (visible && !this._element.parentElement) {

View File

@ -3,7 +3,6 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import { computeCardSize } from "../common/compute-card-size"; import { computeCardSize } from "../common/compute-card-size";
import { HuiConditionalBase } from "../components/hui-conditional-base"; import { HuiConditionalBase } from "../components/hui-conditional-base";
import { createCardElement } from "../create-element/create-card-element";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
import { ConditionalCardConfig } from "./types"; import { ConditionalCardConfig } from "./types";
@ -38,28 +37,13 @@ class HuiConditionalCard extends HuiConditionalBase implements LovelaceCard {
} }
private _createCardElement(cardConfig: LovelaceCardConfig) { private _createCardElement(cardConfig: LovelaceCardConfig) {
const element = createCardElement(cardConfig) as LovelaceCard; const element = document.createElement("hui-card");
if (this.hass) { element.hass = this.hass;
element.hass = this.hass; element.editMode = this.editMode;
} element.config = cardConfig;
element.addEventListener(
"ll-rebuild",
(ev) => {
ev.stopPropagation();
this._rebuildCard(cardConfig);
},
{ once: true }
);
return element; return element;
} }
private _rebuildCard(config: LovelaceCardConfig): void {
this._element = this._createCardElement(config);
if (this.lastChild) {
this.replaceChild(this._element, this.lastChild);
}
}
protected setVisibility(conditionMet: boolean): void { protected setVisibility(conditionMet: boolean): void {
const visible = this.editMode || conditionMet; const visible = this.editMode || conditionMet;
const previouslyHidden = this.hidden; const previouslyHidden = this.hidden;

View File

@ -1,5 +1,6 @@
import { PropertyValues, ReactiveElement } from "lit"; import { PropertyValues, ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { computeCardSize } from "../common/compute-card-size"; import { computeCardSize } from "../common/compute-card-size";
@ -11,11 +12,10 @@ import {
checkConditionsMet, checkConditionsMet,
extractConditionEntityIds, extractConditionEntityIds,
} from "../common/validate-condition"; } from "../common/validate-condition";
import { createCardElement } from "../create-element/create-card-element";
import { EntityFilterEntityConfig } from "../entity-rows/types"; import { EntityFilterEntityConfig } from "../entity-rows/types";
import { LovelaceCard } from "../types"; import { LovelaceCard } from "../types";
import { HuiCard } from "./hui-card";
import { EntityFilterCardConfig } from "./types"; import { EntityFilterCardConfig } from "./types";
import { fireEvent } from "../../../common/dom/fire_event";
@customElement("hui-entity-filter-card") @customElement("hui-entity-filter-card")
export class HuiEntityFilterCard export class HuiEntityFilterCard
@ -59,7 +59,7 @@ export class HuiEntityFilterCard
@state() private _config?: EntityFilterCardConfig; @state() private _config?: EntityFilterCardConfig;
private _element?: LovelaceCard; private _element?: HuiCard;
private _configEntities?: EntityFilterEntityConfig[]; private _configEntities?: EntityFilterEntityConfig[];
@ -173,12 +173,12 @@ export class HuiEntityFilterCard
} }
if (!this.lastChild) { if (!this.lastChild) {
this._element.setConfig({ this._element.config = {
...this._baseCardConfig!, ...this._baseCardConfig!,
entities: entitiesList, entities: entitiesList,
}); };
this._oldEntities = entitiesList; this._oldEntities = entitiesList;
} else if (this._element.tagName !== "HUI-ERROR-CARD") { } else {
const isSame = const isSame =
this._oldEntities && this._oldEntities &&
entitiesList.length === this._oldEntities.length && entitiesList.length === this._oldEntities.length &&
@ -186,10 +186,10 @@ export class HuiEntityFilterCard
if (!isSame) { if (!isSame) {
this._oldEntities = entitiesList; this._oldEntities = entitiesList;
this._element.setConfig({ this._element.config = {
...this._baseCardConfig!, ...this._baseCardConfig!,
entities: entitiesList, entities: entitiesList,
}); };
} }
} }
@ -245,33 +245,12 @@ export class HuiEntityFilterCard
} }
private _createCardElement(cardConfig: LovelaceCardConfig) { private _createCardElement(cardConfig: LovelaceCardConfig) {
const element = createCardElement(cardConfig) as LovelaceCard; const element = document.createElement("hui-card");
if (this.hass) { element.hass = this.hass;
element.hass = this.hass;
}
element.isPanel = this.isPanel;
element.editMode = this.editMode; element.editMode = this.editMode;
element.addEventListener( element.config = cardConfig;
"ll-rebuild",
(ev) => {
ev.stopPropagation();
this._rebuildCard(element, cardConfig);
},
{ once: true }
);
return element; return element;
} }
private _rebuildCard(
cardElToReplace: LovelaceCard,
config: LovelaceCardConfig
): void {
const newCardEl = this._createCardElement(config);
if (cardElToReplace.parentElement) {
cardElToReplace.parentElement!.replaceChild(newCardEl, cardElToReplace);
}
this._element = newCardEl;
}
} }
declare global { declare global {

View File

@ -1,6 +1,6 @@
import { dump } from "js-yaml"; import { dump } from "js-yaml";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import "../../../components/ha-alert"; import "../../../components/ha-alert";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { LovelaceCard } from "../types"; import { LovelaceCard } from "../types";
@ -10,6 +10,8 @@ import { ErrorCardConfig } from "./types";
export class HuiErrorCard extends LitElement implements LovelaceCard { export class HuiErrorCard extends LitElement implements LovelaceCard {
public hass?: HomeAssistant; public hass?: HomeAssistant;
@property({ attribute: false }) public editMode = false;
@state() private _config?: ErrorCardConfig; @state() private _config?: ErrorCardConfig;
public getCardSize(): number { public getCardSize(): number {

View File

@ -1,19 +1,12 @@
import { import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
nothing,
} from "lit";
import { property, state } from "lit/decorators"; import { property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event"; import { computeRTLDirection } from "../../../common/util/compute_rtl";
import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { createCardElement } from "../create-element/create-card-element";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
import "./hui-card";
import type { HuiCard } from "./hui-card";
import { StackCardConfig } from "./types"; import { StackCardConfig } from "./types";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig> export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
extends LitElement extends LitElement
@ -32,7 +25,7 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
@property({ type: Boolean }) public editMode = false; @property({ type: Boolean }) public editMode = false;
@state() protected _cards?: LovelaceCard[]; @state() protected _cards?: HuiCard[];
@state() protected _config?: T; @state() protected _config?: T;
@ -49,30 +42,36 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
} }
this._config = config; this._config = config;
this._cards = config.cards.map((card) => { this._cards = config.cards.map((card) => {
const element = this._createCardElement(card) as LovelaceCard; const element = this._createCardElement(card);
return element; return element;
}); });
} }
protected updated(changedProps: PropertyValues) { protected update(changedProperties) {
super.updated(changedProps); super.update(changedProperties);
if (
!this._cards ||
(!changedProps.has("hass") && !changedProps.has("editMode"))
) {
return;
}
for (const element of this._cards) { if (this._cards) {
if (this.hass) { if (changedProperties.has("hass")) {
element.hass = this.hass; this._cards.forEach((card) => {
card.hass = this.hass;
});
} }
if (this.editMode !== undefined) { if (changedProperties.has("editMode")) {
element.editMode = this.editMode; this._cards.forEach((card) => {
card.editMode = this.editMode;
});
} }
} }
} }
private _createCardElement(cardConfig: LovelaceCardConfig) {
const element = document.createElement("hui-card");
element.hass = this.hass;
element.editMode = this.editMode;
element.config = cardConfig;
return element;
}
protected render() { protected render() {
if (!this._config || !this._cards) { if (!this._config || !this._cards) {
return nothing; return nothing;
@ -110,34 +109,4 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
} }
`; `;
} }
private _createCardElement(cardConfig: LovelaceCardConfig) {
const element = createCardElement(cardConfig) as LovelaceCard;
if (this.hass) {
element.hass = this.hass;
}
element.addEventListener(
"ll-rebuild",
(ev) => {
ev.stopPropagation();
this._rebuildCard(element, cardConfig);
fireEvent(this, "ll-rebuild");
},
{ once: true }
);
return element;
}
private _rebuildCard(
cardElToReplace: LovelaceCard,
config: LovelaceCardConfig
): void {
const newCardEl = this._createCardElement(config);
if (cardElToReplace.parentElement) {
cardElToReplace.parentElement.replaceChild(newCardEl, cardElToReplace);
}
this._cards = this._cards!.map((curCardEl) =>
curCardEl === cardElToReplace ? newCardEl : curCardEl
);
}
} }

View File

@ -3,6 +3,7 @@ import { customElement, property, state } from "lit/decorators";
import { MediaQueriesListener } from "../../../common/dom/media_query"; import { MediaQueriesListener } from "../../../common/dom/media_query";
import { deepEqual } from "../../../common/util/deep-equal"; import { deepEqual } from "../../../common/util/deep-equal";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { HuiCard } from "../cards/hui-card";
import { ConditionalCardConfig } from "../cards/types"; import { ConditionalCardConfig } from "../cards/types";
import { import {
Condition, Condition,
@ -12,7 +13,6 @@ import {
validateConditionalConfig, validateConditionalConfig,
} from "../common/validate-condition"; } from "../common/validate-condition";
import { ConditionalRowConfig, LovelaceRow } from "../entity-rows/types"; import { ConditionalRowConfig, LovelaceRow } from "../entity-rows/types";
import { LovelaceCard } from "../types";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
@ -28,7 +28,7 @@ export class HuiConditionalBase extends ReactiveElement {
@state() protected _config?: ConditionalCardConfig | ConditionalRowConfig; @state() protected _config?: ConditionalCardConfig | ConditionalRowConfig;
protected _element?: LovelaceCard | LovelaceRow; protected _element?: HuiCard | LovelaceRow;
private _listeners: MediaQueriesListener[] = []; private _listeners: MediaQueriesListener[] = [];

View File

@ -152,6 +152,7 @@ const _lazyCreate = <T extends keyof CreateElementConfigTypes>(
customElements.whenDefined(tag).then(() => { customElements.whenDefined(tag).then(() => {
try { try {
customElements.upgrade(element); customElements.upgrade(element);
fireEvent(element, "ll-upgrade");
// @ts-ignore // @ts-ignore
element.setConfig(config); element.setConfig(config);
} catch (err: any) { } catch (err: any) {

View File

@ -1,106 +0,0 @@
import { PropertyValues, ReactiveElement } from "lit";
import { property } from "lit/decorators";
import { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
import { HomeAssistant } from "../../../../types";
import { createCardElement } from "../../create-element/create-card-element";
import { createErrorCardConfig } from "../../create-element/create-element-base";
import { LovelaceCard } from "../../types";
export class HuiCardPreview extends ReactiveElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public config?: LovelaceCardConfig;
private _element?: LovelaceCard;
private get _error() {
return this._element?.tagName === "HUI-ERROR-CARD";
}
constructor() {
super();
this.addEventListener("ll-rebuild", () => {
this._cleanup();
if (this.config) {
this._createCard(this.config);
}
});
}
protected createRenderRoot() {
return this;
}
protected update(changedProperties: PropertyValues) {
super.update(changedProperties);
if (changedProperties.has("config")) {
const oldConfig = changedProperties.get("config") as
| undefined
| LovelaceCardConfig;
if (!this.config) {
this._cleanup();
return;
}
if (!this.config.type) {
this._createCard(
createErrorCardConfig("No card type found", this.config)
);
return;
}
if (!this._element) {
this._createCard(this.config);
return;
}
// in case the element was an error element we always want to recreate it
if (!this._error && oldConfig && this.config.type === oldConfig.type) {
try {
this._element.setConfig(this.config);
} catch (err: any) {
this._createCard(createErrorCardConfig(err.message, this.config));
}
} else {
this._createCard(this.config);
}
}
if (changedProperties.has("hass")) {
if (this._element) {
this._element.hass = this.hass;
}
}
}
private _createCard(configValue: LovelaceCardConfig): void {
this._cleanup();
this._element = createCardElement(configValue);
this._element.editMode = true;
if (this.hass) {
this._element!.hass = this.hass;
}
this.appendChild(this._element!);
}
private _cleanup() {
if (!this._element) {
return;
}
this.removeChild(this._element);
this._element = undefined;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-card-preview": HuiCardPreview;
}
}
customElements.define("hui-card-preview", HuiCardPreview);

View File

@ -5,7 +5,7 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
import { haStyleDialog } from "../../../../resources/styles"; import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import "./hui-card-preview"; import "../../cards/hui-card";
import type { DeleteCardDialogParams } from "./show-delete-card-dialog"; import type { DeleteCardDialogParams } from "./show-delete-card-dialog";
@customElement("hui-dialog-delete-card") @customElement("hui-dialog-delete-card")
@ -45,10 +45,11 @@ export class HuiDialogDeleteCard extends LitElement {
${this._cardConfig ${this._cardConfig
? html` ? html`
<div class="element-preview"> <div class="element-preview">
<hui-card-preview <hui-card
.hass=${this.hass} .hass=${this.hass}
.config=${this._cardConfig} .config=${this._cardConfig}
></hui-card-preview> editMode
></hui-card>
</div> </div>
` `
: ""} : ""}
@ -74,7 +75,7 @@ export class HuiDialogDeleteCard extends LitElement {
.element-preview { .element-preview {
position: relative; position: relative;
} }
hui-card-preview { hui-card {
margin: 4px auto; margin: 4px auto;
max-width: 500px; max-width: 500px;
display: block; display: block;

View File

@ -36,7 +36,7 @@ import { findLovelaceContainer } from "../lovelace-path";
import type { GUIModeChangedEvent } from "../types"; import type { GUIModeChangedEvent } from "../types";
import "./hui-card-element-editor"; import "./hui-card-element-editor";
import type { HuiCardElementEditor } from "./hui-card-element-editor"; import type { HuiCardElementEditor } from "./hui-card-element-editor";
import "./hui-card-preview"; import "../../cards/hui-card";
import type { EditCardDialogParams } from "./show-edit-card-dialog"; import type { EditCardDialogParams } from "./show-edit-card-dialog";
declare global { declare global {
@ -245,11 +245,12 @@ export class HuiDialogEditCard
></hui-card-element-editor> ></hui-card-element-editor>
</div> </div>
<div class="element-preview"> <div class="element-preview">
<hui-card-preview <hui-card
.hass=${this.hass} .hass=${this.hass}
.config=${this._cardConfig} .config=${this._cardConfig}
editMode
class=${this._error ? "blur" : ""} class=${this._error ? "blur" : ""}
></hui-card-preview> ></hui-card>
${this._error ${this._error
? html` ? html`
<ha-circular-progress <ha-circular-progress
@ -452,7 +453,7 @@ export class HuiDialogEditCard
flex-direction: column; flex-direction: column;
} }
.content hui-card-preview { .content hui-card {
margin: 4px auto; margin: 4px auto;
max-width: 390px; max-width: 390px;
} }
@ -470,7 +471,7 @@ export class HuiDialogEditCard
flex-shrink: 1; flex-shrink: 1;
min-width: 0; min-width: 0;
} }
.content hui-card-preview { .content hui-card {
padding: 8px 10px; padding: 8px 10px;
margin: auto 0px; margin: auto 0px;
max-width: 500px; max-width: 500px;
@ -498,7 +499,7 @@ export class HuiDialogEditCard
position: absolute; position: absolute;
z-index: 10; z-index: 10;
} }
hui-card-preview { hui-card {
padding-top: 8px; padding-top: 8px;
margin-bottom: 4px; margin-bottom: 4px;
display: block; display: block;

View File

@ -18,7 +18,7 @@ import {
LovelaceContainerPath, LovelaceContainerPath,
parseLovelaceContainerPath, parseLovelaceContainerPath,
} from "../lovelace-path"; } from "../lovelace-path";
import "./hui-card-preview"; import "../../cards/hui-card";
import { showCreateCardDialog } from "./show-create-card-dialog"; import { showCreateCardDialog } from "./show-create-card-dialog";
import { SuggestCardDialogParams } from "./show-suggest-card-dialog"; import { SuggestCardDialogParams } from "./show-suggest-card-dialog";
@ -84,10 +84,7 @@ export class HuiDialogSuggestCard extends LitElement {
<div class="element-preview"> <div class="element-preview">
${this._cardConfig.map( ${this._cardConfig.map(
(cardConfig) => html` (cardConfig) => html`
<hui-card-preview <hui-card .hass=${this.hass} .config=${cardConfig}></hui-card>
.hass=${this.hass}
.config=${cardConfig}
></hui-card-preview>
` `
)} )}
</div> </div>
@ -191,7 +188,7 @@ export class HuiDialogSuggestCard extends LitElement {
.element-preview { .element-preview {
position: relative; position: relative;
} }
hui-card-preview, hui-card,
hui-section { hui-section {
padding-top: 8px; padding-top: 8px;
margin: 4px auto; margin: 4px auto;

View File

@ -1,104 +0,0 @@
import { PropertyValues, ReactiveElement } from "lit";
import { customElement, property } from "lit/decorators";
import { LovelaceSectionElement } from "../../../../data/lovelace";
import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section";
import { HomeAssistant } from "../../../../types";
import { createSectionElement } from "../../create-element/create-section-element";
import { createErrorSectionConfig } from "../../sections/hui-error-section";
import { LovelaceConfig } from "../../../../data/lovelace/config/types";
@customElement("hui-section-preview")
export class HuiSectionPreview extends ReactiveElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public lovelace?: LovelaceConfig;
@property({ attribute: false }) public config?: LovelaceSectionConfig;
private _element?: LovelaceSectionElement;
private get _error() {
return this._element?.tagName === "HUI-ERROR-SECTION";
}
constructor() {
super();
this.addEventListener("ll-rebuild", () => {
this._cleanup();
if (this.config) {
this._createSection(this.config);
}
});
}
protected createRenderRoot() {
return this;
}
protected update(changedProperties: PropertyValues) {
super.update(changedProperties);
if (changedProperties.has("config")) {
const oldConfig = changedProperties.get("config") as
| undefined
| LovelaceSectionConfig;
if (!this.config) {
this._cleanup();
return;
}
if (!this.config.type) {
this._createSection(createErrorSectionConfig("No section type found"));
return;
}
if (!this._element) {
this._createSection(this.config);
return;
}
// in case the element was an error element we always want to recreate it
if (!this._error && oldConfig && this.config.type === oldConfig.type) {
try {
this._element.setConfig(this.config);
} catch (err: any) {
this._createSection(createErrorSectionConfig(err.message));
}
} else {
this._createSection(this.config);
}
}
if (changedProperties.has("hass")) {
if (this._element) {
this._element.hass = this.hass;
}
}
}
private _createSection(configValue: LovelaceSectionConfig): void {
this._cleanup();
this._element = createSectionElement(configValue) as LovelaceSectionElement;
if (this.hass) {
this._element!.hass = this.hass;
}
this.appendChild(this._element!);
}
private _cleanup() {
if (!this._element) {
return;
}
this.removeChild(this._element);
this._element = undefined;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-section-preview": HuiSectionPreview;
}
}

View File

@ -11,10 +11,10 @@ import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { HuiCard } from "../cards/hui-card";
import "../components/hui-card-edit-mode"; import "../components/hui-card-edit-mode";
import { moveCard } from "../editor/config-util"; import { moveCard } from "../editor/config-util";
import type { Lovelace } from "../types"; import type { Lovelace } from "../types";
import { HuiCard } from "../cards/hui-card";
const CARD_SORTABLE_OPTIONS: HaSortableOptions = { const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
delay: 100, delay: 100,

View File

@ -1,5 +1,6 @@
import { PropertyValues, ReactiveElement } from "lit"; import { PropertyValues, ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { MediaQueriesListener } from "../../../common/dom/media_query"; import { MediaQueriesListener } from "../../../common/dom/media_query";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import type { LovelaceSectionElement } from "../../../data/lovelace"; import type { LovelaceSectionElement } from "../../../data/lovelace";
@ -16,7 +17,6 @@ import {
attachConditionMediaQueriesListeners, attachConditionMediaQueriesListeners,
checkConditionsMet, checkConditionsMet,
} from "../common/validate-condition"; } from "../common/validate-condition";
import { createErrorCardConfig } from "../create-element/create-element-base";
import { createSectionElement } from "../create-element/create-section-element"; import { createSectionElement } from "../create-element/create-section-element";
import { showCreateCardDialog } from "../editor/card-editor/show-create-card-dialog"; import { showCreateCardDialog } from "../editor/card-editor/show-create-card-dialog";
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"; import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
@ -26,7 +26,6 @@ import { parseLovelaceCardPath } from "../editor/lovelace-path";
import { generateLovelaceSectionStrategy } from "../strategies/get-strategy"; import { generateLovelaceSectionStrategy } from "../strategies/get-strategy";
import type { Lovelace } from "../types"; import type { Lovelace } from "../types";
import { DEFAULT_SECTION_LAYOUT } from "./const"; import { DEFAULT_SECTION_LAYOUT } from "./const";
import { fireEvent } from "../../../common/dom/fire_event";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
@ -54,23 +53,15 @@ export class HuiSection extends ReactiveElement {
private _listeners: MediaQueriesListener[] = []; private _listeners: MediaQueriesListener[] = [];
// Public to make demo happy private _createCardElement(cardConfig: LovelaceCardConfig) {
public createCardElement(cardConfig: LovelaceCardConfig) {
const element = document.createElement("hui-card"); const element = document.createElement("hui-card");
element.hass = this.hass; element.hass = this.hass;
element.lovelace = this.lovelace; element.editMode = this.lovelace?.editMode || false;
element.setConfig(cardConfig); element.config = cardConfig;
element.addEventListener( element.addEventListener("card-updated", (ev: Event) => {
"ll-rebuild", ev.stopPropagation();
(ev: Event) => { this._cards = [...this._cards];
// In edit mode let it go to hui-root and rebuild whole section. });
if (!this.lovelace!.editMode) {
ev.stopPropagation();
this._rebuildCard(element, cardConfig);
}
},
{ once: true }
);
return element; return element;
} }
@ -121,22 +112,14 @@ export class HuiSection extends ReactiveElement {
// Config has not changed. Just props // Config has not changed. Just props
if (changedProperties.has("hass")) { if (changedProperties.has("hass")) {
this._cards.forEach((element) => { this._cards.forEach((element) => {
try { element.hass = this.hass;
element.hass = this.hass;
} catch (e: any) {
this._rebuildCard(element, createErrorCardConfig(e.message, null));
}
}); });
this._layoutElement.hass = this.hass; this._layoutElement.hass = this.hass;
} }
if (changedProperties.has("lovelace")) { if (changedProperties.has("lovelace")) {
this._layoutElement.lovelace = this.lovelace; this._layoutElement.lovelace = this.lovelace;
this._cards.forEach((element) => { this._cards.forEach((element) => {
try { element.editMode = this.lovelace?.editMode || false;
element.lovelace = this.lovelace;
} catch (e: any) {
this._rebuildCard(element, createErrorCardConfig(e.message, null));
}
}); });
} }
if (changedProperties.has("_cards")) { if (changedProperties.has("_cards")) {
@ -283,22 +266,8 @@ export class HuiSection extends ReactiveElement {
return; return;
} }
this._cards = config.cards.map((cardConfig) => { this._cards = config.cards.map((cardConfig) =>
const element = this.createCardElement(cardConfig); this._createCardElement(cardConfig)
return element;
});
}
private _rebuildCard(
cardElToReplace: HuiCard,
config: LovelaceCardConfig
): void {
const newCardEl = this.createCardElement(config);
if (cardElToReplace.parentElement) {
cardElToReplace.parentElement!.replaceChild(newCardEl, cardElToReplace);
}
this._cards = this._cards!.map((curCardEl) =>
curCardEl === cardElToReplace ? newCardEl : curCardEl
); );
} }
} }

View File

@ -17,6 +17,7 @@ declare global {
// eslint-disable-next-line // eslint-disable-next-line
interface HASSDomEvents { interface HASSDomEvents {
"ll-rebuild": Record<string, unknown>; "ll-rebuild": Record<string, unknown>;
"ll-upgrade": Record<string, unknown>;
"ll-badge-rebuild": Record<string, unknown>; "ll-badge-rebuild": Record<string, unknown>;
} }
} }

View File

@ -17,7 +17,7 @@ import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { HuiCard } from "../cards/hui-card"; import { HuiCard } from "../cards/hui-card";
import { computeCardSize } from "../common/compute-card-size"; import { computeCardSize } from "../common/compute-card-size";
import type { Lovelace, LovelaceBadge, LovelaceCard } from "../types"; import type { Lovelace, LovelaceBadge } from "../types";
// Find column with < 5 size, else smallest column // Find column with < 5 size, else smallest column
const getColumnIndex = (columnSizes: number[], size: number) => { const getColumnIndex = (columnSizes: number[], size: number) => {
@ -249,7 +249,7 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
} }
private _addCardToColumn(columnEl, index, editMode) { private _addCardToColumn(columnEl, index, editMode) {
const card: LovelaceCard = this.cards[index]; const card: HuiCard = this.cards[index];
if (!editMode || this.isStrategy) { if (!editMode || this.isStrategy) {
card.editMode = false; card.editMode = false;
columnEl.appendChild(card); columnEl.appendChild(card);

View File

@ -17,7 +17,7 @@ import type { HomeAssistant } from "../../../types";
import { HuiCard } from "../cards/hui-card"; import { HuiCard } from "../cards/hui-card";
import { HuiCardOptions } from "../components/hui-card-options"; import { HuiCardOptions } from "../components/hui-card-options";
import { HuiWarning } from "../components/hui-warning"; import { HuiWarning } from "../components/hui-warning";
import type { Lovelace, LovelaceCard } from "../types"; import type { Lovelace } from "../types";
let editCodeLoaded = false; let editCodeLoaded = false;
@ -32,7 +32,7 @@ export class PanelView extends LitElement implements LovelaceViewElement {
@property({ attribute: false }) public cards: HuiCard[] = []; @property({ attribute: false }) public cards: HuiCard[] = [];
@state() private _card?: LovelaceCard | HuiWarning | HuiCardOptions; @state() private _card?: HuiCard | HuiWarning | HuiCardOptions;
public setConfig(_config: LovelaceViewConfig): void {} public setConfig(_config: LovelaceViewConfig): void {}
@ -104,7 +104,7 @@ export class PanelView extends LitElement implements LovelaceViewElement {
return; return;
} }
const card: LovelaceCard = this.cards[0]; const card: HuiCard = this.cards[0];
card.isPanel = true; card.isPanel = true;
if (this.isStrategy || !this.lovelace?.editMode) { if (this.isStrategy || !this.lovelace?.editMode) {

View File

@ -15,7 +15,7 @@ import type { HomeAssistant } from "../../../types";
import { HuiCard } from "../cards/hui-card"; import { HuiCard } from "../cards/hui-card";
import { HuiCardOptions } from "../components/hui-card-options"; import { HuiCardOptions } from "../components/hui-card-options";
import { replaceCard } from "../editor/config-util"; import { replaceCard } from "../editor/config-util";
import type { Lovelace, LovelaceCard } from "../types"; import type { Lovelace } from "../types";
export class SideBarView extends LitElement implements LovelaceViewElement { export class SideBarView extends LitElement implements LovelaceViewElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -140,9 +140,9 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
}); });
} }
this.cards.forEach((card: LovelaceCard, idx) => { this.cards.forEach((card, idx) => {
const cardConfig = this._config?.cards?.[idx]; const cardConfig = this._config?.cards?.[idx];
let element: LovelaceCard | HuiCardOptions; let element: HuiCard | HuiCardOptions;
if (this.isStrategy || !this.lovelace?.editMode) { if (this.isStrategy || !this.lovelace?.editMode) {
card.editMode = false; card.editMode = false;
element = card; element = card;

View File

@ -21,7 +21,6 @@ import "../cards/hui-card";
import type { HuiCard } from "../cards/hui-card"; import type { HuiCard } from "../cards/hui-card";
import { processConfigEntities } from "../common/process-config-entities"; import { processConfigEntities } from "../common/process-config-entities";
import { createBadgeElement } from "../create-element/create-badge-element"; import { createBadgeElement } from "../create-element/create-badge-element";
import { createErrorCardConfig } from "../create-element/create-element-base";
import { createViewElement } from "../create-element/create-view-element"; import { createViewElement } from "../create-element/create-view-element";
import { showCreateCardDialog } from "../editor/card-editor/show-create-card-dialog"; import { showCreateCardDialog } from "../editor/card-editor/show-create-card-dialog";
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"; import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
@ -77,19 +76,12 @@ export class HUIView extends ReactiveElement {
private _createCardElement(cardConfig: LovelaceCardConfig) { private _createCardElement(cardConfig: LovelaceCardConfig) {
const element = document.createElement("hui-card"); const element = document.createElement("hui-card");
element.hass = this.hass; element.hass = this.hass;
element.lovelace = this.lovelace; element.editMode = this.lovelace.editMode;
element.setConfig(cardConfig); element.config = cardConfig;
element.addEventListener( element.addEventListener("card-updated", (ev: Event) => {
"ll-rebuild", ev.stopPropagation();
(ev: Event) => { this._cards = [...this._cards];
// In edit mode let it go to hui-root and rebuild whole view. });
if (!this.lovelace!.editMode) {
ev.stopPropagation();
this._rebuildCard(element, cardConfig);
}
},
{ once: true }
);
return element; return element;
} }
@ -183,11 +175,7 @@ export class HUIView extends ReactiveElement {
}); });
this._cards.forEach((element) => { this._cards.forEach((element) => {
try { element.hass = this.hass;
element.hass = this.hass;
} catch (e: any) {
this._rebuildCard(element, createErrorCardConfig(e.message, null));
}
}); });
this._sections.forEach((element) => { this._sections.forEach((element) => {
@ -226,12 +214,7 @@ export class HUIView extends ReactiveElement {
} }
}); });
this._cards.forEach((element) => { this._cards.forEach((element) => {
try { element.editMode = this.lovelace.editMode;
element.hass = this.hass;
element.lovelace = this.lovelace;
} catch (e: any) {
this._rebuildCard(element, createErrorCardConfig(e.message, null));
}
}); });
} }
if (changedProperties.has("_cards")) { if (changedProperties.has("_cards")) {
@ -388,19 +371,6 @@ export class HUIView extends ReactiveElement {
}); });
} }
private _rebuildCard(
cardElToReplace: HuiCard,
config: LovelaceCardConfig
): void {
const newCardEl = this._createCardElement(config);
if (cardElToReplace.parentElement) {
cardElToReplace.parentElement!.replaceChild(newCardEl, cardElToReplace);
}
this._cards = this._cards!.map((curCardEl) =>
curCardEl === cardElToReplace ? newCardEl : curCardEl
);
}
private _rebuildBadge( private _rebuildBadge(
badgeElToReplace: LovelaceBadge, badgeElToReplace: LovelaceBadge,
config: LovelaceBadgeConfig config: LovelaceBadgeConfig

View File

@ -17,12 +17,13 @@ import {
html, html,
nothing, nothing,
} from "lit"; } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { storage } from "../../common/decorators/storage"; import { storage } from "../../common/decorators/storage";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { computeStateName } from "../../common/entity/compute_state_name"; import { computeStateName } from "../../common/entity/compute_state_name";
import { supportsFeature } from "../../common/entity/supports-feature";
import { navigate } from "../../common/navigate"; import { navigate } from "../../common/navigate";
import { constructUrlCurrentPath } from "../../common/url/construct-url"; import { constructUrlCurrentPath } from "../../common/url/construct-url";
import { import {
@ -40,6 +41,7 @@ import "../../components/ha-two-pane-top-app-bar-fixed";
import { deleteConfigEntry } from "../../data/config_entries"; import { deleteConfigEntry } from "../../data/config_entries";
import { getExtendedEntityRegistryEntry } from "../../data/entity_registry"; import { getExtendedEntityRegistryEntry } from "../../data/entity_registry";
import { fetchIntegrationManifest } from "../../data/integration"; import { fetchIntegrationManifest } from "../../data/integration";
import { LovelaceCardConfig } from "../../data/lovelace/config/card";
import { TodoListEntityFeature, getTodoLists } from "../../data/todo"; import { TodoListEntityFeature, getTodoLists } from "../../data/todo";
import { showConfigFlowDialog } from "../../dialogs/config-flow/show-dialog-config-flow"; import { showConfigFlowDialog } from "../../dialogs/config-flow/show-dialog-config-flow";
import { import {
@ -49,11 +51,8 @@ import {
import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog"; import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
import { haStyle } from "../../resources/styles"; import { haStyle } from "../../resources/styles";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import { HuiErrorCard } from "../lovelace/cards/hui-error-card"; import "../lovelace/cards/hui-card";
import { createCardElement } from "../lovelace/create-element/create-card-element";
import { LovelaceCard } from "../lovelace/types";
import { showTodoItemEditDialog } from "./show-dialog-todo-item-editor"; import { showTodoItemEditDialog } from "./show-dialog-todo-item-editor";
import { supportsFeature } from "../../common/entity/supports-feature";
@customElement("ha-panel-todo") @customElement("ha-panel-todo")
class PanelTodo extends LitElement { class PanelTodo extends LitElement {
@ -63,8 +62,6 @@ class PanelTodo extends LitElement {
@property({ type: Boolean, reflect: true }) public mobile = false; @property({ type: Boolean, reflect: true }) public mobile = false;
@state() private _card?: LovelaceCard | HuiErrorCard;
@storage({ @storage({
key: "selectedTodoEntity", key: "selectedTodoEntity",
state: true, state: true,
@ -128,15 +125,10 @@ class PanelTodo extends LitElement {
if (changedProperties.has("_entityId") || !this.hasUpdated) { if (changedProperties.has("_entityId") || !this.hasUpdated) {
this._setupTodoElement(); this._setupTodoElement();
} }
if (changedProperties.has("hass") && this._card) {
this._card.hass = this.hass;
}
} }
private _setupTodoElement(): void { private _setupTodoElement(): void {
if (!this._entityId) { if (!this._entityId) {
this._card = undefined;
navigate(constructUrlCurrentPath(""), { replace: true }); navigate(constructUrlCurrentPath(""), { replace: true });
return; return;
} }
@ -144,13 +136,16 @@ class PanelTodo extends LitElement {
constructUrlCurrentPath(createSearchParam({ entity_id: this._entityId })), constructUrlCurrentPath(createSearchParam({ entity_id: this._entityId })),
{ replace: true } { replace: true }
); );
this._card = createCardElement({
type: "todo-list",
entity: this._entityId,
}) as LovelaceCard;
this._card.hass = this.hass;
} }
private _cardConfig = memoizeOne(
(entityId: string) =>
({
type: "todo-list",
entity: entityId,
}) as LovelaceCardConfig
);
protected render(): TemplateResult { protected render(): TemplateResult {
const entityRegistryEntry = this._entityId const entityRegistryEntry = this._entityId
? this.hass.entities[this._entityId] ? this.hass.entities[this._entityId]
@ -274,7 +269,16 @@ class PanelTodo extends LitElement {
: nothing} : nothing}
</ha-button-menu> </ha-button-menu>
<div id="columns"> <div id="columns">
<div class="column">${this._card}</div> <div class="column">
${this._entityId
? html`
<hui-card
.hass=${this.hass}
.config=${this._cardConfig(this._entityId)}
></hui-card>
`
: nothing}
</div>
</div> </div>
${entityState && ${entityState &&
supportsFeature(entityState, TodoListEntityFeature.CREATE_TODO_ITEM) supportsFeature(entityState, TodoListEntityFeature.CREATE_TODO_ITEM)