mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Add min/max row/columns to resize card editor (#21244)
* Add min/max row/columns to resize card editor * Add humidifier and thermostat card * Removed unused condition * Don't set max rows * Add media card * Add button card * Use same rule if there is footer * Don't show disabled cell * Add min rows to sensor card * Update sizes * Update sizes * Update sizes * Add min rows to weather forecast card
This commit is contained in:
parent
9a2051a679
commit
094203f0b4
@ -7,6 +7,7 @@ import { mdiRestore } from "@mdi/js";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { conditionalClamp } from "../common/number/clamp";
|
||||
|
||||
type GridSizeValue = {
|
||||
rows?: number;
|
||||
@ -42,6 +43,10 @@ export class HaGridSizeEditor extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const disabledColumns =
|
||||
this.columnMin !== undefined && this.columnMin === this.columnMax;
|
||||
const disabledRows =
|
||||
this.rowMin !== undefined && this.rowMin === this.rowMax;
|
||||
return html`
|
||||
<div class="grid">
|
||||
<ha-grid-layout-slider
|
||||
@ -55,6 +60,7 @@ export class HaGridSizeEditor extends LitElement {
|
||||
.value=${this.value?.columns}
|
||||
@value-changed=${this._valueChanged}
|
||||
@slider-moved=${this._sliderMoved}
|
||||
.disabled=${disabledColumns}
|
||||
></ha-grid-layout-slider>
|
||||
<ha-grid-layout-slider
|
||||
aria-label=${this.hass.localize(
|
||||
@ -68,6 +74,7 @@ export class HaGridSizeEditor extends LitElement {
|
||||
.value=${this.value?.rows}
|
||||
@value-changed=${this._valueChanged}
|
||||
@slider-moved=${this._sliderMoved}
|
||||
.disabled=${disabledRows}
|
||||
></ha-grid-layout-slider>
|
||||
${!this.isDefault
|
||||
? html`
|
||||
@ -100,17 +107,11 @@ export class HaGridSizeEditor extends LitElement {
|
||||
.map((_, index) => {
|
||||
const row = Math.floor(index / this.columns) + 1;
|
||||
const column = (index % this.columns) + 1;
|
||||
const disabled =
|
||||
(this.rowMin !== undefined && row < this.rowMin) ||
|
||||
(this.rowMax !== undefined && row > this.rowMax) ||
|
||||
(this.columnMin !== undefined && column < this.columnMin) ||
|
||||
(this.columnMax !== undefined && column > this.columnMax);
|
||||
return html`
|
||||
<div
|
||||
class="cell"
|
||||
data-row=${row}
|
||||
data-column=${column}
|
||||
?disabled=${disabled}
|
||||
@click=${this._cellClick}
|
||||
></div>
|
||||
`;
|
||||
@ -126,11 +127,16 @@ export class HaGridSizeEditor extends LitElement {
|
||||
|
||||
_cellClick(ev) {
|
||||
const cell = ev.currentTarget as HTMLElement;
|
||||
if (cell.getAttribute("disabled") !== null) return;
|
||||
const rows = Number(cell.getAttribute("data-row"));
|
||||
const columns = Number(cell.getAttribute("data-column"));
|
||||
const clampedRow = conditionalClamp(rows, this.rowMin, this.rowMax);
|
||||
const clampedColumn = conditionalClamp(
|
||||
columns,
|
||||
this.columnMin,
|
||||
this.columnMax
|
||||
);
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { rows, columns },
|
||||
value: { rows: clampedRow, columns: clampedColumn },
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -145,9 +145,16 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
this._config?.show_icon &&
|
||||
(this._config?.show_name || this._config?.show_state)
|
||||
) {
|
||||
return { grid_rows: 2, grid_columns: 2 };
|
||||
return {
|
||||
grid_rows: 2,
|
||||
grid_columns: 2,
|
||||
grid_min_rows: 2,
|
||||
};
|
||||
}
|
||||
return { grid_rows: 1, grid_columns: 1 };
|
||||
return {
|
||||
grid_rows: 1,
|
||||
grid_columns: 1,
|
||||
};
|
||||
}
|
||||
|
||||
public setConfig(config: ButtonCardConfig): void {
|
||||
|
@ -36,7 +36,11 @@ import { findEntities } from "../common/find-entities";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { createHeaderFooterElement } from "../create-element/create-header-footer-element";
|
||||
import { LovelaceCard, LovelaceHeaderFooter } from "../types";
|
||||
import {
|
||||
LovelaceCard,
|
||||
LovelaceHeaderFooter,
|
||||
LovelaceLayoutOptions,
|
||||
} from "../types";
|
||||
import { HuiErrorCard } from "./hui-error-card";
|
||||
import { EntityCardConfig } from "./types";
|
||||
|
||||
@ -241,6 +245,15 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
||||
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
|
||||
}
|
||||
|
||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
||||
return {
|
||||
grid_columns: 2,
|
||||
grid_rows: 2,
|
||||
grid_min_columns: 2,
|
||||
grid_min_rows: 2,
|
||||
};
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
iconColorCSS,
|
||||
|
@ -22,7 +22,11 @@ import { HomeAssistant } from "../../../types";
|
||||
import "../card-features/hui-card-features";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import {
|
||||
LovelaceCard,
|
||||
LovelaceCardEditor,
|
||||
LovelaceLayoutOptions,
|
||||
} from "../types";
|
||||
import { HumidifierCardConfig } from "./types";
|
||||
|
||||
@customElement("hui-humidifier-card")
|
||||
@ -173,6 +177,24 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
||||
const grid_columns = 4;
|
||||
let grid_rows = 5;
|
||||
let grid_min_rows = 2;
|
||||
const grid_min_columns = 2;
|
||||
if (this._config?.features?.length) {
|
||||
const featureHeight = Math.ceil((this._config.features.length * 2) / 3);
|
||||
grid_rows += featureHeight;
|
||||
grid_min_rows += featureHeight;
|
||||
}
|
||||
return {
|
||||
grid_columns,
|
||||
grid_rows,
|
||||
grid_min_rows,
|
||||
grid_min_columns,
|
||||
};
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
|
@ -119,6 +119,7 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
|
||||
return {
|
||||
grid_columns: 4,
|
||||
grid_rows: 4,
|
||||
grid_min_rows: 2,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -432,6 +432,8 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
return {
|
||||
grid_columns: 4,
|
||||
grid_rows: 4,
|
||||
grid_min_columns: 2,
|
||||
grid_min_rows: 2,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,11 @@ import { findEntities } from "../common/find-entities";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import "../components/hui-marquee";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import type {
|
||||
LovelaceCard,
|
||||
LovelaceCardEditor,
|
||||
LovelaceLayoutOptions,
|
||||
} from "../types";
|
||||
import { MediaControlCardConfig } from "./types";
|
||||
|
||||
@customElement("hui-media-control-card")
|
||||
@ -582,6 +586,15 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
}
|
||||
|
||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
||||
return {
|
||||
grid_columns: 4,
|
||||
grid_min_columns: 2,
|
||||
grid_rows: 3,
|
||||
grid_min_rows: 3,
|
||||
};
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-card {
|
||||
|
@ -76,6 +76,8 @@ class HuiSensorCard extends HuiEntityCard {
|
||||
return {
|
||||
grid_columns: 2,
|
||||
grid_rows: 2,
|
||||
grid_min_columns: 2,
|
||||
grid_min_rows: 2,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@ -16,12 +16,12 @@ import "../../../components/ha-alert";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-state-icon";
|
||||
import {
|
||||
StatisticsMetaData,
|
||||
fetchStatistic,
|
||||
getDisplayUnit,
|
||||
getStatisticLabel,
|
||||
getStatisticMetadata,
|
||||
isExternalStatistic,
|
||||
StatisticsMetaData,
|
||||
} from "../../../data/recorder";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { computeCardSize } from "../common/compute-card-size";
|
||||
@ -32,6 +32,7 @@ import {
|
||||
LovelaceCard,
|
||||
LovelaceCardEditor,
|
||||
LovelaceHeaderFooter,
|
||||
LovelaceLayoutOptions,
|
||||
} from "../types";
|
||||
import { HuiErrorCard } from "./hui-error-card";
|
||||
import { EntityCardConfig, StatisticCardConfig } from "./types";
|
||||
@ -254,6 +255,15 @@ export class HuiStatisticCard extends LitElement implements LovelaceCard {
|
||||
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
|
||||
}
|
||||
|
||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
||||
return {
|
||||
grid_columns: 2,
|
||||
grid_rows: 2,
|
||||
grid_min_columns: 2,
|
||||
grid_min_rows: 2,
|
||||
};
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
css`
|
||||
|
@ -22,7 +22,11 @@ import { HomeAssistant } from "../../../types";
|
||||
import "../card-features/hui-card-features";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import {
|
||||
LovelaceCard,
|
||||
LovelaceCardEditor,
|
||||
LovelaceLayoutOptions,
|
||||
} from "../types";
|
||||
import { ThermostatCardConfig } from "./types";
|
||||
|
||||
@customElement("hui-thermostat-card")
|
||||
@ -165,6 +169,24 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
||||
const grid_columns = 4;
|
||||
let grid_rows = 5;
|
||||
let grid_min_rows = 2;
|
||||
const grid_min_columns = 2;
|
||||
if (this._config?.features?.length) {
|
||||
const featureHeight = Math.ceil((this._config.features.length * 2) / 3);
|
||||
grid_rows += featureHeight;
|
||||
grid_min_rows += featureHeight;
|
||||
}
|
||||
return {
|
||||
grid_columns,
|
||||
grid_rows,
|
||||
grid_min_rows,
|
||||
grid_min_columns,
|
||||
};
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
|
@ -122,17 +122,21 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
||||
const options = {
|
||||
grid_columns: 2,
|
||||
grid_rows: 1,
|
||||
};
|
||||
const grid_columns = 2;
|
||||
let grid_rows = 1;
|
||||
if (this._config?.features?.length) {
|
||||
options.grid_rows += Math.ceil((this._config.features.length * 2) / 3);
|
||||
const featureHeight = Math.ceil((this._config.features.length * 2) / 3);
|
||||
grid_rows += featureHeight;
|
||||
}
|
||||
if (this._config?.vertical) {
|
||||
options.grid_rows++;
|
||||
grid_rows!++;
|
||||
}
|
||||
return options;
|
||||
return {
|
||||
grid_columns,
|
||||
grid_rows,
|
||||
grid_min_rows: grid_rows,
|
||||
grid_min_columns: grid_columns,
|
||||
};
|
||||
}
|
||||
|
||||
private _handleAction(ev: ActionHandlerEvent) {
|
||||
|
@ -38,7 +38,11 @@ import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import type {
|
||||
LovelaceCard,
|
||||
LovelaceCardEditor,
|
||||
LovelaceLayoutOptions,
|
||||
} from "../types";
|
||||
import type { WeatherForecastCardConfig } from "./types";
|
||||
|
||||
@customElement("hui-weather-forecast-card")
|
||||
@ -421,6 +425,26 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
return typeof item !== "undefined" && item !== null;
|
||||
}
|
||||
|
||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
||||
if (
|
||||
this._config?.show_current !== false &&
|
||||
this._config?.show_forecast !== false
|
||||
) {
|
||||
return {
|
||||
grid_columns: 4,
|
||||
grid_min_columns: 2,
|
||||
grid_rows: 3,
|
||||
grid_min_rows: 3,
|
||||
};
|
||||
}
|
||||
return {
|
||||
grid_columns: 4,
|
||||
grid_min_columns: 2,
|
||||
grid_rows: 2,
|
||||
grid_min_rows: 1,
|
||||
};
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
weatherSVGStyles,
|
||||
|
@ -255,15 +255,14 @@ export class HaGridLayoutSlider extends LitElement {
|
||||
>
|
||||
<div id="slider" class="slider">
|
||||
<div class="track">
|
||||
<div class="background">
|
||||
<div
|
||||
class="active"
|
||||
style=${styleMap({
|
||||
"--min": `${this.min / this._range}`,
|
||||
"--max": `${1 - this.max / this._range}`,
|
||||
})}
|
||||
></div>
|
||||
</div>
|
||||
<div class="background"></div>
|
||||
<div
|
||||
class="active"
|
||||
style=${styleMap({
|
||||
"--min": `${this.min / this._range}`,
|
||||
"--max": `${1 - this.max / this._range}`,
|
||||
})}
|
||||
></div>
|
||||
</div>
|
||||
${this.value !== undefined
|
||||
? html`<div class="handle"></div>`
|
||||
@ -323,11 +322,12 @@ export class HaGridLayoutSlider extends LitElement {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--disabled-color);
|
||||
opacity: 0.5;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.active {
|
||||
position: absolute;
|
||||
background: grey;
|
||||
opacity: 0.7;
|
||||
top: 0;
|
||||
right: calc(var(--max) * 100%);
|
||||
bottom: 0;
|
||||
@ -375,6 +375,9 @@ export class HaGridLayoutSlider extends LitElement {
|
||||
:host(:disabled) .slider {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
:host(:disabled) .handle:after {
|
||||
background: var(--disabled-color);
|
||||
}
|
||||
.pressed .handle {
|
||||
transition: none;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { HuiCard } from "../../cards/hui-card";
|
||||
import { DEFAULT_GRID_OPTIONS } from "../../sections/hui-grid-section";
|
||||
import { computeSizeOnGrid } from "../../sections/hui-grid-section";
|
||||
import { LovelaceLayoutOptions } from "../../types";
|
||||
|
||||
@customElement("hui-card-layout-editor")
|
||||
@ -38,28 +38,29 @@ export class HuiCardLayoutEditor extends LitElement {
|
||||
|
||||
private _cardElement?: HuiCard;
|
||||
|
||||
private _gridSizeValue = memoizeOne(
|
||||
private _mergedOptions = memoizeOne(
|
||||
(
|
||||
options?: LovelaceLayoutOptions,
|
||||
defaultOptions?: LovelaceLayoutOptions
|
||||
) => ({
|
||||
rows:
|
||||
options?.grid_rows ??
|
||||
defaultOptions?.grid_rows ??
|
||||
DEFAULT_GRID_OPTIONS.grid_rows,
|
||||
columns:
|
||||
options?.grid_columns ??
|
||||
defaultOptions?.grid_columns ??
|
||||
DEFAULT_GRID_OPTIONS.grid_columns,
|
||||
...defaultOptions,
|
||||
...options,
|
||||
})
|
||||
);
|
||||
|
||||
private _gridSizeValue = memoizeOne(computeSizeOnGrid);
|
||||
|
||||
private _isDefault = memoizeOne(
|
||||
(options?: LovelaceLayoutOptions) =>
|
||||
options?.grid_columns === undefined && options?.grid_rows === undefined
|
||||
);
|
||||
|
||||
render() {
|
||||
const options = this._mergedOptions(
|
||||
this.config.layout_options,
|
||||
this._defaultLayoutOptions
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class="header">
|
||||
<p class="intro">
|
||||
@ -123,12 +124,13 @@ export class HuiCardLayoutEditor extends LitElement {
|
||||
: html`
|
||||
<ha-grid-size-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._gridSizeValue(
|
||||
this.config.layout_options,
|
||||
this._defaultLayoutOptions
|
||||
)}
|
||||
.value=${this._gridSizeValue(options)}
|
||||
.isDefault=${this._isDefault(this.config.layout_options)}
|
||||
@value-changed=${this._gridSizeChanged}
|
||||
.rowMin=${options.grid_min_rows}
|
||||
.rowMax=${options.grid_max_rows}
|
||||
.columnMin=${options.grid_min_columns}
|
||||
.columnMax=${options.grid_max_columns}
|
||||
></ha-grid-size-picker>
|
||||
`}
|
||||
`;
|
||||
|
@ -15,6 +15,7 @@ import { HuiCard } from "../cards/hui-card";
|
||||
import "../components/hui-card-edit-mode";
|
||||
import { moveCard } from "../editor/config-util";
|
||||
import type { Lovelace, LovelaceLayoutOptions } from "../types";
|
||||
import { conditionalClamp } from "../../../common/number/clamp";
|
||||
|
||||
const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
|
||||
delay: 100,
|
||||
@ -23,9 +24,41 @@ const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
|
||||
invertedSwapThreshold: 0.7,
|
||||
} as HaSortableOptions;
|
||||
|
||||
export const DEFAULT_GRID_OPTIONS: LovelaceLayoutOptions = {
|
||||
export const DEFAULT_GRID_OPTIONS = {
|
||||
grid_columns: 4,
|
||||
grid_rows: 1,
|
||||
} as const satisfies LovelaceLayoutOptions;
|
||||
|
||||
type GridSizeValue = {
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
};
|
||||
|
||||
export const computeSizeOnGrid = (
|
||||
options: LovelaceLayoutOptions
|
||||
): GridSizeValue => {
|
||||
const rows =
|
||||
typeof options.grid_rows === "number"
|
||||
? conditionalClamp(
|
||||
options.grid_rows,
|
||||
options.grid_min_rows,
|
||||
options.grid_max_rows
|
||||
)
|
||||
: DEFAULT_GRID_OPTIONS.grid_rows;
|
||||
|
||||
const columns =
|
||||
typeof options.grid_columns === "number"
|
||||
? conditionalClamp(
|
||||
options.grid_columns,
|
||||
options.grid_min_columns,
|
||||
options.grid_max_columns
|
||||
)
|
||||
: DEFAULT_GRID_OPTIONS.grid_columns;
|
||||
|
||||
return {
|
||||
rows,
|
||||
columns,
|
||||
};
|
||||
};
|
||||
|
||||
export class GridSection extends LitElement implements LovelaceSectionElement {
|
||||
@ -101,15 +134,13 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
||||
card.layout = "grid";
|
||||
const layoutOptions = card.getLayoutOptions();
|
||||
|
||||
const columnSize =
|
||||
layoutOptions.grid_columns ?? DEFAULT_GRID_OPTIONS.grid_columns;
|
||||
const rowSize =
|
||||
layoutOptions.grid_rows ?? DEFAULT_GRID_OPTIONS.grid_rows;
|
||||
const { rows, columns } = computeSizeOnGrid(layoutOptions);
|
||||
|
||||
return html`
|
||||
<div
|
||||
style=${styleMap({
|
||||
"--column-size": columnSize,
|
||||
"--row-size": rowSize,
|
||||
"--column-size": columns,
|
||||
"--row-size": rows,
|
||||
})}
|
||||
class="card ${classMap({
|
||||
"fit-rows": typeof layoutOptions?.grid_rows === "number",
|
||||
|
@ -43,6 +43,10 @@ export interface LovelaceBadge extends HTMLElement {
|
||||
export type LovelaceLayoutOptions = {
|
||||
grid_columns?: number;
|
||||
grid_rows?: number;
|
||||
grid_max_columns?: number;
|
||||
grid_min_columns?: number;
|
||||
grid_min_rows?: number;
|
||||
grid_max_rows?: number;
|
||||
};
|
||||
|
||||
export interface LovelaceCard extends HTMLElement {
|
||||
|
Loading…
x
Reference in New Issue
Block a user