diff --git a/src/components/ha-grid-size-picker.ts b/src/components/ha-grid-size-picker.ts index 813f1e17ff..c7c15df287 100644 --- a/src/components/ha-grid-size-picker.ts +++ b/src/components/ha-grid-size-picker.ts @@ -14,12 +14,6 @@ import { } from "../panels/lovelace/common/compute-card-grid-size"; import { HomeAssistant } from "../types"; -declare global { - interface HASSDomEvents { - "grid-reset": undefined; - } -} - @customElement("ha-grid-size-picker") export class HaGridSizeEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -190,7 +184,12 @@ export class HaGridSizeEditor extends LitElement { private _reset(ev) { ev.stopPropagation(); - fireEvent(this, "grid-reset"); + fireEvent(this, "value-changed", { + value: { + rows: undefined, + columns: undefined, + }, + }); } private _sliderMoved(ev) { diff --git a/src/data/lovelace/config/card.ts b/src/data/lovelace/config/card.ts index 49482b8b28..2f31503f60 100644 --- a/src/data/lovelace/config/card.ts +++ b/src/data/lovelace/config/card.ts @@ -1,11 +1,16 @@ import type { Condition } from "../../../panels/lovelace/common/validate-condition"; -import type { LovelaceLayoutOptions } from "../../../panels/lovelace/types"; +import type { + LovelaceGridOptions, + LovelaceLayoutOptions, +} from "../../../panels/lovelace/types"; export interface LovelaceCardConfig { index?: number; view_index?: number; view_layout?: any; + /** @deprecated Use `grid_options` instead */ layout_options?: LovelaceLayoutOptions; + grid_options?: LovelaceGridOptions; type: string; [key: string]: any; visibility?: Condition[]; diff --git a/src/panels/lovelace/cards/hui-card.ts b/src/panels/lovelace/cards/hui-card.ts index e8347d0854..800e72710e 100644 --- a/src/panels/lovelace/cards/hui-card.ts +++ b/src/panels/lovelace/cards/hui-card.ts @@ -5,6 +5,7 @@ import { MediaQueriesListener } from "../../../common/dom/media_query"; import "../../../components/ha-svg-icon"; import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import type { HomeAssistant } from "../../../types"; +import { migrateLayoutToGridOptions } from "../common/compute-card-grid-size"; import { computeCardSize } from "../common/compute-card-size"; import { attachConditionMediaQueriesListeners, @@ -12,7 +13,7 @@ import { } from "../common/validate-condition"; import { createCardElement } from "../create-element/create-card-element"; import { createErrorCardConfig } from "../create-element/create-element-base"; -import type { LovelaceCard, LovelaceLayoutOptions } from "../types"; +import type { LovelaceCard, LovelaceGridOptions } from "../types"; declare global { interface HASSDomEvents { @@ -67,20 +68,44 @@ export class HuiCard extends ReactiveElement { return 1; } - public getLayoutOptions(): LovelaceLayoutOptions { - const configOptions = this.config?.layout_options ?? {}; - if (this._element) { - const cardOptions = this._element.getLayoutOptions?.() ?? {}; - return { - ...cardOptions, - ...configOptions, - }; - } - return configOptions; + public getGridOptions(): LovelaceGridOptions { + const elementOptions = this.getElementGridOptions(); + const configOptions = this.getConfigGridOptions(); + return { + ...elementOptions, + ...configOptions, + }; } - public getElementLayoutOptions(): LovelaceLayoutOptions { - return this._element?.getLayoutOptions?.() ?? {}; + // options provided by the element + public getElementGridOptions(): LovelaceGridOptions { + if (!this._element) return {}; + + if (this._element.getGridOptions) { + return this._element.getGridOptions(); + } + if (this._element.getLayoutOptions) { + // eslint-disable-next-line no-console + console.warn( + `This card (${this.config?.type}) is using "getLayoutOptions" and it is deprecated, contact the developer to suggest to use "getGridOptions" instead` + ); + const config = migrateLayoutToGridOptions( + this._element.getLayoutOptions() + ); + return config; + } + return {}; + } + + // options provided by the config + public getConfigGridOptions(): LovelaceGridOptions { + if (this.config?.grid_options) { + return this.config.grid_options; + } + if (this.config?.layout_options) { + return migrateLayoutToGridOptions(this.config.layout_options); + } + return {}; } private _updateElement(config: LovelaceCardConfig) { diff --git a/src/panels/lovelace/common/compute-card-grid-size.ts b/src/panels/lovelace/common/compute-card-grid-size.ts index ceb7b29be9..e7991a8771 100644 --- a/src/panels/lovelace/common/compute-card-grid-size.ts +++ b/src/panels/lovelace/common/compute-card-grid-size.ts @@ -1,8 +1,34 @@ import { conditionalClamp } from "../../../common/number/clamp"; -import { LovelaceLayoutOptions } from "../types"; +import { LovelaceGridOptions, LovelaceLayoutOptions } from "../types"; + +const GRID_COLUMN_MULTIPLIER = 3; + +const multiplyBy = ( + value: T, + multiplier: number +): T => (typeof value === "number" ? ((value * multiplier) as T) : value); + +export const migrateLayoutToGridOptions = ( + options: LovelaceLayoutOptions +): LovelaceGridOptions => { + const gridOptions: LovelaceGridOptions = { + columns: multiplyBy(options.grid_columns, GRID_COLUMN_MULTIPLIER), + max_columns: multiplyBy(options.grid_max_columns, GRID_COLUMN_MULTIPLIER), + min_columns: multiplyBy(options.grid_min_columns, GRID_COLUMN_MULTIPLIER), + rows: options.grid_rows, + max_rows: options.grid_max_rows, + min_rows: options.grid_min_rows, + }; + for (const [key, value] of Object.entries(gridOptions)) { + if (value === undefined) { + delete gridOptions[key]; + } + } + return gridOptions; +}; export const DEFAULT_GRID_SIZE = { - columns: 4, + columns: 12, rows: "auto", } as CardGridSize; @@ -11,27 +37,23 @@ export type CardGridSize = { columns: number | "full"; }; -export const GRID_COLUMN_MULTIPLIER = 3; - export const computeCardGridSize = ( - options: LovelaceLayoutOptions + options: LovelaceGridOptions ): CardGridSize => { - const rows = options.grid_rows ?? DEFAULT_GRID_SIZE.rows; - const columns = options.grid_columns ?? DEFAULT_GRID_SIZE.columns; - const minRows = options.grid_min_rows; - const maxRows = options.grid_max_rows; - const minColumns = options.grid_min_columns; - const maxColumns = options.grid_max_columns; - const precisionMode = options.grid_precision_mode; + const rows = options.rows ?? DEFAULT_GRID_SIZE.rows; + const columns = options.columns ?? DEFAULT_GRID_SIZE.columns; + const minRows = options.min_rows; + const maxRows = options.max_rows; + const minColumns = options.min_columns; + const maxColumns = options.max_columns; const clampedRows = typeof rows === "string" ? rows : conditionalClamp(rows, minRows, maxRows); const clampedColumns = - typeof columns === "string" || precisionMode + typeof columns === "string" ? columns - : conditionalClamp(columns, minColumns, maxColumns) * - GRID_COLUMN_MULTIPLIER; + : conditionalClamp(columns, minColumns, maxColumns); return { rows: clampedRows, diff --git a/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts b/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts index add84d392c..d002b45d88 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts @@ -1,7 +1,7 @@ import type { ActionDetail } from "@material/mwc-list"; import { mdiCheck, mdiDotsVertical } from "@mdi/js"; import { css, html, LitElement, nothing, PropertyValues } from "lit"; -import { customElement, property, query, state } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; @@ -17,7 +17,6 @@ import "../../../../components/ha-slider"; import "../../../../components/ha-svg-icon"; import "../../../../components/ha-switch"; import "../../../../components/ha-yaml-editor"; -import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section"; import { haStyle } from "../../../../resources/styles"; @@ -26,9 +25,9 @@ import { HuiCard } from "../../cards/hui-card"; import { CardGridSize, computeCardGridSize, - GRID_COLUMN_MULTIPLIER, + migrateLayoutToGridOptions, } from "../../common/compute-card-grid-size"; -import { LovelaceLayoutOptions } from "../../types"; +import { LovelaceGridOptions } from "../../types"; @customElement("hui-card-layout-editor") export class HuiCardLayoutEditor extends LitElement { @@ -38,57 +37,48 @@ export class HuiCardLayoutEditor extends LitElement { @property({ attribute: false }) public sectionConfig!: LovelaceSectionConfig; - @state() _defaultLayoutOptions?: LovelaceLayoutOptions; + @state() _defaultGridOptions?: LovelaceGridOptions; @state() public _yamlMode = false; @state() public _uiAvailable = true; - @query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor; - private _cardElement?: HuiCard; private _mergedOptions = memoizeOne( - ( - options?: LovelaceLayoutOptions, - defaultOptions?: LovelaceLayoutOptions - ) => ({ + (options?: LovelaceGridOptions, defaultOptions?: LovelaceGridOptions) => ({ ...defaultOptions, ...options, }) ); - private _computeCardGridSize = memoizeOne( - (options: LovelaceLayoutOptions) => { - const size = computeCardGridSize(options); - if (!options.grid_precision_mode) { - size.columns = - typeof size.columns === "number" - ? Math.round(size.columns / GRID_COLUMN_MULTIPLIER) - : size.columns; - } - return size; - } - ); + private _computeCardGridSize = memoizeOne(computeCardGridSize); private _isDefault = memoizeOne( - (options?: LovelaceLayoutOptions) => - options?.grid_columns === undefined && - options?.grid_rows === undefined && - options?.grid_precision_mode === undefined + (options?: LovelaceGridOptions) => + options?.columns === undefined && options?.rows === undefined ); + private _configGridOptions = (config: LovelaceCardConfig) => { + if (config.grid_options) { + return config.grid_options; + } + if (config.layout_options) { + return migrateLayoutToGridOptions(config.layout_options); + } + return {}; + }; + render() { + const configGridOptions = this._configGridOptions(this.config); const options = this._mergedOptions( - this.config.layout_options, - this._defaultLayoutOptions + configGridOptions, + this._defaultGridOptions ); const value = this._computeCardGridSize(options); - const totalColumns = (this.sectionConfig.column_span ?? 1) * 4; - - const precisionMode = options.grid_precision_mode ?? false; + const totalColumns = (this.sectionConfig.column_span ?? 1) * 12; return html`
@@ -146,44 +136,25 @@ export class HuiCardLayoutEditor extends LitElement { ? html` ` : html` - ${precisionMode - ? html` - - ` - : html` - - `} + ${this.hass.localize( @@ -213,12 +184,7 @@ export class HuiCardLayoutEditor extends LitElement { "ui.panel.lovelace.editor.edit_card.layout.precision_mode_helper" )} - - + `} `; @@ -233,11 +199,10 @@ export class HuiCardLayoutEditor extends LitElement { this._cardElement.config = this.config; this._cardElement.addEventListener("card-updated", (ev: Event) => { ev.stopPropagation(); - this._defaultLayoutOptions = - this._cardElement?.getElementLayoutOptions(); + this._defaultGridOptions = this._cardElement?.getElementGridOptions(); }); this._cardElement.load(); - this._defaultLayoutOptions = this._cardElement.getElementLayoutOptions(); + this._defaultGridOptions = this._cardElement.getElementGridOptions(); } catch (err) { // eslint-disable-next-line no-console console.error(err); @@ -267,47 +232,46 @@ export class HuiCardLayoutEditor extends LitElement { } } - private async _reset() { - const newConfig = { ...this.config }; - delete newConfig.layout_options; - this._yamlEditor?.setValue({}); - fireEvent(this, "value-changed", { value: newConfig }); - } - private _gridSizeChanged(ev: CustomEvent): void { ev.stopPropagation(); const value = ev.detail.value as CardGridSize; const newConfig: LovelaceCardConfig = { ...this.config, - layout_options: { - ...this.config.layout_options, - grid_columns: value.columns, - grid_rows: value.rows, + grid_options: { + ...this.config.grid_options, + columns: value.columns, + rows: value.rows, }, }; - if (newConfig.layout_options!.grid_columns === undefined) { - delete newConfig.layout_options!.grid_columns; - } - if (newConfig.layout_options!.grid_rows === undefined) { - delete newConfig.layout_options!.grid_rows; - } - if (Object.keys(newConfig.layout_options!).length === 0) { - delete newConfig.layout_options; - } + this._updateValue(newConfig); + } - fireEvent(this, "value-changed", { value: newConfig }); + private _updateValue(value: LovelaceCardConfig): void { + if (value.grid_options!.columns === undefined) { + delete value.grid_options!.columns; + } + if (value.grid_options!.rows === undefined) { + delete value.grid_options!.rows; + } + if (Object.keys(value.grid_options!).length === 0) { + delete value.grid_options; + } + if (value.layout_options) { + delete value.layout_options; + } + fireEvent(this, "value-changed", { value }); } private _valueChanged(ev: CustomEvent): void { ev.stopPropagation(); - const options = ev.detail.value as LovelaceLayoutOptions; + const options = ev.detail.value as LovelaceGridOptions; const newConfig: LovelaceCardConfig = { ...this.config, - layout_options: options, + grid_options: options, }; - fireEvent(this, "value-changed", { value: newConfig }); + this._updateValue(newConfig); } private _fullWidthChanged(ev): void { @@ -315,42 +279,12 @@ export class HuiCardLayoutEditor extends LitElement { const value = ev.target.checked; const newConfig: LovelaceCardConfig = { ...this.config, - layout_options: { - ...this.config.layout_options, - grid_columns: value - ? "full" - : (this._defaultLayoutOptions?.grid_min_columns ?? 1), + grid_options: { + ...this.config.grid_options, + columns: value ? "full" : (this._defaultGridOptions?.min_columns ?? 1), }, }; - fireEvent(this, "value-changed", { value: newConfig }); - } - - private _precisionModeChanged(ev): void { - ev.stopPropagation(); - const value = ev.target.checked; - - const preciseMode = value; - const gridColumns = this.config.layout_options?.grid_columns; - - const newGridColumns = - typeof gridColumns === "number" - ? preciseMode - ? gridColumns * GRID_COLUMN_MULTIPLIER - : Math.round(gridColumns / GRID_COLUMN_MULTIPLIER) - : gridColumns; - - const newConfig: LovelaceCardConfig = { - ...this.config, - layout_options: { - ...this.config.layout_options, - grid_columns: newGridColumns, - grid_precision_mode: preciseMode || undefined, - }, - }; - if (Object.keys(newConfig.layout_options!).length === 0) { - delete newConfig.layout_options; - } - fireEvent(this, "value-changed", { value: newConfig }); + this._updateValue(newConfig); } static styles = [ diff --git a/src/panels/lovelace/editor/structs/base-card-struct.ts b/src/panels/lovelace/editor/structs/base-card-struct.ts index 8d504c2d4c..bef404c39b 100644 --- a/src/panels/lovelace/editor/structs/base-card-struct.ts +++ b/src/panels/lovelace/editor/structs/base-card-struct.ts @@ -4,5 +4,6 @@ export const baseLovelaceCardConfig = object({ type: string(), view_layout: any(), layout_options: any(), + grid_options: any(), visibility: any(), }); diff --git a/src/panels/lovelace/sections/hui-grid-section.ts b/src/panels/lovelace/sections/hui-grid-section.ts index 109c9651c6..3d3e05e48a 100644 --- a/src/panels/lovelace/sections/hui-grid-section.ts +++ b/src/panels/lovelace/sections/hui-grid-section.ts @@ -84,9 +84,9 @@ export class GridSection extends LitElement implements LovelaceSectionElement { (_cardConfig, idx) => { const card = this.cards![idx]; card.layout = "grid"; - const layoutOptions = card.getLayoutOptions(); + const gridOptions = card.getGridOptions(); - const { rows, columns } = computeCardGridSize(layoutOptions); + const { rows, columns } = computeCardGridSize(gridOptions); return html`
diff --git a/src/panels/lovelace/types.ts b/src/panels/lovelace/types.ts index ca7bc938d1..5c07c63e38 100644 --- a/src/panels/lovelace/types.ts +++ b/src/panels/lovelace/types.ts @@ -49,7 +49,15 @@ export type LovelaceLayoutOptions = { grid_min_columns?: number; grid_min_rows?: number; grid_max_rows?: number; - grid_precision_mode?: boolean; +}; + +export type LovelaceGridOptions = { + columns?: number | "full"; + rows?: number | "auto"; + max_columns?: number; + min_columns?: number; + min_rows?: number; + max_rows?: number; }; export interface LovelaceCard extends HTMLElement { @@ -57,7 +65,9 @@ export interface LovelaceCard extends HTMLElement { preview?: boolean; layout?: string; getCardSize(): number | Promise; + /** @deprecated Use `getGridOptions` instead */ getLayoutOptions?(): LovelaceLayoutOptions; + getGridOptions?(): LovelaceGridOptions; setConfig(config: LovelaceCardConfig): void; }