;
+ badges?: LovelaceBadge[];
+}
+
export interface ShowViewConfig {
user?: string;
}
@@ -91,6 +106,7 @@ export interface LovelaceBadgeConfig {
export interface LovelaceCardConfig {
index?: number;
view_index?: number;
+ layout?: any;
type: string;
[key: string]: any;
}
diff --git a/src/panels/lovelace/create-element/create-element-base.ts b/src/panels/lovelace/create-element/create-element-base.ts
index b1afedcb28..23b2083e1e 100644
--- a/src/panels/lovelace/create-element/create-element-base.ts
+++ b/src/panels/lovelace/create-element/create-element-base.ts
@@ -2,9 +2,12 @@ import { fireEvent } from "../../../common/dom/fire_event";
import {
LovelaceBadgeConfig,
LovelaceCardConfig,
+ LovelaceViewConfig,
+ LovelaceViewElement,
} from "../../../data/lovelace";
import { CUSTOM_TYPE_PREFIX } from "../../../data/lovelace_custom_cards";
import type { HuiErrorCard } from "../cards/hui-error-card";
+import type { ErrorCardConfig } from "../cards/types";
import { LovelaceElement, LovelaceElementConfig } from "../elements/types";
import { LovelaceRow, LovelaceRowConfig } from "../entity-rows/types";
import { LovelaceHeaderFooterConfig } from "../header-footer/types";
@@ -14,7 +17,6 @@ import {
LovelaceCardConstructor,
LovelaceHeaderFooter,
} from "../types";
-import type { ErrorCardConfig } from "../cards/types";
const TIMEOUT = 2000;
@@ -44,6 +46,11 @@ interface CreateElementConfigTypes {
element: LovelaceHeaderFooter;
constructor: unknown;
};
+ view: {
+ config: LovelaceViewConfig;
+ element: LovelaceViewElement;
+ constructor: unknown;
+ };
}
export const createErrorCardElement = (config: ErrorCardConfig) => {
diff --git a/src/panels/lovelace/create-element/create-view-element.ts b/src/panels/lovelace/create-element/create-view-element.ts
new file mode 100644
index 0000000000..d8cec5fa48
--- /dev/null
+++ b/src/panels/lovelace/create-element/create-view-element.ts
@@ -0,0 +1,23 @@
+import {
+ LovelaceViewConfig,
+ LovelaceViewElement,
+} from "../../../data/lovelace";
+import "../views/hui-masonry-view";
+import { createLovelaceElement } from "./create-element-base";
+
+const ALWAYS_LOADED_LAYOUTS = new Set(["masonry"]);
+
+const LAZY_LOAD_LAYOUTS = {
+ panel: () => import("../views/hui-panel-view"),
+};
+
+export const createViewElement = (
+ config: LovelaceViewConfig
+): LovelaceViewElement => {
+ return createLovelaceElement(
+ "view",
+ config,
+ ALWAYS_LOADED_LAYOUTS,
+ LAZY_LOAD_LAYOUTS
+ );
+};
diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts
index 01c55e5e95..a0f0a6dfa3 100644
--- a/src/panels/lovelace/hui-root.ts
+++ b/src/panels/lovelace/hui-root.ts
@@ -58,17 +58,14 @@ import { swapView } from "./editor/config-util";
import { showEditLovelaceDialog } from "./editor/lovelace-editor/show-edit-lovelace-dialog";
import { showEditViewDialog } from "./editor/view-editor/show-edit-view-dialog";
import type { Lovelace } from "./types";
-import "./views/hui-panel-view";
-import type { HUIPanelView } from "./views/hui-panel-view";
-import { HUIView } from "./views/hui-view";
+import "./views/hui-view";
+import type { HUIView } from "./views/hui-view";
class HUIRoot extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public lovelace?: Lovelace;
- @property() public columns?: number;
-
@property({ type: Boolean }) public narrow = false;
@property() public route?: { path: string; prefix: string };
@@ -396,15 +393,7 @@ class HUIRoot extends LitElement {
super.updated(changedProperties);
const view = this._viewRoot;
- const huiView = view.lastChild as HUIView | HUIPanelView;
-
- if (
- changedProperties.has("columns") &&
- huiView &&
- huiView instanceof HUIView
- ) {
- huiView.columns = this.columns;
- }
+ const huiView = view.lastChild as HUIView;
if (changedProperties.has("hass") && huiView) {
huiView.hass = this.hass;
@@ -675,15 +664,8 @@ class HUIRoot extends LitElement {
if (!force && this._viewCache![viewIndex]) {
view = this._viewCache![viewIndex];
} else {
- if (viewConfig.panel && viewConfig.cards && viewConfig.cards.length > 0) {
- view = document.createElement("hui-panel-view");
- view.config = viewConfig;
- view.index = viewIndex;
- } else {
- view = document.createElement("hui-view");
- view.columns = this.columns;
- view.index = viewIndex;
- }
+ view = document.createElement("hui-view");
+ view.index = viewIndex;
this._viewCache![viewIndex] = view;
}
diff --git a/src/panels/lovelace/views/hui-view-editable.ts b/src/panels/lovelace/views/default-view-editable.ts
similarity index 100%
rename from src/panels/lovelace/views/hui-view-editable.ts
rename to src/panels/lovelace/views/default-view-editable.ts
diff --git a/src/panels/lovelace/views/hui-masonry-view.ts b/src/panels/lovelace/views/hui-masonry-view.ts
new file mode 100644
index 0000000000..30b9f61395
--- /dev/null
+++ b/src/panels/lovelace/views/hui-masonry-view.ts
@@ -0,0 +1,317 @@
+import { mdiPlus } from "@mdi/js";
+import {
+ css,
+ CSSResult,
+ html,
+ internalProperty,
+ LitElement,
+ property,
+ PropertyValues,
+ TemplateResult,
+} from "lit-element";
+import { classMap } from "lit-html/directives/class-map";
+import { computeRTL } from "../../../common/util/compute_rtl";
+import { nextRender } from "../../../common/util/render-status";
+import "../../../components/entity/ha-state-label-badge";
+import "../../../components/ha-svg-icon";
+import type {
+ LovelaceViewConfig,
+ LovelaceViewElement,
+} from "../../../data/lovelace";
+import type { HomeAssistant } from "../../../types";
+import type { HuiErrorCard } from "../cards/hui-error-card";
+import { computeCardSize } from "../common/compute-card-size";
+import { showCreateCardDialog } from "../editor/card-editor/show-create-card-dialog";
+import type { Lovelace, LovelaceBadge, LovelaceCard } from "../types";
+
+let editCodeLoaded = false;
+
+// Find column with < 5 size, else smallest column
+const getColumnIndex = (columnSizes: number[], size: number) => {
+ let minIndex = 0;
+ for (let i = 0; i < columnSizes.length; i++) {
+ if (columnSizes[i] < 5) {
+ minIndex = i;
+ break;
+ }
+ if (columnSizes[i] < columnSizes[minIndex]) {
+ minIndex = i;
+ }
+ }
+
+ columnSizes[minIndex] += size;
+
+ return minIndex;
+};
+
+export class MasonryView extends LitElement implements LovelaceViewElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @property({ attribute: false }) public lovelace?: Lovelace;
+
+ @property({ type: Number }) public index?: number;
+
+ @property({ attribute: false }) public cards: Array<
+ LovelaceCard | HuiErrorCard
+ > = [];
+
+ @property({ attribute: false }) public badges: LovelaceBadge[] = [];
+
+ @internalProperty() private _columns?: number;
+
+ private _createColumnsIteration = 0;
+
+ private _mqls?: MediaQueryList[];
+
+ public constructor() {
+ super();
+ this.addEventListener("iron-resize", (ev: Event) => ev.stopPropagation());
+ }
+
+ public setConfig(_config: LovelaceViewConfig): void {}
+
+ protected render(): TemplateResult {
+ return html`
+ 0 ? "display: block" : "display: none"}
+ >
+ ${this.badges.map((badge) => html`${badge}`)}
+
+
+ ${this.lovelace?.editMode
+ ? html`
+
+
+
+ `
+ : ""}
+ `;
+ }
+
+ protected firstUpdated(): void {
+ this._mqls = [300, 600, 900, 1200].map((width) => {
+ const mql = matchMedia(`(min-width: ${width}px)`);
+ mql.addEventListener("change", this._updateColumns);
+ return mql;
+ });
+ }
+
+ protected updated(changedProperties: PropertyValues): void {
+ super.updated(changedProperties);
+
+ if (this.lovelace?.editMode && !editCodeLoaded) {
+ editCodeLoaded = true;
+ import(
+ /* webpackChunkName: "default-layout-editable" */ "./default-view-editable"
+ );
+ }
+
+ if (changedProperties.has("hass")) {
+ const oldHass = changedProperties.get("hass") as HomeAssistant;
+
+ if (
+ (oldHass && this.hass!.dockedSidebar !== oldHass.dockedSidebar) ||
+ (!oldHass && this.hass)
+ ) {
+ this._updateColumns();
+ }
+
+ if (changedProperties.size === 1) {
+ return;
+ }
+ }
+
+ const oldLovelace = changedProperties.get("lovelace") as Lovelace;
+
+ if (
+ oldLovelace?.config !== this.lovelace?.config ||
+ oldLovelace?.editMode !== this.lovelace?.editMode ||
+ changedProperties.has("_columns")
+ ) {
+ this._createColumns();
+ }
+ }
+
+ private _addCard(): void {
+ showCreateCardDialog(this, {
+ lovelaceConfig: this.lovelace!.config,
+ saveConfig: this.lovelace!.saveConfig,
+ path: [this.index!],
+ });
+ }
+
+ private async _createColumns() {
+ this._createColumnsIteration++;
+ const iteration = this._createColumnsIteration;
+ const root = this.shadowRoot!.getElementById("columns")!;
+
+ // Remove old columns
+ while (root.lastChild) {
+ root.removeChild(root.lastChild);
+ }
+
+ // Track the total height of cards in a columns
+ const columnSizes: number[] = [];
+ const columnElements: HTMLDivElement[] = [];
+ // Add columns to DOM, limit number of columns to the number of cards
+ for (let i = 0; i < Math.min(this._columns!, this.cards.length); i++) {
+ const columnEl = document.createElement("div");
+ columnEl.classList.add("column");
+ root.appendChild(columnEl);
+ columnSizes.push(0);
+ columnElements.push(columnEl);
+ }
+
+ let tillNextRender: Promise | undefined;
+ let start: Date | undefined;
+
+ // Calculate the size of every card and determine in what column it should go
+ for (const [index, el] of this.cards.entries()) {
+ if (tillNextRender === undefined) {
+ // eslint-disable-next-line no-loop-func
+ tillNextRender = nextRender().then(() => {
+ tillNextRender = undefined;
+ start = undefined;
+ });
+ }
+
+ let waitProm: Promise | undefined;
+
+ // We should work for max 16ms (60fps) before allowing a frame to render
+ if (start === undefined) {
+ // Save the time we start for this frame, no need to wait yet
+ start = new Date();
+ } else if (new Date().getTime() - start.getTime() > 16) {
+ // We are working too long, we will prevent a render, wait to allow for a render
+ waitProm = tillNextRender;
+ }
+
+ const cardSizeProm = computeCardSize(el);
+ // @ts-ignore
+ // eslint-disable-next-line no-await-in-loop
+ const [cardSize] = await Promise.all([cardSizeProm, waitProm]);
+
+ if (iteration !== this._createColumnsIteration) {
+ // An other create columns is started, abort this one
+ return;
+ }
+ // Calculate in wich column the card should go based on the size and the cards already in there
+ this._addCardToColumn(
+ columnElements[getColumnIndex(columnSizes, cardSize as number)],
+ index,
+ this.lovelace!.editMode
+ );
+ }
+
+ // Remove empty columns
+ columnElements.forEach((column) => {
+ if (!column.lastChild) {
+ column.parentElement!.removeChild(column);
+ }
+ });
+ }
+
+ private _addCardToColumn(columnEl, index, editMode) {
+ const card: LovelaceCard = this.cards[index];
+ if (!editMode) {
+ card.editMode = false;
+ columnEl.appendChild(card);
+ } else {
+ const wrapper = document.createElement("hui-card-options");
+ wrapper.hass = this.hass;
+ wrapper.lovelace = this.lovelace;
+ wrapper.path = [this.index!, index];
+ card.editMode = true;
+ wrapper.appendChild(card);
+ columnEl.appendChild(wrapper);
+ }
+ }
+
+ private _updateColumns() {
+ const matchColumns = this._mqls!.reduce(
+ (cols, mql) => cols + Number(mql.matches),
+ 0
+ );
+ // Do -1 column if the menu is docked and open
+ this._columns = Math.max(
+ 1,
+ matchColumns - Number(this.hass!.dockedSidebar === "docked")
+ );
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ #badges {
+ margin: 8px 16px;
+ font-size: 85%;
+ text-align: center;
+ }
+
+ #columns {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ }
+
+ .column {
+ flex: 1 0 0;
+ max-width: 500px;
+ min-width: 0;
+ }
+
+ .column > * {
+ display: block;
+ margin: 4px 4px 8px;
+ }
+
+ mwc-fab {
+ position: sticky;
+ float: right;
+ right: calc(16px + env(safe-area-inset-right));
+ bottom: calc(16px + env(safe-area-inset-bottom));
+ z-index: 1;
+ }
+
+ mwc-fab.rtl {
+ float: left;
+ right: auto;
+ left: calc(16px + env(safe-area-inset-left));
+ }
+
+ @media (max-width: 500px) {
+ :host {
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ .column > * {
+ margin-left: 0;
+ margin-right: 0;
+ }
+ }
+
+ @media (max-width: 599px) {
+ .column {
+ max-width: 600px;
+ }
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-masonry-view": MasonryView;
+ }
+}
+
+customElements.define("hui-masonry-view", MasonryView);
diff --git a/src/panels/lovelace/views/hui-panel-view.ts b/src/panels/lovelace/views/hui-panel-view.ts
index 87feecb634..44f9d27fe3 100644
--- a/src/panels/lovelace/views/hui-panel-view.ts
+++ b/src/panels/lovelace/views/hui-panel-view.ts
@@ -1,93 +1,118 @@
+import { mdiPlus } from "@mdi/js";
import {
- customElement,
+ css,
+ CSSResult,
+ html,
+ internalProperty,
+ LitElement,
property,
PropertyValues,
- UpdatingElement,
+ TemplateResult,
} from "lit-element";
-import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
-import { LovelaceViewConfig } from "../../../data/lovelace";
-import { HomeAssistant } from "../../../types";
-import { createCardElement } from "../create-element/create-card-element";
-import { Lovelace, LovelaceCard } from "../types";
+import { classMap } from "lit-html/directives/class-map";
+import { computeRTL } from "../../../common/util/compute_rtl";
+import type {
+ LovelaceViewConfig,
+ LovelaceViewElement,
+} from "../../../data/lovelace";
+import type { HomeAssistant } from "../../../types";
+import { HuiErrorCard } from "../cards/hui-error-card";
+import { HuiCardOptions } from "../components/hui-card-options";
+import { HuiWarning } from "../components/hui-warning";
+import { showCreateCardDialog } from "../editor/card-editor/show-create-card-dialog";
+import type { Lovelace, LovelaceCard } from "../types";
let editCodeLoaded = false;
-@customElement("hui-panel-view")
-export class HUIPanelView extends UpdatingElement {
- @property({ attribute: false }) public hass?: HomeAssistant;
+export class PanelView extends LitElement implements LovelaceViewElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public lovelace?: Lovelace;
- @property() public config?: LovelaceViewConfig;
+ @property({ type: Number }) public index?: number;
- @property({ type: Number }) public index!: number;
+ @property({ attribute: false }) public cards: Array<
+ LovelaceCard | HuiErrorCard
+ > = [];
- protected firstUpdated(changedProperties: PropertyValues): void {
- super.firstUpdated(changedProperties);
+ @internalProperty() private _card?:
+ | LovelaceCard
+ | HuiWarning
+ | HuiCardOptions;
+
+ public constructor() {
+ super();
this.style.setProperty("background", "var(--lovelace-background)");
}
- protected update(changedProperties: PropertyValues): void {
- super.update(changedProperties);
+ public setConfig(_config: LovelaceViewConfig): void {}
- const hass = this.hass!;
- const lovelace = this.lovelace!;
- const hassChanged = changedProperties.has("hass");
- const oldHass = changedProperties.get("hass") as this["hass"] | undefined;
- const configChanged = changedProperties.has("config");
+ protected updated(changedProperties: PropertyValues): void {
+ super.updated(changedProperties);
- if (lovelace.editMode && !editCodeLoaded) {
+ if (this.lovelace?.editMode && !editCodeLoaded) {
editCodeLoaded = true;
- import(/* webpackChunkName: "hui-view-editable" */ "./hui-view-editable");
+ import(
+ /* webpackChunkName: "default-layout-editable" */ "./default-view-editable"
+ );
}
- let editModeChanged = false;
-
- if (changedProperties.has("lovelace")) {
- const oldLovelace = changedProperties.get("lovelace") as Lovelace;
- editModeChanged =
- !oldLovelace || lovelace.editMode !== oldLovelace.editMode;
- }
-
- if (editModeChanged || configChanged) {
- this._createCard();
- } else if (hassChanged) {
- (this.lastChild! as LovelaceCard).hass = this.hass;
- }
+ const oldLovelace = changedProperties.get("lovelace") as Lovelace;
if (
- configChanged ||
- (hassChanged &&
- oldHass &&
- (hass.themes !== oldHass.themes ||
- hass.selectedTheme !== oldHass.selectedTheme))
+ oldLovelace?.config !== this.lovelace?.config ||
+ (oldLovelace && oldLovelace?.editMode !== this.lovelace?.editMode)
) {
- applyThemesOnElement(this, hass.themes, this.config!.theme);
+ this._createCard();
}
}
- private _createCard(): void {
- while (this.lastChild) {
- this.removeChild(this.lastChild);
- }
+ protected render(): TemplateResult {
+ return html`
+ ${this._card}
+ ${this.lovelace?.editMode && this.cards.length === 0
+ ? html`
+
+
+
+ `
+ : ""}
+ `;
+ }
- const card: LovelaceCard = createCardElement(this.config!.cards![0]);
- card.hass = this.hass;
+ private _addCard(): void {
+ showCreateCardDialog(this, {
+ lovelaceConfig: this.lovelace!.config,
+ saveConfig: this.lovelace!.saveConfig,
+ path: [this.index!],
+ });
+ }
+
+ private _createCard(): void {
+ const card: LovelaceCard = this.cards[0];
card.isPanel = true;
- if (!this.lovelace!.editMode) {
- this.appendChild(card);
- return;
+ if (!this.lovelace?.editMode) {
+ this._card = card;
}
const wrapper = document.createElement("hui-card-options");
wrapper.hass = this.hass;
wrapper.lovelace = this.lovelace;
- wrapper.path = [this.index, 0];
+ wrapper.path = [this.index!, 0];
card.editMode = true;
wrapper.appendChild(card);
- this.appendChild(wrapper);
- if (this.config!.cards!.length > 1) {
+ this._card = wrapper;
+
+ if (this.cards!.length > 1) {
const warning = document.createElement("hui-warning");
warning.setAttribute(
"style",
@@ -96,13 +121,33 @@ export class HUIPanelView extends UpdatingElement {
warning.innerText = this.hass!.localize(
"ui.panel.lovelace.editor.view.panel_mode.warning_multiple_cards"
);
- this.appendChild(warning);
+ this._card = warning;
}
}
+
+ static get styles(): CSSResult {
+ return css`
+ mwc-fab {
+ position: sticky;
+ float: right;
+ right: calc(16px + env(safe-area-inset-right));
+ bottom: calc(16px + env(safe-area-inset-bottom));
+ z-index: 1;
+ }
+
+ mwc-fab.rtl {
+ float: left;
+ right: auto;
+ left: calc(16px + env(safe-area-inset-left));
+ }
+ `;
+ }
}
declare global {
interface HTMLElementTagNameMap {
- "hui-panel-view": HUIPanelView;
+ "hui-panel-view": PanelView;
}
}
+
+customElements.define("hui-panel-view", PanelView);
diff --git a/src/panels/lovelace/views/hui-view.ts b/src/panels/lovelace/views/hui-view.ts
index eb627cf1ff..de0bae5ea6 100644
--- a/src/panels/lovelace/views/hui-view.ts
+++ b/src/panels/lovelace/views/hui-view.ts
@@ -1,73 +1,43 @@
import {
- html,
- LitElement,
- property,
+ customElement,
internalProperty,
+ property,
PropertyValues,
- TemplateResult,
- CSSResult,
- css,
+ UpdatingElement,
} from "lit-element";
-import { classMap } from "lit-html/directives/class-map";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
-import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/entity/ha-state-label-badge";
-import {
+import "../../../components/ha-svg-icon";
+import type {
LovelaceBadgeConfig,
LovelaceCardConfig,
LovelaceViewConfig,
+ LovelaceViewElement,
} from "../../../data/lovelace";
-import { HomeAssistant } from "../../../types";
-import { HuiErrorCard } from "../cards/hui-error-card";
-import { computeCardSize } from "../common/compute-card-size";
+import type { HomeAssistant } from "../../../types";
+import type { HuiErrorCard } from "../cards/hui-error-card";
import { processConfigEntities } from "../common/process-config-entities";
import { createBadgeElement } from "../create-element/create-badge-element";
import { createCardElement } from "../create-element/create-card-element";
-import { Lovelace, LovelaceBadge, LovelaceCard } from "../types";
-import "../../../components/ha-svg-icon";
-import { mdiPlus } from "@mdi/js";
-import { nextRender } from "../../../common/util/render-status";
-import { showCreateCardDialog } from "../editor/card-editor/show-create-card-dialog";
+import { createViewElement } from "../create-element/create-view-element";
+import type { Lovelace, LovelaceBadge, LovelaceCard } from "../types";
-let editCodeLoaded = false;
+const DEFAULT_VIEW_LAYOUT = "masonry";
+const PANEL_VIEW_LAYOUT = "panel";
-// Find column with < 5 size, else smallest column
-const getColumnIndex = (columnSizes: number[], size: number) => {
- let minIndex = 0;
- for (let i = 0; i < columnSizes.length; i++) {
- if (columnSizes[i] < 5) {
- minIndex = i;
- break;
- }
- if (columnSizes[i] < columnSizes[minIndex]) {
- minIndex = i;
- }
- }
-
- columnSizes[minIndex] += size;
-
- return minIndex;
-};
-
-export class HUIView extends LitElement {
+@customElement("hui-view")
+export class HUIView extends UpdatingElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public lovelace?: Lovelace;
- @property({ type: Number }) public columns?: number;
-
@property({ type: Number }) public index?: number;
@internalProperty() private _cards: Array = [];
@internalProperty() private _badges: LovelaceBadge[] = [];
- private _createColumnsIteration = 0;
-
- public constructor() {
- super();
- this.addEventListener("iron-resize", (ev) => ev.stopPropagation());
- }
+ private _layoutElement?: LovelaceViewElement;
// Public to make demo happy
public createCardElement(cardConfig: LovelaceCardConfig) {
@@ -75,7 +45,7 @@ export class HUIView extends LitElement {
element.hass = this.hass;
element.addEventListener(
"ll-rebuild",
- (ev) => {
+ (ev: Event) => {
// In edit mode let it go to hui-root and rebuild whole view.
if (!this.lovelace!.editMode) {
ev.stopPropagation();
@@ -100,40 +70,14 @@ export class HUIView extends LitElement {
return element;
}
- protected render(): TemplateResult {
- return html`
-
-
- ${this.lovelace!.editMode
- ? html`
-
-
-
- `
- : ""}
- `;
- }
-
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
const hass = this.hass!;
const lovelace = this.lovelace!;
- if (lovelace.editMode && !editCodeLoaded) {
- editCodeLoaded = true;
- import(/* webpackChunkName: "hui-view-editable" */ "./hui-view-editable");
- }
-
const hassChanged = changedProperties.has("hass");
+ const oldLovelace = changedProperties.get("lovelace") as Lovelace;
let editModeChanged = false;
let configChanged = false;
@@ -141,30 +85,55 @@ export class HUIView extends LitElement {
if (changedProperties.has("index")) {
configChanged = true;
} else if (changedProperties.has("lovelace")) {
- const oldLovelace = changedProperties.get("lovelace") as Lovelace;
editModeChanged =
oldLovelace && lovelace.editMode !== oldLovelace.editMode;
configChanged = !oldLovelace || lovelace.config !== oldLovelace.config;
}
+ let viewConfig: LovelaceViewConfig | undefined;
+
if (configChanged) {
- this._createBadges(lovelace.config.views[this.index!]);
- } else if (hassChanged) {
+ viewConfig = lovelace.config.views[this.index!];
+ viewConfig = {
+ ...viewConfig,
+ type: viewConfig.panel
+ ? PANEL_VIEW_LAYOUT
+ : viewConfig.type || DEFAULT_VIEW_LAYOUT,
+ };
+ }
+
+ if (configChanged && !this._layoutElement) {
+ this._layoutElement = createViewElement(viewConfig!);
+ }
+
+ if (configChanged) {
+ this._createBadges(viewConfig!);
+ this._createCards(viewConfig!);
+
+ this._layoutElement!.hass = this.hass;
+ this._layoutElement!.lovelace = lovelace;
+ this._layoutElement!.index = this.index;
+ }
+
+ if (hassChanged) {
this._badges.forEach((badge) => {
badge.hass = hass;
});
- }
- if (configChanged) {
- this._createCards(lovelace.config.views[this.index!]);
- } else if (editModeChanged || changedProperties.has("columns")) {
- this._createColumns();
- }
-
- if (hassChanged && !configChanged) {
this._cards.forEach((element) => {
element.hass = hass;
});
+
+ this._layoutElement!.hass = this.hass;
+ }
+
+ if (editModeChanged) {
+ this._layoutElement!.lovelace = lovelace;
+ }
+
+ if (configChanged || hassChanged || editModeChanged) {
+ this._layoutElement!.cards = this._cards;
+ this._layoutElement!.badges = this._badges;
}
const oldHass = changedProperties.get("hass") as this["hass"] | undefined;
@@ -183,25 +152,14 @@ export class HUIView extends LitElement {
lovelace.config.views[this.index!].theme
);
}
- }
- private _addCard(): void {
- showCreateCardDialog(this, {
- lovelaceConfig: this.lovelace!.config,
- saveConfig: this.lovelace!.saveConfig,
- path: [this.index!],
- });
+ if (this._layoutElement && !this.lastChild) {
+ this.appendChild(this._layoutElement);
+ }
}
private _createBadges(config: LovelaceViewConfig): void {
- const root = this.shadowRoot!.getElementById("badges")!;
-
- while (root.lastChild) {
- root.removeChild(root.lastChild);
- }
-
if (!config || !config.badges || !Array.isArray(config.badges)) {
- root.style.display = "none";
this._badges = [];
return;
}
@@ -212,97 +170,8 @@ export class HUIView extends LitElement {
const element = createBadgeElement(badge);
element.hass = this.hass;
elements.push(element);
- root.appendChild(element);
});
this._badges = elements;
- root.style.display = elements.length > 0 ? "block" : "none";
- }
-
- private async _createColumns() {
- this._createColumnsIteration++;
- const iteration = this._createColumnsIteration;
- const root = this.shadowRoot!.getElementById("columns")!;
-
- // Remove old columns
- while (root.lastChild) {
- root.removeChild(root.lastChild);
- }
-
- // Track the total height of cards in a columns
- const columnSizes: number[] = [];
- const columnElements: HTMLDivElement[] = [];
- // Add columns to DOM, limit number of columns to the number of cards
- for (let i = 0; i < Math.min(this.columns!, this._cards.length); i++) {
- const columnEl = document.createElement("div");
- columnEl.classList.add("column");
- root.appendChild(columnEl);
- columnSizes.push(0);
- columnElements.push(columnEl);
- }
-
- let tillNextRender: Promise | undefined;
- let start: Date | undefined;
-
- // Calculate the size of every card and determine in what column it should go
- for (const [index, el] of this._cards.entries()) {
- if (tillNextRender === undefined) {
- // eslint-disable-next-line no-loop-func
- tillNextRender = nextRender().then(() => {
- tillNextRender = undefined;
- start = undefined;
- });
- }
-
- let waitProm: Promise | undefined;
-
- // We should work for max 16ms (60fps) before allowing a frame to render
- if (start === undefined) {
- // Save the time we start for this frame, no need to wait yet
- start = new Date();
- } else if (new Date().getTime() - start.getTime() > 16) {
- // We are working too long, we will prevent a render, wait to allow for a render
- waitProm = tillNextRender;
- }
-
- const cardSizeProm = computeCardSize(el);
- // @ts-ignore
- // eslint-disable-next-line no-await-in-loop
- const [cardSize] = await Promise.all([cardSizeProm, waitProm]);
-
- if (iteration !== this._createColumnsIteration) {
- // An other create columns is started, abort this one
- return;
- }
- // Calculate in wich column the card should go based on the size and the cards already in there
- this._addCardToColumn(
- columnElements[getColumnIndex(columnSizes, cardSize as number)],
- index,
- this.lovelace!.editMode
- );
- }
-
- // Remove empty columns
- columnElements.forEach((column) => {
- if (!column.lastChild) {
- column.parentElement!.removeChild(column);
- }
- });
- }
-
- private _addCardToColumn(columnEl, index, editMode) {
- const card: LovelaceCard = this._cards[index];
- if (!editMode) {
- card.editMode = false;
- columnEl.appendChild(card);
- } else {
- const wrapper = document.createElement("hui-card-options");
- wrapper.hass = this.hass;
- wrapper.lovelace = this.lovelace;
- wrapper.path = [this.index!, index];
- card.editMode = true;
- wrapper.appendChild(card);
- columnEl.appendChild(wrapper);
- }
}
private _createCards(config: LovelaceViewConfig): void {
@@ -311,14 +180,9 @@ export class HUIView extends LitElement {
return;
}
- const elements: LovelaceCard[] = [];
- config.cards.forEach((cardConfig) => {
- const element = this.createCardElement(cardConfig);
- elements.push(element);
- });
-
- this._cards = elements;
- this._createColumns();
+ this._cards = config.cards.map((cardConfig) =>
+ this.createCardElement(cardConfig)
+ );
}
private _rebuildCard(
@@ -344,63 +208,6 @@ export class HUIView extends LitElement {
curBadgeEl === badgeElToReplace ? newBadgeEl : curBadgeEl
);
}
-
- static get styles(): CSSResult {
- return css`
- :host {
- display: block;
- box-sizing: border-box;
- padding: 4px 4px env(safe-area-inset-bottom);
- transform: translateZ(0);
- position: relative;
- color: var(--primary-text-color);
- background: var(--lovelace-background, var(--primary-background-color));
- }
-
- #badges {
- margin: 8px 16px;
- font-size: 85%;
- text-align: center;
- }
-
- #columns {
- display: flex;
- flex-direction: row;
- justify-content: center;
- }
-
- .column {
- flex: 1 0 0;
- max-width: 500px;
- min-width: 0;
- }
-
- .column > * {
- display: block;
- margin: 4px 4px 8px;
- }
-
- mwc-fab {
- position: sticky;
- float: right;
- right: calc(16px + env(safe-area-inset-right));
- bottom: calc(16px + env(safe-area-inset-bottom));
- z-index: 1;
- }
-
- mwc-fab.rtl {
- float: left;
- right: auto;
- left: calc(16px + env(safe-area-inset-left));
- }
-
- @media (max-width: 599px) {
- .column {
- max-width: 600px;
- }
- }
- `;
- }
}
declare global {
@@ -408,5 +215,3 @@ declare global {
"hui-view": HUIView;
}
}
-
-customElements.define("hui-view", HUIView);