mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-11-04 08:29:52 +00:00 
			
		
		
		
	Compare commits
	
		
			44 Commits
		
	
	
		
			20230223.0
			...
			lit-grid-l
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2fef57f0b2 | ||
| 
						 | 
					9b279632f8 | ||
| 
						 | 
					480e781364 | ||
| 
						 | 
					adc10ff0c6 | ||
| 
						 | 
					57a718e409 | ||
| 
						 | 
					23462bacfa | ||
| 
						 | 
					d99f8b0da8 | ||
| 
						 | 
					991d1f0997 | ||
| 
						 | 
					54e99357b5 | ||
| 
						 | 
					d263b5550a | ||
| 
						 | 
					a2b6ce3437 | ||
| 
						 | 
					9458f7ae62 | ||
| 
						 | 
					4d2ae36b0d | ||
| 
						 | 
					ebf0050e81 | ||
| 
						 | 
					3d0f492d62 | ||
| 
						 | 
					f5d3237b06 | ||
| 
						 | 
					07671ba1d4 | ||
| 
						 | 
					354d74ce1d | ||
| 
						 | 
					e892d14af0 | ||
| 
						 | 
					8d034fb7e7 | ||
| 
						 | 
					249456e6a0 | ||
| 
						 | 
					d3a6f31310 | ||
| 
						 | 
					4c31aead8b | ||
| 
						 | 
					3caf91c5d3 | ||
| 
						 | 
					9c3e754d53 | ||
| 
						 | 
					f48765c2b6 | ||
| 
						 | 
					236f26e652 | ||
| 
						 | 
					0fed96aba3 | ||
| 
						 | 
					70b77833f1 | ||
| 
						 | 
					307694a820 | ||
| 
						 | 
					402391c3e1 | ||
| 
						 | 
					81b5866e4d | ||
| 
						 | 
					09985bf5f7 | ||
| 
						 | 
					71d12a9e7a | ||
| 
						 | 
					5fd733b6c9 | ||
| 
						 | 
					bc3f827b4a | ||
| 
						 | 
					a807a182e4 | ||
| 
						 | 
					0a1fb843ca | ||
| 
						 | 
					fceb1568f5 | ||
| 
						 | 
					74e93bbefc | ||
| 
						 | 
					5c0f4b564b | ||
| 
						 | 
					76734f7a0b | ||
| 
						 | 
					440d10e4cd | ||
| 
						 | 
					a901072695 | 
@@ -35,6 +35,9 @@ const createWebpackConfig = ({
 | 
			
		||||
            loader: "babel-loader",
 | 
			
		||||
            options: bundle.babelOptions({ latestBuild }),
 | 
			
		||||
          },
 | 
			
		||||
          resolve: {
 | 
			
		||||
            fullySpecified: false,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          test: /\.css$/,
 | 
			
		||||
 
 | 
			
		||||
@@ -105,6 +105,7 @@
 | 
			
		||||
    "leaflet": "^1.4.0",
 | 
			
		||||
    "leaflet-draw": "^1.0.4",
 | 
			
		||||
    "lit-element": "^2.4.0",
 | 
			
		||||
    "lit-grid-layout": "^1.1.11",
 | 
			
		||||
    "lit-html": "^1.3.0",
 | 
			
		||||
    "lit-virtualizer": "^0.4.2",
 | 
			
		||||
    "marked": "^1.1.1",
 | 
			
		||||
@@ -121,6 +122,7 @@
 | 
			
		||||
    "superstruct": "^0.10.12",
 | 
			
		||||
    "tinykeys": "^1.1.1",
 | 
			
		||||
    "unfetch": "^4.1.0",
 | 
			
		||||
    "uuid": "^8.3.0",
 | 
			
		||||
    "vue": "^2.6.11",
 | 
			
		||||
    "vue2-daterange-picker": "^0.5.1",
 | 
			
		||||
    "web-animations-js": "^2.3.2",
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ const ALWAYS_LOADED_LAYOUTS = new Set(["masonry"]);
 | 
			
		||||
 | 
			
		||||
const LAZY_LOAD_LAYOUTS = {
 | 
			
		||||
  panel: () => import("../views/hui-panel-view"),
 | 
			
		||||
  grid: () => import("../views/hui-grid-view"),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const createViewElement = (
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ import {
 | 
			
		||||
  TemplateResult,
 | 
			
		||||
} from "lit-element";
 | 
			
		||||
import {
 | 
			
		||||
  any,
 | 
			
		||||
  array,
 | 
			
		||||
  assert,
 | 
			
		||||
  boolean,
 | 
			
		||||
@@ -62,6 +63,7 @@ const cardConfigStruct = object({
 | 
			
		||||
  theme: optional(string()),
 | 
			
		||||
  show_header_toggle: optional(boolean()),
 | 
			
		||||
  state_color: optional(boolean()),
 | 
			
		||||
  layout: optional(any()),
 | 
			
		||||
  entities: array(entitiesConfigStruct),
 | 
			
		||||
  header: optional(headerFooterConfigStructs),
 | 
			
		||||
  footer: optional(headerFooterConfigStructs),
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@ export interface LovelaceCard extends HTMLElement {
 | 
			
		||||
  hass?: HomeAssistant;
 | 
			
		||||
  isPanel?: boolean;
 | 
			
		||||
  editMode?: boolean;
 | 
			
		||||
  layout?: any;
 | 
			
		||||
  getCardSize(): number | Promise<number>;
 | 
			
		||||
  setConfig(config: LovelaceCardConfig): void;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								src/panels/lovelace/views/grid/grid-view-editable.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/panels/lovelace/views/grid/grid-view-editable.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
// hui-grid-view dependencies for when in edit mode.
 | 
			
		||||
import "@material/mwc-fab";
 | 
			
		||||
import "./hui-grid-card-options";
 | 
			
		||||
							
								
								
									
										195
									
								
								src/panels/lovelace/views/grid/hui-grid-card-options.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								src/panels/lovelace/views/grid/hui-grid-card-options.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,195 @@
 | 
			
		||||
import "@material/mwc-icon-button";
 | 
			
		||||
import {
 | 
			
		||||
  mdiContentDuplicate,
 | 
			
		||||
  mdiDelete,
 | 
			
		||||
  mdiDotsVertical,
 | 
			
		||||
  mdiPencil,
 | 
			
		||||
} from "@mdi/js";
 | 
			
		||||
import {
 | 
			
		||||
  css,
 | 
			
		||||
  CSSResult,
 | 
			
		||||
  customElement,
 | 
			
		||||
  LitElement,
 | 
			
		||||
  property,
 | 
			
		||||
  queryAssignedNodes,
 | 
			
		||||
} from "lit-element";
 | 
			
		||||
import { html, TemplateResult } from "lit-html";
 | 
			
		||||
import "../../../../components/ha-svg-icon";
 | 
			
		||||
import { HomeAssistant } from "../../../../types";
 | 
			
		||||
import { computeCardSize } from "../../common/compute-card-size";
 | 
			
		||||
import { showEditCardDialog } from "../../editor/card-editor/show-edit-card-dialog";
 | 
			
		||||
import { confDeleteCard } from "../../editor/delete-card";
 | 
			
		||||
import { Lovelace, LovelaceCard } from "../../types";
 | 
			
		||||
 | 
			
		||||
@customElement("hui-grid-card-options")
 | 
			
		||||
export class HuiGridCardOptions extends LitElement {
 | 
			
		||||
  @property({ attribute: false }) public hass?: HomeAssistant;
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public lovelace?: Lovelace;
 | 
			
		||||
 | 
			
		||||
  @property({ type: Array }) public path?: [number, number];
 | 
			
		||||
 | 
			
		||||
  @queryAssignedNodes() private _assignedNodes?: NodeListOf<LovelaceCard>;
 | 
			
		||||
 | 
			
		||||
  public getCardSize() {
 | 
			
		||||
    return this._assignedNodes ? computeCardSize(this._assignedNodes[0]) : 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected render(): TemplateResult {
 | 
			
		||||
    return html`
 | 
			
		||||
      <slot></slot>
 | 
			
		||||
      <div class="parent-card-actions">
 | 
			
		||||
        <div class="overlay"></div>
 | 
			
		||||
        <div class="card-actions">
 | 
			
		||||
          <mwc-icon-button
 | 
			
		||||
            .title=${this.hass!.localize(
 | 
			
		||||
              "ui.panel.lovelace.editor.edit_card.edit"
 | 
			
		||||
            )}
 | 
			
		||||
            @click=${this._editCard}
 | 
			
		||||
          >
 | 
			
		||||
            <ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
 | 
			
		||||
          </mwc-icon-button>
 | 
			
		||||
          <mwc-icon-button
 | 
			
		||||
            .title=${this.hass!.localize(
 | 
			
		||||
              "ui.panel.lovelace.editor.edit_card.duplicate"
 | 
			
		||||
            )}
 | 
			
		||||
            @click=${this._duplicateCard}
 | 
			
		||||
          >
 | 
			
		||||
            <ha-svg-icon .path=${mdiContentDuplicate}></ha-svg-icon>
 | 
			
		||||
          </mwc-icon-button>
 | 
			
		||||
          <mwc-icon-button
 | 
			
		||||
            .title=${this.hass!.localize(
 | 
			
		||||
              "ui.panel.lovelace.editor.edit_card.delete"
 | 
			
		||||
            )}
 | 
			
		||||
            @click=${this._deleteCard}
 | 
			
		||||
          >
 | 
			
		||||
            <ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
 | 
			
		||||
          </mwc-icon-button>
 | 
			
		||||
          <div>
 | 
			
		||||
            <ha-button-menu corner="BOTTOM_START">
 | 
			
		||||
              <mwc-icon-button
 | 
			
		||||
                slot="trigger"
 | 
			
		||||
                aria-label=${this.hass!.localize(
 | 
			
		||||
                  "ui.panel.lovelace.editor.edit_card.options"
 | 
			
		||||
                )}
 | 
			
		||||
                .title=${this.hass!.localize(
 | 
			
		||||
                  "ui.panel.lovelace.editor.edit_card.options"
 | 
			
		||||
                )}
 | 
			
		||||
              >
 | 
			
		||||
                <ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
 | 
			
		||||
              </mwc-icon-button>
 | 
			
		||||
              <mwc-list-item>
 | 
			
		||||
                ${this.hass!.localize(
 | 
			
		||||
                  "ui.panel.lovelace.editor.edit_card.move"
 | 
			
		||||
                )}
 | 
			
		||||
              </mwc-list-item>
 | 
			
		||||
            </ha-button-menu>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static get styles(): CSSResult {
 | 
			
		||||
    return css`
 | 
			
		||||
      slot {
 | 
			
		||||
        pointer-events: none;
 | 
			
		||||
        z-index: 0;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .overlay {
 | 
			
		||||
        transition: all 0.25s;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        top: 0;
 | 
			
		||||
        left: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        bottom: 0;
 | 
			
		||||
        z-index: 1;
 | 
			
		||||
        opacity: 0;
 | 
			
		||||
        cursor: move;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .parent-card-actions:hover .overlay {
 | 
			
		||||
        outline: 2px solid var(--primary-color);
 | 
			
		||||
        background: rgba(0, 0, 0, 0.3);
 | 
			
		||||
        /* background-color: grey; */
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .parent-card-actions {
 | 
			
		||||
        transition: all 0.25s;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        top: 0;
 | 
			
		||||
        left: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        bottom: 0;
 | 
			
		||||
        opacity: 0;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .parent-card-actions:hover {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .card-actions {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-wrap: wrap;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        z-index: 2;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        left: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        bottom: 24px;
 | 
			
		||||
        color: white;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .card-actions > * {
 | 
			
		||||
        margin: 0 4px;
 | 
			
		||||
        border-radius: 24px;
 | 
			
		||||
        background: rgba(0, 0, 0, 0.7);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      mwc-list-item {
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      mwc-list-item.delete-item {
 | 
			
		||||
        color: var(--error-color);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .drag-handle {
 | 
			
		||||
        cursor: move;
 | 
			
		||||
      }
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _duplicateCard(): void {
 | 
			
		||||
    const path = this.path!;
 | 
			
		||||
    const cardConfig = this.lovelace!.config.views[path[0]].cards![path[1]];
 | 
			
		||||
    showEditCardDialog(this, {
 | 
			
		||||
      lovelaceConfig: this.lovelace!.config,
 | 
			
		||||
      cardConfig,
 | 
			
		||||
      saveConfig: this.lovelace!.saveConfig,
 | 
			
		||||
      path: [path[0]],
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _editCard(): void {
 | 
			
		||||
    showEditCardDialog(this, {
 | 
			
		||||
      lovelaceConfig: this.lovelace!.config,
 | 
			
		||||
      saveConfig: this.lovelace!.saveConfig,
 | 
			
		||||
      path: this.path!,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _deleteCard(): void {
 | 
			
		||||
    confDeleteCard(this, this.hass!, this.lovelace!, this.path!);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface HTMLElementTagNameMap {
 | 
			
		||||
    "hui-grid-card-options": HuiGridCardOptions;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										363
									
								
								src/panels/lovelace/views/hui-grid-view.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										363
									
								
								src/panels/lovelace/views/hui-grid-view.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,363 @@
 | 
			
		||||
import "@material/mwc-fab/mwc-fab";
 | 
			
		||||
import { mdiPlus, mdiResizeBottomRight } from "@mdi/js";
 | 
			
		||||
import {
 | 
			
		||||
  css,
 | 
			
		||||
  CSSResult,
 | 
			
		||||
  customElement,
 | 
			
		||||
  html,
 | 
			
		||||
  internalProperty,
 | 
			
		||||
  LitElement,
 | 
			
		||||
  property,
 | 
			
		||||
  PropertyValues,
 | 
			
		||||
  TemplateResult,
 | 
			
		||||
} from "lit-element";
 | 
			
		||||
import "lit-grid-layout";
 | 
			
		||||
import { classMap } from "lit-html/directives/class-map";
 | 
			
		||||
import { v4 as uuidv4 } from "uuid";
 | 
			
		||||
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 { computeCardSize } from "../common/compute-card-size";
 | 
			
		||||
import { HuiCardOptions } from "../components/hui-card-options";
 | 
			
		||||
import { showCreateCardDialog } from "../editor/card-editor/show-create-card-dialog";
 | 
			
		||||
import { replaceView } from "../editor/config-util";
 | 
			
		||||
import type { Lovelace, LovelaceBadge, LovelaceCard } from "../types";
 | 
			
		||||
import { HuiGridCardOptions } from "./grid/hui-grid-card-options";
 | 
			
		||||
 | 
			
		||||
let editCodeLoaded = false;
 | 
			
		||||
const mediaQueryColumns = [2, 6, 9, 12];
 | 
			
		||||
 | 
			
		||||
interface LovelaceGridCard extends LovelaceCard, HuiGridCardOptions {
 | 
			
		||||
  key: string;
 | 
			
		||||
  grid?: {
 | 
			
		||||
    key: string;
 | 
			
		||||
    width: number;
 | 
			
		||||
    height: number;
 | 
			
		||||
    posX: number;
 | 
			
		||||
    posY: number;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const RESIZE_HANDLE = document.createElement("div") as HTMLElement;
 | 
			
		||||
RESIZE_HANDLE.style.cssText =
 | 
			
		||||
  "width: 100%; height: 100%; cursor: se-resize; fill: var(--primary-text-color)";
 | 
			
		||||
RESIZE_HANDLE.innerHTML = `
 | 
			
		||||
  <svg
 | 
			
		||||
    viewBox="0 0 24 24"
 | 
			
		||||
    preserveAspectRatio="xMidYMid meet"
 | 
			
		||||
    focusable="false"
 | 
			
		||||
  >
 | 
			
		||||
    <g><path d=${mdiResizeBottomRight}></path></g>
 | 
			
		||||
  </svg>
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@customElement("hui-grid-view")
 | 
			
		||||
export class GridView 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<LovelaceGridCard> = [];
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public badges: LovelaceBadge[] = [];
 | 
			
		||||
 | 
			
		||||
  @internalProperty() private _columns?: number;
 | 
			
		||||
 | 
			
		||||
  @internalProperty() private _layout?: Array<{
 | 
			
		||||
    width: number;
 | 
			
		||||
    height: number;
 | 
			
		||||
    posX: number;
 | 
			
		||||
    posY: number;
 | 
			
		||||
    key: string;
 | 
			
		||||
  }>;
 | 
			
		||||
 | 
			
		||||
  @internalProperty() public _cards: {
 | 
			
		||||
    [key: string]: LovelaceCard | HuiCardOptions;
 | 
			
		||||
  } = {};
 | 
			
		||||
 | 
			
		||||
  private _config?: LovelaceViewConfig;
 | 
			
		||||
 | 
			
		||||
  private _createColumnsIteration = 0;
 | 
			
		||||
 | 
			
		||||
  private _mqls?: MediaQueryList[];
 | 
			
		||||
 | 
			
		||||
  public constructor() {
 | 
			
		||||
    super();
 | 
			
		||||
    this.addEventListener("iron-resize", (ev: Event) => ev.stopPropagation());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public setConfig(config: LovelaceViewConfig): void {
 | 
			
		||||
    this._config = config;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected render(): TemplateResult {
 | 
			
		||||
    return html`
 | 
			
		||||
      <div
 | 
			
		||||
        id="badges"
 | 
			
		||||
        style=${this.badges.length > 0 ? "display: block" : "display: none"}
 | 
			
		||||
      >
 | 
			
		||||
        ${this.badges.map((badge) => html`${badge}`)}
 | 
			
		||||
      </div>
 | 
			
		||||
      <lit-grid-layout
 | 
			
		||||
        rowHeight="15"
 | 
			
		||||
        .resizeHandle=${RESIZE_HANDLE}
 | 
			
		||||
        .itemRenderer=${this._itemRenderer}
 | 
			
		||||
        .layout=${this._layout}
 | 
			
		||||
        .columns=${this._columns}
 | 
			
		||||
        .dragDisabled=${!this.lovelace?.editMode}
 | 
			
		||||
        .resizeDisabled=${!this.lovelace?.editMode}
 | 
			
		||||
        @item-changed=${this._saveLayout}
 | 
			
		||||
      ></lit-grid-layout>
 | 
			
		||||
      ${this.lovelace?.editMode
 | 
			
		||||
        ? html`
 | 
			
		||||
            <mwc-fab
 | 
			
		||||
              class=${classMap({
 | 
			
		||||
                rtl: computeRTL(this.hass!),
 | 
			
		||||
              })}
 | 
			
		||||
              .title=${this.hass!.localize(
 | 
			
		||||
                "ui.panel.lovelace.editor.edit_card.add"
 | 
			
		||||
              )}
 | 
			
		||||
              @click=${this._addCard}
 | 
			
		||||
            >
 | 
			
		||||
              <ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
 | 
			
		||||
            </mwc-fab>
 | 
			
		||||
          `
 | 
			
		||||
        : ""}
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected firstUpdated(): void {
 | 
			
		||||
    this._updateColumns = this._updateColumns.bind(this);
 | 
			
		||||
    this._mqls = [300, 600, 900, 1200].map((width) => {
 | 
			
		||||
      const mql = matchMedia(`(min-width: ${width}px)`);
 | 
			
		||||
      mql.addEventListener("change", this._updateColumns);
 | 
			
		||||
      return mql;
 | 
			
		||||
    });
 | 
			
		||||
    this._updateColumns();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected updated(changedProperties: PropertyValues): void {
 | 
			
		||||
    super.updated(changedProperties);
 | 
			
		||||
 | 
			
		||||
    if (this.lovelace?.editMode && !editCodeLoaded) {
 | 
			
		||||
      editCodeLoaded = true;
 | 
			
		||||
      import(
 | 
			
		||||
        /* webpackChunkName: "grid-view-editable" */ "./grid/grid-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
 | 
			
		||||
      | undefined;
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
      (changedProperties.has("lovelace") &&
 | 
			
		||||
        (oldLovelace?.config !== this.lovelace?.config ||
 | 
			
		||||
          oldLovelace?.editMode !== this.lovelace?.editMode)) ||
 | 
			
		||||
      changedProperties.has("_columns")
 | 
			
		||||
    ) {
 | 
			
		||||
      this._createLayout();
 | 
			
		||||
      this._createCards();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async _saveLayout(ev: CustomEvent): Promise<void> {
 | 
			
		||||
    console.log("save");
 | 
			
		||||
 | 
			
		||||
    const viewConf: LovelaceViewConfig = {
 | 
			
		||||
      ...this._config,
 | 
			
		||||
      layout: ev.detail.layout,
 | 
			
		||||
    };
 | 
			
		||||
    await this.lovelace?.saveConfig(
 | 
			
		||||
      replaceView(this.lovelace!.config, this.index, viewConf)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _itemRenderer = (key: string): TemplateResult => {
 | 
			
		||||
    if (!this._cards) {
 | 
			
		||||
      return html``;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return html`${this._cards[key]}`;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  private _addCard(): void {
 | 
			
		||||
    showCreateCardDialog(this, {
 | 
			
		||||
      lovelaceConfig: this.lovelace!.config,
 | 
			
		||||
      saveConfig: this.lovelace!.saveConfig,
 | 
			
		||||
      path: [this.index!],
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async _createLayout() {
 | 
			
		||||
    this._createColumnsIteration++;
 | 
			
		||||
    const iteration = this._createColumnsIteration;
 | 
			
		||||
 | 
			
		||||
    if (this._layout?.length) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const newLayout: Array<{
 | 
			
		||||
      width: number;
 | 
			
		||||
      height: number;
 | 
			
		||||
      posX: number;
 | 
			
		||||
      posY: number;
 | 
			
		||||
      key: string;
 | 
			
		||||
      minHeight: number;
 | 
			
		||||
    }> = [];
 | 
			
		||||
 | 
			
		||||
    let tillNextRender: Promise<unknown> | undefined;
 | 
			
		||||
    let start: Date | undefined;
 | 
			
		||||
 | 
			
		||||
    // Calculate the size of every card and determine in what column it should go
 | 
			
		||||
    for (const [index, card] of this.cards.entries()) {
 | 
			
		||||
      const cardConfig = this._config!.cards![index];
 | 
			
		||||
      if (tillNextRender === undefined) {
 | 
			
		||||
        // eslint-disable-next-line no-loop-func
 | 
			
		||||
        tillNextRender = nextRender().then(() => {
 | 
			
		||||
          tillNextRender = undefined;
 | 
			
		||||
          start = undefined;
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      let waitProm: Promise<unknown> | 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(card);
 | 
			
		||||
      // @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;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const layout = {
 | 
			
		||||
        width: 3,
 | 
			
		||||
        height: cardSize,
 | 
			
		||||
        key: uuidv4(),
 | 
			
		||||
        minHeight: 4,
 | 
			
		||||
        ...cardConfig.layout,
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      newLayout.push(layout);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this._layout = newLayout;
 | 
			
		||||
    this._createCards();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _createCards(): void {
 | 
			
		||||
    const elements = {};
 | 
			
		||||
    this.cards.forEach((card: LovelaceGridCard, index) => {
 | 
			
		||||
      const layout = this._layout![index];
 | 
			
		||||
 | 
			
		||||
      card.editMode = this.lovelace?.editMode;
 | 
			
		||||
      let element = card;
 | 
			
		||||
 | 
			
		||||
      if (this.lovelace?.editMode) {
 | 
			
		||||
        const wrapper = document.createElement(
 | 
			
		||||
          "hui-grid-card-options"
 | 
			
		||||
        ) as LovelaceGridCard;
 | 
			
		||||
        wrapper.hass = this.hass;
 | 
			
		||||
        wrapper.lovelace = this.lovelace;
 | 
			
		||||
        wrapper.path = [this.index!, index];
 | 
			
		||||
        wrapper.appendChild(card);
 | 
			
		||||
        element = wrapper;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      elements[layout.key] = element;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this._cards = elements;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _updateColumns() {
 | 
			
		||||
    if (!this._mqls) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    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, mediaQueryColumns[matchColumns - 1]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      lit-grid-layout {
 | 
			
		||||
        --placeholder-background-color: var(--accent-color);
 | 
			
		||||
        --resize-handle-size: 32px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      #badges {
 | 
			
		||||
        margin: 8px 16px;
 | 
			
		||||
        font-size: 85%;
 | 
			
		||||
        text-align: center;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      mwc-fab {
 | 
			
		||||
        position: sticky;
 | 
			
		||||
        float: right;
 | 
			
		||||
        right: calc(16px + env(safe-area-inset-right));
 | 
			
		||||
        bottom: calc(16px + env(safe-area-inset-bottom));
 | 
			
		||||
        z-index: 5;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      mwc-fab.rtl {
 | 
			
		||||
        float: left;
 | 
			
		||||
        right: auto;
 | 
			
		||||
        left: calc(16px + env(safe-area-inset-left));
 | 
			
		||||
      }
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface HTMLElementTagNameMap {
 | 
			
		||||
    "hui-grid-view": GridView;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -114,7 +114,7 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
 | 
			
		||||
    if (this.lovelace?.editMode && !editCodeLoaded) {
 | 
			
		||||
      editCodeLoaded = true;
 | 
			
		||||
      import(
 | 
			
		||||
        /* webpackChunkName: "default-layout-editable" */ "./default-view-editable"
 | 
			
		||||
        /* webpackChunkName: "default-view-editable" */ "./default-view-editable"
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								yarn.lock
									
									
									
									
									
								
							@@ -8608,6 +8608,14 @@ lit-element@2.4.0, lit-element@^2.0.0, lit-element@^2.2.1, lit-element@^2.3.0, l
 | 
			
		||||
  dependencies:
 | 
			
		||||
    lit-html "^1.1.1"
 | 
			
		||||
 | 
			
		||||
lit-grid-layout@^1.1.11:
 | 
			
		||||
  version "1.1.11"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/lit-grid-layout/-/lit-grid-layout-1.1.11.tgz#0eb160ac61fcd25fd1929963776ff5d2df051320"
 | 
			
		||||
  integrity sha512-NSI7QuLllKvjrQEYdAFOv8Ucznu3TWDtxxroyDcv8Qv/zsjkuAJDK/bQjjuDSw/x7yy3NVn/c1I864fSUb47Rw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    lit-element "^2.4.0"
 | 
			
		||||
    lit-html "^1.3.0"
 | 
			
		||||
 | 
			
		||||
lit-html@1.3.0, lit-html@^1.0.0, lit-html@^1.1.1, lit-html@^1.1.2, lit-html@^1.3.0:
 | 
			
		||||
  version "1.3.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.3.0.tgz#c80f3cc5793a6dea6c07172be90a70ab20e56034"
 | 
			
		||||
@@ -12583,6 +12591,11 @@ uuid@^3.4.0:
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
 | 
			
		||||
  integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
 | 
			
		||||
 | 
			
		||||
uuid@^8.3.0:
 | 
			
		||||
  version "8.3.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea"
 | 
			
		||||
  integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==
 | 
			
		||||
 | 
			
		||||
v8-compile-cache@^2.0.3:
 | 
			
		||||
  version "2.1.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user